Add these dependencies in your pom.xml
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
Add SecurityConfiguration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/api/v1/**")
.authenticated()
.antMatchers("**")
.permitAll()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());
}
}
By default, Spring Security returns errors in HTTP headers. If you have a custom error DTO following method will trigger
your ExceptionAdvice. See SecurityConfiguration
for use of this class.
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException)
throws ServletException, IOException {
resolver.resolveException(request, response, null, authenticationException);
}
}
Example of ExceptionAdvice
@ControllerAdvice
public class ExceptionAdvices {
private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvices.class);
@ExceptionHandler(InsufficientAuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ResponseEntity<ErrorDto> handleAccessDeniedException(
InsufficientAuthenticationException insufficientAuthenticationException) {
String errorMessage = "To access the resource you need to provide valid JWT token";
logger.error(errorMessage, insufficientAuthenticationException);
ErrorDto errorResponseDTO = new ErrorDto();
errorResponseDTO.setMessage(errorMessage);
return new ResponseEntity<>(errorResponseDTO, HttpStatus.UNAUTHORIZED);
}
/*
This is not working. Raised the issue here
https://stackoverflow.com/questions/66885068/
spring-security-handle-invalidbearertokenexception-in-exceptionhandler
*/
@ExceptionHandler(value = InvalidBearerTokenException.class)
public ResponseEntity<ErrorDto> handleInvalidBearerTokenException(
InvalidBearerTokenException invalidBearerTokenException) {
logger.error("Access denied for given token", invalidBearerTokenException);
ErrorDto errorResponseDTO = new ErrorDto();
errorResponseDTO.setMessage(invalidBearerTokenException.getMessage());
return new ResponseEntity<>(errorResponseDTO, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ResponseEntity<ErrorDto> handleAccessDeniedException(AuthenticationException authenticationException) {
logger.error("Access denied for given token", authenticationException);
ErrorDto errorResponseDTO = new ErrorDto();
errorResponseDTO.setMessage(authenticationException.getMessage());
return new ResponseEntity<>(errorResponseDTO, HttpStatus.UNAUTHORIZED);
}
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ResponseEntity<ErrorDto> handleAccessDeniedException(AccessDeniedException accessDeniedException) {
logger.error("Access denied for given role", accessDeniedException);
ErrorDto errorResponseDTO = new ErrorDto();
errorResponseDTO.setMessage(accessDeniedException.getMessage());
return new ResponseEntity<>(errorResponseDTO, HttpStatus.UNAUTHORIZED);
}
}
In resource server, you need to get JWT roles and add to SpringSecurity. Following file does exactly that.
See SecurityConfiguration
for use of this class.
@Component
public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
@Override
public AbstractAuthenticationToken convert(final Jwt jwt)
{
return new JwtAuthenticationToken(jwt, extractResourceRoles(jwt));
}
private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt)
{
Collection<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<>();
JSONArray jsonArray = jwt.getClaim("<roles claim path>");
jsonArray.forEach(json -> grantedAuthorities.add(new SimpleGrantedAuthority(json.toString())));
return grantedAuthorities;
}
}
Then add @PreAuthorize
annotation your method
@PreAuthorize("hasAuthority('ADMIN')")
public void mySecureMethodForAdmin() {
...
}