在与您讨论后,我发现您要做的实际上是对通过另一个(单独托管的)登录页面(实际上是一个单独的系统)进行身份验证的用户进行预身份验证。这个想法是另一个系统将重定向回来签署智威汤逊在查询参数中。
到那时,这实际上更像是一个联合登录问题,这正是 SAML 2.0 和 OAuth 2.0 旨在解决的问题。但是,如果您必须坚持使用签名 JWT(类似于 SAML 断言)之类的东西,我们可以建模一个相当简单的预身份验证authorization_code
使用 Spring 授权服务器的流程。
Note:我还没有探索过以下选项OAuth 2.0 客户端身份验证和授权授予的 JWT 配置文件但这可能是一个可行的替代方案。看这个问题(#59).
附加说明:下面概述的方法涉及许多安全考虑因素。以下是该方法的草图。其他注意事项包括 CSRF 保护、使用表单后响应模式(类似于 SAML 2.0)来保护访问令牌而不是查询参数、主动使访问令牌过期(2 分钟或更短)等。换句话说,在可能的情况下,始终建议使用 SAML 2.0 或 OAuth 2.0 等联合登录方法,而不是此方法。
您可以从现有的开始Spring 授权服务器示例并从那里发展它。
这是重定向到外部身份验证提供程序的变体,并在重定向返回时包含预身份验证机制:
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// @formatter:off
http
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("https://some-other-sso.example/login"))
);
// @formatter:on
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// @formatter:on
return http.build();
}
@Bean
public JwtDecoder jwtDecoder(PublicKey publicKey) {
return NimbusJwtDecoder.withPublicKey((RSAPublicKey) publicKey).build();
}
@Bean
public BearerTokenResolver bearerTokenResolver() {
DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
bearerTokenResolver.setAllowUriQueryParameter(true);
return bearerTokenResolver;
}
第一个过滤器链在授权服务器端点上运行,例如/oauth2/authorize
, /oauth2/token
等。请注意/oauth2/authorize
端点需要经过身份验证的用户才能运行,这意味着如果调用端点,则必须对用户进行身份验证,否则将调用身份验证入口点,该入口点将重定向到外部提供者。另请注意,两方之间必须存在信任关系,因为我们没有将 OAuth 用于外部 SSO。
当来自 oauth 客户端的重定向到达/oauth2/authorize?...
端点,该请求由 Spring Security 缓存,以便稍后重播(请参阅下面的控制器)。
第二个过滤器链使用签名的 JWT 对用户进行身份验证。它还包括一个定制的BearerTokenResolver
它从 URL 中的查询参数读取 JWT (?access_token=...
).
The PublicKey
注入到JwtDecoder
将来自外部 SSO 提供商,因此您可以将其插入,但它对您的设置有意义。
我们可以创建一个存根身份验证端点,将签名的 JWT 转换为授权服务器上经过身份验证的会话,如下所示:
@Controller
public class SsoController {
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
@GetMapping("/login")
public void login(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
this.successHandler.onAuthenticationSuccess(request, response, authentication);
}
}
The .oauth2ResourceServer()
DSL 使用户在以下情况下进行身份验证:/login
端点被调用。它需要一个access_token
参数(由BearerTokenResolver
)通过验证签名的 JWT 作为用户已通过外部身份验证的断言来对用户进行预身份验证。此时,将创建一个会话,该会话将验证该浏览器将来的所有请求。
然后调用控制器,并使用以下命令简单地重定向回真正的授权端点SavedRequestAwareAuthenticationSuccessHandler
,这将愉快地启动authorization_code
flow.