Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adopt authorities from JWT in Spring Resource Server

My Spring OAuth2 client only grants the ROLE_USER authority to authenticated users, ignoring the authorities from resource_access in the provided JWT.

{
"wdb": {
  "roles": [
    "TestRole",
    "TestRoleFoo",
    "TestRoleBar"
  ]
}

How can I setup my OAuth2 client to also grant the authorities from resource_access (TestRole, TestRoleFoo, TestRoleBar)? Am I missing some crucial configuration here?

My configuration in detail

On my Resource Server, I'm using Springs default OAuth2 client with the following configuration:

security:
  oauth2:
    client:
      client-id: wdb
      client-secret: some-secret
      access-token-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/token
      user-authorization-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/auth
      scope: openid profile email
      authorized-grant-types: code
    resource:
      user-info-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/userinfo 

My Keycloak Authorization Server provides me with the following JWT payload:

{
  "jti": "6a666808-2b69-4de0-ab94-9ceebdac13de",
  "exp": 1569674641,
  "nbf": 0,
  "iat": 1569674341,
  "iss": "http://localhost:8080/auth/realms/master",
  "aud": "account",
  "sub": "f19b0443-4cce-495a-8479-ff36f82628fc",
  "typ": "Bearer",
  "azp": "wdb",
  "auth_time": 1569674341,
  "session_state": "0a411eda-0efb-4f29-99c4-b54da6298d6c",
  "acr": "1",
  "allowed-origins": [
    "/*"
  ],
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "wdb": {
      "roles": [
        "TestRole",
        "TestRoleFoo",
        "TestRoleBar"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid profile email",
  "email_verified": true,
  "user_name": "sullrich",
  "name": "Sebastian Ullrich",
  "preferred_username": "sullrich",
  "given_name": "Sebastian",
  "locale": "de",
  "family_name": "Ullrich",
  "email": "[email protected]"
}

Within my Resource Server, this JWT will be derived to the following OAuth2Authentication:

{
   "authorities":[
      {
         "authority":"ROLE_USER"
      }
   ],
   "details":{
      "remoteAddress":"0:0:0:0:0:0:0:1",
      "sessionId":"... session id ...",
      "tokenValue":"... encoded payload ...",
      "tokenType":"bearer"
   },
   "authenticated":true,
   "userAuthentication":{
      "authorities":[
         {
            "authority":"ROLE_USER"
         }
      ],
      "details":{
         "sub":"f19b0443-4cce-495a-8479-ff36f82628fc",
         "email_verified":true,
         "user_name":"sullrich",
         "name":"Sebastian Ullrich",
         "preferred_username":"sullrich",
         "given_name":"Sebastian",
         "locale":"de",
         "family_name":"Ullrich",
         "email":"[email protected]"
      },
      "authenticated":true,
      "principal":"Sebastian Ullrich",
      "credentials":"N/A",
      "name":"Sebastian Ullrich"
   },
   "principal":"Sebastian Ullrich",
   "credentials":"",
   "clientOnly":false,
   "oauth2Request":{
      "clientId":"wdb",
      "scope":[

      ],
      "requestParameters":{

      },
      "resourceIds":[

      ],
      "authorities":[

      ],
      "approved":true,
      "refresh":false,
      "responseTypes":[

      ],
      "extensions":{

      }
   },
   "name":"Sebastian Ullrich"
}
like image 923
Sebastian Ullrich Avatar asked Sep 11 '25 10:09

Sebastian Ullrich


1 Answers

Sounds like you need a custom JwtAuthenticationConverter Spring will only map scopes into granted authorities by default.

You can create a class that extends the default implementation and overrides the extractAuthorities method. Then you have access to the claims and you can map them to the roles you want.

public class JwtGrantedAuthoritiesConverter extends JwtAuthenticationConverter { 


    @Override 
    protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) { 
        Collection<GrantedAuthority> authorities =  super.extractAuthorities(jwt); 
        if(jwt.containsClaim("roles") && jwt.getClaimAsStringList("roles").contains("TestRole")) { 
            authorities.add(new SimpleGrantedAuthority("ROLE_TestRole")); 
        } else { 
            .........
        } 
        return authorities; 
    } 

Then you plug in your version into the resource server in your WebSecurityConfigurationAdapter:

@Override 
    protected void configure(HttpSecurity http) throws Exception { 
        http.
......
            .oauth2ResourceServer() 
                .jwt() 
                    .jwtAuthenticationConverter(new JwtGrantedAuthoritiesConverter());


Your roles are a bit more nested i.e. under resource_access . wdb You can always create a keycloak mapper to add them under roles in the parent node to simplify things.

Here is an example of a resource server that does something similar https://github.com/wlesniak/effective-oauth2-with-spring-security-and-spring-boot/tree/master/module_8/mod8_support-service

like image 58
Wojciech Lesniak Avatar answered Sep 15 '25 20:09

Wojciech Lesniak