【问题标题】:Spring Boot, OAuth2 authentication is lost between requestsSpring Boot,请求之间的 OAuth2 身份验证丢失
【发布时间】:2022-01-02 10:30:01
【问题描述】:

编辑:

来自 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"

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

调试 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], Granted Authorities=[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@, 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]] ]" 调试

2022-01-17 12:31:03.945 IST “检索到 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]] ]" 默认

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

看起来会话 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);

如果我在 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());

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

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

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

提前感谢您的帮助

【问题讨论】:

  • 可以添加日志吗?
  • 我不确定你在做什么,但我很想看到答案。
  • 日志只是说没有经过身份验证的用户
  • Spring Security 在用户会话中持有 auth 上下文,所以也许有什么问题?无论哪种方式都打开 org.springframework.security 的调试日志记录,这应该可以让您知道哪里出错了
  • @stringy05 我将 org.springframework.security 更改为调试并得到以下身份验证丢失:2022-01-17 12:31:03.495 IST “请求请求的无效会话 id D5F8BAJKL23JKL32LLEA26A4F037”调试2022-01-17 12:31:03.495 IST “将 SecurityContextHolder 设置为匿名 SecurityContext” 默认 2022-01-17 12:31:03.494 IST 2022-01-17 10:31:03.494 调试 [080-exec-5] o.s.s.w.c.SecurityContextPersistenceFilter - 将 SecurityContextHolder 设置为空 SecurityContext 调试 2022-01-17 12:31:03.494 IST “将 SecurityContextHolder 设置为空 SecurityContext”

标签: java spring spring-boot spring-security spring-security-oauth2


【解决方案1】:

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

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

我使用 application.properties 中的以下配置切换了 Session 平台以使用 spring-session jdbc:

spring.session.store-type=jdbc

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

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

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

并添加了以下配置:

    http.csrf().disable()
            .sessionManagement()
            .maximumSessions(1)
            .and()
            .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)

除了@stringy05 提到的 authrizenClient 存储库也需要更新:

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

并将 .authorizedClientRepository 行添加到 httpconfig:

....
                .oauth2Login()
            .loginPage("/Login")
            .authorizedClientRepository(authorizedClientRepository)
            .authorizationEndpoint().and()
            .userInfoEndpoint()
            .userService(oAuth2UserService)
            .and()
            .successHandler(new OAuth2LoginSuccess())

....

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

  network:
    session_affinity: true

【讨论】:

    【解决方案2】:

    这不是答案,但评论太长了..

    看起来会话由于某种原因丢失了,一定要专注于此。

    在默认的 Spring Boot 配置中,会话由底层 servlet 容器管理,因此值得检查它是否正常运行。检查事项:

    • 您是否正在运行超过 1 个应用服务器节点?如果是这样,请确保会话使用某种集群感知配置(即 Redis / JDBC),本地会话肯定会在这里失败
    • 值得在 Spring Boot 中使用 OAuth2 登录检查默认设置。例如,您可以尝试使用 HttpSessionOAuth2AuthorizedClientRepositorySpringSessionBackedSessionRegistry 指定 OAuth2 会话

    基本上启用所有日志,并在出现问题时尝试从 servlet 容器观察会话状态。

    让 oauth2 会话正常工作并非易事,尤其是考虑到描述 Spring Boot 正在做什么的优秀博客/文档并不多。

    也就是说,这里有一个使用 OAuth 2 登录的 Redis 支持的 Spring Boot 配置的工作示例,它可能对您有用作为参考:

    应用配置:

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

    【讨论】:

      猜你喜欢
      • 2017-10-08
      • 2019-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多