I configured my spring security as below and both endpoints /123
and /asfsadf
are not being ignored. Any idea on this?
Thank you for your time
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
@Value("${URL_PREFIX}")
private String url_prefix;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/123").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/asfsadf");
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// Get AuthenticationManager bean
return super.authenticationManagerBean();
}
}
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserServiceImpl userService;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(httpServletRequest);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
int userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = userService.loadUserById(userId);
if(userDetails != null) {
UsernamePasswordAuthenticationToken
authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
else{
Map<String, ArrayList<String>> responseBody = new HashMap<>();
responseBody.put("errors", new ArrayList<>(List.of("Failed to authenticate.")));
httpServletResponse.setStatus(UNAUTHORIZED.value());
httpServletResponse.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getOutputStream(), responseBody);
}
} catch (Exception ex) {
log.info(ex.getMessage());
Map<String, ArrayList<String>> responseBody = new HashMap<>();
responseBody.put("errors", new ArrayList<>(List.of(ex.getMessage())));
httpServletResponse.setStatus(UNAUTHORIZED.value());
httpServletResponse.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getOutputStream(), responseBody);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
@Component
@Slf4j
public class JwtTokenProvider {
@Value("${JWT_SECRET}")
private String JWT_SECRET;
@Value("${ACCESS_TOKEN_EXPIRATION}")
private long ACCESS_JWT_EXPIRATION;
@Value("${REFRESH_TOKEN_EXPIRATION}")
private long REFRESH_JWT_EXPIRATION;
public String generateAccessToken(CustomUserDetails userDetails) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() ACCESS_JWT_EXPIRATION);
return Jwts.builder()
.setSubject(Long.toString(userDetails.getUser().getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, JWT_SECRET)
.compact();
}
public String generateRefreshToken(CustomUserDetails userDetails){
Date now = new Date();
Date expiryDate = new Date(now.getTime() REFRESH_JWT_EXPIRATION);
return Jwts.builder()
.setSubject(Long.toString(userDetails.getUser().getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, JWT_SECRET)
.compact();
}
public int getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(JWT_SECRET)
.parseClaimsJws(token)
.getBody();
return Integer.parseInt(claims.getSubject());
}
public boolean validateToken(String token){
try{
Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token);
return true;
} catch (MalformedJwtException ex) {
log.error("Invalid JWT token");
} catch (ExpiredJwtException ex) {
log.error("Expired JWT token");
} catch (UnsupportedJwtException ex) {
log.error("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
log.error("JWT claims string is empty.");
}
return false;
}
}
CodePudding user response:
The JWT Authentication Filter will be called for all public and protected requests.
For endpoints that are supposed to be authenticated, the JWTAuthenticationFilter
will expect the JWT to be passed in the request (eg: Authorization Header).
The token passed in then validated thoroughly and this filter then sets an authentication object inside the SecurityContextHolder
.
In case of public requests(the ones that are permitted and not to be authenticated), the JWTAuthenticationFilter
will still be called, however, since this public(or permitted) endpoint will not have a JWT passed in the request, the JWTAuthenticationFilter will not do anything, It will simply pass on the request to the filters down in the FilterChain.
filterChain.doFilter(httpServletRequest, httpServletResponse);//continue applying other filters
In case you do not send a JWT for a protected endpoint, the AnonymousAuthenticationFilter
(which is a built-in filter from Spring Security) fails the authentication. However, the same filter is not called when the endpoint is public.
I believe the reason why JWTAuthenticationFilter is called for all endpoints also depends on its position in the FilterChain.
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
Edit: Your code
else{
Map<String, ArrayList<String>> responseBody = new HashMap<>();
responseBody.put("errors", new ArrayList<>(List.of("Failed to authenticate.")));
httpServletResponse.setStatus(UNAUTHORIZED.value());
httpServletResponse.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getOutputStream(), responseBody);
}
should be removed. If userDetails
is null, then the SecurityContextHolder
will not be populated, and leave the job of failing the authentication to the AnonymousAuthenticationFilter
. You do not need the else block. Remove the try-catch as well. Let Spring Security take care of any exceptions.
CodePudding user response:
Well, I've been trying many different solutions and this came to my mind. It seems to get the work done.
But this is not a good way because there's already a built in JWTFilter in spring security, thanks to @Toerktumlare. You can find the link to the document in his comment above.
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if(httpServletRequest.getServletPath().equals("/api/v1/login")){
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
else{
try {
String jwt = getJwtFromRequest(httpServletRequest);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
int userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = userService.loadUserById(userId);
if (userDetails != null) {
UsernamePasswordAuthenticationToken
authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} else {
Map<String, ArrayList<String>> responseBody = new HashMap<>();
responseBody.put("errors", new ArrayList<>(List.of("Failed to authenticate.")));
httpServletResponse.setStatus(UNAUTHORIZED.value());
httpServletResponse.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getOutputStream(), responseBody);
}
} catch (Exception ex) {
log.info(ex.getMessage());
Map<String, ArrayList<String>> responseBody = new HashMap<>();
responseBody.put("errors", new ArrayList<>(List.of(ex.getMessage())));
httpServletResponse.setStatus(UNAUTHORIZED.value());
httpServletResponse.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getOutputStream(), responseBody);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
However, I still don't understand, isn't it supposed to be that permitted endpoints will be processed without going through the JWTAuthFilter.