Spring Security and Sessions over Web Services - java

Summary
How do you get the session of a web service client using spring web services and spring security?
Details
After submitting
<form method="POST" action="<c:url value="/j_spring_security_check" />">...</form>
I've noticed that you can:
public class MyUserDetailsService implements UserDetailsService {}
Which will allow you to override methods like loadUserByUsername(String username) therefore being able to retrieve the submitted username and do a database lookup to return a user object.
The issue I have, however, is that I'm unsure where SecurityContextHolder gets set. I'm able to get the user object by using this line of code:
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
But I'm not sure how it gets set in the first place. I would like to know the flow after submitting the above-mentioned form so that I can identify how SecurityContextHolder gets set.
The reason why I want to know this is because I want to use it as a "session" for web service client authentication instead of having the client resubmit credentials with every request.
Spring Version: 3.0.2.RELEASE

/j_spring_security_check is handled by the UsernamePasswordAuthenticationFilter which extends AbstractAuthenticationFilter. The security context is set in the latter's successfulAuthentication method.
However, web-service clients are usually stateless and would be more likely to use something like Basic authentication with a shared secret. I'm not sure there would be much benefit in rolling your own session system based on the security context contents. If you are worried about performance then you could use a cache of authentication information on the server.

Related

Get the OAuth2/OIDC access token with every request in Spring

I'm trying to enable multi-tenancy for a previously single-user system. The application used to run on a local server and had a relatively simple frontend baked in.
Now I want to allow multiple users to simultaneously use it in a cloud environment. I went ahead and implemented Auth2 with OIDC and PKCE to redirect users to an external Auth Provider. What I want now is that for every request, the user sends his Access token with the request in order for me to decide what data to provide with the answer.
I could not figure out how to obtain that data, as it seems that the spring framework (by default) only sends the ID token with the request. I suspect the fact that my software would simultaneously be the client and the resource server has something to do with it.
This is my first question, so I'm very happy to modify or extend my question if I've forgotten anything.
What I've tried to far:
I've used Postman to verify that the three tokens, ID token, refresh token and access token are issued correctly and can be retrieved with my credentials.
I tried getting the access token from the request itself. Any parameters (like #AuthenticationPrincipal OidcUser oidcUser) in the controller that include the token, however, are only showing the ID token and not the access token.
Getting the token via the OAuth2AuthorizedClientService does not work either, same problem, as I can only get the ID token, but not the access token.
Update #1, 13.12.2022/11:40: I am using PingOne by PingIdentity as the authentication provider and the following dependencies are or might be related or helpful to this matter:
spring-boot-starter-web
spring-boot-starter-security
spring-boot-starter-thymeleaf
spring-boot-starter-web-services
spring-boot-starter-oauth2-resource-server
Split your security in two with http.securityMatcher in the first picked SecurityFilterChain bean to restrict the endpoints it applies to (use #Order to control that), and configure WebClient to add access-token as Authorization header to its requests to resource-server (REST endpoints). This is demoed in the tutorial I already linked in my comment to your question. This tutorial matches exactly what you are asking, the fact that it uses Keycloak instead of PingOne as authorization server is a detail (solved by editing a property or two).
Resource-server security filter-chain
As reminder, a resource-server is a REST API secured with OAuth2. All end-points of #RestController and #Controller with #ResponseBody should be secured with a SecurityFilterChain containing http.oauth2ResourceServer(). The easiest way to configure a resource-server with Spring is using spring-boot-starter-oauth2-resource-server (or one of the very thin wrappers I wrote around it which enable to configure it from properties with 0 java conf)
By default with such filter-chains, Spring populates the security-context with a sub-class of AbstractOAuth2TokenAuthenticationToken<?>. You can retrieve the access-token from it. At least 2 options to access this Authentication instance:
SecurityContextHolder.getContext().getAuthentication()
have Spring auto-magically inject AbstractOAuth2TokenAuthenticationToken<?> auth as controller method parameter
You can also have the original authorization header injected as controller method parameter with #RequestHeader(value = HttpHeaders.AUTHORIZATION) String authorizationHeader.
I expose various ways to configure resource-servers security filter-chain in this tutorials.
Client security filter-chain
End-points of #Controller returning a template name and secured with OAuth2 are clients. The easiest way to configure a Spring client is with spring-boot-starter-oauth2-client and http.oauth2Login().
Note that in this configuration, the request between the browser and the Spring client is not OAuth2 (it is most frequently secured with a session cookie, not a Bearer access-token in Authorization header). Only requests sent by the Spring client (on the server) to resource-servers are secured with OAuth2.
With client security filter-chain, security-context is populated with OAuth2AuthenticationToken which, on purpose, exposes ID-token and not access-token. As reminder, ID tokens are holding user identity data and are intended to be used by clients when access-tokens audience is resource-servers and is designed for access-control. Clients should consider access-tokens as black box and use it only to authorize their requests to resource-servers (set Bearer Authorization header).
You can get the access-token string from OAuth2AuthorizedClient: authorizedClient.getAccessToken().getTokenValue(), which is itself retrieved from the OAuth2AuthorizedClientService you can auto-wire in your controllers: authorizedClientService.loadAuthorizedClient("your-client-id", auth.getName()) (auth being the OAuth2AuthenticationToken instance retrieved from security-context via SecurityContextHolder or controller method parameter injection)
If you need to authorize a WebClient request from client to resource-server, you can do simpler than retrieve access token and position authorization header: do as in the UiController of the tutorial already linked in my comment to your question:
final var authorizedClient = authorizedClientService.loadAuthorizedClient("spring-addons-public", auth.getName());
final var response = webClient.get().uri("http://localhost:8080/api/greet")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(authorizedClient))
...
With WebClient configured as follow:
#Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
authorizedClientService));
oauth.setDefaultClientRegistrationId("spring-addons-public");
return WebClient.builder().apply(oauth.oauth2Configuration()).build();
}
Thanks to those who tried to help me, but eventually I figured it out myself.
I extended my Controllers by two attributes: OAuth2AuthenticationToken authentication and HttpServletRequest request.
Also, I #Autowired in the OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository.
This then allows the following call returning the value of the accessToken:
(oAuth2AuthorizedClientRepository.loadAuthorizedClient(myClientRegistrationId, authentication, request)).client.getAccessToken().getTokenValue();.
After that, it's just parsing the token and retrieving the values using JWTParser.parse() and provided methods from the JWT-result.
Personal note: Don't do the parsing and retrieving value parts in your controller to keep any more complex logic out of it.
I hope this helps somebody!

