c⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖
第一步是创建我们的 Spring Security Java
配置。该配置创建了一个称为 springSecurityFilterChain
的 Servlet 过滤器,它负责所有的安全性(保护应用程序 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.*;@EnableWebSecuritypublic class WebSecurityConfig {@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());return manager;}}
这种配置确实没什么,但它做了很多。您可以找到以下功能的摘要:
AbstractSecurityWebApplicationInitializer
下一步是将 springSecurityFilterChain
注册到 war 中。这可以在 Java 配置中通过 Spring 的 WebApplicationInitializer
支持在 Servlet 3.0+
环境中完成。毫不奇怪,pring Security
提供了一个基类 bstractSecurityWebApplicationInitializer
,它将确保为您注册 springSecurityFilterChain
。我们使用 AbstractSecurityWebApplicationInitializer
的方式会有所不同,这取决于我们是否已经在使用 Spring,或者 Spring Security
是否是我们应用程序中唯一的 Spring 组件。
AbstractSecurityWebApplicationInitializer without Existing Spring
- 如果您尚未使用 Spring,请使用这些说明AbstractSecurityWebApplicationInitializer 与 Spring MVC
- 如果您已经在使用 Spring,请使用这些说明AbstractSecurityWebApplicationInitializer
这章可以忽略不看。
如果您不使用 Spring
或 Spring MVC
,则需要将 WebSecurityConfig
传入超类以确保获取配置。您可以在下面找到一个示例:
import org.springframework.security.web.context.*;public class SecurityWebApplicationInitializerextends AbstractSecurityWebApplicationInitializer {public SecurityWebApplicationInitializer() {super(WebSecurityConfig.class);}}
SecurityWebApplicationInitializer
将执行以下操作:
URL
自动注册 springSecurityFilterChain
过滤器WebSecurityConfig
的 ContextLoaderListener
。如果我们在应用程序的其他地方使用 Spring,我们可能已经有一个 WebApplicationInitializer
正在加载我们的 Spring 配置。如果我们使用以前的配置,我们会得到一个错误。相反,我们应该使用现有的 ApplicationContext
注册 Spring Security
。例如,如果我们使用 Spring MVC
,我们的 SecurityWebApplicationInitializer
将如下所示:
import org.springframework.security.web.context.*;public class SecurityWebApplicationInitializerextends AbstractSecurityWebApplicationInitializer {}
这只会为应用程序中的每个 URL 注册 springSecurityFilterChain
过滤器。之后,我们将确保 WebSecurityConfig
已加载到我们现有的 ApplicationInitializer
中。例如,如果我们使用 Spring MVC
,它将被添加到 getRootConfigClasses()
public class MvcWebApplicationInitializer extendsAbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[] { WebSecurityConfig.class };}// ... other overrides ...}
到目前为止,我们的 WebSecurityConfig 仅包含有关如何验证用户身份的信息。Spring Security 是如何知道我们想要要求所有用户进行身份验证的?Spring Security
如何知道我们想要支持基于表单的身份验证?实际上,有一个在后台调用的配置类称为 WebSecurityConfigurerAdapter
。它有一个名为 configure
的方法,具有以下默认实现:
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests(authorize -> authorize.anyRequest().authenticated()).formLogin(withDefaults()).httpBasic(withDefaults());}
上面的默认配置:
如果看源代码,可以看到不推荐WebSecurityConfigurerAdapter
的注释:
DeprecatedUse a org.springframework.security.web.SecurityFilterChain Bean to configure HttpSecurity or a WebSecurityCustomizer Bean to configure WebSecurity
在很早以前,是通过继承WebSecurityConfigurerAdapter
,并重载configure
来实现相关的配置。但是后来简化了这个做法,而是通过Bean
来实现的。
看下面的例子:
@Configuration@EnableWebSecuritypublic class SecurityConfiguration {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// @formatter:offhttp.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).httpBasic(withDefaults()).formLogin(withDefaults());// @formatter:onreturn http.build();}// @formatter:off@Beanpublic InMemoryUserDetailsManager userDetailsService() {UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build();return new InMemoryUserDetailsManager(user);}// @formatter:on}
我们可以配置多个 HttpSecurity
实例,就像我们可以有多个 http
块一样。关键是多次扩展 WebSecurityConfigurerAdapter
。例如,以下是对以 /api/
开头的 URL 进行不同配置的示例。
@EnableWebSecuritypublic class MultiHttpSecurityConfig {@Bean ①public UserDetailsService userDetailsService() throws Exception {// ensure the passwords are encoded properlyUserBuilder 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;}@Configuration@Order(1) ②public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {protected void configure(HttpSecurity http) throws Exception {http.antMatcher("/api/**") ③.authorizeHttpRequests(authorize -> authorize.anyRequest().hasRole("ADMIN")).httpBasic(withDefaults());}}@Configuration ④public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()).formLogin(withDefaults());}}}
①②③④
正常配置身份验证
创建一个包含 @Order
的 WebSecurityConfigurerAdapter
实例,以指定应首先考虑哪个 WebSecurityConfigurerAdapter
。
http.antMatcher
声明此 HttpSecurity
仅适用于以/api/
开头的 URL
创建另一个WebSecurityConfigurerAdapter
实例。如果 URL 不以 /api/
开头,则将使用此配置。此配置在 ApiWebSecurityConfigurationAdapter
之后考虑,因为它在 1 之后有一个 @Order
值(没有 @Order
默认为最后一个)。
您可以在 Spring Security 中提供自己的自定义 DSL。例如,您可能有如下所示的内容:
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {private boolean flag;@Overridepublic void init(HttpSecurity http) throws Exception {// any method that adds another configurer// must be done in the init methodhttp.csrf().disable();}@Overridepublic 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();}}
备注:这实际上是 HttpSecurity.authorizeRequests() 等方法的实现方式。
然后可以像这样使用自定义 DSL:
@EnableWebSecuritypublic class Config extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.apply(customDsl()).flag(true).and()...;}}
代码按以下顺序调用:
Config
的 configure 方法中的代码MyCustomDsl
的 init 方法中的代码MyCustomDsl
的配置方法中的代码如果需要,您可以使用 SpringFactories
让 WebSecurityConfigurerAdapter
默认添加 MyCustomDsl
。例如,您将在名为 META-INF/spring.factories
的类路径上创建一个资源,其内容如下:
META-INF/spring.factories
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
希望禁用默认设置的用户可以明确执行此操作。
@EnableWebSecuritypublic class Config extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.apply(customDsl()).disable()...;}}
提示:
- 今后自己可以添加类似的配置,例如短信登陆、其他登陆等等。
Spring Security
的 Java 配置不会公开它配置的每个对象的每个属性。这简化了大多数用户的配置。如果每个属性都被暴露,用户可以使用标准的 bean 配置。
虽然有充分的理由不直接公开每个属性,但用户可能仍需要更高级的配置选项。为了解决这个问题,Spring Security
引入了 ObjectPostProcessor
的概念,它可用于修改或替换 Java 配置创建的许多 Object 实例。例如,如果您想在 FilterSecurityInterceptor
上配置 filterSecurityPublishAuthorizationSuccess
属性,您可以使用以下内容:
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests(authorize -> authorize.anyRequest().authenticated().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {fsi.setPublishAuthorizationSuccess(true);return fsi;}}));}
withObjectPostProcessor
在spring mfa
例子中也能见到,这个例子中,当出现了exceptionHandling
,然后修改ExceptionTranslationFilter
的内容。
@BeanSecurityFilterChain web(HttpSecurity http,AuthorizationManager<RequestAuthorizationContext> mfaAuthorizationManager) throws Exception {MfaAuthenticationHandler mfaAuthenticationHandler = new MfaAuthenticationHandler("/second-factor");// @formatter:offhttp.authorizeHttpRequests((authorize) -> authorize.mvcMatchers("/second-factor", "/third-factor").access(mfaAuthorizationManager).anyRequest().authenticated()).formLogin((form) -> form.successHandler(mfaAuthenticationHandler).failureHandler(mfaAuthenticationHandler)).exceptionHandling((exceptions) -> exceptions.withObjectPostProcessor(new ObjectPostProcessor<ExceptionTranslationFilter>() {@Overridepublic <O extends ExceptionTranslationFilter> O postProcess(O filter) {filter.setAuthenticationTrustResolver(new MfaTrustResolver());return filter;}}));// @formatter:onreturn http.build();}