相信博主一文带你理解Spring Security,从入门、到理解、到最终吊打面试官!
本来想简单写个Demo,但是越动笔就越想写的再详细一点,发觉思绪像滚滚长江,一发不可收拾。耗时三个礼拜,最终2万字啦,码字不易,且看且珍惜,欢迎点赞、收藏、评论,一文带你理解系列刚刚开始,后来还会有更多干货等你呦🤣😜🤒
- 一.Spring Security概述
- 二.Spring Security初步使用
- 三. Spring Security认证
- 3.2 核心类讲解
- 3.2.1 AuthenticationManager
- 3.2.2 Authentication
- 3.2.4 SecurityContextHolder
- 3.2.5 UsernamePasswordAuthenticationToken
- 3.2.7 UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter
- 3.2.3 AuthenticationProvider
- 3.2.2 ProviderManager (重中之重!!!)
- 3.2.4 SecurityContextHolder
- 3.2.6 UsernamePasswordAuthenticationFilter
- 3.2.7 AbstractAuthenticationProcessingFilter
- 3.3 认证流程详解
- 五. 参考文献
一.Spring Security概述
1.1 Spring Security简介
在Java企业开发中,市面上常见的开源安全框架非常少,主要有以下几种方案:
- Shiro
- Spring Security
- 企业自行开发的方案
几年前,微服务还没有大火的时候,Shiro以其轻量、简单、易于集成的优点独当一面。
而最近今年,随着微服务的大火,Spring Security作为Spring家族的首推的安全框架,在与Spring等其他组件的无缝整合的特点,导致其市面占有率也是逐年提高。
1.2 Spring Security初步理解
Spring Security是Spring全家桶里面的一个项目,提供认证、授权以及应对漏洞攻击的保护。
- 认证
authentication
: 可以简单理解成”你是谁“,最简单的例子就是用户登录,这就是认证,下文中登录操作代表认证。- 授权
authorization
:可以简单理解成“你有哪些权限,你能做什么”,比如登录进来的用户是具有管理员或是普通用户的权限。- 保护
protection
:应对遭受漏洞利用的保护。
1.2.1 Spring Security是如何完成认证的
Spring Security通过一系列过滤器完成认证与授权的工作。
对于SpringBoot工程,并没有引入其他依赖。
客户端发起请求时,tomcat容器会创建一个包括Filter和Servlet的FilterChain(过滤器链)。通过Filter可以控制请求与响应,以及是否调用下游的过滤器或Servlet。
接来下博主简要说明下Spring Security中起到核心作用的几个类,这是通过这几个类Spring Security才能集成到SpringBoot当中,并发挥作用。
此处参考了Spring Security的官网文档:链接: Spring Security官方文档
1.2.2 DelegatingFilterProxy
Spring Seucrity实现认证与授权的功能提供了很多过滤器,通过这些过滤器来拦截请求,并做相应处理。那么如何将这些过滤器嵌入到Spring的IOC容器呢,最好的做法就是将Spring Security这些过滤器注册成Bean,这样就可以统一的进行管理了,DelegatingFilterProxy
就是为了实现这个目的。
Delegating
这个名字很绕口ˈdelɪɡeɪtɪŋ'
,是委托的意思。DelegatingFilterProxy
合到一起就是委托过滤器代理
。
整体意思就是DelegatingFilterProxy
是一个代理,他委托了某个类(FilterChainProxy
下文会提到),并让那个类完成后续拦截操作。
可以把他理解成一个胶水,由他连接了web应用的原生过滤器和Spring Security的过滤器。
DelegatingFilterProxy
是一个过滤器,里面有个成员变量
private volatile Filter delegate;
他就是委托对象。
在客户端请求来临的时候会执行doFilter()
方法。
首先会判断delegate是否为空,若为空的话从IOC容器中通过getBean()
的方法拿到这个代理对象FilterChainProxy
。
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
并执行代理对象的doFilter()
方法。
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
这样后续的操作就交由这个代理对象去做了。
1.2.3 FilterChainProxy & SecurityFilterChain(二者关系密切,放一起讲述)
FilterChainProxy
这个类可以理解成过滤器链代理。DelegatingFilterProxy
正是委托给FilterChainProxy
,就是上文提到的delegate
来完成拦截等操作。
FilterChainProxy
是Spring Security发挥作用的入口,一切Spring Security的过滤器都是从这之后开始调用的。
另外值得注意的是,DelegatingFilterProxy
是注册到Tomcat容器的一个过滤器,他的生命周期由Tomcat来控制。而FilterChainProxy
则是Spring的IOC容器中的一个Bean。
这幅图展示了客户端client请求到系统中时,经过Tomcat的某些原生过滤器后,到达DelegatingFilterProxy
。并委托给FilterChainProxy
,而FilterChainProxy
通过SecurityFilterChain
来代理各种Filter实例。之后再到Tomcat的原生过滤器,最终到达Servet。
简而言之,FilterChainProxy
使用SecurityFilterChain
确定应对此请求调用哪些Spring Security过滤器。
可以看到delegate对象中包括一个过滤器链的列表(SecurityFilterChain
)。其中DefaultSecurityFilterChain
对象就是Spring Security的一个过滤器链,如前一个图片所示的SecurityFilterChain
。
FilterChainProxy
作为一个代理类,他的doFilter()
方法最终会调到下面的doFilterInternal()
。
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(firewallRequest);
if (filters == null || filters.size() == 0) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
}
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
return;
}
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
}
// 看这里
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
将后续的执行操作交由他的一个内部静态类去实现。执行VirtualFilterChain#doFilter()
方法。
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
//退出循环
return;
}
this.currentPosition++;
// 执行Spring Security的过滤器
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
this.currentPosition, this.size));
}
nextFilter.doFilter(request, response, this);
}
在这里面会循环的调用每个Spring Security提供的过滤器进行各种拦截处理操作,并在最后退出循环,进入Tomcat的其他过滤器中…
1.2.4 多个SecurityFilterChain
在Spring Security中,可以配置多个SecurityFilterChain
,由FilterChainProxy
决定应使用哪个SecurityFilterChain
。
FilterChainProxy
会根据请求的路由匹配第一个符合条件的SecurityFilterChain
,并执行其过滤器。
二.Spring Security初步使用
很多人对Spring Security的感觉都是太繁琐,其实到了微服务的天下,Spring Security的使用非常简单。
接下来博主以一个简单的例子给大家演示一下。
2.1 集成Spring Security
引入pom依赖。
<!--标识一个springboot的web工程-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring Security是通过一系列过滤器来完成认证与授权的功能的。客户端请求之后逐个通过Spring Security的各种过滤器。当引入Spring Security依赖时,其实已经加载了Spring Security提供的许多个默认过滤器。
添加请求URL,当做用来测试的资源URL。
@RequestMapping("hello")
public class HelloController {
@GetMapping()
public String hello() {
return "hello";
}
}
启动项目,可以看到控制台输出的日志中,包括了如下的内容。
按照Spring Security官网的描述,其实生成了名为user
的用户,密码为如下71c36beb-7af5-4116-b807-ab84e484e6fa
。
并且可以看到控制台打印了Spring Security默认加载的15个过滤器,正是他们支撑着Spring Security做到了认证相关的操作。
稍后博主会挑常见的过滤器给大家说明一下,值得注意的是,这15个过滤器的先后执行顺序就是控制台打印的顺序。
Using generated security password: 71c36beb-7af5-4116-b807-ab84e484e6fa
2022-08-22 20:22:07.179 INFO 10672 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4784013e,
org.springframework.security.web.context.SecurityContextPersistenceFilter@2ca6546f,
org.springframework.security.web.header.HeaderWriterFilter@aa10649,
org.springframework.security.web.csrf.CsrfFilter@c4c0b41,
org.springframework.security.web.authentication.logout.LogoutFilter@3af356f,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@267517e4,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@231baf51,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@6f952d6c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@56ba8773,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7923f5b3,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6050462a,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5965844d,
org.springframework.security.web.session.SessionManagementFilter@37095ded,
org.springframework.security.web.access.ExceptionTranslationFilter@368d5c00,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1763992e
]
2.2 访问测试
接下来使用浏览器访问该资源。请求地址http://localhost:8080/hello
(SpringBoot默认启动端口为8080)
可以观察到页面直接跳转到了http://localhost:8080/login
并打开了一个登录页面。
F12可以看到页面请求http://localhost:8080/hello
之后,返回响应302,并重定向到http://localhost:8080/login
接口进行请求,该接口响应为一个页面。让我们完成登录操作。
输入账号密码后,点击登录(用户:user
,密码:71c36beb-7af5-4116-b807-ab84e484e6fa
),此时可以看到页面返回了接口hello
,这也意味着只有认证成功才会允许访问资源。
这就是Spring Security的魅力。博主只是引入了一个Spring Security依赖就做到了所有资源的保护,那他是怎么做到的呢,且听我慢慢道来。
2.3 为什么默认访问资源会返回登录页面?
该图片来自《深入浅出Spring Security》
当客户端发起一个资源的请求时(http://localhost:8080/hello
),会经过上文所述的15个Spring Security的过滤器依次执行。
直到走到FilterSecurityInterceptor
这个过滤器的时候,抛出一个访问被拒绝的异常。
此处代码走到AbstractSecurityInterceptor
类的原因是FilterSecurityInterceptor的doFilter()
调用到了父类的代码,在父类的方法中抛出了AccessDeniedException
,该异常会继续往上抛出。
直到ExceptionTranslationFilter的catch模块捕获到了这个异常。
并最终调用
authenticationEntryPoint.commence(request, response, reason);
最终将请求重定向到http://localhost:8080/login
页面。
紧接着,客户端再次向服务请求http://localhost:8080/login
。
老规矩又开始按顺序执行这15个过滤器,直到到达DefaultLoginPageGeneratingFilter
过滤器的时候,会判断若是访问登录请求URL或是登录失败或是退出成功中的一个,会执行下面的逻辑。
很明显isLoginUrlRequest(request) == true
然后代码来到了generateLoginPageHtml()
可以看到通过StringBuilder拼接了一个HTML的登录页面。
后续操作就是往response写入了这个html的登录页面,并返回。所以就有了当初请求http://localhost:8080/hello
时,出现了一个登录页面。
这便是集成Spring Security后,Spring Security的默认安全策略。
博主简单梳理一下这块逻辑。
- 客户端请求一个资源URL。
- 请求会按照顺序经过Spring Security默认提供的15个过滤器,在FilterSecurityInterceptor过滤器中发现用户没有认证会抛出AccessDeniedException异常。
- 异常会被ExceptionTranslationFilter过滤器被捕获到,并调用authenticationEntryPoint#commence方法将请求重定向到/login接口。
- 客户端再次请求/login接口。
- 请求被DefaultLoginPageGeneratingFilter过滤器拦截,并生成了一个登录页面并返回给客户端。
三. Spring Security认证
~~2.1.4 又是如何完成表单的认证操作?
当我们使用刚刚的用户密码是可以登录成功的,但是我们并没有创建密码,那这个用户是谁创建的呢,又是怎么进行登录的呢?且听我慢慢道来。
又是如何完成表单的认证操作?
- 自动配置 UserDetailsServiceAutoConfiguration //TODO 自动配置了之后,服务都干了什么事情,包括创建了默认的用户,默认的userdetailservice,自动创建的过滤器链(15个过滤器)
- 自动配置了哪些内容。auto 默认 configure() 默认的userdetail 内存中的用户
- 请求经过自动生成登录页面的过滤器,然后到usernamePasswordFilter登录~~
在这一小结博主会带领大家稍微深入的理解一下Spring Security是怎样完成认证操作的。当然在此之前博主还要将Spring Security的一些比较重要的类先列出来给大家讲些一下。
3.2 核心类讲解
3.2.1 AuthenticationManager
AuthenticationManager
是整个Spring Security中最核心的接口,中文名字可以叫他认证管理器。他只提供了一个方法authenticate()
用于认证。由于比较简单在这里将源码粘贴如下。
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
他接受一个Authentication
的对象,认证后并发挥认证后的这个对象,并填充用户的具体信息。这样说不太容易理解。下面举一个例子。
针对用户名密码的登录,进行登录认证时会将用户名和密码放到这个对象内部,如果认证成功,则会将用户的具体的一些信息,填充进去,比如用户的角色,能看到的菜单,手机号等信息。
3.2.2 Authentication
上文提到了Authentication
这个对象,在这个小节博主详细讲解一下这个对象的含义。
Look at the source code!
public interface Authentication extends Principal, Serializable {
/**
* 获取权限
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 证明主体是正确的凭证,通常是密码
*/
Object getCredentials();
/**
* 登录验证成功之后,一般将用户具体信息写入这里面,可以看到是个Object对象,代表着在子类实现的时候可以自定义业务需要的内容。
*/
Object getDetails();
/**
* 被验证主体的身份。
* 在身份验证的情况下使用用户名和密码请求登录,这将是用户名。
*/
Object getPrincipal();
/**
* 是否已经验证通过
*/
boolean isAuthenticated();
/**
* 设置认证状态
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
很简单可以看到Authentication
就是个接口,打个比方,用户通过账号密码登录的话,就要将账号密码传递给系统,这个对象就是接受用户密码的实体。并且在后续登录成功之后,系统会将一些用户信息保存在内。
在这里顺便说一下他的实现类。
上图仅仅截取了部分实现类。在业务开发时,都是使用其实现类,或自行创建实现类。他的实现类习惯称之为Token
。继续以用户密码登录打比方,登录时会创建一个UsernamePasswordAuthenticationToken
(下文会说明在什么时候创建这个Token)
对象内的字段principal
和credentials
就是所谓的用户名与密码。后续认证会从UsernamePasswordAuthenticationToken
取出相应信息进行认证。
3.2.4 SecurityContextHolder
使用Spring Security的目前是为了保证系统的安全性,那么在登录系统之后,在代码中怎样获得当前系统交互的用户信息呢。SecurityContextHolder就是为了解决这个问题而产生的,在用户登录系统之后,Spring Security会将查询到的用户信息保存到上文提到的Authentication
中,而Authentication
对象会被保存到SecurityContextHolder
中,此时我们直接从SecurityContextHolder
中就能获取到当前的登录人信息。
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String name = authentication.getName();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
值得注意的是SecurityContextHolder
,并不是直接维护Authentication
对象。SecurityContextHolder
使用了设计模式中的策略模式,Spring Security提供了三种策略。
对我们来说Spring Security主要应用的web服务,身份信息应该只存在于每一次的请求中存在,并在请求结束之后丢弃。所以默认情况下Spring Security使用了ThreadLocal的方式来保证Authentication只在单个线程内生效。
3.2.5 UsernamePasswordAuthenticationToken
好的,朋友们。刚刚我们讲解了Authentication
接口,接下来我们趁热打铁级说明一下Spring Security提供的Authentication
的实现类吧。
UsernamePasswordAuthenticationToken
是一个非常常见的Authentication
接口的实现类。他用于用户名密码的登录认证。应用在2.2 节中登录的例子。当认证的时候Spring Security会将请求/login的url中传递的参数的用户名和密码封装到UsernamePasswordAuthenticationToken
的principal
与credentials
变量中。并使用该对象进行后续的认证操作。
3.2.7 UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter
博主在上文说过,Spring Securiy是通过一系列过滤器链完成认证操作。
在默认情况下会开启了15个过滤器。而当通过账号密码登录时,请求经过UsernamePasswordAuthenticationFilter
过滤器时,就会开始具体的认证操作。
UsernamePasswordAuthenticationFilter
代码比较简单,博主带领大家阅读一下。既然要读一个过滤器,那么自然要从他的doFilter()
开始看起。
但是实际上UsernamePasswordAuthenticationFilter
本身并没有doFilter()
方法,该类继承了一个抽象类AbstractAuthenticationProcessingFilter
。并由AbstractAuthenticationProcessingFilter
来给出一个doFilter()
的实现,在这里说句题外话,可以看到Spring Security经常会对一个接口给出一个抽象类,该抽象类会给出公共的一些默认实现方式,并且其他具体的逻辑由其子类再去实现,比方说针对token,给出了AbstractAuthenticationToken
,针对provider,给出了AbstractUserDetailsAuthenticationProvider
。
下面从doFilter()
看起。
3.2.3 AuthenticationProvider
可以将AuthenticationProvider
理解成认证提供的方式,Provider
可以理解成供应商、认证方案的提供者。Spring Security包括多种认证方式,比如通过用户名密码登录、通过‘记住我’的方式登录,还支持自定义认证的方式,因此每有一种认证方式就会有一个AuthenticationProvider的实现类。
AuthenticationProvider
是一个接口,接口比较简单,将代码粘贴如下。
public interface AuthenticationProvider {
/**
* 具体的认证实现
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
/**
* 该认证方式所支持的Authentication
*/
boolean supports(Class<?> authentication);
}
实现的时候需要注意实现两个方法。
authenticate()
需要传递一个Authentication
对象,从上文中我们知道需要传递一个装载登录信息的实体,比如UsernamePasswordAuthenticationToken
这样一个Token。supports()
他的入参是一个Class,这代表着该provider
(下文一般以provider
代表多种认证方式的提供)支持哪种类型Token的认证。
我们在系统中要进行各种类型的认证时,就需要创建一个个provider
的实现类,并在其Authentication authenticate(Authentication authentication)
中实现具体的认证逻辑。当然作为安全框架的集大成者,Spring Security也给我们提供了需要默认的provider
。
例如:DaoAuthenticationProvider
是用于UsernamePasswordAuthenticationToken
的认证处理。
下文中会详细说明如何创建provider
的实现类,以及DaoAuthenticationProvider
是怎样完成认证的。
3.2.2 ProviderManager (重中之重!!!)
ProviderManager
是AuthenticationManager
的默认实现类,一般情况我们都是通过他来完成真正的用户登录操作。Spring Security就是通过这个类来协调各种Provider来完成认证的。
先来看看他长什么样。
我们挑主要的来分析,可以看到他有一个叫providers
的变量。这里面保存的就是当前ProviderManager
所支持的认证方式,就是一个包括多个AuthenticationProvider
的列表。
另外一个重要的变量是parent
。可以看到这个变量装的也是AuthenticationManager
这个接口的实现类,也是ProviderManager
。
当前类ProviderManager
,我们将它称之为局部的ProviderManager
,而他的变量parent
所引用的ProviderManager
我们称之为全局ProviderManager
。
Spring Security提供了局部和全局的ProviderManager
,当局部ProviderManager
无法完成认证时,还可以通过全局的ProviderManager
来再次进行认证,就像是个父亲,可以做一个兜底的感觉。
在实际开发过程中,我们可以根据实际情况来做考量,比如说最终需求其实就一种认证方式,并且不会有一种方式验证失败后采用另一种认证的时候,那其实我们不用关心局部和全局两个Manager,随便用哪个都行。
而当需求是存在多种认证方式,并且在验证失败时还要进行某种认证的时候可以考虑局部和全局做不同的认证,用全局的来做兜底。
PS:引入了AuthenticationManager
接口,实际上就是引入了一个ProviderManager
。
接下来,博主将会带领大家学习ProviderManager到底是怎么实现认证的。以及局部和全局ProviderManager是如何配合使用的。
请注意这里的知识点比较重要,请仔细阅读!
重点要看authenticate(Authentication authentication)()
这个认证的实现方法。上文提到该方法实现自AuthenticationManager
接口。
该方法很长,博主就挑重点给大家截取一下说明。
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
// 1. 进入认证方法,首先遍历所有的 provider
for (AuthenticationProvider provider : getProviders()) {
// 判断provider是否匹配这个具体要验证的token。
if (!provider.supports(toTest)) {
continue;
}
// 2. 如果该provider匹配这个token,那么用该provider进行认证。
try {
result = provider.authenticate(authentication);
if (result != null) {
// 3. 认证成功之后,将result内容,传递到token对象中,上文有提到set改内容的属性!
copyDetails(authentication, result);
break;
}
}
// do something
catch (AuthenticationException e) {
// do something
}
}
// 4. 如果局部ProviderManager没有认证成功,那就去他的父ProviderManager去认证。
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);
}
// do something
catch (AuthenticationException e) {
// do something
}
}
// do something
return result;
// do something
}
可以看到ProviderManager
实现认证的做法就是通过遍历他自身的provider
,找到匹配当前token
的provider
,然后通过provider
来完成认证。如果所有的provider
都验证失败。则会将认证处理交由其parent的AuthenticationManager
。并由parent的provider
来完成认证操作。
一旦认证成功会将用户信息封装到Authentication
并返回。
注:有关局部AuthenticationManager
和全局AuthenticationManager
博主会重新开一篇文章书写。
3.2.4 SecurityContextHolder
TODO在认证通过之后再说明
可以画个图 说明一下,怎么 ThreadLocal 放session 并在代码里怎么取session
3.2.6 UsernamePasswordAuthenticationFilter
3.2.7 AbstractAuthenticationProcessingFilter
3.3 认证流程详解
【在这个地方绘制一幅图 流程图 时序图 】
描述一下 几个概念
- authoractionManger
- povviderManager
新建一个认证需要做下面的事情
+filter
+ authorationProvider
+ authoration (各种token)
+ UserdetailService
然后再实现一个新的认证provider
2. 源码追踪
热热他任务