Handle OAuth2 callback in Spring Boot - java

I am trying to build a Spring Boot project with requires being signed into an OAuth2 SSO.
I have the following Maven dependencies:
org.springframework.boot:spring-boot-starter-web
org.springframework.security:spring-security-oauth2-client
org.springframework.security:spring-security-config
I use HttpSecurity to enforce OAuth2 authentication for the app, using the following:
#EnableWebSecurity
#Order(101)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatcher("/api/auth/oauth2/callback").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login();
}
}
Now, what this does is: it sees that the user is not logged in, redirects them to the SSO, and after they have signed in it redirects the user back to the /api/auth/oauth2/callback?code=...&state=... endpoint. That all works fine. However, I am fairly new to Spring Boot and I don't understand how I persist the fact the user is now authenticated (I know I still need to validate the callback, that's not a problem).
Here is the authentication model that I would like to implement: I want to generate a hash within the callback endpoint, and store that hash in-memory within the app, and as a cookie on the user's browser. Then, in any subsequent requests, the app would read that cookie's value, find the row in the in-memory database with the hash in it and grab the corresponding user data from the database row.
I have looked extensively for a good example of this, however, all of the Spring Boot based OAuth2 examples use Github/Google OAuth and it seems to handle a lot of stuff under the hood (or perhaps I'm not understanding those properly).
Any help/guidance would be greatly appreciated!
In case it helps, here is my application.yml file:
spring:
security:
oauth2:
client:
registration:
custom_sso_name:
clientId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
clientSecret: SUPER_SECRET
authorizationGrantType: authorization_code
redirectUri: https://dev.localhost/api/auth/oauth2/callback
provider:
custom_sso_name:
userInfoUri: https://sso.example.com/nidp/oauth/nam/userinfo
authorizationUri: https://sso.example.com/nidp/oauth/nam/authz
tokenUri: https://sso.example.com/nidp/oauth/nam/token
preferTokenInfo: false

You can check the Authentication Provider.
When you have set this, you can autowire the custom
AuthenticationProvider and login a user in your controller whith SecurityContext like this:
SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(new UserInfo(yourHash, token, expirationDate)));
Here, UserInfo is an example class (which extends AbstractAuthenticationToken) that can hold the hash that you want to save, as any other data you may need.
In the example of the above link they use UsernamePasswordAuthenticationToken, which may me enough to you if you only want to store a hash. If you want to store extra info I would sugest to use a custom AuthenticationToken as it is UserInfo:
public class UserInfo extends AbstractAuthenticationToken {
private String hash;
private String oAuthToken;
private DateTime expirationTime;
public UserInfo (String hash, String oAuthToken, DateTime expirationTime){
super(Collections.emptyList());
this.hash= hash;
this.oAuthToken = oAuthToken;
this.expirationTime = expirationTime;
}
#Override
public String getCredentials() {
if(expirationTime.isAfter(DateTime.now())){
return oAuthToken;
} else {
return null;
}
}
#Override
public String getPrincipal() {
return hash; //Or anything you stored that may be useful for checking the authentication
}
Then, in any subsequent requests, the app would read that cookie's
value, find the row in the in-memory database with the hash in it and
grab the corresponding user data from the database row.
After this authentication is finished, you can customly check authentication in any request:
#GetMapping("/")
public String profileSettings(Principal principal) {
if (principal instanceof UserInfo){
String hash = ((UserInfo) principal).getPrincipal();
//Now you can use the hash for your custom logic, such like database reading
return "profileSettings";
} else {
return "login";
}
}

Related

Spring Security Custom Auth Manager approach

I would like to access the http request, specifically auth header in AuthenticationManager.authenticate() context.
Requirement is to authenticate a custom token. There is an external library which does that and so I don't have the luxury to read out principal from the token. Hence, in the custom filter, I am returning the full token in the getPreAuthenticatedPrincipal() method. This seems borderline incorrect and I would like to not pass the token pretending it to be principal.
Is there any way I can get it without violating any framework constraints?
Or is there a better way to handle the scenario which I'm trying to achieve?
Here's the config class:
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity httpSecurity) throws Exception{
CustomTokenFilter customTokenFilter = new CustomTokenFilter();
customTokenFilter.setAuthenticationManager(new CustomAuthenticationMgr());
httpSecurity
// csrf etc etc
.addFilter(customTokenFilter)
.authorizeRequests()
.mvcMatchers("/users/**")
.authenticated()
.and()
.authorizeRequests()
.mvcMatchers("/other-api/**")
.permitAll()
.and()
.httpBasic();
}
Here's the custom token filter class:
public class CustomTokenFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String authorization = request.getHeader("authorization");
if(authorization.indexOf("Custom") == 0){
return Map.of("Custom",authorization.split(" ")[1]);
}
return null;
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "";
}
}
And finally, the custom authentication manager class:
public class CustomAuthenticationMgr implements AuthenticationManager {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Map<String,String> map = (Map) authentication.getPrincipal();
String token = map.get("Custom");
// Custom validation - checking length here just to simplify
if(token.length() > 0)
authentication.setAuthenticated(true);
return authentication;
}
}
Version: Spring Boot 2.6.7 (transitive: spring-core 5.3.19)
Constraints: Cannot upgrade to other versions at the moment
Thanks in advance!
You're right, this isn't a good way to do it. (It's great you noticed -- too few people care whether their code is idiomatic.)
A better way would be to start by writing your own filter that actually just... does the authentication. You can extend OncePerRequestFilter rather than something more specific. That's what Spring Security itself does, both for basic authentication (BasicAuthenticationFilter) and for OAuth bearer tokens (BearerTokenAuthenticationFilter). You may want to take a careful look at the code for BearerTokenAuthenticationFilter since the problem it solves is very similar to yours. (I wouldn't extend it, though, since it's very clearly intended to do OAuth specifically. I wouldn't straight up copy the code either -- it's fairly simple as Spring Security filters go but probably still does more than you need. Try to understand the code instead; that will help a lot with your understanding of Spring Security in general.)
Okay, so you have a filter which looks a lot like BearerTokenAuthenticationFilter. That is, it contains an AuthenticationManager and its doFilter method consists of extracting the token from the request, passing that into the AuthenticationManager and then doing some SecurityContext-related stuff. Except, problem: AuthenticationManager.authenticate() expects an Authentication, not a String, and the token is a String.
The solution is to write a wrapper object for your token which implements Authentication. You can do this a couple of ways. Personally, what I'd do is use two classes: one which you pass into AuthenticationManager.authenticate(), and one which you get back. So we have, say, CustomTokenAuthenticationRequest implements Authentication and CustomTokenAuthentication implements Authentication. Both are immutable.
CustomTokenAuthenticationRequest basically just contains the token; its isAuthenticated() is return false, its getPrincipal() returns the token and its getCredentials() also returns the token. This is essentially what Spring Security itself does with BearerTokenAuthenticationToken.
CustomTokenAuthentication, on the other hand, probably contains a UserDetails of some sort; its isAuthenticated() is return true, its getName() is a username or user id or something, etc.
Now you need to teach the AuthenticationManager to authenticate CustomTokenAuthenticationRequests. The way to do this isn't to implement AuthenticationManager, it's to implement AuthenticationProvider. So you write a class that looks roughly like
public class CustomTokenAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication a) {
String token = ((CustomTokenAuthenticationRequest) a).getToken();
if (/* the token is valid */) {
CustomTokenAuthentication returnValue = // whatever you want it to be
return returnValue;
}
throw new BadCredentialsException("Invalid token");
}
#Override
public boolean supports(Class<?> authClass) {
return authClass == CustomTokenAuthenticationRequest.class;
}
}
Finally, wire it all up. Add the authentication provider to your HttpSecurity using its authenticationProvider() method. (If you do this, and you don't change the default authentication manager configuration, authenticationProvider() results in your authentication provider getting added to an AuthenticationManager which Spring Security configures for you -- an instance of ProviderManager.) Add the filter using addFilterAt(BasicAuthenticationFilter.class). Also, don't call httpBasic() because this adds a BasicAuthenticationFilter which I am guessing you don't want. Or maybe you want basic authentication and also your custom token authentication? But you didn't say that. If you do want both, you'll want to add your filter with addFilterBefore or addFilterAfter, and you need to think about ordering. Generally filter ordering is important in Spring Security.
I glossed over a lot of stuff here, barely gave you any code, and still wrote something of blog post length. Spring Security is very complex, and the thing you're trying to do isn't easily done in an idiomatic manner if you don't have much experience. I highly recommend just reading the Spring Security reference documentation from start to finish before you try implementing any of my suggestions. You'll also need to read quite a lot of Javadoc and tutorials and/or framework code. If there's something specific you want to follow up on I might respond to a comment, but I don't promise it; I had to do some research for this answer and have already spent more time on it than I planned to.
you should look spring-security-lambda-dsl,add filter,add auth provider

How to create a custom authentication filter in Spring Security?

I'm trying to create a custom Spring Security Authentication Filter in order to implement a custom authentication scheme. I've spent a couple hours reading up on Spring Security, but all of the guides I've found explain how to configure basic setups; I'm trying to write a custom setup, and I'm having trouble finding documentation on how to do so.
For the sake of example, lets say that my custom authentication scheme is as follows:
If the client provides a "foo_username" header and a "foo_password" header in the http request (both unencrypted for the sake of example), then my custom filter needs to construct a UsernamePasswordAuthenticationToken. Of course if the password is wrong, that's an authentication error. If either header is missing that's an authentication error. If both headers are missing, I want to delegate down the filter chain without changing anything.
That seems simple enough in theory, but I don't know how to implement that in Spring. Am I intended to check the password against the DB myself? or is that the reponsibility of the UserDetailsPasswordService? Am I intended to modify the SecurityContextHolder.getContext().authentication field myself? What responsibilities do I delegate to the AuthenticationManager? Which exceptions do I throw when authentication fails in various ways? Do I implement Filter, OncePerRequestFilter, or AbstractAuthenticationFilter? Is there any documentation on how to do all this???
Admittedly, this is a duplciate of How to create your own security filter using Spring security?, but I'm not him, and he got no answers.
Thanks for the help!
Edit: This is the not best way to do things. It doesn't follow best practices. I haven't had time to udpate this answer with an example that does follow best practices.
As others have pointed out, it's better to use Basic auth or OAuth2, both of which are built into Spring. But if you really want to implement a custom filter, you can do something like this. (Please correct me if I'm doing this wrong.) But don't do this exactly. This is not a very secure example; it's a simple example.
class CustomAuthenticationFilter(val authManager: AuthenticationManager) : OncePerRequestFilter() {
override fun doFilterInternal(request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain) {
val username = request.getHeader("foo_username")
val password = request.getHeader("foo_password")
if(username==null && password==null){
// not our responsibility. delegate down the chain. maybe a different filter will understand this request.
chain.doFilter(request, response)
return
}else if (username==null || password==null) {
// user is clearly trying to authenticate against the CustomAuthenticationFilter, but has done something wrong.
response.status = 401
return
}
// construct one of Spring's auth tokens
val authentication = UsernamePasswordAuthenticationToken(username, password, ArrayList())
// delegate checking the validity of that token to our authManager
val userPassAuth = this.authManager.authenticate(authRequest)
// store completed authentication in security context
SecurityContextHolder.getContext().authentication = userPassAuth
// continue down the chain.
chain.doFilter(request, response)
}
}
Once you've created your auth filter, don't forget to add it to your HttpSecurity config, like this:
override fun configure(http: HttpSecurity?) {
http!!.addFilterBefore(CustomAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter::class.java)
}
I think what you want to do is implement AuthenticationProvider. It allows your code to explicitly manage the authentication portion. It has a fairly simple method signature to implement as well.
public class YourAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
...
return new UsernamePasswordAuthenticationToken(principal, password, principal.getAuthorities())
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
You can register it by adding it to the AuthenticationManagerBuilder in a config that extends WebSecurityConfigurerAdapter
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) {
AuthenticationProvider provider= new YourAuthenticationProvider();
auth.authenticationProvider(provider);
}
}

