Spring Security 6.4 新特性

Spring Security 6.4 提供了许多新特性。以下是该版本的主要亮点,您也可以查看版本说明以获取每个特性和错误修复的详细列表。

弃用通知

随着我们接近 Spring Security 7,及时了解弃用情况非常重要。因此,本节指出了 6.4 版本中的弃用项。

  • 方法安全 (Method Security) - 建议使用 AuthorizationManager#authorize 代替 AuthorizationManager#check,后者已被弃用。这主要是为了让返回类型成为接口而不是具体类。如果您正在调用 AuthorizationManager#check,请改为调用 AuthorizationManager#authorize

    与此相关,接受 AuthorizationDecision 参数的 AuthorizationEventPublisher#publishEvent 方法已被弃用,建议使用接受 AuthorizationResult 接口参数的同名方法。

  • 方法安全 (Method Security) - PrePostTemplateDefaults 已被弃用,建议使用更通用的 AnnotationTemplateExpressionDefaults,因为现在 `@AuthenticationPrincipal` 和 `@CurrentSecurityContext` 也支持元注解属性。如果您正在构造 PrePostTemplateDefaults,请将其更改为 AnnotationTemplateExpressionDefaults

  • OAuth 2.0 - NimbusOpaqueTokenIntrospector 已被弃用,建议使用 SpringOpaqueTokenIntrospector,以移除 Spring Security OAuth 2.0 资源服务器对 oidc-oauth2-sdk 包的依赖。如果您正在构造 NimbusOpaqueTokenIntrospector,请将其替换为 SpringOpaqueTokenIntrospector 的构造函数。

  • OAuth 2.0 - DefaultAuthorizationCodeTokenResponseClientDefaultClientCredentialsTokenResponseClientDefaultJwtBearerTokenResponseClientDefaultPasswordTokenResponseClientDefaultRefreshTokenTokenResponseClientDefaultTokenExchangeTokenResponseClient 已被弃用,建议使用其 RestClient 等效实现。

    与此相关,JwtBearerGrantRequestEntityConverterOAuth2AuthorizationCodeGrantRequestEntityConverterOAuth2ClientCredentialsGrantRequestEntityConverterOAuth2PasswordGrantRequestEntityConverterOAuth2RefreshTokenGrantRequestEntityConverter 已被弃用,建议改为向上述令牌响应客户端之一提供 DefaultOAuth2TokenRequestParametersConverter 的实例。

    例如,如果您有以下配置

    private static class MyCustomConverter
        extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<OAuth2AuthorizationCodeGrantRequest> {
    	@Override
        protected MultiValueMap<String, String> createParameters
                (OAuth2AuthorizationCodeGrantRequest request) {
    		MultiValueMap<String, String> parameters = super.createParameters(request);
    		parameters.add("custom", "value");
    		return parameters;
        }
    }
    
    @Bean
    OAuth2AccessTokenResponseClient authorizationCode() {
    	DefaultAuthorizationCodeTokenResponseClient client =
            new DefaultAuthorizationCodeTokenResponseClient();
    	Converter<AuthorizationCodeGrantRequest, RequestEntity<?>> entityConverter =
            new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    	entityConverter.setParametersConverter(new MyCustomConverter());
    	client.setRequestEntityConverter(entityConverter);
        return client;
    }

    此配置已弃用,因为它使用了 DefaultAuthorizationCodeTokenResponseClientOAuth2AuthorizationCodeGrantRequestEntityConverter。现在推荐的配置是

    private static class MyCustomConverter implements Converter<OAuth2AuthorizationCodeGrantRequest, Map<String, String>> {
    	@Override
        public MultiValueMap<String, String> convert(OAuth2AuthorizeCodeGrantRequest request) {
    		MultiValueMap<String, String> parameters = OAuth2AuthorizationCodeGrantRequest.defaultParameters(request);
    		parameters.add("custom", "value");
    		return parameters;
        }
    }
    
    @Bean
    OAuth2AccessTokenResponseClient authorizationCode() {
    	RestClientAuthorizationCodeTokenResponseClient client =
            new RestClientAuthorizationCodeTokenResponseClient();
    	client.setParametersConverter(new MyCustomConverter());
        return client;
    }
  • SAML 2.0 - Spring Security SAML 2.0 服务提供者接口的未版本化 OpenSAML 实现已被弃用,建议使用版本化实现。例如,OpenSamlAuthenticationTokenConverter 现在已被 OpenSaml4AuthenticationTokenConverterOpenSaml5AuthenticationTokenConverter 替代。如果您正在构造这些已弃用版本之一,请将其替换为您正在使用的 OpenSAML 版本对应的实现。

  • SAML 2.0 - 关于 AssertingPartyDetails 的方法已被弃用,建议使用接受 AssertingPartyMetadata 接口参数的等效方法。

  • LDAP - DistinguishedName 的用法现已弃用,以与 Spring LDAP 的弃用保持一致。

