Sping Boot 2 -为不同的端点配置不同的Spring会话存储类型

ih99xse1  于 2023-03-22  发布在  Spring
关注(0)|答案(1)|浏览(110)

bounty将在6天后过期。回答此问题可获得+100声望奖励。Dilip Raj Baral希望引起更多人关注此问题。

我正在开发一个库(基于Sping Boot 2.7),它提供了一个具有身份验证支持的控制台(各种管理控制台)。这个控制台将在/console上下文中公开。所以,我有下面的过滤器链配置。

@Bean
@Order(-1)
public SecurityFilterChain consoleFilterChain(HttpSecurity http) throws Exception {
    http.antMacher("/console/**")
         .csrf().disable()
         .authorizeRequests().anyRequest().authenticated()
         .and()
         .authenticationManger(customConsoleAuthenticationManager(http))
         .formlogin()
         .and()
         .logout().permitAll();

     return http.build();
}

Filter仅限于/console/**端点,以便客户端应用程序可以为非控制台端点实现自己的Filter。
不过,对于控制台,我需要一个数据库支持的会话(以便它可以跨多个容器工作)。因此,我使用spring-session-jdbc,并在Spring的环境中将spring.session.store-type设置为jdbc(通过AppEnvironmentPostProcessor)。
但问题是,如果客户端应用程序选择使用Spring Session,它将被迫使用数据库会话(或覆盖属性,在这种情况下Console登录将中断)。
因此,我需要为控制台端点本身配置会话存储类型(最好是其他属性,如表名)。
本文档部分讨论了自定义会话存储,但如何使其与spring-session-jdbc一起工作?

z4iuyo4d

z4iuyo4d1#

如果你想为不同的端点使用单独的过滤器链,你可以在HttpSecurity上使用“requestFilter”(单数,而不是复数)方法来限制构建的过滤器应用于其他请求。
如果我们考虑使用HTTP basic而不是表单登录来作为示例,那么我假设您想要的行为看起来像这样:

package org.example;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;

@AutoConfigureWebTestClient(timeout = "PT24H")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ExampleTest {
    @Autowired
    WebTestClient webTestClient;

    @Test
    void test() {
        webTestClient.get().uri("/console").exchange().expectStatus().isUnauthorized();
        webTestClient.get().uri("/console")
                .headers(h -> h.setBasicAuth("user", "console"))
                .exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("console");

        webTestClient.get().uri("/hello").exchange().expectStatus().isUnauthorized();
        webTestClient.get().uri("/hello")
                .headers(h -> h.setBasicAuth("user", "hello"))
                .exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("hello");
    }
}

如果是这样话,下面是使用这种方法实现的方法:

package org.example;

import lombok.SneakyThrows;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class Example {
    public static void main(String[] args) {
        SpringApplication.run(Example.class, args);
    }

    @RestController
    @RequestMapping
    public static class ExampleController {
        @GetMapping("/hello")
        String hello() {
            return "hello";
        }

        @GetMapping("/console")
        String console() {
            return "console";
        }
    }

    @Configuration
    public static class SecurityConfig {
        @SneakyThrows
        @Bean
        @Order(-1)
        SecurityFilterChain consoleFilterChain(HttpSecurity http) {
            return http.requestMatcher(r -> r.getRequestURI().startsWith("/console"))
                    .csrf().disable()
                    .authorizeRequests().anyRequest().authenticated()
                    .and().httpBasic()
                    .and().authenticationManager(authentication -> {
                        if ("user".equals(authentication.getPrincipal()) &&
                                "console".equals(authentication.getCredentials())) {
                            return new UsernamePasswordAuthenticationToken(
                                    authentication.getPrincipal(),
                                    authentication.getCredentials());
                        }
                        throw new BadCredentialsException("bad password");
                    })
                    .build();
        }

        @SneakyThrows
        @Bean
        @Order(-1)
        SecurityFilterChain helloFilterChain(HttpSecurity http) {
            return http.requestMatcher(r -> r.getRequestURI().startsWith("/hello"))
                    .csrf().disable()
                    .authorizeRequests().anyRequest().authenticated()
                    .and().httpBasic()
                    .and().authenticationManager(authentication -> {
                        if ("user".equals(authentication.getPrincipal()) &&
                                "hello".equals(authentication.getCredentials())) {
                            return new UsernamePasswordAuthenticationToken(
                                    authentication.getPrincipal(),
                                    authentication.getCredentials());
                        }
                        throw new BadCredentialsException("bad password");
                    })
                    .build();
        }
    }
}

相关问题