Skip to content

Instantly share code, notes, and snippets.

@ghsatpute
Last active April 7, 2021 11:13
Show Gist options
  • Save ghsatpute/47fc3c53fa20975ff2ae479d1d0ede0e to your computer and use it in GitHub Desktop.
Save ghsatpute/47fc3c53fa20975ff2ae479d1d0ede0e to your computer and use it in GitHub Desktop.
Spring Security: Resource Server

Spring Security: Resource Server

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() {
       ...
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment