icon: edit
date: 2022-01-01
category:
- CategoryA
tag: - tag A
- tag B
Spring boot security 2.7.2
吐槽
- spring security项目已经在spring全家桶建起之初已经是spring的 之一了,其中的内部结构和设计原理都比较复杂,单从设计和实现的角度而言都已经劝退很多人了.
- 网上的大多教程都只是皮毛,但起码也能跑跑程序完成老板的需求不是吗(不是),笔者刚开始接触它的时候也是一头雾水,抄抄网上的例子,勉勉强强的也能运行。。。
- 这就是人家给你提供了技术,但是你 , , ,对比国内开发者可能更倾向于shiro,我以前也搞过shiro,的确
shiro
对比spring security
较更为灵活(很正常,高度集成的缺点就是不灵活),但我到最后才发现spring security是真的牛逼…
简介
- Spring Security是一个 、 的 和 框架。它是 的标准实现。
- Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以很容易地扩展以满足定制需求。(源于各种 以及
Bean装配
)
准备
-
本文档基于
spring boot2.7.2
-
Spring Security下面简称 => security,该文档是传统的
servlet
实现。 -
maven坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.7.2</version> </dependency>
-
该starter引入了两个很重要的spring security的基础项目
//该项目包含了security的基本实现,包括 认证 授权 debug ldap 全局方法访问控制 oauth2 saml2 等 <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.7.2</version> </dependency> //该项目包含上面提到的Web的授权验证实现 <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.7.2</version> </dependency>
自动配置
在sprongboot starter项目中,官方的starter项目 ,最终还是要看 spring-boot-autoconfigure
项目的代码把相应的Bean注入到IOC的情况(如果我是初学者,这句话我大概率听不懂,什么鸟语,最后才发现年轻人还是太年轻了),如果你想看懂下面的代码的话,你需要对spring-framework
核心 && spring-boot
自动配置 都需要一定的知识积累。
spring-boot-starter-security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.2</version>
</dependency>
↓(包含)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.7.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.7.2</version>
<scope>compile</scope>
</dependency>
- 首先
spring-boot-starter-security
包含的spring-security-config
和spring-security-web
我就不多说了,上面提到过;它引入了spring-aop
,为什么呢,小脑袋瓜想一想,尽然它引入了,就说明他用到了Aop
,用Aop
做什么呢,做 ,切什么的面?想想security可以对方法级别的权限控制,当然是可以切代码中方法切面啦,所以说基本上被在IOC的所有Bean的方法,理论上都可以被securit作为 ,进行 等功能,此Aop
的精髓所在,他是实现 几乎所有项目的核心,是 实现的一种,被归为spring-framework
的核心成员之一。 - 最后剩下了一个
spring-boot-starter
,偷偷的告诉你,他可是实现springboot 的核心哦,让我们一起去看看吧 , , 。
spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.2</version>
</dependency>
↓(包含)
<!-- 该项目包含了spring-context spring-core,自身实现了springboot的基础-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.7.2</version>
<scope>compile</scope>
</dependency>
<!-- !!!!!! 自动配置实现的核心 !!!!-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.7.2</version>
<scope>compile</scope>
</dependency>
<!-- spring-bot自带的日志实现-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.7.2</version>
<scope>compile</scope>
</dependency>
<!-- 我不到这个干什么的 -->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<scope>compile</scope>
</dependency>
<!-- spring-framework-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.22</version>
<scope>compile</scope>
</dependency>
<!-- yaml文件的解析操作 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.30</version>
<scope>compile</scope>
</dependency>
前面几个都没啥,着重看看 spring-boot-autoconfigure
。
spring-boot-autoconfigure
spring-boot-autoconfigure
项目的包结构很规整,都是按照项目名来命名的包结构,一目了然,是不是看到了我们熟悉的security。
security实现了reactive
和servlet
两种web的实现,我们只看servlet
实现。
对servlet的security自动配置
ConditionalOnDefaultWebSecurity
打开该包,我肯看到有一个注解:org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultWebSecurityCondition.class)
public @interface ConditionalOnDefaultWebSecurity {
}
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
static class Classes {}
@ConditionalOnMissingBean({
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.class,
SecurityFilterChain.class })
@SuppressWarnings("deprecation")
static class Beans {}
}
ConditionalOnDefaultWebSecurity
该注解表明被该注解修饰的类只在当web安全可用且用户没有定义自己的配置时
才匹配。
看源码,它是基于DefaultWebSecurityCondition
实现的,条件是:
- 类路径需要有
SecurityFilterChain
和HttpSecurity
这两个类存在。 - 当前spring-boot项目的 中没有
WebSecurityConfigurerAdapter
和SecurityFilterChain
这两个类型的Bean存在。
SecurityAutoConfiguration
spring-security自动配置的核心:org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
@AutoConfiguration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
- 该类被
AutoConfiguration
修饰,标识在spring-boot自动装配作业时会参加自动装配的工作。 - 在类路径中有
DefaultAuthenticationEventPublisher
类时才会配置下面的类容。 - 使类
SecurityProperties
读取配置文件的值并注入到 中。 - 将
SpringBootWebSecurityConfiguration
和SecurityDataConfiguration
注入到 中。 - 如果容器中缺少类型为
AuthenticationEventPublisher
的Bean,则注入该Bean。(该Bean可以接受订阅认证相关的事件,并在发生认证事件时发布(调用)给订阅者们)。
SpringBootWebSecurityConfiguration
负责装配Web环境
相关的Bean组件,SecurityDataConfiguration
则是对数据层装配了一个类型为 SecurityEvaluationContextExtension
的Bean(我还没研究过).
我们着重看看 SpringBootWebSecurityConfiguration
这个Bean又做了什么。
SpringBootWebSecurityConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated(); //⑴
http.formLogin(); //⑵
http.httpBasic(); //⑶
return http.build(); //⑷
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebInvocationPrivilegeEvaluator.class)
@ConditionalOnBean(WebInvocationPrivilegeEvaluator.class)
static class ErrorPageSecurityFilterConfiguration {
@Bean
FilterRegistrationBean<ErrorPageSecurityFilter> errorPageSecurityFilter(ApplicationContext context) {
FilterRegistrationBean<ErrorPageSecurityFilter> registration = new FilterRegistrationBean<>(
new ErrorPageSecurityFilter(context));
registration.setDispatcherTypes(DispatcherType.ERROR);
return registration;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {}
}
其实也没有啥嘛,就是注入几个Bean嘛,,我们来分析一下:
-
首先,
@ConditionalOnWebApplication(type = Type.SERVLET)
表示下面的一系列配置只在Servlet环境
下生效。(没毛病,人家都说了是web) -
注入了一个名为
SecurityFilterChain
的Bean,并做了如下几件事(这个Bean很重要,下文会细讲):⑴ 任何http请求都需要认证处理。
⑵ 开启表单登录功能(别怀疑,就是html form的那个表单)
⑶ 开启http basic 认证 (我不大了解)
⑷ 构建类型为
SecurityFilterChain
的Bean,他的实际类型为:DefaultSecurityFilterChain
-
ErrorPageSecurityFilterConfiguration
类构建了一个错误页面,就是出现安全相关的错误时会走这个Filter会拦截该请求并调用HttpServletResponse
交由后续的Filter
及其DispatcherServlet
处理。
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
Integer errorCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (DispatcherType.ERROR.equals(request.getDispatcherType()) && !isAllowed(request, errorCode)) {
response.sendError((errorCode != null) ? errorCode : 401);
return;
}
chain.doFilter(request, response);
}
WebSecurityEnablerConfiguration
其实就是默认给你标注了WebSecurity
,就是当用户的类没有被该注解修饰时,他自动给你标注上,让这个注解配置的Bean生效,如果你做自己的类上用了该注解,那么也会应用该注解配置的Bean。(怎么觉得怪怪的 ,配没配都会生效)
不管了不管了,我们往下走,看看EnableWebSecurity
。
EnableWebSecurity
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
好家伙,不看不知道,一看又配置了这么多的Bean,我们一个一个看吧(Oh,对了该注解有一个debug
属性,把他设置成true的话他会打印所有跟认证授权相关的信息哦
):
WebSecurityConfiguration
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private WebSecurity webSecurity; //我们的认证主体
private Boolean debugEnabled; //上面说的debug会直影响这个属性
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers; //配置所有安全Filter的基础
//安全过滤链,默认引入security的话只会引入我们上面说的那个Bean,如果你还引入们oauth2相关,那么它还会引入一个oauth2的过滤器链
private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
//这是实现拓展的基础,用户通过实现WebSecurityCustomizer接口来配置WebSecurity(需要注入到IOC)
private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();
private ClassLoader beanClassLoader;
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;
@Bean
public static DelegatingApplicationListener delegatingApplicationListener() {
return new DelegatingApplicationListener();
}
//该Bean可以解析一些security或者其他的一些表达式,具体实现是由其实现类决定(默认注入的是SPEL解析类)
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
return this.webSecurity.getExpressionHandler();
}
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
Assert.state(!(hasConfigurers && hasFilterChain),
"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
if (!hasConfigurers && !hasFilterChain) {
WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
this.webSecurity.apply(adapter);
}
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
for (Filter filter : securityFilterChain.getFilters()) {
if (filter instanceof FilterSecurityInterceptor) {
this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
break;
}
}
}
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
return this.webSecurity.build();
}
//这个我不太懂,网上说是类似授权的判断(投票)
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public WebInvocationPrivilegeEvaluator privilegeEvaluator() {
return this.webSecurity.getPrivilegeEvaluator();
}
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
ConfigurableListableBeanFactory beanFactory) throws Exception {
this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (this.debugEnabled != null) {
this.webSecurity.debug(this.debugEnabled);
}
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(
beanFactory).getWebSecurityConfigurers();
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
+ " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
this.webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
this.securityFilterChains = securityFilterChains;
}
@Autowired(required = false)
void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
this.webSecurityCustomizers = webSecurityCustomizers;
}
@Bean
public static BeanFactoryPostProcessor conversionServicePostProcessor() {
return new RsaKeyConversionServicePostProcessor();
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableWebSecurityAttrMap = importMetadata
.getAnnotationAttributes(EnableWebSecurity.class.getName());
AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
this.debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
if (this.webSecurity != null) {
this.webSecurity.debug(this.debugEnabled);
}
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
private static class AnnotationAwareOrderComparator extends OrderComparator {
private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
@Override
protected int getOrder(Object obj) {
return lookupOrder(obj);
}
private static int lookupOrder(Object obj) {
if (obj instanceof Ordered) {
return ((Ordered) obj).getOrder();
}
if (obj != null) {
Class<?> clazz = ((obj instanceof Class) ? (Class<?>) obj : obj.getClass());
Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
if (order != null) {
return order.value();
}
}
return Ordered.LOWEST_PRECEDENCE;
}
}
}
SpringWebMvcImportSelector
class SpringWebMvcImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", getClass().getClassLoader())) {
return new String[0];
}
return new String[] {
"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" };
}
}
这个嘛看表面意思就是对 SpringWebMvc 进行了配置,这里他实现了ImportSelector
接口,selectImports
方法返回的就是需要注入的目标Bean;代码中首先判断了DispatcherServlet
在不在类路径,如果不在就不配置了,如果在就配置org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration
这个类。
咱们先看看这个类:
class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware {
private BeanResolver beanResolver;
@Override
@SuppressWarnings("deprecation")
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
authenticationPrincipalResolver.setBeanResolver(this.beanResolver);
argumentResolvers.add(authenticationPrincipalResolver); //⑴
argumentResolvers
.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());//⑵
CurrentSecurityContextArgumentResolver currentSecurityContextArgumentResolver = new CurrentSecurityContextArgumentResolver();
currentSecurityContextArgumentResolver.setBeanResolver(this.beanResolver);
argumentResolvers.add(currentSecurityContextArgumentResolver);//⑶
argumentResolvers.add(new CsrfTokenArgumentResolver());//⑷
}
@Bean
RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanResolver = new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
}
}
我们看到这个类实现了WebMvcConfigurer
接口,这个接口就是对webmvc进行DIY的一个接口呀,那我们看看他做了什么吧。
addArgumentResolvers
方法传来了一个List,泛型为HandlerMethodArgumentResolver
,这个接口实现了对方法里面注入参数的解析,什么意思呢,就是在springmvc
里控制的方法你为什么能直接注入HttpServletResponse
,HttpServletRequest
的原因,因为这两个参数都已经被实现HandlerMethodArgumentResolver
的方法解析过啦,感兴趣的可以看看他的好很多实现。
@RequestMapping("user")
fun getUser(httpServletResponse: HttpServletResponse, httpServletRequest: HttpServletRequest) {}
知道了这个我们来看看下面加了哪里个参数解析器吧:
⑴ 这个参数解析器是对参数列表参数标注了AuthenticationPrincipal
注解的参数进行解析;
⑵ 其实我看了代码和⑴很像,作用差不多;
⑶ 这个参数解析器是对参数列表参数标注了CurrentSecurityContext
注解的参数进行解析(当前认证用户的信息);
⑷ 这个参数解析器是对参数列表中类型为了CsrfToken
的参数进行解析;
这里就是配置了支持spring security的参数支持,方便在方法中直接注入支持的参数,随用随注入,前提是你得支持。
OAuth2ImportSelector
这个就是对spring Oauth2
的一些支持,这里有专门写spring Oauth2
的文章。
HttpSecurityConfiguration
@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {
//Bean前缀名字(好长....)
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.";
//当前这个类的类型Bean名字
private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";
//不清楚
private ObjectPostProcessor<Object> objectPostProcessor;
//身份验证管理器
private AuthenticationManager authenticationManager;
//身份验证配置
private AuthenticationConfiguration authenticationConfiguration;
//当前spring应用程序的实体
private ApplicationContext context;
@Autowired
void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Autowired
void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
@Autowired
void setApplicationContext(ApplicationContext context) {
this.context = context;
}
//这个就是构建前面认证的主体所在
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
this.context); //用委托实现了一个密码加密器
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder); //身份验证管理器的构建器
authenticationBuilder.parentAuthenticationManager(authenticationManager());
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
// @formatter:off
http //withDefaults其实什么也没做,也就是说下面的用了这个的,就是什么也不配置
.csrf(withDefaults()) //csrf配置
.addFilter(new WebAsyncManagerIntegrationFilter()) //异步的servlet支持(虽然我搞不懂异步的servlet)
.exceptionHandling(withDefaults()) // 异常的处理器,认证失败啊成功啊,权限验证失败什么的
.headers(withDefaults()) // 将Security头添加到响应中
.sessionManagement(withDefaults()) //session的管理策略,在无状态的应用中,都是禁用session的
.securityContext(withDefaults()) // 没看懂...
.requestCache(withDefaults()) //字面意思请求缓存(没看懂...)
.anonymous(withDefaults()) //对匿名用户的配置
.servletApi(withDefaults()) // 没看懂...
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults()); //登出的配置
// @formatter:on
applyDefaultConfigurers(http);
return http;
}
//在这里默认实现是ProviderManager(别问我为什么知道,我Debug过)
private AuthenticationManager authenticationManager() throws Exception {
return (this.authenticationManager != null) ? this.authenticationManager
: this.authenticationConfiguration.getAuthenticationManager();
}
//对所有继承了AbstractHttpConfigurer的类都应用上述HttpSecurity的配置
private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);
return sharedObjects;
}
}
好啦,自此spring security
的自动配置就到此位置啦!
SecurityFilterAutoConfiguration
好啦,接下来该配置Filter的主场了,有人就会问了,你怎么知道这个类要配置?了解springboot底层的童鞋肯定就晓得自动配置类需要在jar包的下有一个名叫的文件,这个文件描述的org.springframework.boot.autoconfigure.EnableAutoConfiguration
这个属性就是会自动配置的类。
这个类的全类名是:org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
@Configuration( proxyBeanMethods = false)
@ConditionalOnWebApplication( type = Type.SERVLET) //只在Servlet环境下会生效如下配置
@EnableConfigurationProperties({SecurityProperties.class}) //开启会SecurityProperties属性的注入,并将该Bean加到IOC容器里
@ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class}) //类路径下需要有这两个class
@AutoConfigureAfter({SecurityAutoConfiguration.class}) //在SecurityAutoConfiguration配置类之后再配置(上面我们刚讲了这个类配置)
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
public SecurityFilterAutoConfiguration() {
}
//DelegatingFilterProxy这个类可不得了,这是security认证授权的核心组织者,他将security的一个类型为FilterChainProxy进行了一个代理
//FilterChainProxy是一个特殊的filter,他代理了一堆类型为SecurityFilterChain的过滤器链
//每一个SecurityFilterChain都是可以说是一种认证方式,就好比密码登录和扫码登陆他两都是一种认证方式
//每一个SecurityFilterChain下维护了一堆filter专门做认证
//在这里DelegatingFilterProxyRegistrationBean这个Bean注册了一个DelegatingFilterProxy对象
@Bean
@ConditionalOnBean( name = {"springSecurityFilterChain"} )
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("springSecurityFilterChain", new ServletRegistrationBean[0]);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(this.getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
return securityProperties.getFilter().getDispatcherTypes() == null ? null : (EnumSet)securityProperties.getFilter().getDispatcherTypes().stream().map((type) -> {
return DispatcherType.valueOf(type.name());
}).collect(Collectors.toCollection(() -> {
return EnumSet.noneOf(DispatcherType.class);
}));
}
}
DelegatingFilterProxy
::: tabs
@tab:active FilterChain
@tab DelegatingFilterProxy
@tab SecurityFilterChain
@tab Security Filters
@tab Multiple SecurityFilterChain
:::
自动配置总结
通过上述的源码,我们发现spring security
为我们做了如下的事情:
- 自动引入了
Aop
- 配置了一个
HttpSecurity
实体 - 配置了一个
Spring security
的核心DelegatingFilterProxy
- 配置了一个拦截所有请求的
SecurityFilterChain
来做security的认证 - 配置了一个Servlet异常的异常处理
- 配置了支持
AuthenticationPrincipal
,CurrentSecurityContext
,CsrfToken
参数解析支持 - 配置了 支持
- 配置了一个
ProviderManager
- 等等…
实战
WebSecurityConfigurerAdapter已弃用
//Deprecated
//Use a org.springframework.security.web.SecurityFilterChain Bean to configure HttpSecurity or a WebSecurityCustomizer Bean to //configure WebSecurity
@Order(100)
@Deprecated
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
//..........
}
该类已经标注弃用,似乎是从5.0开始的,具体记不清了,以往配置security都是继承该类,重写两个configure方法,现在我们来实现新的配置方式。
注释提到现在的配置方式:
- 使用
SecurityFilterChain
配置HttpSecurity
- 使用
WebSecurityCustomizer
配置WebSecurity
配置SecurityFilterChain
//这两个注解其实任意一个就可以
@Configuration
@EnableWebSecurity
class WebSecurityConfiguration {
@Bean
@Throws(Exception::class )
@Order(SecurityProperties.BASIC_AUTH_ORDER)
fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
//配置HttpSecurity....
return http.build()
}
}
::: info 注意
上文中我们提到了 SpringBootWebSecurityConfiguration
对SecurityFilterChain
在ConditionalOnDefaultWebSecurity
注解条件下才会注入IOC,但很显然我们这里手动配置了SecurityFilterChain
,所以我们他就不会配置这个Bean,在复习一下这个注解ConditionalOnDefaultWebSecurity
。
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
static class Classes {}
@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class,SecurityFilterChain.class })
@SuppressWarnings("deprecation")
static class Beans {}
}
:::
:::info HttpSecurity注入
上面我们看到了HttpSecurityConfiguration
配置了一个多例的HttpSecurity
,默认IOC只有一个,你注入没问题,当有多这个该类型的Bean的时候,这就会报错啦,所以你要明确我为什么这么写。
其次我们刚好拿到HttpSecurity
实例对象,进行我们自定义的配置。
:::
配置HttpSecurity
:::info 配置HttpSecurity
@EnableWebSecurity
class WebSecurityConfiguration {
@Bean
@Throws(Exception::class)
fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeRequests()
//antMatchers 使用的是 AntPathMatcher类来做路径匹配的
//匹配该路径不做认证授权
.antMatchers("/user/**", "/book/**").permitAll()
//匹配改路径的GET请求不做认证授权
.antMatchers(HttpMethod.GET, "/oauth2/**").permitAll()
//匹配改路径的GET请求通过指定的spel表达式授权
.antMatchers(HttpMethod.GET, "/kern/**").access("authentication.authenticated")
//其余的请求一律需要认证
.anyRequest().authenticated()
//资源授权管理器
.accessDecisionManager(object : AccessDecisionManager {
//在认证之后对资源授权的最后关卡,默认有三种实现
//1.AffirmativeBased 如果其中人一个投票人对其授权,那么他就默认获得这个资源的访问权限,前期是设置了allowIfAllAbstainDecisions为true
//2.ConsensusBased 投赞成票人如果大于反对票的人则授权(少数服从多数),如果一样则取决于allowIfEqualGrantedDeniedDecisions的配置
//3.UnanimousBased 全部投赞成票才能访问该资源
//一般我们自定义的话默认实现AbstractAccessDecisionManager ,它包含 decisionVoters(选民),allowIfAllAbstainDecisions
//
override fun decide(authentication: Authentication, `object`: Any, configAttributes: MutableCollection<ConfigAttribute>) = Unit
override fun supports(attribute: ConfigAttribute) = true
override fun supports(clazz: Class<*>): Boolean = true
})
//表单验证配置
http.formLogin()
.permitAll() //表单认证的所有路径不做认证授权
.failureForwardUrl("/login/fail") //登陆失败将调用该接口
.successForwardUrl("/login/success")//登陆成功将调用该接口
.usernameParameter("ace-username") //登陆的form表单的用户名字段
.passwordParameter("ace-password") //登陆的form表单的口令字段
.loginProcessingUrl("/login") //处理登录逻辑的接口地址
.failureHandler { request, response, exception -> } //登陆失败后做什么
.successHandler { request, response, authentication -> } //登陆成功后做什么
//session策略
http
.sessionManagement()
.sessionConcurrency {
it.expiredSessionStrategy { } //session过期时调用该方法
it.maxSessionsPreventsLogin(true) //当同一个用户被登陆多次时是否阻止其登录
it.expiredUrl("/login/expired") //session过期后重定向到该地址
it.maximumSessions(1) //控制一个用户最大的session数量 (同上一起配合)
}
http
.authenticationManager(ProviderManager()) //配置认证的管理器(一般不用配置,默认配置的就是ProviderManager)
//增加一个filter和制定filter同级,他两的调用顺序不确定
.addFilterAt({ servletRequest, servletResponse, filterChain -> }, UsernamePasswordAuthenticationFilter::class.java)
//增加一个filter 位置源于该filter的 Order配置
.addFilter { servletRequest, servletResponse, filterChain-> }
//增加一个位于制定filter之前的filter,
.addFilterBefore({ servletRequest, servletResponse, filterChain-> },UsernamePasswordAuthenticationFilter::class.java)
//增加一个位于制定filter之后的filter,
.addFilterAfter({ servletRequest, servletResponse, filterChain-> },UsernamePasswordAuthenticationFilter::class.java)
.cors { it.disable() } //配置cors支持
.csrf { it.disable() } //csrf
.exceptionHandling { } //配置授权认证出现异常时的处理
return http.build()
}
}
:::