3年前 (2021-06-19)  相关技术 |   抢沙发  439 
文章评分 0 次,平均分 0.0

在本文中,我们将探讨Spring Security 5框架的新特性,以保护被动应用程序。此释放装置与Spring5和Spring Boot2对齐。

在本文中,我们将不深入讨论反应式应用程序本身的细节,这是Spring5框架的一个新特性。

Maven设置

我们将使用springbootstarters来引导我们的项目以及所有必需的依赖项。

基本设置需要父声明、web启动程序和安全启动程序依赖项。我们还需要Spring Security测试框架:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

我们可以在Maven Central查看当前版本的Spring Boot security starter。

项目设置

应式应用程序引导反

我们不使用标准的@SpringBootApplication配置,而是配置一个基于Netty的web服务器。NETY是一个异步的基于NIO的框架,它是无功应用的良好基础。

@EnableWebFlux注释启用应用程序的标准Spring-Web响应式配置:

@ComponentScan(basePackages = {"com.baeldung.security"})
@EnableWebFlux
public class SpringSecurity5Application {

    public static void main(String[] args) {
        try (AnnotationConfigApplicationContext context 
         = new AnnotationConfigApplicationContext(
            SpringSecurity5Application.class)) {
 
            context.getBean(NettyContext.class).onClose().block();
        }
    }

在这里,我们创建一个新的应用程序上下文,并通过调用Netty上下文上的.onClose().block()链来等待Netty关闭。

关闭Netty之后,将使用try with resources块自动关闭上下文。

我们还需要创建一个基于Netty的HTTP服务器、HTTP请求的处理程序以及服务器和处理程序之间的适配器:

@Bean
public NettyContext nettyContext(ApplicationContext context) {
    HttpHandler handler = WebHttpHandlerBuilder
      .applicationContext(context).build();
    ReactorHttpHandlerAdapter adapter 
      = new ReactorHttpHandlerAdapter(handler);
    HttpServer httpServer = HttpServer.create("localhost", 8080);
    return httpServer.newHandler(adapter).block();
}

Spring Security Configuration类

对于我们的基本Spring安全配置,我们将创建一个配置类SecurityConfig

要在Spring Security 5中启用WebFlux支持,我们只需要指定@EnableWebFluxSecurity注释:

@EnableWebFluxSecurity
public class SecurityConfig {
    // ...
}

现在我们可以利用ServerHttpSecurity类来构建我们的安全配置。

这个类是Spring5的一个新特性,它类似于HttpSecurity builder,但它只对WebFlux应用程序启用。

ServerHttpSecurity已经预先配置了一些合理的默认值,因此我们可以完全跳过此配置。但对于初学者,我们将提供以下最低配置:

@Bean
public SecurityWebFilterChain securitygWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .anyExchange().authenticated()
      .and().build();
}

此外,我们还需要一个用户详细信息服务。Spring Security为我们提供了方便的模拟用户生成器和用户详细信息服务的内存实现:

@Bean
public MapReactiveUserDetailsService userDetailsService() {
    UserDetails user = User
      .withUsername("user")
      .password(passwordEncoder().encode("password"))
      .roles("USER")
      .build();
    return new MapReactiveUserDetailsService(user);
}

既然我们处于被动领域,用户详细信息服务也应该是被动的。如果我们检查ReactiveUserDetailsService接口,我们将看到它的findByUsername方法实际上返回一个Mono发布者:

public interface ReactiveUserDetailsService {

    Mono<UserDetails> findByUsername(String username);
}

现在我们可以运行我们的应用程序并观察一个常规的HTTP基本身份验证表单。

样式化登录表单

springsecurity5的一个小而显著的改进是一个新样式的登录表单,它使用bootstrap4css框架。登录表单中的样式表链接到CDN,因此我们只会在连接到Internet时看到改进。

要使用新的登录窗体,我们将相应的formLogin()builder方法添加到ServerHttpSecurity builder:

public SecurityWebFilterChain securitygWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .anyExchange().authenticated()
      .and().formLogin()
      .and().build();
}

如果现在打开应用程序的主页面,我们会看到它看起来比以前版本的Spring Security使用的默认窗体要好得多:

基于Spring Security5的响应式应用

请注意,这不是一个可用于生产的表单,但它是应用程序的引导。

如果我们现在登录然后转到http://localhost:8080/logoutURL,我们将看到注销确认窗体,它也是样式化的。

反应式控制器安全

要查看身份验证表单背后的内容,让我们实现一个简单的反应式控制器来向用户致意:

@RestController
public class GreetController {

    @GetMapping("/")
    public Mono<String> greet(Mono<Principal> principal) {
        return principal
          .map(Principal::getName)
          .map(name -> String.format("Hello, %s", name));
    }

}

登录后,我们将看到问候语。让我们添加另一个只能由管理员访问的反应式处理程序:

@GetMapping("/admin")
public Mono<String> greetAdmin(Mono<Principal> principal) {
    return principal
      .map(Principal::getName)
      .map(name -> String.format("Admin access: %s", name));
}

现在,让我们在用户详细信息服务中创建第二个角色为ADMIN:的用户:

UserDetails admin = User.withDefaultPasswordEncoder()
  .username("admin")
  .password("password")
  .roles("ADMIN")
  .build();

我们现在可以为管理员URL添加一个匹配规则,该规则要求用户具有角色\管理权限。

请注意,我们必须在.anyExchange()链调用之前放置匹配器。此调用适用于其他匹配程序尚未覆盖的所有其他URL:

return http.authorizeExchange()
  .pathMatchers("/admin").hasAuthority("ROLE_ADMIN")
  .anyExchange().authenticated()
  .and().formLogin()
  .and().build();

如果我们现在使用user或admin登录,我们将看到它们都遵守初始问候语,因为我们已经使所有经过身份验证的用户都可以访问它。

但是只有管理员用户可以访问http://localhost:8080/admin URL并查看她的问候语。

反应式安全方法

我们已经看到了如何保护URL,但是方法呢?

要为被动方法启用基于方法的安全性,我们只需将@EnableReactiveMethodSecurity注释添加到SecurityConfig类:

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
    // ...
}

现在,让我们创建一个包含以下内容的反应式问候服务:

@Service
public class GreetService {

    public Mono<String> greet() {
        return Mono.just("Hello from service!");
    }
}

我们可以把它注入控制器,转到http://localhost:8080/greetService并查看它是否实际工作:

@RestController
public class GreetController {

    private GreetService greetService

    @GetMapping("/greetService")
    public Mono<String> greetService() {
        return greetService.greet();
    }

    // standard constructors...
}

但是如果我们现在在带有ADMIN角色的服务方法上添加@PreAuthorize注释,那么普通用户将无法访问greet服务URL:

@Service
public class GreetService {

@PreAuthorize("hasRole('ADMIN')")
public Mono<String> greet() {
    // ...
}

在测试中Mocking用户

让我们看看测试我们的反应式Spring应用程序有多容易。

首先,我们将使用注入的应用程序上下文创建一个测试:

@ContextConfiguration(classes = SpringSecurity5Application.class)
public class SecurityTest {

    @Autowired
    ApplicationContext context;

    // ...
}

现在我们将建立一个简单的反应式web测试客户端,这是Spring5测试框架的一个特性:

@Before
public void setup() {
    this.rest = WebTestClient
      .bindToApplicationContext(this.context)
      .configureClient()
      .build();
}

这使我们能够快速检查未经授权的用户是否从应用程序的主页重定向到登录页:

@Test
public void whenNoCredentials_thenRedirectToLogin() {
    this.rest.get()
      .uri("/")
      .exchange()
      .expectStatus().is3xxRedirection();
}

如果我们现在将@MockWithUser注释添加到一个测试方法中,我们可以为这个方法提供一个经过身份验证的用户。

此用户的登录名和密码分别为user和password,角色为user。当然,这都可以用@MockWithUser注释参数进行配置。

现在我们可以检查授权用户是否看到问候语:

@Test
@WithMockUser
public void whenHasCredentials_thenSeesGreeting() {
    this.rest.get()
      .uri("/")
      .exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, user");
}

@WithMockUser注释从SpringSecurity4开始就可用了。但是,在SpringSecurity5中,它也被更新,以覆盖反应性端点和方法。

代码完整地址:https://github.com/eugenp/tutorials/tree/master/spring-5-reactive-security

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2025.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册