How do I get the #AuthenticatedPrincipal from a web socket simpUser?

I'm trying to get logged in users' details who are connected via websocket on Spring Boot 2. To do this, I'm currently using SimpUserRegistry to find a list of connected SimpUsers.
Since Spring Boot 2.4, I noticed the SimpUser class has a getPrincipal() method that returns a generic Principal object. As each user is supposed to login via Spring Security's mechanisms, I thought I was able to cast it to Spring Security's UserDetails to get the logged in user , but I realize it wasn't the case.
Does anyone know how I can make use of getPrincipal or other ways to get logged in userDetails?
First of all, let's make it clear that, to use getPrincipal() with websocket, you have to implement websocket authentication and authorization through Interceptor (as far as I know SpringSecurity doesn't do this automatically).
After doing the above correctly, you can now use the getPrincipal () method. It will return The identity of the principal being authenticated (maybe Username, email,...)
You can use code that looks like this:
#MessageMapping("/test")
public void doSomething(#Payload AppMessage appMessage, Principal principal) {
String username = principal.getName();
// find userDetail with username here
}

Spring SAML credentials and platform accounts

I was able to integrate Spring Boot and Spring Security SAML by using the #ImportResource annotation.
Now, I'd like to go on as follows:
the user selects an IdP [DONE].
it performs the login (successful) [DONE].
the SP obtains user data (by parsing the SAMLCredential object) [DONE].
the webapp had to check if the userID (e.g email) retrieved via SAML exists in my own DB.
a) If yes, the webapp reads from the DB the role and sets related privileges.
b) If no, the webapp had to perform a redirect to a sign-up page, in order to insert the user into the system.
Make it sense perform the points 4 and 5 by using a UserDetailsService implementation or have I to setup the security context defining authentication providers, filters, etc?
You should implement org.springframework.security.saml.userdetails.SAMLUserDetailsService and plug it into the samlAuthenticationProvider bean. In case user doesn't exist you should throw UsernameNotFoundException exception, otherwise just populate and return data from your DB.
On top of that you should implement your own org.springframework.security.web.authentication.AuthenticationFailureHandler and plug it into samlWebSSOProcessingFilter bean. This implementation will be called with the UsernameNotFoundException sent as a parameter and you can then redirect the user to the correct sign-up page as a reaction to it.

Spring Security 3.2 Token Authentication

I know this has been asked already, but I am not able to get it to work.
Here is what I would like to get accomplished:
I am using Spring Security 3.2 to secure a REST-like service. No server side sessions.
I am not using basic auth, because that would mean that I need to store the user's password in a cookie on client side. Otherwise the user would need to login with each page refresh/ change. Storing a token is I guess the lesser evil.
A web client (browser, mobile app) calls a REST-like URL to login "/login" with username and password
The server authenticates the user and sends a token back to the client
The client stores the token and adds it to the http request header with each api call
The server checks the validity of the token and sends a response accordingly
I did not even look at the token generation part yet. I know it is backwards, but I wanted to get the token validation part implemented first.
I am trying to get this accomplished by using a custom filer (implementation of AbstractAuthenticationProcessingFilter), however I seem to have the wrong idea about it.
Defining it like this:
public TokenAuthenticationFilter() {
super("/");
}
will only trigger the filter for this exact URL.
I am sticking to some sample implementation, where it calls AbstractAuthenticationProcessingFilter#requiresAuthentication which does not accept wildcards.
I can of course alter that behavior, but this somehow makes me think that I am on the wrong path.
I also started implementing a custom AuthenticationProvider. Maybe that is the right thing?
Can someone give me a push into the right direction?
I think pre-auth filter is a better fit for your scenario.
Override AbstractPreAuthenticatedProcessingFilter's getPrincipal and getCredentials methods.
In case the token is not present in the header, return null from getPrincipal.
Flow:
User logs in for the first time, no header passed, so no
authentication object set in securityContext, normal authentication
process follows i.e. ExceptionTranslation filter redirtects the user
to /login page based on form-logon filter or your custom authenticationEntryPoint
After successful authentication, user requests secured url, pre-auth filter gets token from header authentication object set in
securityContext, if user have access he is allowed to access secured
url

Keeping "current user" in threadlocal

I have a spring-mvc application that currently has two channels - web application and a REST service. Both have user's http session and I can easily get the "current user" in my service classes.
Now I need to develop another REST service where there are no http sessions and the current user depends on a request parameter. So the controller would read that request parameter and would find the current user.
Now I either need to:
1. modify my service layer methods to accept current user as parameter
or
2. just modify the class that gets the current user from the http session.
I also have the requirement to create an audit log and I'm going to use Spring AOP for that. The Aspect will need access to the "current user" too. So option #1 probably won't work for me and I will go with #2.
For option #2 I'll create an interceptor that will put the current user in a ThreadLocal variable. The controller for the new REST service will do the same and then in my service layer and in the audit log aspect I can get the current user from there.
I haven't done anything like this before and was wondering if there is a better approach. Or what kind of issues I should expect with this approach.
I will appreciate any comments and ideas.
Oz
Here is how I currently get the current user:
#Override
public User getCurrentUser()
{
Authentication currentUser = getAuthentication();
return userService.getByLoginName(currentUser.getName());
}
protected Authentication getAuthentication()
{
return SecurityContextHolder.getContext().getAuthentication();
}
I think a simple way to do what you need is to configure a servlet filter for your new REST webservice to populate the SecurityContextHolder with an Authentication object build from request parameters.
You can read this : http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#d0e2171 for more details.
With this solution you don't need to modify the code to retrieve the current user. (note that the SecurityContextHolder is already using a ThreadLocal to store the SecurityContext and so the Authentication)

Categories

Resources