Spring Boot 3 Redis Indexed Session - Failure to save login - spring-boot

I am upgrading my team's Spring Boot project from 2.7.5 to 3.0.0, which includes a migration of the now deprecated WebSecurityConfigurerAdapter. I am working in Kotlin, but I do not believe the language impacts the performance of the component.
In both the old and new versions of the security configuration class, I have declared this Autowired field and corresponding bean:
#Autowired
private lateinit var sessionRepository: FindByIndexNameSessionRepository<out Session>
#Bean
fun sessionRegistry(): SessionRegistry {
return SpringSessionBackedSessionRegistry(sessionRepository)
}
At first, my application would not start, because Spring could not wire the session repository into my configuration, but after some digging, it was because I had to change the #EnableRedisHttpSession annotation to
#EnableRedisIndexedHttpSession
class SecurityConfiguration(
Now, my current issue with the application is that I cannot log into my application, and I think it has to do with the Redis session management.
I am able to load the login page, and submit a login request. When I submit the request, I am able to see all of the normal debug messages my application prints associated with logging in, taking me through a successful authentication. When all of that finishes, my application redirects the user to the main page, which checks if a user's authentication != null, and redirects them to the main page if so. However, the authentication at this point is null, and there is not an entry in redis for this user's authentication.
In case it helps, this is what the security configuration had defined in the SecurityFilterChain bean for the session parts before the migration:
.sessionManagement().sessionFixation().migrateSession().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).maximumSessions(1).sessionRegistry(sessionRegistry()).expiredUrl("/api/v1/logout").and()
and after:
.sessionManagement { ssm ->
ssm.sessionFixation { it.migrateSession() }
ssm.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1).sessionRegistry(sessionRegistry()).expiredUrl("/api/v1/logout")
}
Is there something I am missing to allow the user authentication to persist? I have not touched any part of the code for this migration besides the pom.xml file and the rewriting of the security configuration.
UPDATE: I have altered the tags of the security configuration, in a desperate attempt at getting something to save.
#Configuration
#EnableWebSecurity
#EnableSpringHttpSession
#EnableRedisIndexedHttpSession(redisNamespace = "admindev:session", flushMode = FlushMode.IMMEDIATE, saveMode = SaveMode.ALWAYS)
class SecurityConfiguration(
I have increased the logging level for spring security, and everything looks fine, until the session token is not found.
Update 2:
I have tweaked my application as I have described in my answer to this post, and I am now able to access the frontend of my application. However, when I attempt to login through the backend, I get caught up in an infinite redirection loop. The app tries to send me to the error/4xx page, which is the correct behavior, since we don't have a "/" link on the backend, but then the app tries to redirect me according to the "your session has expired" page, and since I'm logged in, it tries to redirect me back to "/"...
Looking through the logs, it seems to be related to the exception handling I set up on my filter chain:
http.exceptionHandling { e -> e.authenticationEntryPoint(LoginRedirectHandler()) }
//...
class LoginRedirectHandler : LoginUrlAuthenticationEntryPoint("/login") {
override fun determineUrlToUseForThisRequest(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AuthenticationException?
): String {
return "${this.loginFormUrl}?error=timeout&requestedUrl=${request.requestURL}"
}
}
Now, the question becomes: the backend webpages were not looping before, what could have changed about this behavior?

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

Why is the JPA repository called from spring schedular not able to get the authentication from Security Context

I have a springboot application where with authentication available in SecurityContext post login. Any call from Rest Controller to persist any entity, getCurrentAuditor() method is called which returns the current principle which is used for auto updating the created date column.
I created an schedular using spring "awaitility" dependency. However, this schedular calls an update on a entity. When update is called and spring authentication is checked, it comes as null, even though i have logged in from front end. From front end i am able to persist other entities and gets the authentication object as well.
As per my understanding, this might be happening because the schedular starts as soon as Springboot kicks in and making save request independently. If that understanding is correct, how should i resolve this?
If the Scheduler can use a "system" user for update the entity, you can do something like the following and in the scheduler code perform the authentication:
public void authenticate() {
Authentication auth = authenticationManager.authenticate(getBatch());
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
}
public UsernamePasswordAuthenticationToken getBatch() {
return UsernamePasswordAuthenticationTokenBuilder.anUsernamePasswordAuthenticationToken()
.withCredentials(batchProperties.getPassword()).withUserCode(batchProperties.getUser()).withUserDto(
userDtoFactory.getBatch()).build();
}

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.

Preventing need to re-login with Angular6/Spring Boot 2 web app using oauth2

I'm writing a small web-app using Spring Boot 2 as the backend and Angular6/Ionic as the front end. The intention is to have users add the site to their home screen and for it to basically look/feel like a native app. This is working pretty well but I would like to use Google for login with Spring Security Oauth2. My problem is that Spring Boot keeps the user auth tokens on the server associated with the session, and the IOS home screen icon loads with all cookies cleared every time the icon is clicked. Since the cookie is gone when the page loads, the user needs to log in again.
Apparently html5 local storage should persist from launch to launch, so I'm thinking I need generate a key for the user after auth which can be stored in in local storage on the device, then when the the user accesses the page it can provide this key which I can use to "Authenticate" them on the server... something along those lines.
I'm looking for ideas of how to allow the user to stay "Logged in" without the availability of cookies being reliably stored for any length of time.
Currently Using
Spring Boot 2
Angular 6
Ionic 4
Spring Security
Spring Oauth2
Everything is behind security except for the login page.
At the moment I'm persisting sessions to jdbc and my configs look like this:
applicaion.yml
spring.security.oauth2.client.registration.google.client-id=XXXXX
spring.security.oauth2.client.registration.google.client-secret=XXXX
server.servlet.session.persistent=true
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
MvcConfig.java
#EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static");
registry.addResourceHandler("/static/*.js", "/static/*.css", "/static/*.svg")
.addResourceLocations("/static")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
SecurityConfig.java
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login().and().logout().logoutSuccessUrl("/");
}
}
I've tried telling spring to put session ID's as x-auth headers, but google oauth appears to stop working then. As in I go to a page, get the screen to click on to log in with google, log in with google and am returned to my login page with an error: "Your login attempt was not successful, try again."
So basically google oauth works with config above but fails with this added to SecurityConfig.java
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return new HeaderHttpSessionIdResolver("X-Auth-Token");
}
This is apparently failing because The appropriate session information isn't getting passed to/back from Google. My login process produces 3 "sessions"
1) When the user first tries to access the page and gets the login page
2) When the token response is returned from google. This session indicates an error of "authorization_request_not_found"
3) When the user is re-directed back to the login page.
It looks like some info about the session is being passed to/back from google but the session ID's doing look right
Request to google auth is:
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=1111111111111-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com&scope=openid%20profile%20email&state=NGW6kTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%3D&redirect_uri=http://localhost.com:9733/login/oauth2/code/google
Callback from google auth:
http://localhost:9733/login/oauth2/code/google?state=NGW6kTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%3D&code=4/xxxx_xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&scope=openid+email+profile+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/plus.me+https://www.googleapis.com/auth/userinfo.email&authuser=0&session_state=6ee92xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..2618&prompt=none

Enforce separate sessions for different tabs in spring security

I am using spring security v 3.1.3 in my web application. The app has a single entry login form customized with custom-filter in spring security. For now, my configurations are allowing a user to automatically log in the app if he opens the URL from a different tab in same browser, which is the default behavior of spring security session management.
I want to ensure that whenever a user log into the application, the session should not get shared across different tabs. On opening a new tab, he should get login page and logging in would create a new session in the same browser. For now i could not find any way to do this with spring security framework. I wouldn't mind integrating JsessionID in URLs, but it would be better if there is another way.
This is not a limitation on Spring Security, this is a general limitation of how the browsers work with cookies; if you set a cookie it's going to be shared by all tabs.
Said that the only reasonable option I can think of right now would be to include the session id in the URL as you suggested.
You can make use of HeaderWebSessionIdResolver.
Spring uses CookieWebSessionIdResolver by default.
To make use of it, use a random sessionId and save it in session storage, and send it along with your headers. This will vary across tabs, and will provide you with different web sessions.
val headerName = "SomeHeaderName"
#Configuration
class SessionConfig {
#Bean
fun headerWebSessionIdResolver(): WebSessionIdResolver {
return HeaderWebSessionIdResolver().apply {
headerName = headerName
}
}
#Bean
fun webSessionManager(webSessionIdResolver: WebSessionIdResolver): DefaultWebSessionManager {
return DefaultWebSessionManager().apply {
sessionIdResolver = webSessionIdResolver
}
}
}

Resources