Spring Boot security/OAuth2 - 丢弃远程身份验证并在访问远程 userInfo 端点后恢复本地

Posted

技术标签:

【中文标题】Spring Boot security/OAuth2 - 丢弃远程身份验证并在访问远程 userInfo 端点后恢复本地【英文标题】:Spring Boot security/OAuth2 - discard remote authentication and restore local once accessed remote userInfo endpoint 【发布时间】:2021-10-27 12:54:18 【问题描述】:

我有一个基于 Spring boot (2.1.3)/Java 的 Web 应用程序,它使用带有表单登录的 Spring 安全性。这一切都很好。

我现在需要连接到使用 OAuth2/OIDC 的远程服务。从我的网络应用程序中,用户单击一个链接并被重定向到远程服务上的一个页面,完成一些信息,然后返回到我的站点。我收到访问令牌,对其进行验证,然后调用 userInfo 端点来查询其他用户属性。

到目前为止,这一切都可以部分工作,并且不需要对 Spring 类进行任何额外的自定义。

我已经实现了一个额外的 WebSecurityConfigurerAdpater 来处理 oauth 安全配置,并将我的注册和远程提供程序详细信息包含在一个 yaml 属性文件中。我现在处于用户登录到我的应用程序、单击链接并被重定向到远程站点的位置。他们做需要做的事,然后返回。然后它会中断 - 用户身份验证已恢复为匿名。

更新:我在此处包含了我的安全配置,显示了本地登录和远程 oauth2 连接的 WebSecurityConfigurerAdapters。

@Configuration
@EnableWebSecurity
public class AppSecurityConfig 

    @Autowired
    @Qualifier("Basic")
    private UserDetailsService userDetailsService;

    @Autowired
    @Qualifier("bcrypt")
    private PasswordEncoderBean passwordEncoderBean;

    @Bean
    public AppDaoAuthenticationProvider appAuthProvider() 
        AppDaoAuthenticationProvider authProvider = new AppDaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoderBean.getEncoder());
        return authProvider;
    

    @Bean
    public AppAuthenticationSuccessHandler appAuthenticationSuccessHandler() 
        return new AppAuthenticationSuccessHandler();
    

   
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
         auth.authenticationProvider(appAuthProvider());
    


    @Configuration
    @Order(1)
    public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter 

        private final ClientRegistrationRepository clientRegistrationRepository;

        @Autowired
        public OAuth2LoginSecurityConfig(ClientRegistrationRepository clientRegistrationRepository) 
             this.clientRegistrationRepository = clientRegistrationRepository;
        

        @Override
        protected void configure(HttpSecurity http) throws Exception 
            http
                    .antMatcher("/identity/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                        .oauth2Login()
                            .loginPage("/identity")
                            .authorizationEndpoint()
                            .baseUri("/identity/oauth2/authorization")
                            .authorizationRequestResolver(
                                    new CustomOAuth2AuthorizationRequestResolver(
                                            clientRegistrationRepository, "/identity/oauth2/authorization"
                                    ))
                        .and()
                            .redirectionEndpoint()
                                .baseUri("/identity/oauth2/code/srvdev")
                        .and()
                            .defaultSuccessUrl("/identity/success")
            ;
        
    

    @Configuration
    @Order(2)
    public static class UserApplConfigurationAdaptor extends WebSecurityConfigurerAdapter 

        private final AccessDeniedHandler accessDeniedHandler;

        private final AppWebAuthenticationDetailsSource webAuthenticationDetailsSource;

        private final AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;

        @Autowired
        public UserApplConfigurationAdaptor(AccessDeniedHandler accessDeniedHandler,
                                            AppWebAuthenticationDetailsSource webAuthenticationDetailsSource,
                                            AppAuthenticationSuccessHandler appAuthenticationSuccessHandler) 
            this.accessDeniedHandler = accessDeniedHandler;
            this.webAuthenticationDetailsSource = webAuthenticationDetailsSource;
            this.appAuthenticationSuccessHandler = appAuthenticationSuccessHandler;
        

        @Override
        protected void configure(HttpSecurity http) throws Exception 
            http
                    .authorizeRequests()
                    .antMatchers(
                            "/",
                            "/index",
                            "/signup",
                            "/homePage*",
                            "/email*",
                            "/register",
                            "/registrationConfirm*",
                            "/badUser*",
                            "/forgotPassword*",
                            "/resetPassword*",
                            "/changePassword*",
                            "/confirmaccount*",
                            "/confirmPasswordAdmin*",
                            "/savePassword*",
                            "/setupAuthenticator",
                            "/QRimage",
                            "/generalError",
                            "/icons/**",
                            "/scss/**",
                            "/css/**",
                            "/font/**",
                            "/img/**",
                            "/js/**",
                            "/policydocs/*",
                            "/favicon.ico").permitAll()
                    .antMatchers("/error/**").authenticated()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/loginUser").permitAll()
                    .loginProcessingUrl("/doLoginUser")
                    .defaultSuccessUrl("/landing")
                    .authenticationDetailsSource(webAuthenticationDetailsSource)
                    .successHandler(appAuthenticationSuccessHandler)
                    .and()
                    .logout().permitAll().logoutUrl("/logout")
                    .and()
                    .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                    .and()
                    .csrf().disable()
            ;
        
    

使用调试器,我可以在课堂上看到这一点

org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider

我能够接收一个 ID 令牌,然后使用它从 UserInfo 端点获取用户属性。

此时,在检索到用户属性后,我想放弃此身份验证并允许用户像以前一样继续使用本地 Web 应用程序。但用户现在已经匿名,需要重新登录。

任何关于我如何实现这一目标的指针?我想我需要以某种方式拦截 OAuth/OIDC 身份验证的处理,然后获取用户属性,将其丢弃并恢复到前一个。

任何指针表示赞赏。

谢谢。

【问题讨论】:

请分享您的安全配置。 已更新安全配置 【参考方案1】:

在经历了几个兔子洞之后,我找到了一个似乎可行的解决方案。

基本上,我需要防止由成功的 OIDC 调用产生的身份验证替换当前登录用户的身份验证。您可以在AbstractAuthenticationProcessingFilter 中对successfulAuthentication 的调用中明确地看到这种情况发生的位置。通过扩展 OAuth2LoginAuthenticationFilter 来覆盖这种行为是我遇到的一个兔子洞。

这是我的解决方案的摘要。

    扩展OidcUserService 类以修改loadUser() 方法。在创建 OidcUser 对象的位置 (DefaultOidcUser),我现在检索当前主体并将其添加到我的扩展 DefaultOidcUser。

    我已经有一个 AuthenticationSuccessHandler(SimpleUrlAuthenticationSuccessHandler 的扩展)。我已经更新了这个,所以当身份验证是 OidcUser 时,我从我的 OidcUser 对象中检索以前的主体,使用它创建一个新的身份验证,然后在安全上下文中替换它。

可能不是最好的解决方案,而且似乎有点老套,但没有任何其他建议。

【讨论】:

以上是关于Spring Boot security/OAuth2 - 丢弃远程身份验证并在访问远程 userInfo 端点后恢复本地的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Spring Boot 应用程序 pom 同时需要 spring-boot-starter-parent 和 spring-boot-starter-web?

《02.Spring Boot连载:Spring Boot实战.Spring Boot核心原理剖析》

spring-boot-quartz, 依赖spring-boot-parent

spring-boot系列:初试spring-boot

Spring Boot:Spring Boot启动原理分析

Spring Boot:Spring Boot启动原理分析