Spring Security 如何将Spring OAuth2 Auth Server配置为同时充当Auth和资源服务器,独立于会话上下文保护端点?

kkbh8khc  于 2023-10-20  发布在  Spring
关注(0)|答案(2)|浏览(137)

我目前正在使用Spring Security开发一个OAuth 2.0登录/用户管理系统。由于我正在使用spring-security-oauth2-authorization-server模块编写自己的授权服务器(基于像here这样的文档),我还实现了一个用户管理/管理 Jmeter 板。
当然,用户管理的端点在auth服务器上。因此,auth服务器充当授权服务器和 (有点像) 资源服务器。要授权用户使用管理 Jmeter 板,他们当然需要登录,所以首先他们被重定向到授权服务器/authorize端点,然后重定向到登录菜单。然后正常地遵循授权代码授予流程。但是每一步都是在同一台服务器上完成的 (即,身份验证和访问受保护的管理端点)
由于以下问题,我请求将我们的认证服务器配置为认证服务器资源服务器:
授权服务器将securityContext保存到会话中。会话ID(JSESSIONID)然后作为cookie留在用户浏览器中。问题是,当用户试图访问授权服务器上的安全端点(如{...}/admin/users端点)时,仅cookie就足以授权他们向该端点发出请求。这意味着当应该首先请求承载令牌以访问受保护端点时,可以绕过整个授权流。我们希望安全端点可使用承载令牌访问,并且仅使用承载令牌,而不是会话 (或两者的组合)
以下是当前安全配置的缩短版本:

@Bean
@Order(1)
public CorsFilter corsFilter(CorsConfigurationSource corsConfigurationSource) {
    logger.info("Creating corsFilter bean");
    return new CorsFilter(corsConfigurationSource);
}

/**
 * Configures the authorization server endpoints.
 */
@Bean
@Order(2)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, RegisteredClientRepository clientRepository) throws Exception {

    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .registeredClientRepository(clientRepository) // autowired from ClientConfig.java
            .oidc(Customizer.withDefaults());

    http.exceptionHandling((exceptions) -> exceptions
            .defaultAuthenticationEntryPointFor(
                    new LoginUrlAuthenticationEntryPoint("/login"),
                    new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
            )
    );

    http.oauth2ResourceServer((resourceServer) -> resourceServer
            .jwt(Customizer.withDefaults()));

    http.csrf(AbstractHttpConfigurer::disable);

    return http.build();
}

@Bean
@Order(3)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.securityMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/admin/**")));

    http.authorizeHttpRequests((authorize) ->
            authorize
                    .requestMatchers(new AntPathRequestMatcher("/register")).permitAll()
                    .requestMatchers(new AntPathRequestMatcher("/recover")).permitAll()
                    .requestMatchers(new AntPathRequestMatcher("/error/**")).permitAll()
                    .requestMatchers(new AntPathRequestMatcher("/css/**")).permitAll()
                    .requestMatchers(new AntPathRequestMatcher("/js/**")).permitAll()
                    .requestMatchers(new AntPathRequestMatcher("/favicon.ico")).permitAll()
                    .anyRequest().authenticated());

    http.oauth2ResourceServer((resourceServer) -> resourceServer
            .jwt(Customizer.withDefaults()));

    // set custom login form
    http.formLogin(form -> {
        form.loginPage("/login");
        form.permitAll();
    });

    http.logout(conf -> {
        // default logout url
        conf.logoutSuccessHandler(logoutSuccessHandler());
    });

    http.csrf(AbstractHttpConfigurer::disable);
    http.cors(AbstractHttpConfigurer::disable);

    return http.build();
}

@Bean
@Order(4)
public SecurityFilterChain adminResourceFilterChain(HttpSecurity http) throws Exception {

    // handle out custom endpoints in this filter chain
    http.authorizeHttpRequests((authorize) ->
            authorize
                    .requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN")
                    .anyRequest().authenticated());

    http.sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

    http.oauth2ResourceServer((resourceServer) -> resourceServer
            .jwt(Customizer.withDefaults()));

    http.csrf(AbstractHttpConfigurer::disable);
    http.cors(AbstractHttpConfigurer::disable);

    return http.build();
}

如何配置授权服务器,使管理端点独立于会话的安全上下文而受到保护?

出于披露的目的,我调试了 * 很多 ,基本上尝试了整个basics!我还尝试了一些其他的东西:
根据Spring Security Documentation,可以在安全配置中将.sessionManagement设置为STATELESS。我曾希望这能解决这个问题,但在资源服务器过滤器链中设置这个会导致另一个问题:如果会话管理标志设置为STATELESS,则无法正确处理登录。在登录表单的POST请求之后,认证服务器重定向到“/",而不是从/authorize请求重定向到“redirect_url”。
我认为这是因为auth服务器模块依赖于保存到会话的安全上下文来进行某些过滤。*
我也有一些问题与CORS,并认为这可能会导致这一点。考虑到docs说:
CORS必须在Spring Security之前处理,因为飞行前请求不会包含任何Cookie(即:JSESSIONID)。如果请求不包含任何cookie,并且Spring Security是第一个,则请求将确定用户未通过身份验证(因为请求中没有cookie)并拒绝它。
这可以解释前端Vue.js应用程序不能正常工作,但不是通过Postman的调试调用。现在我停用了CORS,不处理这些问题。

tpxzln5u

tpxzln5u1#

你的资源服务器过滤链不应该包含任何关于登录和注销的内容。
资源服务器过滤器链可以配置为“无状态”(没有会话),因为安全上下文是在访问令牌之外(而不是会话)构建的。
登录是OAuth2客户端关注点,而不是资源服务器关注点:这是客户端向资源服务器发送请求,资源服务器获取并存储令牌(资源服务器业务仅是令牌验证和访问控制)。
使用oauth2Login的客户端需要会话(以及针对CSRF的保护)(它在authorization_code流期间使用,用于存储令牌和授权传入请求)。
如果您的应用程序还托管管理 Jmeter 板的客户端(带有Tymeleaf等模板引擎的Spring MVC控制器),那么您可以为其定义第三个过滤器链(带有会话,CSRF保护和oauth2Login)。
但是,由于您的 Jmeter 板UI似乎是一个SPA(Vue应用程序),它不能是一个机密客户端,登录应该由中间件处理:

  • 服务器上
  • 维护前端会话
  • 配置为具有登录名的OAuth2机密客户端
  • 存储token(Spring默认在session中执行,这很好)
  • 防止CSRF(在SPA的情况下,cookie的http-only标志设置为false)
  • 在将请求从前端转发到资源服务器之前,将会话cookie替换为包含会话中的访问令牌的Authorization头

这通常被称为BackendFFrontend模式(BFF),我写了a tutorial for that

wgmfuz8q

wgmfuz8q2#

在您的defaultSecurityFilterChain配置中,强制验证需要不记名令牌

http.oauth2ResourceServer((resourceServer) -> resourceServer
                .jwt(Customizer.withDefaults()).bearerTokenResolver(new CustomBearerTokenResolver() ));

public class CustomBearerTokenResolver implements BearerTokenResolver {

    private final BearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver();
    
    @Override
    public String resolve(HttpServletRequest request) {
        String resolveToken = defaultBearerTokenResolver.resolve(request);
        if(resolveToken == null){
            throw new RuntimeException("missing jwt token");
        }
        return resolveToken;
    }

}

相关问题