此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.5! |
Java 配置
在 Spring 3.1 中,Spring Framework 中添加了对 Java 配置的一般支持。 Spring Security 3.2 引入了 Java 配置,允许用户在不使用任何 XML 的情况下配置 Spring Security。
如果您熟悉 Security Namespace Configuration,您应该会发现它与 Spring Security Java 配置之间有很多相似之处。
Spring Security 提供了许多示例应用程序来演示 Spring Security Java Configuration 的使用。 |
Hello Web Security Java 配置
第一步是创建我们的 Spring Security Java 配置。
该配置将创建一个称为springSecurityFilterChain
,它负责应用程序中的所有安全性(保护应用程序 URL、验证提交的用户名和密码、重定向到登录表单等)。
以下示例显示了 Spring Security Java 配置的最基本示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
return manager;
}
}
此配置并不复杂或广泛,但它的作用很大:
-
要求对应用程序中的每个 URL 进行身份验证
-
为您生成登录表单
-
让 Username 为
user
且 Password 为password
使用基于表单的身份验证进行身份验证 -
允许用户注销
-
CSRF 攻击预防
-
安全标头集成:
-
用于安全请求的 HTTP 严格传输安全性
-
缓存控制(您可以稍后在应用程序中覆盖它以允许缓存静态资源)
-
X-Frame-Options 集成有助于防止点击劫持
-
-
与以下 Servlet API 方法集成:
AbstractSecurityWebApplicationInitializer
下一步是注册springSecurityFilterChain
替换为 WAR 文件。
您可以在 Java 配置中使用Spring的WebApplicationInitializer
支持在 Servlet 3.0+ 环境中。
毫不奇怪,Spring Security 提供了一个基类 (AbstractSecurityWebApplicationInitializer
) 以确保springSecurityFilterChain
为您注册。
我们使用AbstractSecurityWebApplicationInitializer
根据我们是否已经在使用 Spring,或者 Spring Security 是否是我们应用程序中唯一的 Spring 组件而有所不同。
-
没有现有 Spring 的 AbstractSecurityWebApplicationInitializer - 如果您尚未使用 Spring,请使用这些说明
-
AbstractSecurityWebApplicationInitializer 与 Spring MVC - 如果您已经在使用 Spring,请使用这些说明
没有现有 Spring 的 AbstractSecurityWebApplicationInitializer
如果您不使用 Spring 或 Spring MVC,则需要将WebSecurityConfig
添加到超类中,以确保获取配置:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
这SecurityWebApplicationInitializer
:
-
自动注册
springSecurityFilterChain
筛选应用程序中的每个 URL。 -
添加
ContextLoaderListener
加载 WebSecurityConfig 的 WebSecurityConfig 的 Web 实例。
使用 Spring MVC 的 AbstractSecurityWebApplicationInitializer
如果我们在应用程序中的其他位置使用 Spring,则可能已经有一个WebApplicationInitializer
即加载我们的 Spring Configuration。
如果我们使用以前的配置,则会收到错误。
相反,我们应该使用现有的ApplicationContext
.
例如,如果我们使用 Spring MVC,则我们的SecurityWebApplicationInitializer
可能如下所示:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
这只会注册springSecurityFilterChain
对于应用程序中的每个 URL。
之后,我们需要确保WebSecurityConfig
加载到我们现有的ApplicationInitializer
.
例如,如果我们使用 Spring MVC,它会被添加到getServletConfigClasses()
:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
这样做的原因是 Spring Security 需要能够检查一些 Spring MVC 配置,以便适当地配置底层请求匹配器,因此它们需要位于相同的应用程序上下文中。
将 Spring Security 置于getRootConfigClasses
将其放入可能无法找到 Spring MVC 的HandlerMappingIntrospector
.
配置多个 Spring MVC 调度器
如果需要,任何与 Spring MVC 无关的 Spring Security 配置都可以放置在不同的配置类中,如下所示:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { NonWebSecurityConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
如果您有多个AbstractAnnotationConfigDispatcherServletInitializer
并且不想在两者之间复制常规安全配置。
HttpSecurity 安全
到目前为止,我们的WebSecurityConfig
仅包含有关如何验证用户的信息。
Spring Security 如何知道我们要要求所有用户都经过身份验证?
Spring Security 如何知道我们想要支持基于表单的身份验证?
实际上,有一个配置类(称为SecurityFilterChain
),该 API 的调用正在后台调用。
它使用以下默认实现进行配置:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
默认配置(如前面的示例所示):
-
确保对我们的应用程序的任何请求都需要对用户进行身份验证
-
允许用户使用基于表单的登录进行身份验证
-
允许用户使用 HTTP 基本身份验证进行身份验证
请注意,此配置与 XML 命名空间配置类似:
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
多个 HttpSecurity 实例
为了有效地管理某些区域需要不同保护的应用程序的安全性,我们可以在应用程序上使用多个过滤器链以及securityMatcher
DSL 方法。
这种方法允许我们定义针对应用程序的特定部分量身定制的不同安全配置,从而增强整体应用程序安全性和控制力。
我们可以配置多个HttpSecurity
实例,就像我们可以有多个<http>
块。
关键是要注册多个SecurityFilterChain
@Bean
s.
以下示例对以/api/
:
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") (3)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean (4)
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
1 | 照常配置 Authentication。 |
2 | 创建SecurityFilterChain 包含@Order 以指定SecurityFilterChain 应首先考虑。 |
3 | 这http.securityMatcher() 声明此HttpSecurity 仅适用于以/api/ . |
4 | 创建SecurityFilterChain .
如果 URL 不以/api/ ,则使用此配置。
此配置在apiFilterChain ,因为它有一个@Order 值1 (否@Order 默认为 last)。 |
选择securityMatcher
或requestMatchers
一个常见的问题是:
这两者之间有什么区别
http.securityMatcher()
method 和requestMatchers()
用于请求授权(即在http.authorizeHttpRequests()
)?
要回答这个问题,了解每个HttpSecurity
实例用于构建SecurityFilterChain
包含一个RequestMatcher
匹配传入请求。
如果请求与SecurityFilterChain
具有更高的优先级(例如@Order(1)
),则可以针对优先级较低的过滤器链(例如 no@Order
).
多个过滤器链的匹配逻辑由 |
默认的RequestMatcher
匹配任何请求,以确保 Spring Security 默认保护所有请求。
指定 |
如果没有过滤器链与特定请求匹配,则该请求不受 Spring Security 保护。 |
以下示例演示了一个筛选条件链,该链仅保护以/secured/
:
@Configuration
@EnableWebSecurity
public class PartialSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/secured/user").hasRole("USER") (2)
.requestMatchers("/secured/admin").hasRole("ADMIN") (3)
.anyRequest().authenticated() (4)
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
1 | 以/secured/ 将受到保护,但任何其他请求都不受保护。 |
2 | 请求/secured/user 需要ROLE_USER 柄。 |
3 | 请求/secured/admin 需要ROLE_ADMIN 柄。 |
4 | 任何其他请求(例如/secured/other ) 只需要经过身份验证的用户。 |
建议提供 |
请注意,requestMatchers
method 仅适用于单个授权规则。
此处列出的每个请求也必须与总体securityMatcher
对于这个特定的HttpSecurity
实例用于创建SecurityFilterChain
.
用anyRequest()
在此示例中匹配此特定SecurityFilterChain
(必须以/secured/
).
有关更多信息,请参见授权 HttpServletRequests |
SecurityFilterChain
端点
S 中的SecurityFilterChain
直接提供终端节点,例如UsernamePasswordAuthenticationFilter
它由http.formLogin()
并提供POST /login
端点。
在上面的示例中,/login
endpoint 不匹配http.securityMatcher("/secured/**")
因此,该应用程序不会有任何GET /login
或POST /login
端点。
此类请求将返回404 Not Found
.
这通常让用户感到惊讶。
指定http.securityMatcher()
影响SecurityFilterChain
.
但是,它不会自动影响筛选条件链提供的终端节点。
在这种情况下,您可能需要自定义您希望筛选条件链提供的任何终端节点的 URL。
以下示例演示了一个配置,该配置保护以/secured/
并拒绝所有其他请求,同时还自定义SecurityFilterChain
:
@Configuration
@EnableWebSecurity
public class SecuredSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
@Order(1)
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated() (2)
)
.formLogin(formLogin -> formLogin (3)
.loginPage("/secured/login")
.loginProcessingUrl("/secured/login")
.permitAll()
)
.logout(logout -> logout (4)
.logoutUrl("/secured/logout")
.logoutSuccessUrl("/secured/login?logout")
.permitAll()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().denyAll() (5)
);
return http.build();
}
}
1 | 以/secured/ 将受到此过滤器链的保护。 |
2 | 以/secured/ 需要经过身份验证的用户。 |
3 | 自定义表单登录以在 URL 前添加前缀/secured/ . |
4 | 自定义注销以在 URL 前添加/secured/ . |
5 | 所有其他请求都将被拒绝。 |
此示例自定义登录和注销页面,这将禁用 Spring Security 生成的页面。
您必须为 |
真实示例
以下示例演示了将所有这些元素放在一起的更真实的配置:
@Configuration
@EnableWebSecurity
public class BankingSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
manager.createUser(users.username("user2").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain approvalsSecurityFilterChain(HttpSecurity http) throws Exception {
String[] approvalsPaths = { "/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**" };
http
.securityMatcher(approvalsPaths)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
@Order(2) (3)
public SecurityFilterChain bankingSecurityFilterChain(HttpSecurity http) throws Exception {
String[] bankingPaths = { "/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**" };
String[] viewBalancePaths = { "/balances/**" };
http
.securityMatcher(bankingPaths)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(viewBalancePaths).hasRole("VIEW_BALANCE")
.anyRequest().hasRole("USER")
);
return http.build();
}
@Bean (4)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(allowedPaths).permitAll()
.anyRequest().authenticated()
)
.formLogin(formLogin -> formLogin
.loginPage("/user-login")
.loginProcessingUrl("/user-login")
)
.logout(logout -> logout
.logoutUrl("/user-logout")
.logoutSuccessUrl("/?logout")
);
return http.build();
}
}
1 | 首先配置身份验证设置。 |
2 | 定义一个SecurityFilterChain 实例替换为@Order(1) ,这意味着此筛选条件链将具有最高优先级。
此筛选条件链仅适用于以/accounts/approvals/ ,/loans/approvals/ 或/credit-cards/approvals/ .
对此筛选条件链的请求需要ROLE_ADMIN 颁发机构并允许 HTTP 基本身份验证。 |
3 | 接下来,创建另一个SecurityFilterChain 实例替换为@Order(2) 这将被视为第二名。
此筛选条件链仅适用于以/accounts/ ,/loans/ ,/credit-cards/ 或/balances/ .
请注意,由于此筛选条件链是第二个,因此包含/approvals/ 将匹配上一个过滤器链,并且不会与此过滤器链匹配。
对此筛选条件链的请求需要ROLE_USER 柄。
此过滤器链未定义任何身份验证,因为下一个(默认)过滤器链包含该配置。 |
4 | 最后,创建一个额外的SecurityFilterChain 实例中没有@Order 注解。
此配置将处理其他过滤器链未涵盖的请求,并将最后处理(否@Order 默认为 last)。
匹配的请求数 ,/ /user-login ,/user-logout ,/notices ,/contact 和/register 允许无需身份验证的访问。
任何其他请求都要求用户进行身份验证才能访问其他筛选条件链未明确允许或保护的任何 URL。 |
自定义 DSL
您可以在 Spring Security 中提供自己的自定义 DSL:
-
Java
-
Kotlin
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
private boolean flag;
@Override
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable();
}
@Override
public void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance.
MyFilter myFilter = context.getBean(MyFilter.class);
myFilter.setFlag(flag);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
public MyCustomDsl flag(boolean value) {
this.flag = value;
return this;
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
}
class MyCustomDsl : AbstractHttpConfigurer<MyCustomDsl, HttpSecurity>() {
var flag: Boolean = false
override fun init(http: HttpSecurity) {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable()
}
override fun configure(http: HttpSecurity) {
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
// here we lookup from the ApplicationContext. You can also just create a new instance.
val myFilter: MyFilter = context.getBean(MyFilter::class.java)
myFilter.setFlag(flag)
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter::class.java)
}
companion object {
@JvmStatic
fun customDsl(): MyCustomDsl {
return MyCustomDsl()
}
}
}
这实际上是像 |
然后,您可以使用自定义 DSL:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.flag(true)
)
// ...
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
flag = true
}
// ...
return http.build()
}
}
代码按以下顺序调用:
-
代码中的
Config.filterChain
method 被调用 -
代码中的
MyCustomDsl.init
method 被调用 -
代码中的
MyCustomDsl.configure
method 被调用
如果你愿意,你可以拥有HttpSecurity
加MyCustomDsl
默认情况下,使用SpringFactories
.
例如,您可以在名为META-INF/spring.factories
包含以下内容:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
您还可以显式禁用默认值:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.disable()
)
...;
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
disable()
}
// ...
return http.build()
}
}
后处理已配置的对象
Spring Security 的 Java 配置不会公开它配置的每个对象的每个属性。 这简化了大多数用户的配置。 毕竟,如果每个属性都公开了,用户就可以使用标准的 bean 配置。
虽然有充分的理由不直接公开每个属性,但用户可能仍需要更高级的配置选项。
为了解决这个问题,Spring Security 引入了ObjectPostProcessor
,它可用于修改或替换许多Object
由 Java 配置创建的实例。
例如,要配置filterSecurityPublishAuthorizationSuccess
属性FilterSecurityInterceptor
中,您可以使用以下内容:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi) {
fsi.setPublishAuthorizationSuccess(true);
return fsi;
}
})
);
return http.build();
}