Spring Boot: Secured RESTful API using Spring Social and Spring Security - spring

I am trying to define and secure a RESTful API using Spring Boot. Ideally, I would like to use Spring Social and allow clients (web and mobile) to login via Facebook.
What is working
So far, I managed to have a working API using #RestController and secure it with a basic Spring Security configuration as follows:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/**").authenticated()
.antMatchers(HttpMethod.PUT, "/api/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/**").authenticated()
.anyRequest().permitAll()
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
The antMatchers could probably be improved, but I made it like this for my own clarity for now and it works fine. Doing GET requests is allowed and all others required to send the standard user:password given by Spring Security at runtime. An example using httpie:
http POST user:a70fd629-1e29-475d-aa47-6861feb6900f#localhost:8080/api/ideas/ title="My first idea"
Which right credentials, it sends a 200 OK back, otherwise a 401 Unauthorized.
Spring Social
Now, I am stuck and can't get my head around using Spring-Social-Facebook to get working with my current setup and keep fully RESTful controllers. Using standard forms and redirects seems trivial, but I couldn't find any solution for a REST-based approach that easily supports web and mobile clients for example.
As I understand, the client will have to handle the flow, since the back-end won't send any redirects to the /connect/facebook URL.
I followed the tutorial Accessing Facebook Data and it works on its own. However, I would like to avoid having to have those facebookConnect.html and facebookConnected.html templates like in the tutorial. So I don't know how to have change that.
Another Spring Boot tutorial for OAuth is also nice and working, but I would like to stick with Spring Social if possible due to the simplicity.
This post, helped for the Method not allowed issue of the /connect/facebook redirect when using those views mentioned above.
Post about Social Config. Probably, I am missing something there.
Any advice, solution or link to a better tutorial would be really helpful.
Thanks!
UPDATE 1
Now, I have a working website with traditional User SignUp and Login over forms. I have a "Login with Facebook" button that sends me over the "OAuth dance". So next issue is that I have to create somehow the User manually after the Facebook login has been successful, because for the moment both "logins" are not related, so even though the user is logged in with Facebook, he doesn't yet have an associated User object with the right authorisations.

SocialAuthenticationFilter by default, redirects to '/signup' in the case you described, user is signed in from a social app, however, no local account exists. You can provide a handler to create a local account. This is also covered in the spring-socal samples.
#RequestMapping(value = { "/signup" }, method = RequestMethod.GET)
public String newRegistrationSocial(WebRequest request, Model model) throws Exception {
String view = "redirect:/home";
try {
Connection<?> connection = providerSignInUtils.getConnectionFromSession(request);
if (connection != null) {
UserProfile up = connection.fetchUserProfile();
registerUser(up.getFirstName(), up.getLastName(), up.getEmail(), "DummyPassword");
providerSignInUtils.doPostSignUp(up.getEmail(), request);
//SignInUtils.signin(up.getEmail());
...
...
}
}
return view;
}

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

Spring Security OAuth2 client with authorization_code grant - How to handle token request?

