Authorization mechanism in Keycloak looking in DB for roles/permissions - java

I need to implement authorization mechanism in Keycloak which looks into my customer's DB for user's permission based in roles.
I can't manage such roles and permissions in Keycloak's admin console but I will have to write some SPI to implement my authorization logic. Something like implementing a User Storage Provider to look in DB for user/password autentication.
But I can't find how to implement it. I think that, after obtaining a valid token for the user in authentication process, the application should send that token along with the permission needed to perform an action so Keycloak (my own SPI implementation) would validate the token and search the DB to grant permission.
Maybe the flow of authorization is different to what I think (what I wrote before).
Any clue would be appreciated.
How should the application send the token + permission to Keycloak?
How to implement a SPI which responds to such petition and validates the token?

To answer your first question: Keycloak will handle the authorization flow for you, you don't need to worry about that part. All you need to do is to provide the User Storage SPI by implementing the methods of the UserStorageProvider interface.
Keycloak documentation: https://www.keycloak.org/docs/latest/server_development/index.html#_user-storage-spi
To also add the roles/permissions you need to implement the UserModel interface which has methods for handling roles/permissions. keycloak documentation: https://www.keycloak.org/docs/latest/server_development/index.html#model-interfaces
To combine these two, you would fetch the user record from the Oracle DB using the UserStorageProvider and fill in the UserModel fields (email, name, roles, etc..).
Ex pseudo code:
NOTE: look at keycloak documentation for a more detailed walkthrough: https://www.keycloak.org/docs/latest/server_development/index.html#simple-read-only-lookup-example
public class OracleDbUserStorageProvider implements UserStorageProvider, UserLookupProvider {
private Map<String, UserModel> loadedUsers = new HashMap<>();
private OracleDbRepository oracleDbRepository;
​ #Override
​public UserModel getUserByUsername(String username, RealmModel realm) {
​UserModel user = loadedUsers.get(username);
​if (user == null) {
​OracleUser oracleUser = oracleDbRepository.getUser(username);
​if (oracleUser != null) {
​user = convertToUserModel(oracleUser);
​loadedUsers.put(username, user);
​}
​}
​return user;
​}
private UserModel convertToUserModel(OracleUser oracleUser) {
// take oracleUser attributes and assign to a UserModel instance
return userModel; // user model converted
}
}
Hope this helps set you in the right direction.

Related

How to filter data by user in spring boot applcation

I'm making a simple spring boot application and I want to incorporate filtering data by logged-in user or user session. But I don't know what is the best way to set up the application in a way that every user can access data specific to him. I have made the authentification and authorization part of the app.
I'm also working on that kind of application and I've done this by adding as argument#AuthenticationPrincipal CustomUserDetails userDetails to method where you want to get userId. Then you can call: Integer userId = userDetails.getUserId();
So for example something like this:
#GetMapping("/demo")
public String showTemplate(#AuthenticationPrincipal CustomUserDetails userDetails) {
Integer userId = userDetails.getUserId();
return "index";
}
And then you can do with this userId whatever you want. For example, create method in repository findByUserId

Spring Boot oauth2 with local user database

I have implemented OIDC authentication in my Spring Boot web application by adding the spring-boot-starter-oauth2-client dependency and configuring OAuth2 client settings in application.properties.
In the Spring Boot and OAuth2 guide there is a section "How to Add a Local User Database":
How to Add a Local User Database
Many applications need to hold data
about their users locally, even if authentication is delegated to an
external provider. We don’t show the code here, but it is easy to do
in two steps.
Choose a backend for your database, and set up some repositories
(using Spring Data, say) for a custom User object that suits your
needs and can be populated, fully or partially, from external
authentication.
Implement and expose OAuth2UserService to call the Authorization
Server as well as your database. Your implementation can delegate to
the default implementation, which will do the heavy lifting of calling
the Authorization Server. Your implementation should return something
that extends your custom User object and implements OAuth2User.
Hint: add a field in the User object to link to a unique identifier in
the external provider (not the user’s name, but something that’s
unique to the account in the external provider).
I have searched a bit but I have not found a code example for the scenario described in the excerpt.
What is the best way to implement the scenario above?
I guess the main parts would be:
On OIDC login, automatically create a user in the database if it does not exist
The web application controller methods have access to the database object that represents the logged-in user
Update:
The guide has a github issue comment that suggests to look at the custom-error sample from the guide's source code. I guess the first part (on OIDC login, automatically create a user if one does not exist) can be done after the call to DefaultOAuth2UserService().loadUser(request). But what about the second part - how can my custom db-backed-user-object be made available to my web application's controller methods?
#Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService(WebClient rest) {
DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
return request -> {
OAuth2User user = delegate.loadUser(request);
if (!"github".equals(request.getClientRegistration().getRegistrationId())) {
return user;
}
OAuth2AuthorizedClient client = new OAuth2AuthorizedClient
(request.getClientRegistration(), user.getName(), request.getAccessToken());
String url = user.getAttribute("organizations_url");
List<Map<String, Object>> orgs = rest
.get().uri(url)
.attributes(oauth2AuthorizedClient(client))
.retrieve()
.bodyToMono(List.class)
.block();
if (orgs.stream().anyMatch(org -> "spring-projects".equals(org.get("login")))) {
return user;
}
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_token", "Not in Spring Team", ""));
};
}
Github uses OAuth2UserService<OAuth2UserRequest, OAuth2User> what you need is OAuth2UserService<OidcUserRequest, OidcUser>. So
did you try creating another #Bean which plugs into the correct place spring expects?
If not, create one like this
#Bean
public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
OidcUser user = delegate.loadUser(userRequest);
log.info("User from oauth server: " + user);
//OAuth2AccessToken accessToken = userRequest.getAccessToken();
//Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
//Fetch the authority information from the protected resource using accessToken
//Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
//Create a copy of user using mappedAuthorities
//Insert/update local DB
//user = new DefaultOidcUser(mappedAuthorities, user.getIdToken(), user.getUserInfo());
return user;
};
}