一次性令牌登录

通行密钥

Spring Security 现在支持通行密钥

方法安全

  • 所有方法安全注解现在都支持 Spring Framework 的 `@AliasFor`

  • `@AuthenticationPrincipal` 和 `@CurrentSecurityContext` 现在支持注解模板

    这意味着您现在可以像这样使用 Spring 的元注解支持

    • Java

    • Kotlin

    @Target(TargetType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @AuthenticationPrincipal("claims['{claim}']")
    @interface CurrentUsername {
    	String claim() default "sub";
    }
    
    // ...
    
    @GetMapping
    public String method(@CurrentUsername("username") String username) {
    	// ...
    }
    annotation CurrentUsername(val claim: String = "sub")
    
    // ...
    
    @GetMapping
    fun method(@CurrentUsername("username") val username: String): String {
    	// ...
    }
  • 对齐 Security 注解搜索AbstractFallbackMethodSecurityMetadataSource 的算法进行了一些改进。这有助于从早期版本的 Spring Security 进行迁移。

  • Native 应用现在可以使用 `@AuthorizeReturnObject`

  • Native 应用现在可以在 `@PreAuthorize` 和 `@PostAuthorize` 中引用 bean

  • SecurityAnnotationScanners 提供了一个方便的 API,用于扫描 Security 注解以及将 Security 的选择和模板特性添加到自定义注解中。

OAuth 2.0

  • oauth2Login() 现在接受将 OAuth2AuthorizationRequestResolver 作为 `@Bean`

  • ClientRegistrations 现在支持外部获取的配置。

  • 在响应式 oauth2Login() 中向 DSL 添加了 loginPage()

  • OIDC Back-Channel 支持现在接受类型为 logout+jwt 的退出登录令牌

  • RestClient 现在可以配置 OAuth2ClientHttpRequestInterceptor访问受保护的资源请求

  • 添加了基于 RestClientOAuth2AccessTokenResponseClient 实现,以便更一致地配置访问令牌请求。

    要启用 RestClient 支持,只需为每种授权类型发布一个 bean,如下例所示

    • Java

    • Kotlin

    @Configuration
    public class SecurityConfig {
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
    		return new RestClientAuthorizationCodeTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
    		return new RestClientRefreshTokenTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
    		return new RestClientClientCredentialsTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
    		return new RestClientJwtBearerTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
    		return new RestClientTokenExchangeTokenResponseClient();
    	}
    
    }
    @Configuration
    class SecurityConfig {
    
    	@Bean
    	fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
    		return RestClientAuthorizationCodeTokenResponseClient()
    	}
    
    	@Bean
    	fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
    		return RestClientRefreshTokenTokenResponseClient()
    	}
    
    	@Bean
    	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
    		return RestClientClientCredentialsTokenResponseClient()
    	}
    
    	@Bean
    	fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
    		return RestClientJwtBearerTokenResponseClient()
    	}
    
    	@Bean
    	fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
    		return RestClientTokenExchangeTokenResponseClient()
    	}
    
    }
  • 令牌交换现在支持刷新令牌

