Spring Boot 中 Spring Security 自动配置

1、概览

本文将带你了解 Spring Boot 中 Spring Security 的自动配置、默认安全配置,以及如何在需要时禁用或自定义它。

2、默认的 Spring Security 设置

首先添加 security starter 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

这包含初始/默认 Security 配置的 SecurityAutoConfiguration 类。

这里没有指定版本,因为项目使用了 spring-boot-starter-parent 作为 parent

默认情况下,应用会启用身份验证,内容协商(Content Negotiation)用于确定应使用 basic 还是 formLogin

有一些预定义的配置属性:

spring.security.user.name=
spring.security.user.password=

如果不使用预定义属性 spring.security.user.password 配置密码并启动应用,默认密码将随机生成并打印在控制台日志中:

Using default security password: c8be15da-4489-4491-9dc6-fab3f91435c7

有关更多默认值,请参阅 Spring Boot 中文文档中的 属性配置

3、禁用自动配置

要禁止 Security 自动配置并添加我们的自定义配置,需要排除 SecurityAutoConfiguration 自动配置类。

可以通过 @SpringBootApplication 注解中的 exclude 属性来实现:

@SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
public class SpringBootSecurityApplication {

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

或者,可以在 application.properties 文件中添加一些配置:

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration

不过,在某些特殊情况下,这种设置也是不够的。

例如,几乎每个 Spring Boot 应用都在 classpath 中启动了 Actuator。这会导致问题,因为另一个自动配置类需要我们刚刚排除掉的那个。因此,应用将无法启动。

为了解决这个问题,需要排除那个类;特别是在 Actuato 的情况下,还需要排除 ManagementWebSecurityAutoConfiguration

3.1、“禁用” 和 “绕过” Security 自动配置

禁用自动配置和绕过自动配置之间有很大区别。

禁用它就像添加 Spring Security 依赖和从头开始整个设置一样。这在多种情况下都很有用:

  1. 将应用 Security 与自定义 Security Provider 集成。
  2. 将已存在 Security 设置的遗留 Spring 应用迁移到 Spring Boot。

但大多数情况下,我们并不需要完全禁用 Security 自动配置。

这是因为 Spring Boot 的配置允许我们通过添加自定义配置类来绕过自动配置的 Security。这通常比较容易,因为我们只需要自定义现有的 Security 设置,以满足我们的需求。

4、Spring Boot 配置 Spring Security

如果选择了禁用 Security 自动配置,自然就需要提供自己的配置。

如上所述,默认的 Security 配置,可以通过修改 properties 文件对其进行自定义。

例如,可以通过添加自己的密码来覆盖默认密码:

spring.security.user.password=123456

如果想要更灵活的配置,例如多用户和多角色配置,就需要使用完整的 @Configuration 类:

@Configuration
@EnableWebSecurity
public class BasicConfiguration {

    @Bean
    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("user")
            .password(passwordEncoder.encode("password"))
            .roles("USER")
            .build();

        UserDetails admin = User.withUsername("admin")
            .password(passwordEncoder.encode("admin"))
            .roles("USER", "ADMIN")
            .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.authorizeHttpRequests(request -> request.anyRequest()
                .authenticated())
            .httpBasic(Customizer.withDefaults())
            .build();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return encoder;
    }
}

如果我们禁用默认的 Security 配置,@EnableWebSecurity 注解就显得至关重要。

如果缺失,应用将无法启动。

另外,在使用 Spring Boot 2 时,需要使用 PasswordEncoder 来设置密码。更多详情,请参阅 官方文档

现在,通过几个测试来验证我们的 Security 配置是否正确应用:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class BasicConfigurationIntegrationTest {

    TestRestTemplate restTemplate;
    URL base;
    @LocalServerPort int port;

    @Before
    public void setUp() throws MalformedURLException {
        restTemplate = new TestRestTemplate("user", "password");
        base = new URL("http://localhost:" + port);
    }

    @Test
    public void whenLoggedUserRequestsHomePage_ThenSuccess()
     throws IllegalStateException, IOException {
        ResponseEntity<String> response =
          restTemplate.getForEntity(base.toString(), String.class);
 
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertTrue(response.getBody().contains("Baeldung"));
    }

    @Test
    public void whenUserWithWrongCredentials_thenUnauthorizedPage() 
      throws Exception {
 
        restTemplate = new TestRestTemplate("user", "wrongpassword");
        ResponseEntity<String> response =
          restTemplate.getForEntity(base.toString(), String.class);
 
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
        assertTrue(response.getBody().contains("Unauthorized"));
    }
}

