Spring Security OAuth2 with multiple IDPs and form login: matching users - spring-boot

I've used Spring Security multiple times on several projects, including 3 legged OAuth2 authentication on Zuul API Gateway, etc. All works brilliant and official documentation is very neat and simple.
But there is one point that I still don't get from docs. Imagine you have a spring based Resource Server with several ID Providers, and also you have your own user database and form login.
Thus, users can be authenticated either via form login or via one of IDPs (let's say Google or Facebook).
The question is: how to match Authentication from any of your IDPs to Authentication object that is enhanced by/mapped to your local user?
I.e.: Alice has an account in your system (in your database). She goes into her "profile" and declares that she also has a Google or Facebook account. OK, done, you save this info somewhere in your system.
Now, when Alice login into your system via social network, what spring API do you use to understand that Alice entered via Google is exact same Alice that is already registered in your DB? In what API do you enhance her Authentication with authorities based on your DB?
Thanks in advance, guys

The way this is typically done is by creating a composite that contains both the OidcUser object and your domain object. For example:
#Component
public class MyOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcUserService delegate = new OidcUserService();
#Override
public OidcUser loadUser(OidcUserRequest oidcUserRequest) {
// the information that comes back from google, et al
OidcUser oidcUser = this.delegate.loadUser(oidcUserRequest);
// the information from your DB
MyUser user = this.myRepository.findUserByXYZ(oidcUser.getXYZ());
return new MyOidcUser(user, oidcUser);
}
private static class MyOidcUser extends MyUser implements OidcUser {
private final OidcUser delegate;
public MyOidcUser(MyUser user, OidcUser oidcUser) {
super(user);
this.delegate = oidcUser;
}
// ... implement delegate methods
}
}
Note that XYZ is some attribute that allows you to know that the user from Google is the user from your system. Maybe that's the email address, for example.
The benefit to this extra bit of work is that Spring Security will place this MyOidcUser object into Authentcation#getPrincipal. So now, if you need to get your domain bits, you do (MyUser) authentication.getPrincipal(), but if you need the OIDC bits, you do (OidcUser) authentication.getPrincipal(). Depending on your use cases, you may be able to do something as simple as:
#GetMapping("/endpoint1")
public String endpoint1(#AuthenticationPrincipal MyUser myUser) {
// ...
}
#GetMapping("/endpoint2")
public String endpoint2(#AuthenticationPrincipal OidcUser oidcUser) {
URL issuer = oidcUser.getIdToken().getIssuer();
// ...
}

Related

Spring boot auth server client/ resource server: OAuth 2.1 Authorization Code Grant programatic simulation? Password grant no longer exists

Spring Authorization Server OAuth 2.1.
How can i programatically simulate the authorization_code grant?
Since all grants except for authorization_code and client_credentials have been dropped this has become quite a headache.
The scenario calls for a #Scheduled job to login as a specific user where the client credentials are encoded properties within the server performing the login.
The user roles are important when executing downstream resources and is considered a regular user of the registered Client.
Using the Password grant was perfect for this scenario in OAuth 2.0.
Before i start hacking our Spring Auth server and implement a Password grant for registered resources or maybe overloading the client_credentials for user_credentialed payloads.
Quite a pain if you ask me, so please enlighten me? Are there any patterns for implementing this that i have not yet discovered?
While I'm curious what specific use case you have that needs to perform tasks as a particular user (as opposed to a single confidential client), it should still be possible with customization.
maybe overloading the client_credentials for user_credentialed payloads
This approach makes the most sense to me as a way to adapt supported flows in OAuth 2.1 to emulate a deprecated flow like the resource owner password grant. You can use a variation of this github gist, extending it with your user's authorities if needed. One possible solution might look like the following:
#Component
public final class DaoRegisteredClientRepository implements RegisteredClientRepository {
private final RegisteredClient registeredClient;
private final UserDetailsService userDetailsService;
public DaoRegisteredClientRepository(RegisteredClient registeredClient, UserDetailsService userDetailsService) {
this.registeredClient = registeredClient;
this.userDetailsService = userDetailsService;
}
#Override
public void save(RegisteredClient registeredClient) {
throw new UnsupportedOperationException();
}
#Override
public RegisteredClient findById(String id) {
return this.registeredClient.getId().equals(id) ? this.registeredClient : null;
}
#Override
public RegisteredClient findByClientId(String clientId) {
UserDetails userDetails;
try {
userDetails = this.userDetailsService.loadUserByUsername(clientId);
} catch (UsernameNotFoundException ignored) {
return null;
}
return RegisteredClient.from(this.registeredClient)
.clientId(userDetails.getUsername())
.clientSecret(userDetails.getPassword())
.clientSettings(ClientSettings.builder().setting("user.authorities", userDetails.getAuthorities()).build())
.build();
}
}
This uses a single client registration, but makes use of a UserDetailsService to resolve a subject representing your user's username and a secret which is actually the user's password. You would then need to provide an #Bean of type OAuth2TokenCustomizer<JwtEncodingContext> to access the user.authorities setting and add those authorities to the resulting access token (JWT) using whatever claim your resource server expects them in.
Alternatively, you could just override the scopes parameter of the returned RegisteredClient if desired.
I had the similar problem and ended up creating a password grant emulation servlet filter. Please refer to my example:
https://github.com/andreygrigoriev/spring_authorization_server_password_grant

