Spring 启动 security/OAuth2 - 丢弃远程身份验证并在访问远程 userInfo 端点后恢复本地
Spring Boot security/OAuth2 - discard remote authentication and restore local once accessed remote userInfo endpoint
我有一个基于 Spring 引导 (2.1.3)/Java 的 Web 应用程序,使用 Spring 安全性和表单登录。这一切都很好。
我现在需要连接到使用 OAuth2/OIDC 的远程服务。从我的网络应用程序中,用户单击 link 并被重定向到远程服务上的一个页面,完成一些信息,然后 returned 到我的站点。我收到访问令牌,对其进行验证,然后调用 userInfo 端点来查询其他用户属性。
到目前为止,这一切都部分有效,并且不需要对 Spring classes 进行任何额外的自定义。
我已经实现了一个额外的 WebSecurityConfigurerAdpater 来处理 oauth 安全配置,并将我的注册和远程提供程序详细信息包含在 yaml 属性文件中。我现在正处于用户登录到我的应用程序的位置,单击 link 并被重定向到远程站点。他们按照要求去做,然后 return。然后中断 - 用户身份验证已恢复为匿名。
更新 :我在这里包含了我的安全配置,显示了本地登录和远程 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()
;
}
}
}
使用调试器,我可以在 class
中看到
org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider
我能够收到 ID 令牌,然后使用它从 UserInfo 端点获取用户属性。
此时,在检索到用户属性后,我想放弃此身份验证并允许用户像以前一样继续使用本地网络应用程序。但是用户现在已经匿名了,需要重新登录
关于如何实现这一点的任何指示?我想我需要以某种方式拦截 OAuth/OIDC 身份验证的处理,然后获得用户属性,丢弃它并恢复到前一个。
感谢任何指点。
谢谢。
在探索了几个兔子洞之后,我找到了一个似乎有效的解决方案。
基本上我需要防止由成功的 OIDC 调用产生的身份验证替换当前登录用户的身份验证。您可以在 AbstractAuthenticationProcessingFilter
中对 successfulAuthentication
的调用中明确看到这种情况发生的位置。通过扩展 OAuth2LoginAuthenticationFilter
来覆盖此行为是我遇到的问题之一。
这是我的解决方案的摘要。
扩展了 OidcUserService
class 以修改 loadUser
() 方法。在创建 OidcUser 对象的位置 (DefaultOidcUser
),我现在检索当前主体并将其添加到我的扩展 DefaultOidcUser。
我已经有一个 AuthenticationSuccessHandler(SimpleUrlAuthenticationSuccessHandler
的扩展)。我已经更新了这个,这样当身份验证是一个 OidcUser 时,我从我的 OidcUser 对象中检索以前的主体,使用它创建一个新的身份验证,然后在安全上下文中替换它。
可能不是最好的解决方案,而且似乎有点老套,但缺少任何其他建议。
我有一个基于 Spring 引导 (2.1.3)/Java 的 Web 应用程序,使用 Spring 安全性和表单登录。这一切都很好。
我现在需要连接到使用 OAuth2/OIDC 的远程服务。从我的网络应用程序中,用户单击 link 并被重定向到远程服务上的一个页面,完成一些信息,然后 returned 到我的站点。我收到访问令牌,对其进行验证,然后调用 userInfo 端点来查询其他用户属性。
到目前为止,这一切都部分有效,并且不需要对 Spring classes 进行任何额外的自定义。
我已经实现了一个额外的 WebSecurityConfigurerAdpater 来处理 oauth 安全配置,并将我的注册和远程提供程序详细信息包含在 yaml 属性文件中。我现在正处于用户登录到我的应用程序的位置,单击 link 并被重定向到远程站点。他们按照要求去做,然后 return。然后中断 - 用户身份验证已恢复为匿名。
更新 :我在这里包含了我的安全配置,显示了本地登录和远程 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()
;
}
}
}
使用调试器,我可以在 class
中看到org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider
我能够收到 ID 令牌,然后使用它从 UserInfo 端点获取用户属性。
此时,在检索到用户属性后,我想放弃此身份验证并允许用户像以前一样继续使用本地网络应用程序。但是用户现在已经匿名了,需要重新登录
关于如何实现这一点的任何指示?我想我需要以某种方式拦截 OAuth/OIDC 身份验证的处理,然后获得用户属性,丢弃它并恢复到前一个。
感谢任何指点。
谢谢。
在探索了几个兔子洞之后,我找到了一个似乎有效的解决方案。
基本上我需要防止由成功的 OIDC 调用产生的身份验证替换当前登录用户的身份验证。您可以在 AbstractAuthenticationProcessingFilter
中对 successfulAuthentication
的调用中明确看到这种情况发生的位置。通过扩展 OAuth2LoginAuthenticationFilter
来覆盖此行为是我遇到的问题之一。
这是我的解决方案的摘要。
扩展了
OidcUserService
class 以修改loadUser
() 方法。在创建 OidcUser 对象的位置 (DefaultOidcUser
),我现在检索当前主体并将其添加到我的扩展 DefaultOidcUser。我已经有一个 AuthenticationSuccessHandler(
SimpleUrlAuthenticationSuccessHandler
的扩展)。我已经更新了这个,这样当身份验证是一个 OidcUser 时,我从我的 OidcUser 对象中检索以前的主体,使用它创建一个新的身份验证,然后在安全上下文中替换它。
可能不是最好的解决方案,而且似乎有点老套,但缺少任何其他建议。