I am building a Spring Boot application using version 2.3.4 with spring-boot-starter-oauth2-client and spring-boot-starter-security dependencies.
I am trying to implement the JIRA Tempo plugin OAuth support.
I have it partially working using the following properties:
spring.security.oauth2.client.registration.tempo.redirect-uri=http://localhost:8080
spring.security.oauth2.client.registration.tempo.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.tempo.client-id=<the-client-id>
spring.security.oauth2.client.registration.tempo.client-secret=<the-client-secret>
spring.security.oauth2.client.registration.tempo.provider=tempo
spring.security.oauth2.client.provider.tempo.authorization-uri=https://mycompany.atlassian.net/plugins/servlet/ac/io.tempo.jira/oauth-authorize/?access_type=tenant_user
spring.security.oauth2.client.provider.tempo.token-uri=https://api.tempo.io/oauth/token/
and this config:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(expressionInterceptUrlRegistry -> expressionInterceptUrlRegistry.anyRequest().authenticated())
.oauth2Login();
}
When I access http://localhost:8080, it redirects to JIRA/Tempo and shows the approval dialog there to grant access to the Tempo data for my application. I can grant access, but after that, it just redirects again to that page instead of showing my own application.
With debugging, I noticed that there is a redirect to http://localhost:8080/?code=.... but Spring Security is not handling that. What else do I need to configure?
I also tried to set some breakpoints in DefaultAuthorizationCodeTokenResponseClient, but they are never hit.
UPDATE:
I changed the redirect-uri to be:
spring.security.oauth2.client.registration.tempo.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
(and I changed the Redirect URIs setting in Tempo to http://localhost:8080/login/oauth2/code/tempo).
This now redirects back to my localhost, but it fails with authorization_request_not_found.
UPDATE 2:
The reason for the authorization_request_not_found seems to be mismatch in HttpSessionOAuth2AuthorizationRequestRepository.removeAuthorizationRequest(HttpServletRequest request) between what is in the authorizationRequests and the stateParameters.
Note how one ends with = and the other with %3D, which makes them not match. It is probably no coincidence that the URL encoding of = is %3D. It is unclear to me if this is something that is a Spring Security problem, or a problem of the Tempo resource server, or still a misconfiguration on my part.
The redirect-uri property should not point to the root of your application, but to the processing filter, where the code after redirect is processed.
Best practice for you would be to leave the redirect-uri alone for the time being. Then it will default to /login/oauth2/code/* and this Url is handled by org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.

How do I modify spring boot security redirect on login to not redirect back to login?

We have a spring boot application configured as an oauth2 client. Occasionally, we have people where their browser sends a request like this to the application:
https://app/login?code=XXX&state=ZZZ
The code and state were cached from a previous authentication attempt and are invalid right now.
Spring security sees that this person is not authenticated, so it redirects them to /login which then does the whole oauth2 authentication but then after they are authenticated, spring security sends them back to /login?code=XXX&state=ZZZ because that was their original request. When that happens, it tries to validate the code and state but fails and sends them to an error page. This is a problem when supporting the app because the user is authentcated.
Is there a way to change the logic of the the storing of the initial request so that if it is /login we can replace that with /? There might be other solutions we haven't thought of. Any help would be appreciated.
Our app is currently using Spring boot 2 but I've tried this with the latest version of Spring boot 3 and it is still an issue. We have been unable to change the browser behavior so would like to solve this on the server if possible.
Here is our configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/info", "/static/**").permitAll()
.anyRequest().authenticated().and()
.csrf();
}
If I understand you correctly, you want to avoid redirect only sometimes (so SpringSecurity's defaultSuccessUrl is not an option).
If so, you can implement your own AuthenricationSuccessHandler like this:
...
.successHandler(
(request, response, authentication) -> {
if (request.getRequestURI().equals("/your/invalid/path"))
response.sendRedirect("/");
}
...

How do I setup login service for Spring-social and spring-security over a REST API?

I want to have a JS application in on client-side (no jsps) that will communicate with back-end only with REST calls. I want also to enable users to be able to login with FB, Twitter accounts. In addition, I also want to enable users to register their own accounts. For this purpose I want to use Spring-security and spring-social on backend and Javascript SDK in front to get access_token from the FB, which will be then passed to backend.
The question is: how do I create a REST controller that would authenticate using spring-social and spring-security facilities?
I read through the examples in:
https://github.com/spring-projects/spring-social-samples
but couldn't really find how I could make use of ProviderSignInController or SpringSocialConfigurer for this purpose. I guess I cannot use the SocialAuthenticationFilter in my case since the "/auth/{providerid}" url is not what I'm looking for. However, I guess the ProviderSingInController seems to be of use here neither. Please correct me if I'm wrong. Ideally I would like to benefit from all capabilities of Spring Security framework.
I will appreciate any suggestions.
Best regards
EDIT
I would like to follow a flow like here: http://porterhead.blogspot.com/2013/01/writing-rest-services-in-java-part-4.html but using the Spring Social and Spring Security combined.
The front-end application is written in AngularJS
2nd EDIT
It turns out that you can simply make use of all the Spring Social modules benefits out of the box. The only thing a client has to do is call a GET on the auth/facebook or whatever link to fire entire 0auth dance which will eventually return the authentication result. Then you can control the flow easily (register account or return some relevant information to the client to let know registration is needed). So the SpringSocialConfigurer works well in this case (apart from the fact that it doesn't support scope setting yet, however, this can be changed manually, check my pull request # github.com/spring-projects/spring-social/pull/141)
3rd EDIT - 14.10.2014
As requested, I will share how I managed to make it work.
Given I have configured my security filter in the following way:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
public void configure(final HttpSecurity http) throws Exception {
http.formLogin()
...
.and().apply(getSpringSocialConfigurer());
}
private SpringSocialConfigurer getSpringSocialConfigurer() {
final SpringSocialConfigurer config = new SpringSocialConfigurer();
config.alwaysUsePostLoginUrl(true);
config.postLoginUrl("http://somehost.com:1000/myApp");
return config;
}
Once my application is set up, the only thing I need to call is http://somehost.com:1000/myApp/auth/facebook
with GET request.
"In addition, I also want to enable users to register their own
accounts"
If you say that you want to allow users to login with their own credentials (without FB/twiter), you need to let them also to create account, and to support forgot password, etc...
If that is the case, maybe this SO thread might be helpful. The auth-flows package also supports REST API.
Create Account, Forgot Password and Change Password

Resources