SAML 2.0

  • 添加了OpenSAML 5 支持。现在您可以使用 OpenSAML 4 或 OpenSAML 5;默认情况下,Spring Security 将根据您 classpath 中存在的版本选择正确的实现。

  • 简化了使用 EntityID 作为 registrationId 的过程。

    一种常见模式是通过其 entityID 来标识断言方。在以前的版本中,这需要直接配置 OpenSamlAuthenticationRequestResolver。现在,请求解析器除了在路径中查找 registrationId 外,默认还将其作为请求参数查找。这允许您使用 RelyingPartyRegistrationsOpenSaml4/5AssertingPartyMetadataRepository,而无需修改 registrationId 值或自定义请求解析器。

    与此相关,您现在可以配置您的 authenticationRequestUri 包含一个查询参数

  • 现在可以根据元数据的过期时间在后台刷新断言方 (Asserting Parties)。

    例如,您现在可以使用 OpenSaml5AssertingPartyMetadataRepository 来实现

    • Java

    • Kotlin

    @Component
    public class RefreshableRelyingPartyRegistrationRepository implements IterableRelyingPartyRegistrationRepository {
    	private final AssertingPartyMetadataRepository assertingParties = OpenSaml5AssertingPartyMetadataRepository
    		.fromTrustedMetadataLocation("https://idp.example.org").build();
    
    	@Override
    	public RelyingPartyRegistration findByRegistrationId(String registrationId) {
    		AssertingPartyMetadata assertingParty = this.assertingParties.findByEntityId(registrationId);
    		return RelyingPartyRegistration.withAssertingPartyMetadata(assertingParty)
    			// relying party configurations
    			.build();
    	}
    
    	// ...
    }
    @Component
    open class RefreshableRelyingPartyRegistrationRepository: IterableRelyingPartyRegistrationRepository {
    	private val assertingParties: AssertingPartyMetadataRepository = OpenSaml5AssertingPartyMetadataRepository
    		.fromTrustedMetadataLocation("https://idp.example.org").build()
    
    	override fun findByRegistrationId(String registrationId): RelyingPartyRegistration {
    		val assertingParty = this.assertingParties.findByEntityId(registrationId)
    		return RelyingPartyRegistration.withAssertingPartyMetadata(assertingParty)
    			// relying party configurations
    			.build()
    	}
    
    	// ...
    }

    此实现还支持验证元数据的签名。

  • 您现在可以签署依赖方元数据

  • RelyingPartyRegistrationRepository 的结果现在可以缓存。如果您希望在应用启动后延迟加载注册值,这将非常有用。如果您希望通过 Spring Cache 控制何时刷新元数据,这也很有用。

  • 为了与 SAML 2.0 标准对齐,元数据端点现在使用 application/samlmetadata+xml MIME 类型

Web

  • CSRF BREACH 令牌现在更加一致

  • “记住我” cookie 现在更加可定制

  • 安全过滤器链能发现更多无效配置。例如,在 `any-request` 过滤器链之后声明的过滤器链是无效的,因为它永远不会被调用。

    • Java

    • Kotlin

    @Bean
    @Order(0)
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        http
            // implicit securityMatcher("/**")
            .authorizeHttpRequests(...)
            .httpBasic(...)
    
        return http.build();
    }
    
    @Bean
    @Order(1)
    SecurityFilterChain app(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/app/**")
            .authorizeHttpRequests(...)
            .formLogin(...)
    
        return http.build();
    }
    @Bean
    @Order(0)
    fun api(val http: HttpSecurity): SecurityFilterChain {
        http {
    		authorizeHttpRequests {
    			// ...
    		}
    	}
        return http.build()
    }
    
    @Bean
    @Order(1)
    fun app(val http: HttpSecurity): SecurityFilterChain {
        http {
    		securityMatcher("/app/**")
    		authorizeHttpRequests {
    			// ...
    		}
    	}
        return http.build()
    }

    您可以在相关 ticket 中阅读更多信息。

  • ServerHttpSecurity 现在将 ServerWebExchangeFirewall 作为一个 `@Bean` 拾取。

可观测性

可观测性现在支持分别开关授权、认证和请求的可观测性。例如,要关闭过滤器链的可观测性,您可以发布一个如下所示的 `@Bean`。

  • Java

  • Kotlin

@Bean
SecurityObservationSettings allSpringSecurityObservations() {
	return SecurityObservationSettings.withDefaults()
            .shouldObserveFilterChains(false).build();
}
@Bean
fun allSpringSecurityObservations(): SecurityObservationSettings {
    return SecurityObservationSettings.builder()
            .shouldObserveFilterChains(false).build()
}

Kotlin

ACL