Assigning Roles to external User in Keycloak Using User SPI

I have implemented User Storage SPI in Keycloak using Spring Boot example given in official docs. For External data store i have used Sql Server. The Keycloak User SPI is working fine to authenticate the user present in Sql Server and I can get successfully access token using Keycloak REST API.
Now I want to assign a keycloak role to the external user and I am not sure how to do it.
If further details are required regarding the question then feel free to ask.
Here is the User Federation details that i've registered:
User Federation:
Note: During the login process using keycloak User SPI, these functions are getting called getUserById() & getUserByEmail()/getUserByUserName()
Keycloak is storing your user data under FED_* tables. If you want to map roles you can override method which will be called by keycloak in your UserAdapter (AbstractUserAdapterFederatedStorage):
override fun getRoleMappingsInternal(): MutableSet<RoleModel> {
val roles = mutableSetOf<RoleModel>()
val myProviderRoles: List<String> = user.roles
for (it in myProviderRoles) {
realm.getClientsStream(-1, -1).forEach { client ->
val role = client.getRole(it)
if (role != null) roles += role
}
}
return roles
}
Then when you are creating adapter fill in the roles you want:
override fun createAdapter(myUser: MyUser, session: KeycloakSession, realm: RealmModel, model: UserStorageProviderModel): UserModel {
myUser.roles = repositoryRoles.getUserRoles(myUser.id).map { it.name }
return UserAdapter(myUser, session, realm, model)
}

Spring Security Logout From Stateless server

I am creating a stateless REST API with spring boot.
Therefore I am using a token based authentication.
Currently the logout functionality is only implemented on the client side.
I just clear all cookies.
Problem is that the user object seems to survive the request so it still exists in the next requests.
My service to get the current user is simply:
#Service
public class UserService {
private User user;
#Autowired
private UserRepository;
public User get() {
if (user != null) {
return user;
}
Integer id = (Integer) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
user = userRepository.findById(id);
return user;
}
}
I would expect that the user variable is null on every request? The funny thing is that the correct user id is set in the security context. But the service returns the user object because it already exists.
You shouldn't use user as a class attribute.
UserService is a singleton, what happens when you have concurrent requests coming from different users?
Move this variable inside the get method.
Moreover, if you are using JWT as the token based authentication take a look at this project.
With JWT you can retrieve the user required informations directly from the token without performing any queries.

Manually set user's Roles in JSF

I have some JSF 2 applications that are currently working with a JSF Secutiry LoginModule (auth-method = FORM). But authentication will be done now through a new way, that means I'll have to manually program the authentication interactions.
That's fine, but now I have problems setting the roles. I couldn't find where I can set the Principals, or get subject to do it, or get shared state to put "javax.security.auth.principal" and "javax.security.auth.roles" variables.
Is there a way to do it? Here is a sample of my actual Bean code.
Thanks in advance!
#ManagedBean
#ViewScoped
public class PrincipalController extends AbstractController implements ExcluirRascunhoService.Presenter {
// has get and set
#ManagedProperty(value = "#{autenticacaoController}")
private AutenticacaoController autenticacaoController;
#PostConstruct
private void init() {
try {
// a previous application redirected the user here,
// giving two parameters, including a valid and calculated HASH
// to be passed to authentication
Map<String, String> requestMap = getContext().getRequestParameterMap();
String user = (String) requestMap.get("login");
String hash = (String) requestMap.get("hash");
// this will do the authentication, communicating with a
// webservice and passing these data so the webservice can
// authenticate the data, telling me if the user is Ok
autenticacaoController.authenticate(user, hash);
// do the other things if authentication doesn't throw an exception
// I should now fill all user's Roles accordingly to my database
// I get them correctly, but how to set them into the JSF Roles?
} catch (AuthenticationException e) {
// catch and quit the page
}
}
}
You can't do it with JSF alone. Basically, JSF only provide a utility to get the user and it's role directly from the JSF interface. So, if you want to access the principal user and it's roles from your application you must first authenticate the user.
To authenticate your user you can use an third party solution like JAAS or Apache Shiro to setup the realm, roles and control the authentications of your application.
You can also roll your own authentication layer, which may not give you the possibility to use some useful JSF utilities like getting the principal user or it's roles directly from the realm (note that your custom layer will probably provide other ways to get these values), but will also provide a custom way to do the authentication required.
I have found a pretty nice tutorial about JAAS authentication layer (in portuguese) that may help you setting up a authentication layer.
Wish you good luck and feel free to ask if you have any doubts about what I've said.

Categories

Resources