Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security 5 populating authorities based on JWT claims

As I see Spring Security OAuth2.x project was moved to Spring Security 5.2.x. I try to implement authorization and resource server in new way. Everythin is working correctly except one thing - @PreAuthorize annotation. When I try to use this with standard @PreAuthorize("hasRole('ROLE_USER')") I always get forbidden. What I see is that the Principal object which is type of org.springframework.security.oauth2.jwt.Jwt is not able to resolve authorities and I have no idea why.

org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken@44915f5f: Principal: org.springframework.security.oauth2.jwt.Jwt@2cfdbd3; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffffa64e: RemoteIpAddress: 172.19.0.1; SessionId: null; Granted Authorities: SCOPE_read, SCOPE_write

And claims after casting it to Jwt

{user_name=user, scope=["read","write"], exp=2019-12-18T13:19:29Z, iat=2019-12-18T13:19:28Z, authorities=["ROLE_USER","READ_ONLY"], client_id=sampleClientId}

Security Server Configuration

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

  @Autowired
  private DataSource dataSource;

  @Autowired
  private AuthenticationManager authenticationManager;

  @Bean
  public KeyPair keyPair() {
    ClassPathResource ksFile = new ClassPathResource("mytest.jks");
    KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(ksFile, "mypass".toCharArray());
    return keyStoreKeyFactory.getKeyPair("mytest");
  }

  @Bean
  public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setKeyPair(keyPair());
    return converter;
  }

  @Bean
  public JWKSet jwkSet() {
    RSAKey key = new Builder((RSAPublicKey) keyPair().getPublic()).build();
    return new JWKSet(key);
  }

  @Bean
  public TokenStore tokenStore() {
    return new JwtTokenStore(accessTokenConverter());
  }

  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.jdbc(dataSource);
  }

  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints.tokenStore(tokenStore())
        .accessTokenConverter(accessTokenConverter())
        .authenticationManager(authenticationManager);
  }

  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) {
    security.tokenKeyAccess("permitAll()")
        .checkTokenAccess("isAuthenticated()");
  }
}

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  private UserDetailsService userDetailsService;

  public SecurityConfiguration(UserDetailsService userDetailsService) {
    this.userDetailsService = userDetailsService;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .mvcMatchers("/.well-known/jwks.json")
            .permitAll()
            .anyRequest()
            .authenticated();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
  }
}

Resource server configuration

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResuorceServerConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt();
    }
}

Maybe someone had similar issue?

like image 341
bartoszsokolik Avatar asked Dec 17 '19 17:12

bartoszsokolik


People also ask

How to authenticate using JWT in spring?

Show activity on this post. You can use a combination of a Jackson Object Mapper and Spring Security classes, namely Jwt, JwtHelper and Authentication. You can get the authentication by using Spring Security's static context object and then parse the token you receive using the JwtHelper.

How to add custom claim to JWT using authorization server?

We will create an authorization server and configure it to add a custom claim to JWT. 1.1. Authorization Server Let’s start by creating a configuration class that extends WebSecurityConfigurerAdapter in which we configure http security, set up in-memory authentication manager, and create some beans for further use:

Where are JWT tokens stored in Spring Security?

The token is usually generated in the server and sent to the client where it is stored in the session storage or local storage. To access a protected resource the client would send the JWT in the header as given above. We will see the JWT implementation in Spring Security in the section below.

What kind of filter does JWT Spring Security use?

JWT Filters One important thing to know about Spring Security is that it uses Servlet filters. You can imagine a filter, like the one you find in a coffee machine. You pour water over it, and the filter modifies the water (mixes it with coffee) and lets out some kind of modified fluid: coffee.


1 Answers

By default, the resource server populates the authorities based on the "scope" claim.
If the Jwt contains a claim with the name "scope" or "scp", then Spring Security will use the value in that claim to construct the authorities by prefixing each value with "SCOPE_".

In your example, one of the claims is scope=["read","write"].
This means that the authority list will consist of "SCOPE_read" and "SCOPE_write".

You can modify the default authority mapping behaviour by providing a custom authentication converter in your security configuration.

http
    .authorizeRequests()
        .anyRequest().authenticated()
        .and()
    .oauth2ResourceServer()
        .jwt()
            .jwtAuthenticationConverter(getJwtAuthenticationConverter());

Then in your implementation of getJwtAuthenticationConverter, you can configure how the Jwt maps to the list of authorities.

Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(jwt -> {
        // custom logic
    });
    return converter;
}
like image 66
Eleftheria Stein-Kousathana Avatar answered Oct 18 '22 16:10

Eleftheria Stein-Kousathana