sympatric greg
sympatric greg

Reputation: 3169

Spring boot authorization returns 403 for any authorization request using @RolesAllowed, @Secured or @PreAuthorize

I've been working from this article (and a few other similar ones): https://medium.com/omarelgabrys-blog/microservices-with-spring-boot-authentication-with-jwt-part-3-fafc9d7187e8

The client is an Angular 8 app which acquires a Jwt from an independent microservice. Trying to add filter(s) to a different microservice to require specific authorization via jwt roles.

Consistently receiving 403 errors.

Security Config:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true,
        securedEnabled = true,
        jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurityConfig() {}

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors().and().csrf().disable()
                // make sure we use stateless session; session won't be used to store user's state.
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // Add a filter to validate the tokens with every request
                .addFilterAfter(new JwtAuthorizationFilter2(), UsernamePasswordAuthenticationFilter.class)
                // authorization requests config
                .authorizeRequests()
                // Any other request must be authenticated
                .anyRequest().authenticated();
    }
}

Filter:

public class JwtAuthorizationFilter2 extends OncePerRequestFilter {

    private final String HEADER = "Authorization";
    private final String PREFIX = "Bearer ";
    private final String SECRET = "foo";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String token = request.getHeader(SecurityConstants.HEADER_STRING);
        if (token != null) {
            // parse the token.
            DecodedJWT decoded = JWT.require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes()))
                    .build()
                    .verify(token.replace(SecurityConstants.TOKEN_PREFIX, ""));

            String user = decoded.getSubject();
            List<SimpleGrantedAuthority> sgas = Arrays.stream(
                    decoded.getClaim("roles").asArray(String.class))
                    .map( s -> new SimpleGrantedAuthority(s))
                    .collect( Collectors.toList());
            if (sgas != null) {
                sgas.add(new SimpleGrantedAuthority("FOO_Admin"));
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                        user,
                        null,
                        sgas);
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
            else {
                SecurityContextHolder.clearContext();
            }
            chain.doFilter(request, response);
        }
    }
}

This code works fine without any authorization requirements defined, but if an authorization is defined in WebSecurityConfig, or by decorating a controller method, http 403 is received for all requests in scope.

Examples:

.authorizeRequests().antMatchers("/**").hasRole("FOO_Admin")

// or any of these 
@PreAuthorize("hasRole('FOO_Admin')")
@RolesAllowed({"FOO_Admin"})
@Secured({"FOO_Admin"})
Device get(@PathVariable String id) {
    // some code
}

When code is halted at SecurityContextHolder.getContext().setAuthentication(auth), auth.authenticated = true and auth.authorities includes a SimpleGrantedAuthority for "FOO_Admin"

So I'm wondering whether: The FilterChain needs an Authentication Filter (or does authentication occur in JwtAuthorizationFilter2?)? There is not a spelling or formatting or capitalization difference to role name.

I'm stupefied. Any help would be greatly appreciated.

Upvotes: 1

Views: 465

Answers (1)

Ken Chan
Ken Chan

Reputation: 90427

@PreAuthorize("hasRole('FOO_Admin')) expects the user has an authority ROLE_FOO_Admin, which will be prefixed by ROLE_. However, the user only has the authority FOO_Admin , hence it fails to access the method.

You have several options:

(1) Change the prefix by declaring a GrantedAuthorityDefaults bean:

@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults("FOO");
}

And use @PreAuthorize(hasRole('Admin')) to secure the method.

(2) Or more simpler is to use @PreAuthorize("hasAuthority('FOO_Admin')") , which will directly check if the user has the authority FOO_Admin , without adding any prefix to it.

P.S JwtAuthorizationFilter2 only verifies if an user is valid and get the related user information which prepare for the authorization user later. It is an authentication and I would rename it to JwtAuthenticationFilter2 to describe more exactly what it does actually.

Upvotes: 2

Related Questions