Reputation: 753
I have a Spring/Boot REST API that serves up JWT tokens for sessions.
I have a session end point with 2 methods, one that authenticates the user, one that validates the Auth Token. I can successfully create and validate tokens on an endpoint (these have no filters on them). As soon as I try and hit the endpoints with a client side application, the requests blow up, I can however, use them okay with a REST client like insomnia.
The code that runs over the JWT token is here:
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final UserService userService;
private final JwtTokenUtility jwtTokenUtility;
@Autowired
public JwtRequestFilter(
UserService userService,
JwtTokenUtility jwtTokenUtility
) {
this.userService = userService;
this.jwtTokenUtility = jwtTokenUtility;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization"); <-- null
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtility.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String"); <-- Therefore, this is called
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userService.loadUserByUsername(username);
if (jwtTokenUtility.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
The problem is that the code reads the Authorization request header as null, but when I check request headers, it's clearly there:
Provisional headers are shown
Accept: application/json, text/plain, */*
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJkdWRlY29yMyIsImV4cCI6MTU5NzkzNTQ0NywiaWF0IjoxNTk3OTE3NDQ3fQ.phMnbmkvOW6HKLCnWso-GaX844KMkukS5eADMBko56EEJA-VGgG1qpavwPwHT-tKjxbMuO9VZw3T0rESxQpIqQ
Referer: http://localhost:4200/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36
My authentication set up code is as follows:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final UserService userDetailsService;
private final JwtRequestFilter jwtRequestFilter;
@Autowired
public WebSecurityConfig(
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
UserService userDetailsService,
JwtRequestFilter jwtRequestFilter
) {
super();
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.userDetailsService = userDetailsService;
this.jwtRequestFilter = jwtRequestFilter;
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf()
.disable()
.authorizeRequests()
.antMatchers("/auth", "/auth/validate")
.permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(this.jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(this.jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
The client application that fails is an Angular application, the returned JWT token from auth is stored as a cookie, and interceptor then checks for the cookie, and attaches the value (JWT token) to request headers via an interceptor. This method has worked for me forever (will supply the code if necessary). The screen shot below shows the exact same call returning successfully:
I'm feeling that it may be OPTIONS requests are not allowed on protected endpoints, but I have no evidence that this is the case. Is there an annotation or a utility I can use that will allow me to accept OPTIONS requests? Is there anything here that I have done blatantly wrong? I'm at a loss
Upvotes: 0
Views: 1289
Reputation: 753
Fixed - I just updated my HTTP config to this:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.headers().frameOptions().disable().and().cors().and().csrf().disable();
httpSecurity.addFilterBefore(this.jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
And I can now accept OPTIONS requests server side.
Upvotes: 0