Spring授权服务器:如何使用托管在单独应用程序上的登录表单?

Col*_*ell 7 spring spring-security oauth-2.0 spring-security-oauth2

我正在使用 Spring Security 和 Spring Authorization Server 并尝试创建身份验证服务器。

我有一个基本流程,允许我使用预先构建的登录页面登录(来自baledung 指南- 这是我正在处理的代码)。我假设这个登录页面表单来自formLogin()这样的:

        http.authorizeRequests(authorizeRequests ->
                authorizeRequests.anyRequest().authenticated()
        )
        //.formLogin(withDefaults());
        return http.build();
Run Code Online (Sandbox Code Playgroud)

登录页面

我不想使用这个预构建的表单,因为我需要完全单独托管和运行登录表单前端应用程序。即在不同的服务器、域和代码库上。

提出这个问题的另一种方式可能是:

  • 如何禁用内置表单,authorization-server以便我可以将其与完全独立的表单一起使用?
  • 有没有推荐的方法来学习如何按照SecurityFilterChain这些方式定制我的?这是正确的查看位置吗?我发现 baledung 文章(以及类似的文章)作为起点很有帮助,但很少适用于更实际的用例。我相信 Spring Security 和 oauth2 库将允许我做我想做的事情,但并不完全清楚。

Ste*_*erg 6

在与您讨论后,我发现您要做的实际上是对通过另一个(单独托管的)登录页面(实际上是一个单独的系统)进行身份验证的用户进行预身份验证。这个想法是,另一个系统将在查询参数中使用签名的 JWT重定向回来。

到那时,这实际上更像是一个联合登录问题,这正是 SAML 2.0 和 OAuth 2.0 旨在解决的问题。但是,如果您必须坚持使用签名 JWT(类似于 SAML 断言)之类的东西,我们可以authorization_code使用 Spring 授权服务器建模一个相当简单的预身份验证流程。

注意:我还没有探索用于 OAuth 2.0 客户端身份验证和授权授予的 JWT 配置文件的选项,但它可能是一个可行的替代方案。请参阅本期 (#59)

附加说明:下面概述的方法涉及许多安全考虑因素。下面是该方法的概述。其他注意事项包括 CSRF 保护、使用表单后响应模式(类似于 SAML 2.0)来保护访问令牌而不是查询参数、主动使访问令牌过期(2 分钟或更短)等。换句话说,在可能的情况下,始终建议使用 SAML 2.0 或 OAuth 2.0 等联合登录方法,而不是此方法。

您可以从现有的Spring Authorization Server 示例开始,并从那里改进它。

这是重定向到外部身份验证提供程序的变体,并在重定向返回时包含预身份验证机制:

    @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;
    }
Run Code Online (Sandbox Code Playgroud)

第一个过滤器链在授权服务器端点上运行,例如/oauth2/authorize/oauth2/token等。请注意,/oauth2/authorize端点需要经过身份验证的用户才能运行,这意味着如果调用端点,则必须对用户进行身份验证,否则将调用身份验证入口点,它重定向到外部提供者。另请注意,两方之间必须存在信任关系,因为我们没有将 OAuth 用于外部 SSO。

当来自 oauth 客户端的重定向到达端点时/oauth2/authorize?...,Spring Security 会缓存该请求,以便稍后重播(请参阅下面的控制器)。

第二个过滤器链使用签名的 JWT 对用户进行身份验证。它还包括一个自定义的BearerTokenResolver,它从 URL ( ) 中的查询参数读取 JWT ?access_token=...

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当调用端点时, DSL.oauth2ResourceServer()会导致对用户进行身份验证。/login它需要一个access_token参数(由BearerTokenResolver)来通过验证签名的 JWT 作为用户已通过外部身份验证的断言来对用户进行预身份验证。此时,将创建一个会话,该会话将验证该浏览器将来的所有请求。

然后调用控制器,并使用 简单地重定向回真正的授权端点SavedRequestAwareAuthenticationSuccessHandler,这将愉快地启动authorization_code流程。