I am trying to implement SSO in our app using keycloak-spring-security-adapter. The logging itself is working fine, but inside the app we have modules availability based on user roles/groups and i am not able to get user roles from SecurityContext to show users only what they should see.
SecurityContext context = SecurityContextHolder.getContext();
if(context.getAuthentication() != null) {
KeycloakPrincipal principal = (KeycloakPrincipal) context.getAuthentication().getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
AccessToken accessToken = session.getToken();
AccessToken.Access realmAccess = accessToken.getRealmAccess();
logger.info("KEYCLOAK ROLES: " + realmAccess.getRoles());
above logger for my user always gives this:
KEYCLOAK ROLES: [offline_access, uma_authorization]
And these are not the roles registered in keycloak server, because the one used for authenticating my user is:
GSAP_APPLICATION_SUPPORT
I am not able to log into the app with user that is not a member of any keycloak-registered groups so thats why i know this process works fine.
Is there a way of getting list of current user roles from keycloak based on userId/token?
Hardcoding the roles checking inside the service is not a best practice, it's a common approach to divide role based functionalities by API like:
api/v1/admin/**, api/v1/user/**
Using this you can restrict the access to API by roles:
http.authorizeExchange()
.pathMatchers("your_endpoint").hasAnyRole("desired_role");
PS Please pay attention that keycloak adds the "ROLE_" prefix to the rolename, so you can
use ROLE_admin, ROLE_user in your configuration
or
use role names without "ROLE_" prefix (admin, user), and implement the JWT auth converter(example for Reactive (webFlux), you can do similar for Tomcat):
:
Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> getJwtAuthenticationConverter() {var converter = new ReactiveJwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
Map<String, Object> realmAccess = jwt.getClaim("realm_access");
Collection<String> roles = (Collection<String>) realmAccess.get("roles");
return Flux.fromIterable(roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList());
});
return converter;
}
Related
I've been following this tutorial in order to create an Authentication Server, but I'm facing some problems regarding the concepts, I guess.
Look, when I register a Client in Repository, I have to define some parameters, like its id, secret, authentication method, grant types, redirection uris and scopes:
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
When I'm back to my Resource Server, I find that my client was successfully logged in and it returns with an "articles.read" scope. Everything is fine here, supposing that I want to protect my endpoints with the Client's scope, but this is not my case.
In my situation, I want to protect my endpoints according to my User's role in database.
I'll give you an example, so you don't have to read the whole Baeldung's website:
I try to access: http://localhost:8080/articles.
It redirects to: http://auth-server:9000, where a Spring Security Login Form appears.
When you submit the proper credentials (which are compared from a database using the default Spring Security schema), it basically gets you back to: http://localhost:8080/articles.
Well, in that point, I have an Authorization Token with the Client scope, but not the logged User role.
Is there an standard way to configure my project to achieve this or, do I have to think of a creative way to do so?
Thank you in advance.
For role based authentication you should map authorities in Oauth token.
OAuth2AuthenticationToken.getAuthorities() is used for authorizing requests, such as in hasRole('USER') or hasRole('ADMIN').
For this you need to implement the userAuthoritiesMapper, something like this:
#Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
//.oidcUserService(this.oidcUserService());
super.configure(http);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.containsClaim("role")){
String roleName = "ROLE_" + userInfo.getClaimAsString("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role")){
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
});
return mappedAuthorities;
};
}
}
We can dynamically update a logged-in user's authorities, without having to log out and log in, by resetting the Authentication object (security token) in the Spring SecurityContextHolder by using this code.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> updatedAuthorities = new ArrayList<>(auth.getAuthorities());
updatedAuthorities.add(...); //add your role here [e.g., new SimpleGrantedAuthority("ROLE_NEW_ROLE")]
Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), updatedAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
But this code doesn't update authorities in database and I want to update the authorities in database tables of OAuth oauth_access_token and oauth_refresh_token tables. Actually I am working on a social app where user authorities change frequently.
Does Spring provide this feature out of the box?
Or do you have any custom Logic?
You can use TokenStore::storeAccessToken
This worked for my app, when autorities can be chaneged without user logout/login
private final TokenStore tokenStore;
public void updateAuthorities() {
var auth = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> newAuthorities = <Your autorities list>;
var newAuth = new UsernamePasswordAuthenticationToken(
auth.getPrincipal(),
auth.getCredentials(),
newAuthorities
);
newAuth.setDetails(auth.getDetails());
var oauth2Auth = new OAuth2Authentication(auth.getOAuth2Request(), newAuth);
oauth2Auth.setDetails(auth.getDetails());
oauth2Auth.setAuthenticated(true);
OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(auth);
tokenStore.storeAccessToken(existingAccessToken, oauth2Auth);
SecurityContextHolder.getContext().setAuthentication(oauth2Auth);
}
I've got the following set up:
Central auth server written with spring boot that is currently working (I can curl and receive an access token, jdbc token store, etc)
Multiple applications owned by the same developer, sharing the same customer base on different domains. IE: John Doe for app1 is the same as John Doe for app2.
I have an existing application (app1 above) that is jsf 2.2 with spring security configured for login purposes. That application works stand alone right now, with it's own user base.
This is the flow I am trying to obtain:
Resource Owner Password Credential OAuth Flow
So we would want:
User goes to app1
User enters user and password into app1 login page
User hits "login"
Some sort of configuration in Spring would then take the loginByUsername request, get access token from the central oauth server
We now have app1 access - the user could have one of three roles (ADMIN, USER, SUPERUSER).
When they go to (say) app1/views/createEntry.xhtml, we would confirm the access token we currently have is still active on the auth server.
The resource server would technically be the resources on the app1 server (right?)
I'm new to this oauth2.0 process (and spring really), but I think this is the flow I want. How do I set this up with Spring Security? I've seen a security setting called oauth2login() that I think is what we COULD want, but I think that is more authorization code flow.
I haven't found a very good example of this using the password flow.
I do trust each of the applications in this process, hence the password flow. We control the network that maintains traffic between the auth server and the other applications.
Edit: SSO isn't an option because of requirements and our customer base. The applications are unique enough that it doesn't make sense, but the user should be able to log into any of our applications with those credentials.
Edit 2: Sorry for second edit. I would like to add that I've added a resource configuration on app1 and it actually seems like it works - I've secured anything /views/* and when I attempt to go their, I get the expected "Full Authentication required" message.
Edit 3: I think I am making some progress -
First, I created a spring component that implements AuthenticationProvider and then overwrote the authenticate method so that I created a ResourceOwnerPasswordResourceDetails object with all my properties (client id, client secret, grant type, scope, etc) and called the authorization server to get a token. My excitement to see my log refresh for the authorization server was high.
Next step I need to figure out is how to generate an extension of org.springframework.security.core.userdetails.User so that I can store the privileges for the user.
Also - I can't quite figure out yet how the token is stored. I know the auth server generates a token and stores in jdbc, but where/how does the token get stored on the client side?
For those that were curious, here is how I set up the authentication provider on my client (app1). I still have issues with the resource server (ill ask a separate question), but here is what I did:
Custom authenticator:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AppUserDAO appUserDAO;
private String accessTokenUri = "http://localhost:8080/oauth/token";
private String clientId = "clientid";
private String clientSecret = "clientsecret";
public AccessTokenProvider userAccessTokenProvider() {
ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
return accessTokenProvider;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String username = authentication.getName();
final String password = authentication.getCredentials().toString();
List<String> scopes = new ArrayList<String>();
scopes.add("read");
final ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setUsername(username);
resource.setPassword(password);
resource.setAccessTokenUri(accessTokenUri);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setGrantType("password");
resource.setScope(scopes);
// Generate an access token
final OAuth2RestTemplate template = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
template.setAccessTokenProvider(userAccessTokenProvider());
OAuth2AccessToken accessToken = null;
try {
accessToken = template.getAccessToken();
System.out.println("Grabbed access token from " + accessTokenUri);
}
catch (OAuth2AccessDeniedException e) {
if (e.getCause() instanceof ResourceAccessException) {
final String errorMessage = String.format(
"While authenticating user '%s': " + "Unable to access accessTokenUri '%s'.", username,
accessTokenUri);
throw new AuthenticationServiceException(errorMessage, e);
}
throw new BadCredentialsException(String.format("Access denied for user '%s'.", username), e);
}
catch (OAuth2Exception e) {
throw new AuthenticationServiceException(
String.format("Unable to perform OAuth authentication for user '%s'.", username), e);
}
// Determine roles for user
List<GrantedAuthority> grantList = ...
// Create custom user for the principal
User user = .....
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, null /*dont store password*/, grantList);
return token;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
....
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
}
I am doing a project on library management system in spring boot security.
In order to calculate the fines for the issued books according to the roles i wan the current user role after borrowing a book.
Current user name, role book_id and fine will be stored in other table.
I am able to get the current users username, but not able to get role the current user.
Could someone please help me out!
//Part of Controller class
#RequestMapping("/homepage/borrowBook")
public String addBookings(Bookings bk, HttpServletRequest rqst) {
rqst.setAttribute("mode", "MODE_BORROW");
return "homepage";
}
#PostMapping("/homepage/save-borrow")
public String saveBorrow(Bookings bk, HttpServletRequest rqst, Authentication auth) {
rqst.setAttribute("mode", "MODE_BORROW");
if (BookRepo.exists(bk.getBook_id())) {
bk.setUser(auth.getName());
/////here i want the current user authority to be saved/checked.
bookingsRepo.save(bk);
return "homepage";
} else {
rqst.setAttribute("error", "Book doesn't exist");
return "homepage";
}
}
You can use Authentication.getAuthorities() to get the roles of the currently logged in user.
You can get the authorities using the SecurityContextHolder or through the inject Authentication object at your controller.
Find below through the SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Collection<SimpleGrantedAuthority> list = (Collection<SimpleGrantedAuthority>) auth.getAuthorities();
for (SimpleGrantedAuthority permission : list) {
System.out.println(permission.getAuthority());
}
If you need any other information about the logged in user, you can access the UserDetails as follows
User userDetails = (User) auth.getPrincipal();
I need to retrieve the roles associated to user, but I am working with wildfly, I have installed all jar keycloak in wildfly and my Java project, but can I retrieve this list by Java adapter?
Other options is call the rest api like any api by get, post, put, etc. But my first options was Adapters.
I make the authentication by adapters, but I do not find any way to retrieve roles, clients, realms, etc.
I am wrong or the adapter is just to authentications?
Anyone have a good example?
Set the option use-resource-role-mappings : true in keycloak.json
and you should be able to get roles in servlet as follows
KeycloakPrincipal principal = (KeycloakPrincipal)request.getUserPrincipal();
principal.getKeycloakSecurityContext().getToken().getResourceAccess("testclient").getRoles();
You can also get KeycloakPrincipal from context like this
Subject subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
Set<KeycloakPrincipal> principals = subject.getPrincipals(KeycloakPrincipal.class);
and then get the roles
Thanks, here other way: (retrieve one role by name)
Keycloak keycloak = Keycloak.getInstance("http://localhost/auth", "realm-name", "client-name", authorization);
RoleRepresentation role = keycloak.realm("realm-name").clients().get(idOfClient).roles().get(roleName).toRepresentation();
To listing all user:
UsersResource users = keycloak.realm("realm-name").users();
And "authorization" is the string token bearer
"getInstance" have other methods to send for example pass and user.
If anyone else is still struggling, here's a complete answer:
Create a security context producer:
#RequestScoped
public class SecurityContextProducer {
#Inject
private HttpServletRequest request;
#Produces
public KeycloakSecurityContext getSecurityContext() {
return ((KeycloakPrincipal) request.getUserPrincipal())
.getKeycloakSecurityContext();
}
}
Use it like this:
#Inject
private KeycloakSecurityContext keycloakSecurityContext;
public List<String> getRolesKeycloak() {
Set<String> roleNames = keycloakSecurityContext.getToken().getRealmAccess().getRoles();
List<String> targetList = new ArrayList<>(roleNames);
return targetList;
}
It's not exactly the topic but I needed to find the roles associated with a specific user and this question pops first with my keywords web search. Here's what worked for me with keycloak client 13.0.1
RealmResource realmResource = keycloak.realm(REALM);
UsersResource usersResource = realmResource.users();
UserResource userResource = usersResource.get(USER_ID);
RoleMappingResource roleMappingResource = userResource.roles();
// either realmLevel or clientLevel
RoleScopeResource roleScopeResource = roleMappingResource.realmLevel();
List<RoleRepresentation> rolesRepresentation = roleScopeResource.listAll();
I didn't find it elsewhere, I hope it can be useful.