簡體   English   中英

Spring 啟動,請求之間 OAuth2 身份驗證丟失

[英]Spring Boot, OAuth2 authentication is lost between requests

編輯:

來自 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=WebAuthenticationIpressDetails [Remote= ***, SessionId=9438C880A19C93AADJI206B9B8B3386], 授權=[ROLE_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email, SCOPE_https://www.googleapis.com/openid/userinfo.profile, SCOPE_] ]" 調試

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_openDebug

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] ossecurity.web.FilterChainProxy - 保護 GET /auth/api/getBasicInfo

看起來 session id 不一致


我使用 spring 安全內置 oauth2 社交登錄選項,我使用 onAuthenticationSuccess 方法實現了 OAuth2LoginSuccess class 並在其中我獲取與我從 Z41523572E8ZDFBF44 獲得的社交 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 啟動時說用戶未經身份驗證並且身份驗證丟失。

但在 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());

這是 function 我檢查用戶是否登錄:

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

但是當問題發生時,它不會運行 controller,因為 spring 之前安全返回未經授權的錯誤。

預先感謝您的幫助

我找到了解決方案,我希望這會有所幫助。

對我來說造成問題的原因是 GCP 和 GAE 使用服務器的多個實例,如果用戶登錄某個實例並不意味着其他實例也熟悉它,因為 Spring HTTPSession 在內存中。

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

spring.session.store-type=jdbc

-- 您可以使用 redis 代替 jdbc,只要 session 存儲在所有實例之間的共享位置。

還將事務管理器添加到 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

這不是答案,但是評論太長了..

看起來 session 由於某種原因迷路了,一定要注意這一點。

在默認的 Spring 引導配置中,session 由底層 servlet 容器管理,因此值得檢查它是否正常運行。 檢查事項:

  • 您是否運行超過 1 個應用服務器節點? 如果是這樣,請確保 session 正在使用某種集群感知配置(即 Redis / JDBC),本地 session 肯定會失敗
  • 在 Spring Boot 中使用 OAuth2 登錄檢查默認值是值得的。 例如,您可以嘗試使用HttpSessionOAuth2AuthorizedClientRepository和 SpringSessionBackedSessionRegistry 指定 OAuth2 SpringSessionBackedSessionRegistry

基本上啟用所有日志並嘗試在出現問題時從 servlet 容器中觀察 session 狀態。

讓 oauth2 session 正常工作並非易事,特別是考慮到沒有多少好的博客/文檔描述 spring 引導正在做什么。

也就是說,這是一個工作 Redis 支持的 Spring 引導配置與 OAuth 2 登錄的示例,這可能對您有用:

應用配置:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM