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核心原理剖析》