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:
SecurityFilterChain
bean(s) is (or are) misconfigured.A quite common mistake lately is to write oauth2Login
configuration and then send requests with Bearer
header, when incoming requests should be authorized with session cookies. If you want the requests to your REST API be authorized with access tokens provided in a Bearer
Authorization header, then you have to configure it as an oauth2ResourceServer
.
Another common mistake relates to apps needing more than one authorization mechanism (OAuth2 client, OAuth2 resource server, Basic, etc.). In such cases, you should provide with a filter-chain bean for each. But if one of the first in @Order
intercepts a request it shouldn't (misconfigured securityMatcher
), then the security context initialization fails.
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, or this Baeldung article I wrote.
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.
3.4
update (Security 6.4
)The default authentication converter for resource servers with a JWT decoder is now configurable with the claim to take the username from (provided that this claim is at the root level, not a nested one). So, it is not needed anymore to create a new Authentication
type or to replace the authentication converter:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${issuer-uri}
principal-claim-name: preferred_username
Unfortunately, Keycloak roles can be found in several nested claims (realm_access.roles
and resource_access.{client-id}.roles
), but Spring Boot supports only one claim for it and with the same "root-level" limitation, so it is likely that we need to replace the authentication converter with Keycloak, unless using this starter I wrote (allows to get authorities from as many claims as we need using JsonPath, which enables to read nested claims).