Reputation: 1413
A question for spring-boot gurus!
Use Case:
I want my application to behave differently depending on following routes:
render
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);
}
}
Upvotes: 3
Views: 5852
Reputation: 1413
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;
}
Upvotes: 2