Spring Boot,OAuth2 身份验证在请求之间丢失

MLu*_*ugo 6 java spring spring-security spring-boot spring-security-oauth2

编辑:

从 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"
Run Code Online (Sandbox Code Playgroud)

***但是,如果我在获得有效身份验证后查看日志中的一些请求:

调试 2022-01-17 12:31:03.945 IST“将 SecurityContextHolder 设置为 SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=com..security.oauth.CustomOAuth2User @、Credentials=[PROTECTED]、Authenticated=true、Details=WebAuthenticationDetails [RemoteIpAddress= ***,SessionId=9438C880A19C93AADJI206B9B8B3386],授予权限=[ROLE_USER,SCOPE_https://www.googleapis.com/auth/userinfo.email,SCOPE_https://www.googleapis.com/auth/userinfo.profile,SCOPE_openid]] ]“ 调试

2022-01-17 12:31:03.945 IST“检索到 SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=com..security.oauth.CustomOAuth2User @,凭据=[受保护],Authenticated=true,详细信息=WebAuthenticationDetails [RemoteIpAddress=*** ,SessionId=9438C880A19C93AADJI206B9B8B3386],授予权限=[ROLE_USER,SCOPE_https://www.googleapis.com/auth/userinfo.email,SCOPE_https://www.googleapis.com/auth/userinfo.profile,SCOPE_openid]]]“调试

2022-01-17 12:31:03.945 IST“检索到的 SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=com..security.oauth.CustomOAuth2User @,凭据=[受保护],Authenticated=true,详细信息=WebAuthenticationDetails [RemoteIpAddress=*** ,SessionId=9438C880A19C93AADJI206B9B8B3386],授予权限=[ROLE_USER,SCOPE_https://www.googleapis.com/auth/userinfo.email,SCOPE_https://www.googleapis.com/auth/userinfo.profile,SCOPE_openid]]]“默认

2022-01-17 12:31:03.944 IST 2022-01-17 10:31:03.944 调试 [080-exec-8] ossecurity.web.FilterChainProxy - 保护 GET /auth/api/getBasicInfo

看起来session id不一致


我使用 spring security 内置的 oauth2 社交登录选项,我使用 onAuthenticationSuccess 方法实现了 OAuth2LoginSuccess 类,并在其中获取与我从 oauth 获得的社交 id 相对应的用户:

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);
Run Code Online (Sandbox Code Playgroud)

如果我在 onAuthenticationSuccess 内部进行调试,我可以看到包含所有用户详细信息的有效身份验证。

登录后,我重定向到主页并向服务器发送一个 auth get 请求以检查是否有用户登录。

问题是 50% 的情况下请求会成功完成,并且用户可以发出经过身份验证的请求。

但另外 50% 我会自动重定向到登录页面,当我检查日志时,会看到 Spring boot 说用户未经身份验证并且身份验证丢失。

但在 onAuthenticationSuccess 中我总能看到正确的身份验证。

我的 ApplicationSecurityConfig 如下所示:

    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());
Run Code Online (Sandbox Code Playgroud)

这是我检查用户是否登录的功能:

   @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;
}
Run Code Online (Sandbox Code Playgroud)

但是当问题发生时,它无法运行控制器,因为 spring security 之前返回了未经授权的错误。

预先感谢您的帮助

MLu*_*ugo 1

我找到了解决方案,希望这能有所帮助。

对我来说造成问题的是 GCP 和 GAE 使用服务器的多个实例,如果用户登录某个实例并不意味着其他实例也熟悉它,因为 Spring HTTPSession 是在内存中的。

我使用 application.properties 中的以下配置将会话平台切换为使用 spring-session jdbc :

spring.session.store-type=jdbc
Run Code Online (Sandbox Code Playgroud)

-- 您可以使用redis代替jdbc,只要会话存储在所有实例之间的共享位置即可。

还将事务管理器添加到 SecurtityConfig 中:

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
Run Code Online (Sandbox Code Playgroud)

并添加了以下配置:

    http.csrf().disable()
            .sessionManagement()
            .maximumSessions(1)
            .and()
            .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
Run Code Online (Sandbox Code Playgroud)

此外,像 @stringy05 提到的 authrizenClient 存储库也需要更新:

    /**
 * Use the servlet container session store for authorized OAuth2 Clients
 */
@Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository() {
    return new HttpSessionOAuth2AuthorizedClientRepository();
}
Run Code Online (Sandbox Code Playgroud)

并将 .authorizedClientRepository 行添加到 httpconfig:

....
                .oauth2Login()
            .loginPage("/Login")
            .authorizedClientRepository(authorizedClientRepository)
            .authorizationEndpoint().and()
            .userInfoEndpoint()
            .userService(oAuth2UserService)
            .and()
            .successHandler(new OAuth2LoginSuccess())
Run Code Online (Sandbox Code Playgroud)

....

关于 GAE,我将以下行添加到 app.yaml 文件中:

  network:
    session_affinity: true
Run Code Online (Sandbox Code Playgroud)