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;
};
}
Related
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)
}
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.
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.
I have a successfully working Spring 4 MVC project with lots of RESTful web-services on the back-end. We use Hibernate 5 and Spring Security 4 as well.
1) We authenticate users from a third-party source, OpenAM 10.x, and this creates a token in the browser.
2) From the front-end, we pass in the Ajax call a request header which contains that OpenAM token.
3) In the back-end, using the SiteMinder example, have a service: CustomUserDetailsService which does the following:
a) this uses our code to openAM and pass in the token
b) we get back JSON data we parse to get the username
c) from there we use the Hibernate Spring Security code to get further details for this user and get the roles
System.out.println("loadUserByUsername: username : " + username);
UserAccountEntity userAccountEntity = userAccountDao.getByUsername(username);
System.out.println("loadUserByUsername: userAccountEntity : " + userAccountEntity);
if (userAccountEntity == null)
{
System.out.println("CustomUserDetailsService: userAccountEntity not found");
throw new UsernameNotFoundException("Username not found");
}
System.out.println("CustomUserDetailsService: START: springframework.user");
// this "User" object is: org.springframework.security.core.userdetails.User
User user = new User(userAccountEntity.getUsername(), userAccountEntity.getPassword(), true, true, true, true,
getGrantedAuthorities(userAccountEntity));
From here, we have working security on the URL endpoints ... this works great!
Now, here is the problem ... my boss wants to use our custom proprietary ACL system to make secure query calls. Making a call to get records, we want to make sure we get only the records the user has access to. We have our own ACL tables that allow us security for a user or role to certain objects. Suffice it to say, this was written before Spring Security ACL ever existed. I'd prefer to use Spring Security 4 ACL, but that is out of the question.
Consider that we have 4 levels, Entity, Dao, Services, Web-Services. A Single controller looks like this:
#RequestMapping(value = "", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody ArrayList<SponsorEntity> getSponsorList1() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = null;
if (principal instanceof User)
{
user = ((User) principal);
}
ArrayList<SponsorEntity> sponsorEntityList = (ArrayList) service.getAllList();
return sponsorEntityList;
}
Like I said, this goes through the CustomUserDetailsService and sets the User as defined above. So, the first questions is, in the DAO layer, what is the code I would use to pull this org.springframework.security.core.userdetails.User?
If I can get this User object from the DAO layer, then I can "pipe" that username into our old existing legacy code.
The second question ... in a DAO Unit Test, how can I create/setup this Security? This way I can test to see if my back-end code is really getting the username from an authenticated user.
Please let me know if you need any more information. Thanks!
I've read through a few questions related to this topic ( Jersey REST API as a separate web app, Should web service be separate from web site?), but I still struggle to understand which design practice would work best for an existing application.
I inherited a java web application built on spring and hibernate JPA. It's currently in production with new features being developed.
Concurrently, I will need to design a REST API that will have some form of authentication/user tracking. The web application has its own user authentication system that is likely going to be different than the API implementation (i.e. username/pw vs api keys (?)).
Given this situation, I think it'd be best to develop them separately, but I feel like in the beginning, there will be a lot of code duplication (as a result of all the jpa implementation in the web application). Ideally, once I have the rest api in place, I would switch the web application to use the API as well and remove most of the backend code that currently handles the data retrieval.
Before I start down this route though, I was wondering, is there a better way to do this?
you can use Spring Security and create a custom AuthenticationProvider . You can use a JNDI lookup to get the authservice directly from the Container it self.
Example:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AuthService authService;
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
// use the credentials to try to authenticate against the third party system
if (authService(name, password)) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
return new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
} else {
throw new AuthenticationException("Unable to auth against third party systems");
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
Also, read more about RESTful apis using Spring here:
http://www.baeldung.com/rest-with-spring-series/