简体   繁体   English

Spring 启动,请求之间 OAuth2 身份验证丢失

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

EDIT:编辑:

log from org.springframework.security:来自 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“将 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 "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 “检索到 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 "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.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 DEBUG [080-exec-8] ossecurity.web.FilterChainProxy - Securing GET /auth/api/getBasicInfo 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

it looks like the session id is inconsistent看起来 session id 不一致


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:我使用 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);

If I debug inside the onAuthenticationSuccess I can see a valid auth with all the user details.如果我在 onAuthenticationSuccess 中进行调试,我可以看到一个包含所有用户详细信息的有效身份验证。

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.登录后我重定向到主页并向服务器发送 auth get 请求以检查是否有用户登录。

the problem is that 50% of the times the request is completed successfuly and the user can make authenticated requets.问题是 50% 的请求成功完成并且用户可以进行经过身份验证的请求。

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.但是另外 50% 我被自动重定向到登录页面,当我检查日志时看到 Spring 启动时说用户未经身份验证并且身份验证丢失。

But in the onAuthenticationSuccess i can always see the correct auth.但在 onAuthenticationSuccess 中,我总能看到正确的身份验证。

My ApplicationSecurityConfig looks like this:我的 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());

And this is the function i check if the user is logged in:这是 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;
}

but when the problem occur it dosen't get to run the controller because spring security return unauthrozed error before.但是当问题发生时,它不会运行 controller,因为 spring 之前安全返回未经授权的错误。

Thank you in advance for your help预先感谢您的帮助

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

I Switched the Session platform to use the spring-session jdbc using the following configuration in the application.properties:我使用 application.properties 中的以下配置将 Session 平台切换为使用 spring-session jdbc:

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. -- 您可以使用 redis 代替 jdbc,只要 session 存储在所有实例之间的共享位置。

also added the transaction manager to the SecurtityConfig:还将事务管理器添加到 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:此外,像@stringy05 提到的 authrizenClient 存储库也需要更新:

    /**
 * 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:并将 .authorizedClientRepository 行添加到 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:关于 GAE,我在 app.yaml 文件中添加了以下行:

  network:
    session_affinity: true

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.看起来 session 由于某种原因迷路了,一定要注意这一点。

In a default Spring Boot config the session is managed by the underlying servlet container, so its worth checking that is functioning properly.在默认的 Spring 引导配置中,session 由底层 servlet 容器管理,因此值得检查它是否正常运行。 Things to check:检查事项:

  • Are you running more than 1 app server node?您是否运行超过 1 个应用服务器节点? If so, ensure the session is using some sort of cluster aware config (ie Redis / JDBC), local session will fail here for sure如果是这样,请确保 session 正在使用某种集群感知配置(即 Redis / JDBC),本地 session 肯定会失败
  • It's worth checking the defaults with OAuth2 login in Spring Boot.在 Spring Boot 中使用 OAuth2 登录检查默认值是值得的。 eg you could try and specify the OAuth2 session using the HttpSessionOAuth2AuthorizedClientRepository and a SpringSessionBackedSessionRegistry例如,您可以尝试使用HttpSessionOAuth2AuthorizedClientRepository和 SpringSessionBackedSessionRegistry 指定 OAuth2 SpringSessionBackedSessionRegistry

Basically enable all the logs and try and observe the session states from the servlet container when the problem occurs.基本上启用所有日志并尝试在出现问题时从 servlet 容器中观察 session 状态。

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.让 oauth2 session 正常工作并非易事,特别是考虑到没有多少好的博客/文档描述 spring 引导正在做什么。

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:也就是说,这是一个工作 Redis 支持的 Spring 引导配置与 OAuth 2 登录的示例,这可能对您有用:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM