micronaut-projects / micronaut-security

The official Micronaut security solution
Apache License 2.0
170 stars 126 forks source link

Get role from nested value in Authentication attributes map #1132

Open BramMeerten opened 1 year ago

BramMeerten commented 1 year ago

Feature description

The property micronaut.security.token.roles-name allows you to specify the authentication attributes map key for the user’s roles.

But my JWT token payload looks like this:

...
"realm_access": {
    "roles": [
      "manager",
      "offline_access",
      "uma_authorization"
    ]
  },
...

It would be useful to be able to specify a nested key. In this case that would be: micronaut.security.token.roles-name: realm_access.roles

Relevant code can be found here: https://github.com/micronaut-projects/micronaut-security/blob/master/security/src/main/java/io/micronaut/security/token/DefaultRolesFinder.java#L81

A simular question (with workaround) has been posted on the micronaut gitter channel: https://gitter.im/micronautfw/questions?at=5e4501aab612cc7bb1643207

BramMeerten commented 1 year ago

This is a workaround that I use:

@Singleton
@Replaces(DefaultRolesFinder.class)
public class RolesFinderReplacement extends DefaultRolesFinder {

    public static final String REALM_ACCESS = "realm_access";
    public static final String ROLES = "roles";

    private final TokenConfiguration tokenConfiguration;

    public RolesFinderReplacement(TokenConfiguration tokenConfiguration, TokenConfiguration tokenConfiguration1) {
        super(tokenConfiguration);
        this.tokenConfiguration = tokenConfiguration1;
    }

    @Override
    public List<String> resolveRoles(@Nullable Map<String, Object> attributes) {
        if (attributes != null) {
            var realmAccess = attributes.get(REALM_ACCESS);
            if (realmAccess instanceof Map) {
                var realmRoles = ((Map<?, ?>) realmAccess).get(ROLES);
                return super.resolveRoles(Map.of(tokenConfiguration.getRolesName(), realmRoles));
            }
        }

        return super.resolveRoles(attributes);
    }
}

In the gitter link in description is another workaround available.

sdelamo commented 1 year ago

I am not sure what we ca do. We created the RolesFinder API specifically so that users could create their own custom implementation as you did.

msincuba commented 9 months ago

This is a workaround that I use:

@Singleton
@Replaces(DefaultRolesFinder.class)
public class RolesFinderReplacement extends DefaultRolesFinder {

    public static final String REALM_ACCESS = "realm_access";
    public static final String ROLES = "roles";

    private final TokenConfiguration tokenConfiguration;

    public RolesFinderReplacement(TokenConfiguration tokenConfiguration, TokenConfiguration tokenConfiguration1) {
        super(tokenConfiguration);
        this.tokenConfiguration = tokenConfiguration1;
    }

    @Override
    public List<String> resolveRoles(@Nullable Map<String, Object> attributes) {
        if (attributes != null) {
            var realmAccess = attributes.get(REALM_ACCESS);
            if (realmAccess instanceof Map) {
                var realmRoles = ((Map<?, ?>) realmAccess).get(ROLES);
                return super.resolveRoles(Map.of(tokenConfiguration.getRolesName(), realmRoles));
            }
        }

        return super.resolveRoles(attributes);
    }
}

In the gitter link in description is another workaround available.

I had the same issue on keycloak and realised that it's the role mapping issue. I had Token Claim Name as "realm_access.roles" instead of "roles" and my problem was solved after changing to roles.

Screenshot from 2024-01-02 16-19-32