Access user data after login with Spring Boot OAuth2 Client

I am trying to build an OAuth2 Client using spring boot to access a custom API.
Here is my code so far:
#SpringBootApplication
public class MyClient {
public static void main(String[] args){
SpringApplication.run(MyClient.class, args);
}
}
#Configuration
#EnableOAuth2Sso
public class ApplicationSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception{
http.antMatcher("/**")
.authorizeRequests().antMatchers("/","/callback","/login**","/error**")
.permitAll().anyRequest().authenticated();
}
}
#RestController
public class HomeController {
#RequestMapping("/user")
public String login() {
String userInfoJson = "get data returned from API somehow";
return userInfoJson;
}
#RequestMapping("/callback")
public void callback(HttpServletResponse response) {
response.sendRedirect("/user");
}
}
I have created an application.yml file with all the properties needed and the login process works perfectly, returning the flow to /callback after a successful login.
At that point I should have received a token that I can use to fetch the user's data from the server.
How can I access this token?
Also, does spring boot have any classes to do the token validation process automatically, or do I have to create the request manually?
Thanks
from the documentation
An OAuth2 Client can be used to fetch user details from the provider (if such features are available) and then convert them into an Authentication token for Spring Security.
The Resource Server above support this via the user-info-uri property This is the basis for a Single Sign On (SSO) protocol based on OAuth2, and Spring Boot makes it easy to participate by providing an annotation #EnableOAuth2Sso.
The Github client above can protect all its resources and authenticate using the Github /user/ endpoint, by adding that annotation and declaring where to find the endpoint (in addition to the security.oauth2.client.* configuration already listed above):
application.yml.
security:
oauth2:
...
resource:
userInfoUri: https://api.github.com/user
preferTokenInfo: false
So as you can read this should be done automatically if you set where the user info shall be fetched from, and then it will be populated.
When an Authentication token is populated you can fetch this from the security context in multiple different ways.
#RequestMapping("/callback")
public void callback(Authentication authentication) {
final String name = authentication.getName();
response.sendRedirect("/user");
}
If you wish to access the raw json, you'll probably have to make the rest call yourself. If you want to add custom values to the authentication object you have to implement your own UserDetails class.

