简体   繁体   中英

Spring-boot: Apply Servlet Filter to all routes except one

A question for spring-boot gurus!

Use Case:

I want my application to behave differently depending on following routes:

  • / no authentication, no authorization
  • / render authorization via a json web token (jwt) sent as an URL parameter (I know, it's weird)
  • any other routes: authorization via a json web token (jwt) sent as an URL parameter (I know, it's weird)

The secret for the jwt is stored as an element of the application configuration (application.yaml) (I'm aware that this is not best practice, it's a demo app so I don't care)

I'm using SpringBoot 2.0.5 and io.jsonwebtoken as the jwt library.

I've implemented it using a Servlet Filter, and it is working, but it feels really ugly. I couldn't find a way to say ' Apply this Servlet Filter to all endpoints except this list '. I've resorted to including the logic within the doFilter method, but this seems really ugly .

Is there a 'best practise' for this?

My current code is as follows:

SecurityConfiguration

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers("/render").permitAll()
            .anyRequest().authenticated();
    httpSecurity.headers().frameOptions().disable();
}
}

WebConfigurer

import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.util.EnumSet;

@Configuration
public class WebConfigurer implements ServletContextInitializer {

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
        EnumSet<DispatcherType> disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
        initFilter(servletContext, disps);
}

private void initFilter(ServletContext servletContext,
                            EnumSet<DispatcherType> disps) {
FilterRegistration.Dynamic myFilter =
                servletContext.addFilter("myFilter",
                        new JWTAuthenticationFilter());

myFilter.addMappingForUrlPatterns(disps, true, "/app/*");
myFilter.setAsyncSupported(true);
}
}

JWTAuthenticationFilter

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.TextCodec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JWTAuthenticationFilter extends GenericFilterBean {

    @Value("${security.jwt.token.secret-key}")
    private String secret;

    @Override
    public void doFilter(ServletRequest req,
                         ServletResponse res,
                         FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            String path = request.getRequestURI();
            if (!path.equals("/")) {

                String jwsString = request.getParameter("jwt");
                Jws<Claims> jws;

                String base64_encoded_secret = TextCodec.BASE64.encode(secret);

                jws = Jwts.parser()
                        .setSigningKey(base64_encoded_secret)
                        .parseClaimsJws(jwsString);
            }
        } catch (Exception e) {
            System.out.println(e);
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed");
            SecurityContextHolder.clearContext();
        }
        filterChain.doFilter(request, response);
    }
}

Found the solution! I used a FilterRegistrationBean. There is no way to exclude URLs. My solution is to put all the app under the app/ directory, so I didn't need to put a filter on the root /.

@Bean
public FilterRegistrationBean FilterRegistration() {
    FilterRegistrationBean registration = new  FilterRegistrationBean();
    registration.setFilter(filter);
    registration.setOrder(1);
    registration.addUrlPatterns("/app/*");
    return registration;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM