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";
}
}