Reputation: 93
EDIT:
log from org.springframework.security:
2022-01-17 12:31:03.495 IST
2022-01-17 10:31:03.495 DEBUG [080-exec-5] o.s.s.w.s.SessionManagementFilter - Request requested invalid session id D5F8BA31A3D7466AK3K3C8EA26A4F037
Default
2022-01-17 12:31:03.495 IST
2022-01-17 10:31:03.495 DEBUG [080-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
Debug
2022-01-17 12:31:03.495 IST
"Request requested invalid session id D5F8BA31A3D7466AK3K3C8EA26A4F037"
Debug
2022-01-17 12:31:03.495 IST
"Set SecurityContextHolder to anonymous SecurityContext"
Default
2022-01-17 12:31:03.494 IST
2022-01-17 10:31:03.494 DEBUG [080-exec-5] o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext
Debug
2022-01-17 12:31:03.494 IST
"Set SecurityContextHolder to empty SecurityContext"
Default
2022-01-17 12:31:03.493 IST
2022-01-17 10:31:03.493 DEBUG [080-exec-5] o.s.security.web.FilterChainProxy - Securing GET /logo192.png
Debug
2022-01-17 12:31:03.493 IST
"Securing GET /logo192.png"
***But if I look in the logs some requests after I can get the valid auth:
Debug 2022-01-17 12:31:03.945 IST "Set SecurityContextHolder to SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=com..security.oauth.CustomOAuth2User@, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=***, SessionId=9438C880A19C93AADJI206B9B8B3386], Granted Authorities=[ROLE_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email, SCOPE_https://www.googleapis.com/auth/userinfo.profile, SCOPE_openid]]]" Debug
2022-01-17 12:31:03.945 IST "Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=com..security.oauth.CustomOAuth2User@, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=***, SessionId=9438C880A19C93AADJI206B9B8B3386], Granted Authorities=[ROLE_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email, SCOPE_https://www.googleapis.com/auth/userinfo.profile, SCOPE_openid]]]" Debug
2022-01-17 12:31:03.945 IST "Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=com..security.oauth.CustomOAuth2User@, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=***, SessionId=9438C880A19C93AADJI206B9B8B3386], Granted Authorities=[ROLE_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email, SCOPE_https://www.googleapis.com/auth/userinfo.profile, SCOPE_openid]]]" Default
2022-01-17 12:31:03.944 IST 2022-01-17 10:31:03.944 DEBUG [080-exec-8] o.s.security.web.FilterChainProxy - Securing GET /auth/api/getBasicInfo
it looks like the session id is inconsistent
I use spring security builtin oauth2 social login option, I implemented an OAuth2LoginSuccess class with the onAuthenticationSuccess method and inside of it I fetch the user the corresponds to the social id I got from the oauth:
CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();
int sociald = oAuth2User.getAttribute("id");
User user = usersUtils.getUserBySocailId(socialId);
enter code here
// add the user details to the Auth
SecurityContextHolder.clearContext();
((OAuth2AuthenticationToken) authentication).setDetails(user);
SecurityContextHolder.getContext().setAuthentication(authentication);
If I debug inside the onAuthenticationSuccess I can see a valid auth with all the user details.
after the login I redirect to the home page and i send a auth get request to the server to check if there is a user logged in.
the problem is that 50% of the times the request is completed successfuly and the user can make authenticated requets.
but the other 50% i get redirected automaticly to Login page and when i check the log is see that Spring boot says that the user is unauthenticated and the auth is lost.
But in the onAuthenticationSuccess i can always see the correct auth.
My ApplicationSecurityConfig looks like this:
http.csrf().disable().authorizeRequests()
.antMatchers("/login*", "/signin/**", "/signup/**", "/oauth2/**").permitAll()
.antMatchers(Constants.ADMIN_PREFIX + "/**").hasRole("ADMIN")
.antMatchers(Constants.AUTH_PREFIX + "/**").hasAnyRole("ADMIN", "USER")
.antMatchers(Constants.PUBLIC_PREFIX + "/**").permitAll()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(new UnauthenticatedRequestHandler())
.and()
.formLogin()
.passwordParameter("password")
.usernameParameter("email")
.loginPage("/Login")
.loginProcessingUrl("/loginSecure").permitAll().successHandler(new LoginSuccess()).failureHandler(new FailureSuccess())
.and()
.oauth2Login()
.loginPage("/Login")
.userInfoEndpoint()
.userService(oAuth2UserService)
.and()
.successHandler(new OAuth2LoginSuccess())
.and()
.rememberMe()
.rememberMeParameter("remember-me")
.tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21))
.userDetailsService(this.applicationUserService)
.and()
.logout()
.clearAuthentication(true).invalidateHttpSession(true).logoutSuccessUrl("/login")
.addLogoutHandler(new CustomLogOutHandler());
And this is the function i check if the user is logged in:
@GetMapping(Constants.AUTH_PREFIX + "/checkUserLogged")
public Integer checkUserLogged(Authentication authentication,HttpServletRequest request) {
try{
if (authentication != null) {
User (User) authentication.getDetails();
if (user == null) {
return -1;
}
return user.getId();
}
}
catch (Exception e){
logger.warning(e.getLocalizedMessage());
}
return -1;
}
but when the problem occur it dosen't get to run the controller because spring security return unauthrozed error before.
Thank you in advance for your help
Upvotes: 6
Views: 2478
Reputation: 93
I found the solution, I hope this could help.
The thing that caused the problem for me was that GCP and GAE use multiple instances of the server, and if the user is logged in a certain instance does not mean the other instances are familiar with it too because the Spring HTTPSession is in-memory.
I Switched the Session platform to use the spring-session jdbc using the following configuration in the application.properties :
spring.session.store-type=jdbc
-- you can use redis instead of jdbc, as long as the session is stored in a shared place among all instances.
also added the transaction manager to the SecurtityConfig:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
and added the following configurations :
http.csrf().disable()
.sessionManagement()
.maximumSessions(1)
.and()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
In addition like @stringy05 mentioned the authrizenClient Repository needs ti be updated too:
/**
* Use the servlet container session store for authorized OAuth2 Clients
*/
@Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository() {
return new HttpSessionOAuth2AuthorizedClientRepository();
}
and add the .authorizedClientRepository line to the httpconfig:
....
.oauth2Login()
.loginPage("/Login")
.authorizedClientRepository(authorizedClientRepository)
.authorizationEndpoint().and()
.userInfoEndpoint()
.userService(oAuth2UserService)
.and()
.successHandler(new OAuth2LoginSuccess())
....
Regarding the GAE, I added the following line to the app.yaml file:
network:
session_affinity: true
Upvotes: 1
Reputation: 7067
This isn't an answer, however too long for a comment..
It looks like the session is getting lost for some reason, definitely focus on that.
In a default Spring Boot config the session is managed by the underlying servlet container, so its worth checking that is functioning properly. Things to check:
HttpSessionOAuth2AuthorizedClientRepository
and a SpringSessionBackedSessionRegistry
Basically enable all the logs and try and observe the session states from the servlet container when the problem occurs.
Getting the oauth2 session working correctly can be non-trivial, especially given there are not many good blog / docs that describe what spring boot is doing.
That said, here's an example of a working Redis backed Spring Boot config with OAuth 2 login, which might be useful as a reference for you:
app config:
spring:
session:
store-type: redis
redis:
namespace: sample:api
flush-mode: immediate
redis:
host: localhost
port: 6379
security:
oauth2:
client:
registration:
# add your oauth2 client details here
public class SecurityConfig<S extends Session> extends WebSecurityConfigurerAdapter {
private final ClientRegistrationRepository clientRegistrationRepository;
@Autowired
private RedisIndexedSessionRepository redisIndexedSessionRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors(Customizer.withDefaults())
.sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
.and()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.authorizeRequests(
a -> a.antMatchers("/api/login/callback").permitAll().anyRequest().authenticated())
.oauth2Login()
.authorizationEndpoint()
.authorizationRequestResolver(
new DefaultOauth2AuthorizationRequestResolver(
this.clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI))
.and()
.defaultSuccessUrl("http://localhost:3000/users")
.authorizedClientRepository(authorizedClientRepository());
}
@Bean
public SpringSessionBackedSessionRegistry<?> sessionRegistry() {
return new SpringSessionBackedSessionRegistry<>(this.redisIndexedSessionRepository);
}
/**
* Use the servlet container session store for authorized OAuth2 Clients
*/
@Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository() {
return new HttpSessionOAuth2AuthorizedClientRepository();
}
/**
* specify CORS to work from SPA UI
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("http://localhost:3000*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Configuration
public static class HttpSessionEventPublisherConfig {
/**
* enables session expiry notification
*
* <p>Needs to be declared in a different class from the `SpringSessionBackedSessionRegistry` to
* avoid a circular dependency
*/
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
Upvotes: 1