jhipster fine grain authorization, remove ROLE based authorization

I have been searching on how to remove the ROLE based authorization and replace it with fine grain authorization. What I meant by fine grain is
All method has a #PreAuthorize("isAuthorize('GETCLIENT')") or directly #IsAuthorize("GETCLIENT").
If the user has GETCLIENT in Authorization List, then the method can be executed. Otherwise, the system give error message or just deny access.
Any clue or information regarding how to do that is very much appreciated.
Thank you.
Like I said in the comment one quick and easy way to do this is to add your new custom authorities in the AuthoritiesConstants.java class. You have examples of how to do this here and here.
public final class AuthoritiesConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
public static final String GETCLIENT = "ROLE_GETCLIENT"; // custom
private AuthoritiesConstants() {
}
}
Remember to insert the new role into your jhi_authority database table. You can assign new authorities to a user via the user management interface admin/user-management, it's possible the user needs to relog for the change to take effect.
Then in the method you want to secure just add:
#GetMapping("/clients/{id}")
#PreAuthorize("hasRole(\"" + AuthoritiesConstants.GETCLIENT + "\")")
public ResponseEntity<ClientDTO> getClient(#PathVariable Long id) {
log.debug("REST request to get Client : {}", id);
Optional<ClientDTO> clientDTO = clientService.findOne(id);
return ResponseUtil.wrapOrNotFound(clientDTO);
}
I said #Secured before but in reality you should use #PreAuthorize since it is more powerful and lets you work with Spring Expression Language (SpEL).
The go to resource to understand how JHipster security works is here, but in reality it just follows the standard Spring Security guidelines (as with many other things) so the official documentation about Spring Security should apply too.
Also, if you find this is too simple or that it is breaking the default conventions I found this guide about custom privileges to be particularly great. It's a bit more work, but should work better since you separate authorities (roles) from privileges.

Spring security oauth2: create user if not exists, provision user

Here is an excerpt from spring oauth2 tutorial:
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 (e.g.
using Spring Data) for a custom User object that suits your needs and
can be populated, fully or partially, from the external
authentication.
Provision a User object for each unique user that logs in by
inspecting the repository in your /user endpoint. If there is already
a user with the identity of the current Principal, it can be updated,
otherwise created.
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 is
unique to the account in the external provider).
So, in the user controller we have the following code:
#RequestMapping("/user")
public Map<String, Object> user(Principal user) {
Map<String, Object> map = new HashMap<String, Object>();
// for a facebook the name is facebook id, not an actual name
map.put("name", user.getName());
map.put("roles", AuthorityUtils.authorityListToSet(((Authentication) user)
.getAuthorities()));
return map;
}
Dave Syer(spring maintainer) suggests to:
Downcast the Principal to an Authentication (or possibly
OAuth2Authentication and grab the userAuthentication from that) and
then look in the details property. If your user was authenticated
using a UserInfoTokenServices you will see the Map returned from the
external provider's user info endpoint.
But for me that seems unnatural by two reasons:
Why should we do that in controller call...it smells.
Why should we do all that instanceof and casts..smells as well.
Wouldn't it be better to plugin the OAuth2AuthenticationManager instead: org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// ...
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
// ...
auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
// here we could've create our own Authentication object
// to set as Principal with application specific info.
// Note: no casts are required since we know the actual auth type
return auth;
}
What's the best way of creating a user after oauth2 dance completed?

Spring security Preauth: how to pass user roles through customized authentication filter

I'm new to Spring security, and working on a grails app that connect to external authentication and session management service for authentication/authorization. So what I did is create customized authentication filter
class TokenAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
//in here i call an external service, passing in the a cookie from the request,
//and get username and role information from the service
//not sure what to do with user roles
return username
}
}
Then I looked at super class AbstractPreAuthenticatedProcessingFilter code, in it's doFilter() method, it create an PreAuthenticatedAuthenticationToken (new PreAuthenticatedAuthenticationToken(principal, credentials);) for authentication, but it only takes username and credential, not the role information. Then I tried to inject a different details source J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource into the filter, because looks to me (i might be wrong) this detail source will pass the role information to the authentication object by calling setDetails(). but J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource is reading role information from http request object.
protected Collection<String> getUserRoles(HttpServletRequest request) {
ArrayList<String> j2eeUserRolesList = new ArrayList<String>();
for (String role : j2eeMappableRoles) {
if (request.isUserInRole(role)) {
j2eeUserRolesList.add(role);
}
}
return j2eeUserRolesList;
}
I got confused about the life cycle of authentication. I thought the http request object is getting role information through authentication object in security context, which hasn't been created at this point. I need the role information in order to create the authentication object. Isn't this running in cycle? or am I misunderstanding anything?
I know I can go with another approach to make my app work, just making my own filter to create the authentication object (which takes the role parameter) instead of letting super class (AbstractPreAuthenticatedProcessingFilter) to create the authentication object, but I'm just curious why the first approach is not working. What is J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource trying to do? It calls request.isUserInRole(role), but by who and when is the user role set to http request?
Hopefully I express myself clear enough for someone to understand.

Spring social oauth how to get sign in provider in SocialUserDetailsService?

In my Spring Social app, I'm trying to integrate certain Social Login functionalities. After being redirected from, for example Twitter, Spring calls the following to look up the user.
public class SimpleSocialUserDetailsService implements SocialUserDetailsService {
#Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
/*
Commented
*/
}
However, since I will have multiple social login providers, the userId alone is not enough for me to look up the user in my database. I need at least the sign in provider or access token.
Is there anyway to get the sign in provider, or more information, in SocialUserDetailsService? Any other way to solve my problem would be great!
Spring Social is rather Agnostic to the Sign in Providers when properly implemented. I believe you are confused on the flow of Spring Social. At the point you describe spring social has already looked up the connections table and presumably found a record, so it looks up your user table for the user matching with userId (as referenced in the connections table) This is usually associated with the username.
This connection <-> User matching is done in the SocialAuthenticationProvider before calling the SocialUserDetails loadUserByUserId method.
So the SocialAuthenticationProvider already does what you ask for by querying the usersConnectionRepository and comparing the provider connection to find the appropriate user.
Now for your case you would can go ahead and override the user service that you have setup. As long as the userId used on the doPostSignUp call matches the one you look up in the loadUserByUserId, the proper user will be retrieved.
This is a sample:
Wherever your signup logic is executed, you call the doPostSignup and pass the desired user id (Username or another uniquely identifiable String)
ProviderSignInUtils.doPostSignUp(variableForNewUserObject.getId().toString(), request);
Now you Override the loadUserByUserId in SimpleSocialUserDetailsService
#Autowired
private UserDetailsService userDetailsService;
#Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
return (SocialUserDetails) userDetails;
}

Resources