简体   繁体   中英

Spring Boot with Spring Security and Keycloak only providing Anonymous Authentication Token

so I followed a guide which set up Keycloak to secure my Spring Boot REST API with Spring Security. Previously, my application relied on a standard user details service to authenticate users. My code relies on being able to access the currently logged-in user's username. However, now that I switched to Keycloak, I only receive the username 'anonymousUser'.

How do I configure this correctly for the following code to return the preferred username:

public static String getCurrentUsername() {

return (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

}

I tried to modify the configuration of the client and user on Keycloak, but even for example, adding roles has no effect. Spring Boot only returns the role of [ANONYMOUS_USER].

There is no chance you get any info about current user from AnonymousAuthenticationToken : this implementation of Authentication is put in security-context when the request was not successfully authorized (user could not be identified). This means that either:

  • access token is missing from authorization header
  • access token is invalid (expired, issued by wrong authorization-server, signature check failed, ...)
  • resource-server is misconfigured (which is quite likely as you are writing about client configuration for a REST API which actually is a resource-server )

For basic info on how to configure Spring-boot 3 resource-server (and client) with Keycloak, see my answer to this question: Use Keycloak Spring Adapter with Spring Boot 3

Spring-security default Authentication for successful OAuth2 authorization in resource-servers is JwtAuthenticationToken which return a Jwt instance as principal . You can get any access-token claim from this Jwt instance. Here is a sample with preferred_username . Open an access-token in a tool like https://jwt.io to find the name of the claim with the username value you are looking for (it could be as well sub , email or any private claim you configured in Keycloak)

((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getClaims().get(StandardClaimNames.PREFERRED_USERNAME);

If you want getPrincipal() to return the username instead of a Jwt instance, you'll have to provide your own Authentication implementation. You might use JwtAuthenticationToken as a base (but this not the best option, see the note below):

public class MyAuthentication extends JwtAuthenticationToken {
    public MyAuthentication(Jwt jwt, Collection<? extends GrantedAuthority> authorities) {
        super(jwt, authorities);
    }
    
    @Override
    public String getPrincipal() {
        return getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME);
    }   
}

Just adapt the Jwt2AuthenticationConverter from the answer I linked above

@Bean
public Jwt2AuthenticationConverter authenticationConverter(Jwt2AuthoritiesConverter authoritiesConverter) {
    return jwt -> new MyAuthentication(jwt, authoritiesConverter.convert(jwt));
}

Important note

You'd better use authentication.getName() instead of authentication.getPrincipal() to access username. principal is typed as Object in Authentication which makes your expressions very fragile: you can get absolutely any type of data as principal (depending on the type of authentication in the security context) and there are cases where you don't really control it (for instance with the AnonymousAuthenticationToken instance you currently have because of unauthorized request).

However, JwtAuthenticationToken::getName returns subject ( sub claim), so you'll still have to provide your own Authentication implementation for successful authorizations to return preferred_username in a new @Override of getName() . MyAuthentication would then be:

public class MyAuthentication extends JwtAuthenticationToken {
    public MyAuthentication(Jwt jwt, Collection<? extends GrantedAuthority> authorities) {
        super(jwt, authorities);
    }
    
    //Note that this time getName() is overriden instead of getPrincipal()
    @Override
    public String getName() {
        return getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME);
    }   
}

Use the same Jwt2AuthenticationConverter bean to switch from default JwtAuthenticationToken to MyAuthentication in case of successful request authorization.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM