Attach OAuth2 login to existing users in a Spring Boot application - spring-boot

I have a Spring Boot application that is "invitation only". Ie. users are sent a signup link and there is no "Sign up" functionality. This works fine and users log on with their username and password.
I would like to allow logon with FaceBook and Google using OAuth2 as a supplementary logon method. This would involve mapping the existing users to their social account in some way. The users and their passwords are stored in a MySQL database. I have found a number of articles on OAuth2 and Spring Boot, but none that address this exact use-case.
I can create the Google OAuth2 token/client secret etc, but how do I design the flow to allow only the existing users to logon with their social accounts?
The usernames have been chosen by the users themselves, and are therefore not necessarily the same as their email.
Do I need to create a custom authentication flow in this case?
And do I need to change the authentication mechanism from cookies to JWT tokens?

I found myself in a similar situation, where I needed to know if the OAuth request that I'm receiving is coming from an already authenticated user or not.
Although in my case I needed to know that because I want users to be able to "link" their existing account to social ones.
What I ended up doing was implementing an OAuth2UserService which would have a single method:
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{
// this will be null if the OAuth2UserRequest is not from an authenticated user
// otherwise, it would contain the current user's principle which you can use to check if the OAuth request should be handled or not
Authentication currentAuth = SecurityContextHolder.getContext().getAuthentication();
// for example:
// if(currentAuth == null)
// throw new OAuth2AuthenticationException(OAuth2ErrorCodes.ACCESS_DENIED);
// Use the default service to load the user
DefaultOAuth2UserService defaultService = new DefaultOAuth2UserService();
OAuth2User defaultOAuthUser = defaultService.loadUser(userRequest);
// here you might have extra logic to map the defaultOAuthUser's info to the existing user
// and if you're implementing a custom OAuth2User you should also connect them here and return the custom OAuth2User
return defaultOAuthUser;
}
then just register the custom OAuth2UserService in your security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// your existing config
.oauth2Login()
.userInfoEndpoint()
.userService(oauthUserService())
;
}
#Bean
public OAuth2UserService oauthUserService(){
return new MyCustomOAuth2UserService();
}

Related

How to specify custom return Url after receiving the token or on failure?

I have the following setup:
I'm having an Angular frontend and Spring-Boot backend
Users are logging in to my backend via normal Form login
I'm integrating a third party API which needs oauth2 authentication, so Users need to grant permissions to my App so I can load data on their behalf from that third party
I configured oauth2Client() in my HttpSecurity config to enable oauth2
What currently happens is:
The frontend is calling an endpoint to get data from the third party, lets say /api/get-library which tries to access a protected resource at the third party.
This will lead to a 401 from the third party and trigger the oauth flow in Spring
The User is redirected to the third party to grant permissions to my App
After granting permissions the User is first redirected to the Url I specified as spring.security.oauth2.client.registration.foobar.redirect-uri
Spring Boot then retrieves the token and stores it for my Principal
After that Spring Boot redirects to the original url /api/get-library
But this is just some RestController so the User is presented with some JSON data in the Browser
So point 6 is my problem. I don't want that the User is in the end redirected to some API endpoint, I want him to be redirected to a page of my Angular application.
A similar problem arises if the user rejects the permission grant. Then the user is redirected to spring.security.oauth2.client.registration.foobar.redirect-uri with an query param ?error=true. Also in this case I want a redirect to my Angular application.
Initially I thought I could also configure oauth2Login() which has an failureHandler and successHandler, but those aren't called in my case, since I'm not doing a Login here.
So can somebody help me? How can I configure my own redirects for oauth2Client? In case of success, and on failure? What are relevant Beans here?
I found a solution:
The main Spring class to check is OAuth2AuthorizationCodeGrantFilter. This Filter is invoked when the user granted/rejected the permissions at the OAuth Provider.
Unfortunately there is no way to configure a custom redirect Url for this Filter, so I implemented a hacky solution:
I copied the implementation of OAuth2AuthorizationCodeGrantFilter to an own class and extended it with 2 parameters: success and error return Url. I then used those Urls in the processAuthorizationResponse Method to redirect to my Urls
I then put my ownAppOAuth2AuthorizationCodeGrantFilter before the Spring Filter in the HttpSecurityConfig, so it is used instead of the Spring version
In my Angular App I'm storing the exact location in the App before calling an Endpoint that potentially requires OAuth authentication. So when the User agent returns to the Angular App I can navigate back to the origin location.
It feels very hacky, so if somebody comes up with a better solution I'd be glad to hear it. :-)
Some Code snippets for Spring:
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilterBefore(oAuth2AuthorizationCodeGrantFilter(), OAuth2AuthorizationCodeGrantFilter.class);
...
}
#Bean #Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AppOAuth2AuthorizationCodeGrantFilter oAuth2AuthorizationCodeGrantFilter() throws Exception {
return new AppOAuth2AuthorizationCodeGrantFilter(
clientRegistrationRepository,
oAuth2AuthorizedClientRepository,
authenticationManagerBean(),
oauthSuccessRedirectUrl,
oauthErrorRedirectUrl);
}

Spring Authorization Server: How to use login form hosted on a separate application?

I am using Spring Security along with Spring Authorization Server and experimenting with creating an auth server.
I have a basic flow allowing me to login with the pre-built login page (from a baledung guide - this is the code I'm working off ). I'm assuming this login page form comes from formLogin() like so:
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
//.formLogin(withDefaults());
return http.build();
I would like to not use this pre-built form as I have a need to host and run the login form front-end application completely separately. ie on a different server, domain and codebase.
Another way to ask this question could be:
How do I disable the built in form in authorization-server so I can use it with a completely separate form?
Are there any recommended ways of learning about how customise my SecurityFilterChain along these lines? Is this the correct place to look? I find the baledung article (and articles like that) helpful as a starting point, but seldom works for more practical use case. I'm confident Spring Security and the oauth2 libraries will allow me to do what I want, but not entirely clear.
After discussing this with you, I've gathered that what you're trying to do is essentially pre-authenticate the user that was authenticated through another (separately hosted) login page, actually a separate system. The idea is that the other system would redirect back with a signed JWT in a query parameter.
This really becomes more of a federated login problem at that point, which is what SAML 2.0 and OAuth 2.0 are aimed at solving. However, if you have to stick with things like a signed JWT (similar to a SAML assertion), we could model a fairly simple pre-authenticated authorization_code flow using the Spring Authorization Server.
Note: I haven't explored options for JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants but it could be a viable alternative. See this issue (#59).
Additional note: There are numerous security considerations involved with the approach outlined below. What follows is a sketch of the approach. Additional considerations include CSRF protection, using Form Post Response Mode (similar to SAML 2.0) to protect the access token instead of a query parameter, aggressively expiring the access token (2 minutes or less), and others. In other words, using a federated login approach like SAML 2.0 or OAuth 2.0 will always be RECOMMENDED over this approach when possible.
You could to start with the existing Spring Authorization Server sample and evolve it from there.
Here's a variation that redirects to an external authentication provider and includes a pre-authentication mechanism on the redirect back:
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// #formatter:off
http
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("https://some-other-sso.example/login"))
);
// #formatter:on
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// #formatter:on
return http.build();
}
#Bean
public JwtDecoder jwtDecoder(PublicKey publicKey) {
return NimbusJwtDecoder.withPublicKey((RSAPublicKey) publicKey).build();
}
#Bean
public BearerTokenResolver bearerTokenResolver() {
DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
bearerTokenResolver.setAllowUriQueryParameter(true);
return bearerTokenResolver;
}
The first filter chain operates on authorization server endpoints, such as /oauth2/authorize, /oauth2/token, etc. Note that the /oauth2/authorize endpoint requires an authenticated user to function, meaning that if the endpoint is invoked, the user has to be authenticated, or else the authentication entry point is invoked, which redirects to the external provider. Also note that there must be a trusted relationship between the two parties, since we're not using OAuth for the external SSO.
When a redirect from the oauth client comes to the /oauth2/authorize?... endpoint, the request is cached by Spring Security so it can be replayed later (see controller below).
The second filter chain authenticates a user with a signed JWT. It also includes a customized BearerTokenResolver which reads the JWT from a query parameter in the URL (?access_token=...).
The PublicKey injected into the JwtDecoder would be from the external SSO provider, so you can plug that in however it makes sense to in your setup.
We can create a stub authentication endpoint that converts the signed JWT into an authenticated session on the authorization server, like this:
#Controller
public class SsoController {
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
#GetMapping("/login")
public void login(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
this.successHandler.onAuthenticationSuccess(request, response, authentication);
}
}
The .oauth2ResourceServer() DSL causes the user to be authenticated when the /login endpoint is invoked. It requires an access_token parameter (used by the BearerTokenResolver) to pre-authenticate the user by validating the signed JWT as an assertion that the user has been externally authenticated. At this point, a session is created that will authenticate all future requests by this browser.
The controller is then invoked, and simply redirects back to the real authorization endpoint using the SavedRequestAwareAuthenticationSuccessHandler, which will happily initiate the authorization_code flow.
Re your comnent: "I'm attempting to build an Authorization Server":
Coding your own Authorization Server (AS) or having to build its code yourself is highly inadvisable, since it is easy to get bogged down in plumbing or to make security mistakes.
By all means use Spring OAuth Security in your apps though. It is hard enough to get these working as desired, without taking on extra work.
SUGGESTED APPROACH
Choose a free AS and run it as a Docker Container, then connect to its endpoints from your apps.
If you need to customize logins, use a plugin model, write a small amount of code, then deploy a JAR file or two to the Docker container.
This will get you up and running very quickly. Also, since Spring Security is standards based, you are free to change your mind about providers, and defer decisions on the final one.
EXAMPLE IMPLEMENTATION
Curity, along with other good choices like Keycloak or Ory Hydra are Java based and support plugins:
Curity Community Edition
Custom Authenticator Example

OAuth2 Open ID client authentication followed by LDAP user details & authorities

Our app is currently set up with OAuth OpenID connect authentication with an external (third-party) server. The requirement is to use user details service that loads the user from LDAP (along with the authorities/roles) to complete the authentication. So authentication.getPrincipal() should be returning the custom UserDetails object we use that gets generated by querying LDAP using the username obtained from the Open ID authentication.
I have tried the following:
Followed Similar Issue but it seems like in the answer it's setting up the server-side and it doesn't work either
Tried adding custom UserDetailsService in WebSecurityConfig
#Configuration
public class OAuth2Config extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().and().userDetailsService(myCustomUserDetailsService());
}
}
where myCustomUserDetailsService() handles the call to LDAP and fetches the user details info, including the authorities.
I'm not too familiar with the spring oauth2 framework, correct me if I'm wrong: I'm guessing I need to implement my own user info endpoint to make a call to LDAP, and not the user info endpoint provided to me by the OpenID service?
You could add a new filter in the security filter chain right after that one. The second filter could retrieve the principal/name from authentication and query for users.
Like a two steps authentication
Not as clean as I hoped, but I registered an application listener on InteractiveAuthenticationSuccessEvent and manually updated the authentication after login (similar post's answer https://stackoverflow.com/a/10747856/11204609)
If anyone has better suggestions?
UPDATE: The identity provider agreed to include LDAP info in their claims so I can directly obtain via the token without having to do a call-back (still an on-going discussion at the moment). That being said, having a auth success callback app listener is only way I could solve the problem.

Protect specific resources id with OAuth2 on Spring Boot

I have a working OAUTH2 implementation on Spring Boot, with AuthorizationServer and ResourceServer on the same implementation, using password grant.
About the server:
The TokenStore is custom and uses a WebService to store the token remotely.
I have a custom AuthenticationProvider.
This works for controlling access to resources based on given authorities, for instance, in the ResourceServer config:
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/resource/**")
.hasAnyAuthority("USER", "ADMIN")
.antMatchers("/api/admin/**")
.hasAnyAuthority("ADMIN");
}
Now, I need to control that USER can access to "/api/resource/1" but not "/api/resource/2", this IDs can change and I fetch the list during the authentication.
I've tried to add the ID's list to OAuth2AccessToken additional information and adding a custom filter in the ResourceServer configuration but it always comes empty.
So, How's the proper way for implementing this without using JWT?
After some thinking and research, If someone is trying to achieve something similar, this is what I'll do:
Map the allowed ID's to authorities, e.g. ID_201 and the modules as roles, so I will have a ROLE_ADMIN.
It's possible to refer to path variables in Web Security Expressions, as explained in here. So the idea is to pass the variable (resource id) and check whether it's allowed or not.
public class WebSecurity {
public boolean checkResourceId(Authentication authentication, int id) {
//check if the list of authorities contains ID_{id}.
}
}
And in the Web Security config:
http
.authorizeRequests()
.antMatchers("/resource/{resourceId}/**").access("#webSecurity.checkResourceId(authentication,#resourceId)")
...
If you're working on Spring Boot with spring-security 4.0.4 be sure to upgrade to 4.1.1 as reported in this bug.

Spring boot, oAuth2 sign up with facebook

I have been following #Dave Syers' excellent tutorial on Spring boot and oAuth2
I have been able to create a log in function, so that protected resources need a login to facebook before they can be accessed.
But now I am trying to create a "sign up" page. On stackoverflow, for example, there is an option to sign up with facebook, so your details are sent to Stackoverflow.com from facebook. How can this be performed with oAuth2? I was able to do this with spring-social, but I cannot wrap my head around how to do this with a direct oauth2 approach.
Please help?
The answer was simpler than I expected. All I needed to do was add my custom AuthenticationSuccessHandler to the filter:
All I had to do was add an AuthenticationSuccessHandler handle to the method that returns a Filter ssoFilter()
#Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()));
facebookFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
return facebookFilter;
}
And my CustomAuthenticationSuccessHandler was just a component that extended AuthenticationSuccessHandler
#Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//implementation
}
}
So in my sign up page, I could simply use the same login action, but in the success handler I created the User and stored her in the DB
Make a integrated jwt-oauth2-signup-login is difficult. There are some easy way:
1, to use satellizer-spring-boot, or satellizer.
2,to use spring social.
3, add jwt to spring oauth2 as separate provider:
This is how to do with 3:
I have not use signup+oauth2 yet(Because I like spring social and it can do same function), but in theory it can be done in a very easy and can be done as follow:
First, when user login (Register on facebook will also lead to login page) form facebook, just import the user's information and write the information to user model. It is can be done with a controller and a view.
On front page, it is easy to make user choose to login, or register a new account: As Spring boot support multiple filter and multiple AuthenticationProvider,That means you can use two filters, one for oauth2,and another (jwt local server) filter for local server register.
1,download a standard spring boot jwtFilter.java file and put it in your config directory.
2,Make a controller for register new user.
3, make a /login to return jwt token.
3, make two filter, one for oauth2, one for local jwt.
4, make a Sign up link to /register. and a login tag link to /login.
ps: you can copy all the lines form a standard spring boot jwt project, here is one: https://github.com/mrmodise/senepe

Resources