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:
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));
}
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.