事实上,Spring Security 背后是 Spring Boot Security,因此可以在 Spring Boot 中完成的任何 Security 配置或支持的任何集成也都可以在 Spring Boot 中实现。

5、Spring Boot OAuth2 自动配置(传统技术栈)

Spring Boot 有专门的 OAuth2 自动配置支持。

随着 Spring Boot 1.x 后续版本的推出,Spring Security OAuth 支持被移除,取而代之的是与 Spring Security 5 捆绑在一起的 OAuth 支持。在下一节中,我们将看到如何使用它

对于传统技术栈(使用 Spring Security OAuth),首先需要添加一个 Maven 依赖项

<dependency>
   <groupId>org.springframework.security.oauth</groupId>
   <artifactId>spring-security-oauth2</artifactId>
</dependency>

该依赖包括一组能够触发 OAuth2AutoConfiguration 类中定义的自动配置机制的类。

5.1、OAuth2 Authorization Server 的自动配置

如果希望应用成为 OAuth2 Provider,可以使用 @EnableAuthorizationServer

启动时,可以在日志中看到,自动配置类将为授权服务器生成一个 client id 和一个 client secret,当然还有一个随机密码用于 basic authentication

Using default security password: a81cb256-f243-40c0-a585-81ce1b952a98
security.oauth2.client.client-id = 39d2835b-1f87-4a77-9798-e2975f36972e
security.oauth2.client.client-secret = f1463f8b-0791-46fe-9269-521b86c55b71

这些凭证可用于获取 Access Token:

curl -X POST -u 39d2835b-1f87-4a77-9798-e2975f36972e:f1463f8b-0791-46fe-9269-521b86c55b71 \
 -d grant_type=client_credentials 
 -d username=user 
 -d password=a81cb256-f243-40c0-a585-81ce1b952a98 \
 -d scope=write  http://localhost:8080/oauth/token

5.2、其他 Spring Boot OAuth2 自动配置设置

Spring Boot OAuth2 还涵盖其他的一些东西:

  1. Resource Server(资源服务器) - @EnableResourceServer
  2. Client Application(客户端应用) - @EnableOAuth2Sso@EnableOAuth2Client

如果需要我们的应用成为这些类型中的一种,只需在 application properties 中添加一些配置即可,详情请参阅 属性中文文档

6、Spring Boot OAuth2 自动配置(使用新技术栈)

要使用新的技术栈,需要根据要配置的内容(授权服务器、资源服务器或客户端应用)添加依赖。

让我们一个一个来看看。

6.1、OAuth2 授权服务器支持

Spring Security OAuth 提供了将授权服务器设置为 Spring 应用的可能性。但该项目已被弃用,Spring 目前也不支持自己的授权服务器。建议使用现有的成熟 Provider,如 Okta、Keycloak 和 ForgeRock。

不过,Spring Boot 可以让我们轻松配置此类 Provider。有关 Keycloak 配置的示例,可以参考《在 Spring Boot 中使用 Keycloak》或《在 Spring Boot 中嵌入 Keycloak 服务器》。

6.2、OAuth2 资源服务器支持

要加入对资源服务器的支持,需要添加此依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>    
</dependency>

最新版,可以在 Maven Central 中找到。

此外,在 Security 配置中,需要包含 oauth2ResourceServer() DSL:

@Configuration
public class JWTSecurityConfig {
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          ...
          .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
          ...
    }
}

更多细节,你可以参阅 《使用 Spring Security 构建 OAuth 2.0 资源服务器》。

6.3、OAuth2 客户端支持

与配置资源服务器的方式类似,客户端应用也需要其依赖和 DSL。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

同样,最新版可以在 Maven Central 中找到。

Spring Security 5 还通过其 oath2Login() DSL 提供了登录支持。

有关新技术栈中 SSO 支持的详细信息,可以参阅文章《使用 Spring Security OAuth2 实现 SSO 单点登录》。

7、总结

本文介绍了 Spring Boot 中 Spring Security 提供的默认安全(Security)配置,以及如何禁用或覆盖 Security 自动配置机制。最后,还介绍了如何应用新的 Security 配置。


Ref:https://www.baeldung.com/spring-boot-security-autoconfiguration