Spring boot Basic Authentication and OAuth2 in same project?

Is it possible to use OAuth2 for certain endpoints in my rest application and use basic authentication too for some other endpoints.
It should all work on spring security version 2.0.1.RELEASE. I hope someone can help me further.
Yes, it's possible to use a basic authentication as well as an OAuth2 authentication intertwined, but I doubt you'll be able to set it up easily as HttpSecurity's authenticated() method doesn't allow you to pick which of your authentication method (oauth2Login/formLogin) will work.
However, there's a way to easily bypass that:
You could add a custom authority, let's call it ROLE_BASICAUTH, when an user connects using basic auth, and ROLE_OAUTH2 when an user connects using OAuth2. That way, you can use
.antMatchers("/endpoint-that-requires-basic-auth").hasRole("BASICAUTH")
.antMatchers("/endpoint-that-requires-oauth2").hasRole("OAUTH2")
.anyRequest().authenticated()
When they reach an endpoint that you want basic authentication (and not OAuth2), you check their current authorities, and if it's not BASICAUTH, then you invalidate their session you display a login form without OAuth2 (to force them to use the basic authentication).
The downside to doing that is that you'd need to implement both a custom UserDetailsService as well as a custom OAuth2UserService...
But that's actually not that hard:
#Service
public class UserService extends DefaultOAuth2UserService implements UserDetailsService {
// ...
#Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2User user = super.loadUser(oAuth2UserRequest);
Map<String, Object> attributes = user.getAttributes();
Set<GrantedAuthority> authoritySet = new HashSet<>(user.getAuthorities());
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
authoritySet.add(new SimpleGrantedAuthority("ROLE_OAUTH2"));
return new DefaultOAuth2User(authoritySet, attributes, userNameAttributeName);
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = getUserFromDatabase(username); // you'll need to provide that method (where are the username/password stored?)
if (user == null) { // UserDetailsService doesn't allow loadUserByUsername to return null, so throw exception
throw new UsernameNotFoundException("Couldn't find user with username '"+username+"'");
}
// add ROLE_BASICAUTH (you might need a custom UserDetails implementation here, because by defaut, UserDetails.getAuthorities() is immutable (I think, I might be a liar)
return user;
}
}
Note that this is a rough implementation, so you'll have to work it out a bit on your end as well.
You can also use this repository I made https://github.com/TwinProduction/spring-security-oauth2-client-example/tree/master/custom-userservice-sample as a guideline for the custom OAuth2UserService
Good luck.

Spring session login

I would like to build a web application based on this example.
What I understood from this example is that you build 2 applications:
A UI application containing the web pages where you can login to get a token and a resource application where you ask for data using your token.
My question is where is the login check done?
The application.yml file defines the password "password" for the user "user"
security:
user:
password: password
But this file is never mentioned neither in UIApplication.java file or in any other file:
#SpringBootApplication
#RestController
public class UiApplication {
public static void main(String[] args) {
SpringApplication.run(UiApplication.class, args);
}
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.httpBasic().and()
.logout().and()
.authorizeRequests()
.antMatchers("/index.html", "/pages/home.html", "/pages/login.html", "/").permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// #formatter:on
}
}
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
#RequestMapping("/token")
public Map<String,String> token(HttpSession session) {
return Collections.singletonMap("token", session.getId());
}
}
However it works: when I login using "user" and "password" login succeeds, when I use different credentials, it fails.
Is it some high level Spring layer dealing with that? Thanks for help.
This is a little bit of a confusing example that you have picked because it uses Angular for login. So it is sending off the request using AngularJS.
To break it down, first let's have a look at the login.html page:
<form role="form" ng-submit="controller.login()">
So when we submit, we require the controller.login() method call, this is defined in the hello.js file.
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation',
controllerAs : 'controller'
Here we delegate requests sent to /login and map that to our navigation controller. If you go ahead in the hello.js script you'll see the following:
.controller('navigation',
...
var authenticate = function(credentials, callback) {
var headers = credentials ? {
authorization : "Basic "
+ btoa(credentials.username + ":"
+ credentials.password)
} : {};
$http.get('user', {
...
Here, we use make a GET request to /user. And if you take a look at the controller, you'll see that there is a User Principle being returned which is then checked against credentials.
The controller requires a User principle to be passed in for verification which is being built up in the headers part of the $http request.
I would recommend using this example if you are new to Spring Security and want to start off with something more Java oriented and simple.
Edit
It should also be noted that the user object is being created by the Spring application.yml as you said it is formed of the following:
security:
user:
password: password
This is equivalent to:
security.user.password="password"
Reference to the application.yml appendix can be found here.
So in essence, the yml file is creating a User with the given password which can be retrieved by the Principle. Take a look at the huge guide written by Spring that explains the project built in that repo you are looking at, step by step.

Categories