文章目录
- **前置知识**
- **1.概念介绍**
-
- **1.1权限管理**
- **1.2完成权限管理需要三个对象**
- **1.3Spring Security**
- **1.4Spring Security过滤器链**
- **1.5UserDetailsService接口讲解**
- **1.7Authentication**
- **1.8AuthenticationManager**
- **1.9SecurityContext**
- **1.10SecurityContextHolder**
- **动态鉴权流程解析**
- **动态鉴权的实现**
- **1.10WebSecurityConfigurerAdapter与@EnableWebSecurity**
- **1.11WebSecurityConfiguration**
- **1.9PasswordEncoder接口讲解**
- **1.10前后端分离应用中自定义token整合Spring Security**
- **2.Spring Security简单入门**
- **3.SpringSecurity-web权限方案**
- **4.SpringSecurity微服务权限方案**
- **5.SpringSecurity源码剖析**
前置知识
1、掌握Spring框架
2、掌握SpringBoot使用
3、掌握JavaWeb技术
1.概念介绍
1.1权限管理
权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。权限管理几乎出现在任何系统中,前提是需要有用户和密码认证的系统。一般来说,Web应用的安全性包括**用户认证(Authentication)和用户授权(Authorization)**这两点,这两点也是Spring Security重要核心功能。
在权限管理的概念中,有两个重要的名词:
认证:通过用户名和密码成功登陆系统之后,让系统得到当前用户的角色身份。通俗点说就是系统认为用户是否能登录。
授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源。验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以可以修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情
1.2完成权限管理需要三个对象
用户:主要包含用户名,密码和当前用户的角色信息,可以实现认证操作。
角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可以实现授权操作。
权限:权限也称为菜单,主要包含当前权限名称,url地址等信息,可以实现动态展示菜单。
注:这三个对象中,用户与角色是多对多的关系,角色与权限是多对多的关系,用户与权限没有直接关系,二者是通过角色来建立关联关系的。
1.3Spring Security
Spring Security是Spring采用AOP思想,基于servlet过滤器实现的安全框架。它提供了完善的认证机制和方法级的授权功能。
Spring Security本质上是一个过滤器链,即通过一层层的Filters来对web请求做处理
可以这样表述:
一个web请求会经过一条过滤器链,在经过过滤器链的过程中会完成认证和授权,如果中间发现这条请求未认证或者未授权,惠普官网根据被保护API的权限去抛出异常,然后由异常处理器去处理这些异常。
可以看下面这张图片
如上图,一个请求想要访问到API就会以从左到右的形式经过蓝线框框里面的过滤器,其中绿色部分是我们本篇主要将的负责认证的过滤器,蓝色部分负责异常处理,橙色部分则是负责授权。
图中的这两个绿色过滤器是Spring Security
就应该知道配置中有两个叫formLogin
、httpBasic
的配置项,在配置中打开了它俩就对应着打开了上面的过滤器。
formLogin
对应着form
表单认证方式,即UsernamePasswordAuthenticationFilter
httpBasic
对应着Basic认证方式,即BasicAuthenticationFilter
换言之,你配置了这两种认证方式,过滤器链才会加入它们,否则是不会被加到过滤器链中去的。
因为Spring Security
自带的过滤器是没有针对JWT这种认证方式的,所以我们的demo
中会写一个JWT的认证过滤器,然后放在绿色的位置进行认证工作。
SpringSecurity的重要概念
知道了Spring Security
的大致工作流程之后,还需要知道非常重要的组件。
- SecurityContext:上下文对象,
Authentication
对象会放在里面。 - SecurityContextHolder:用于拿到上下文对象的静态工具类。
- Authentication:认证接口,定义了认证对象的数据形式
- AuthenticationManager:用于校验
Authentication
,返回一个认证完成后的Authentication
对象。
SpringSecurity流程图
流程说明
1、客户端发起一个请求,进入Spring Security
过滤器链
2、当到LogoutFilter
的时候判断是否是登出路径,如果是登出路径则到logoutHandler
,如果登出成功则到logoutSuccessHandler
登出成功处理,如果登出失败则由ExceptionTranslationFilter
;如果不是登出路径的话,则直接进入下一个过滤器。
3、当到UsernamePasswordAuthenticationFilter
的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作;如果登录失败,则到AuthenticationFailureHandler
登录失败处理器处理,如果登录成功则到AuthenticationSuccessHandler
登录成功处理器处理,如果不是登录请求则不进入该过滤器。
4、当到FilterSecurityInterceptor
的时候会拿到URI
,根据URI
去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到Controller
层,否则到AccessDeniedHandler
鉴权失败处理器处理。
1.3.1创建web工程并导入jar包
Spring Security主要jar包功能介绍:
spring-security-core.jar
核心包,任何Spring Security功能都需要此包
spring-secuirty-web.jar
web工程必备,包含过滤器和相关的Web安全基础结构代码。
spring-security-config.jar
用于解析xml配置文件,用到Spring Security的xml配置文件的就需要用到此包。
spring-security-taglibs.jar
Spring Security提供的动态标签库,jsp页面可以用。
1.4Spring Security过滤器链
Spring Security本质上是一个过滤器链
1.4.1Spring Security常用过滤器链介绍
Spring Security本质上是一个过滤器链
过滤器是典型的AOP思想,了解web工程的都知道。
1、org.springframework.security.web.context.SecurityContextPersistenceFilter
这是首当其冲的一个过滤器
SecurityContextPersistenceFilter主要是使用SecurityContextPersistenceFilter在session中保存或者更新一个SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续filter建立所需的上下文。
SecurityContext存储了当前用户的认证以及权限信息。
2、org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager
3、org.springframework.security.web.header.HeaderWriterFilter
向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制
4、org.springframework.security.web.csrf.CsrfFilter
csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错,起到防止csrf攻击的效果。
5、org.springframework.security.web.authentication.logout.LogoutFilter
匹配URL为/logout的请求,实现用户退出,清除认证信息
6、org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求
对/login的POST请求做拦截,校验表单中用户名、密码
7、org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面
8、org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
由此过滤器可以生产一个默认的退出登录页面
9、org.springframework.security.web.authentication.www.BasicAuthenticationFilter
此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。
10、org.springframework.security.web.savedrequest.RequestCacheAwareFilter
通过HttpSessionRequestCache内部维护一个RequestCache,用于缓存HttpServletRequest
11、org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
针对ServletRequest进行了一次包装,使得request具有更加丰富的API
12、org.springframework.security.web.authentication.AnonymousAuthenticationFilter
当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。
spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
13、org.springframework.security.web.session.SessionManagementFilter
SecurityContextRepository限制同一用户开启多个会话的数量
14、org.springframework.security.web.access.ExceptionTranslationFilter
异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常
是一个异常过滤器,用来处理在认证授权过程中抛出的异常
15、org.springframework.security.web.access.intercept.FilterSecurityInterceptor
是一个方法级的权限过滤器,基本位于过滤链的最底部
获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。
1.4.2过滤器如何进行加载的?
Spring Boot已经帮我们配置了这些,因此不需要配置。但是这是基本的原理:
1.5UserDetailsService接口讲解
当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。
UserDetailsService
接口
package org.springframework.security.core.userdetails;
/**
* Core interface which loads user-specific data.
* 加载用户特定数据的核心接口。
* It is used throughout the framework as a user DAO and is the strategy used by the DaoAuthenticationProvider
* 它作为用户DAO在整个框架中使用,也是DaoAuthenticationProvider使用的策略
* The interface requires only one read-only method, which simplifies support for new
* data-access strategies.
* 该接口只需要一个只读方法,这简化了对新数据访问策略的支持。
*/
public interface UserDetailsService {
/**
* 根据用户名定位用户。在实际实现中,搜索可能是区分大小写的,或者不区分大小写,这取决于实现实例的配置方式。在这种情况下,返回的UserDetails对象可能拥有与实际请求不同的用户名。
* @param username 标识需要其数据的用户的用户名。
* @return a fully populated user record (never <code>null</code>)
* 一个完全填充的用户记录(从不null)
* @throws UsernameNotFoundException if the user could not be found or the user has no GrantedAuthority
* 如果找不到用户或用户没有被授予权限,则使用UsernameNotFoundException
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
从代码中可以看到,UserDetailsService
只提供了一个方法。从方法名loaduserByUsername
可以看出,该方法通过用户名来加载用户。我们在实现loadUserByUsername
方法的时候,就可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails
,(通常是一个org.springframework.security.core.userdetails.User
,它继承自UserDetails
) 并返回。
1.6UserDetails
package org.springframework.security.core.userdetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.io.Serializable;
import java.util.Collection;
/**
* Provides core user information.
* 提供核心用户信息
* Implementations are not used directly by Spring Security for security purposes. They
* simply store user information which is later encapsulated into {@link Authentication}
* objects. This allows non-security related user information (such as email addresses,
* telephone numbers etc) to be stored in a convenient location.
* 出于安全目的,Spring Security不会直接使用实现
* 它们只是存储用户信息,这些信息稍后被封装到Authentication对象中。
* 这允许非安全相关的用户信息(如email、电话号码等)被存储在一个方便的位置
* Concrete implementations must take particular care to ensure the non-null contract
* detailed for each method is enforced. See User for a reference implementation (which you might like to extend or use in your code).
* 具体的实现必须特别小心,确保为每个方法详细说明的非空契约得到实施。
* 有关参考实现(您可能想在代码中扩展或者使用它),请参阅User。
*/
public interface UserDetails extends Serializable {
//获取用户权限,本质上是用户的角色信息
Collection<? extends GrantedAuthority> getAuthorities();
//获取用户密码
String getPassword();
//获取用户名
String getUsername();
//账户是否过期
boolean isAccountNonExpired();
//账户是否被锁定
boolean isAccountNonLocked();
//密码是否过期
boolean isCredentialsNonExpired();
//账户可否可用
boolean isEnabled();
}
1.7Authentication
Authentication
即验证,表明当前用户是谁。什么是验证,比如一组用户名和密码就是验证,当然错误的用户名和密码也是验证,只不过Spring Security会校验失败。
package org.springframework.security.core;
import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* Represents the token for an authentication request or for an authenticated principal
* once the request has been processed by the
* {@link AuthenticationManager#authenticate(Authentication)} method.
* 表示身份验证请求或者经过身份验证的主体在请求已由身份验证方法处理后的令牌。
* Once the request has been authenticated, the <tt>Authentication</tt> will usually be
* stored in a thread-local <tt>SecurityContext</tt> managed by the
* {@link SecurityContextHolder} by the authentication mechanism which is being used. An
* explicit authentication can be achieved, without using one of Spring Security's
* authentication mechanisms, by creating an <tt>Authentication</tt> instance and using
* the code:
*一旦请求被身份验证authticated,身份验证通常将被存储在一个thread-local的SecurityContext中,该thread-local由正在使用的身份验证机制来管理。不使用Spring Security的身份验证机制,通过创建一个身份验证实例并使用代码,就可以实现显式地身份验证。
* SecurityContextHolder.getContext().setAuthentication(anAuthentication);
*
* Note that unless the <tt>Authentication</tt> has the <tt>authenticated</tt> property
* set to <tt>true</tt>, it will still be authenticated by any security interceptor (for
* method or web invocations) which encounters it.
* In most cases, the framework transparently takes care of managing the security context
* and authentication objects for you.
*/
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
Authentication
是一个接口,实现类都会定义authorities
、credentials
、details
、principal
、authenticated
等字段。具体含义如下:
- getAuthorities:获取用户权限,一般情况下获取到的是用户的角色信息
- getCredentials:获取证明用户认证的信息,通常情况下获取到的是密码等信息。
- getDetails:获取用户的额外信息,比如IP地址、经纬度等
- getPrincipal:获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是
UserDetails
(暂时理解为,当前应用用户对象的扩展)。 - isAuthenticated:获取当前
Authentication
是否已经认证。 - setAuthenticated:设置当前
Authentication
是否已经认证。
验证前:
principal
填充的是用户名,credentials
填充的是密码,details
填充的是用户的IP或者经纬度之类的信息。
验证后:- Spring Security对
Authentication
重新注入,principal
填充用户信息(包含用户名、年龄等),authorities
会填充用户的角色信息,Authenticated
会被设置为true。重新注入的Authentication
会被填充到SecurityContext
中。
1.8AuthenticationManager
AuthenticationManager
负责校验Authentication
对象。在AuthenticationManager
的authenticate
函数中,实现对Authentication
的校验。
如果校验通过,则返回一个重新注入的Authentication
对象;校验失败,则抛出AuthenticationException
异常。
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
AuthenticationManager
可以将异常抛出的更加明确:
- 当用户不可用时抛出 DisabledException
- 当用户被锁定时抛出 LockedException
- 当用户密码错误时抛出 BadCredentialsException
重新注入的Authentication
会包含当前用户的详细信息,并且被填充到SecurityContext
中,这样SpringSecurity
的验证流程就完成了,SpringSecurity
可以识别到"你是谁"。
1.9SecurityContext
1.10SecurityContextHolder
可以想象这四个部分如何串联成为一个完整的认证流程:
(1)、先是一个请求带着身份信息进来
(2)、经过AuthenticationManager
的认证,
(3)、再通过SecurityContextHolder
获取SecurityContext
(4)、最后将认证后的信息放入到SecurityContext
流程实例
若干组件:
- 定义加密器Bean
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
这个Bean是必不可少的,Spring Security
在认证操作时会使用我们定义的这个加密器,如果没有则会出现异常。
- 定义AuthenticationManager
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
这里将Spring Security
自带的authenticationManager
声明成Bean,声明它的作用是用它帮我们进行认证操作,调用这个Bean的authenticate
方法会由Spring Security
自动帮助我们做认证。
- 实现UserDetailsService
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private RoleInfoService roleInfoService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
log.debug("开始登陆验证,用户名为: {}",s);
// 根据用户名验证用户
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserInfo::getLoginAccount,s);
UserInfo userInfo = userService.getOne(queryWrapper);
if (userInfo == null) {
throw new UsernameNotFoundException("用户名不存在,登陆失败。");
}
// 构建UserDetail对象
UserDetail userDetail = new UserDetail();
userDetail.setUserInfo(userInfo);
List<RoleInfo> roleInfoList = roleInfoService.listRoleByUserId(userInfo.getUserId());
userDetail.setRoleInfoList(roleInfoList);
return userDetail;
}
}
实现UserDetailsService
的抽象方法并返回一个UserDetails
对象,认证过程中SpringSecurity
会调用这个方法访问数据库进行对用户的搜索,逻辑什么的都可以自定义,无论是从数据库中还是从缓存中,但是我们需要将我们查询出来的用户信息和权限信息组装成为一个UserDetails
返回。
UserDetails
也是一个定义了数据形式的接口,用于保存我们从数据库中查出来的数据,其主要的功能时验证账号状态和获取权限。
- TokenUtil
由于我们是JWT的认证模式,所以我们也需要一个帮助我们操作Token的工具类,需要具有下面三个方法:
- 创建token
- 验证token
- 泛解析token中的信息
代码的具体实现
1、认证方法
访问一个系统,最先访问的都是认证方法,这里简写了简略的认证需要的步骤,因为在实际过程中还需要写登录记录、前台密码解密这些操作。
大概可以分为五个步骤:
1、传入用户名和密码创建UsernamePasswordAuthenticationToken
对象,就是我们前面说的Authentication
的实现类,传入用户名和密码做构造参数,这个对象就是我们创建出来的未认证的Authentication
对象。
2、使用我们先前已经声明过的Bean-authenticationManager
调用它的authenticate
方法进行认证,返回一个认证完成的Authentication
对象。
3、认证完成没有出现异常,就会走到第三步,使用SecurityContextHolder
获取SecurityContext
之后,将认证完成后的Authentication
对象,放入上下文对象。
4、从Authentication
对象中拿到我们的UserDetails
对象,之前我们说过的,认证后的Authentication
对象调用它的getPrincipal()
方法就可以拿到之前在数据库查询后组装出来的UserDetails
对象。然后创建token
。
5、把UserDetails
对象放入缓存中,方便后面过滤器使用。
这样的话就算完成了,其实主要的认证操作都会由authenticationManager.authenticate()
帮我们完成。
JWT过滤器
有了token之后,我们需要把过滤器放在过滤器链中,用于解析token,因为没有session,所以当每次去辨别这是哪个用户的请求的时候,都是根据请求中的token来解析出来当前是哪个用户。
所以当我们需要一个过滤器去拦截所有请求,前文我们也说过,这个过滤器会放在绿色部分用来替代UsernamePasswordAuthenticationFilter
,所以我们新建一个JwtAuthenticationTokenFilter
,然后将它注册为Bean
,并在编写配置文件的时候需要加上这个:
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jwtAuthenticationTokenFilter(),
UsernamePasswordAuthenticationFilter.class);
}
addFilterBefore
的语义是添加一个Filter
到xxxFilter
之前,放在这里就是把JwtAuthenticationTokenFilter
放在UsernamePasswordAuthenticationFilter
之前,因为filter
的执行也是有顺序的,我们必须要把我们的filter
放在过滤器链中绿色的部分才会起到自动认证的效果。
下面可以看看JwtAuthenticationTokenFilter
的具体实现了:
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain chain) throws ServletException, IOException {
log.info("JWT过滤器通过校验请求头token进行自动登录...");
// 拿到Authorization请求头内的信息
String authToken = jwtProvider.getToken(request);
// 判断一下内容是否为空且是否为(Bearer )开头
if (StrUtil.isNotEmpty(authToken) && authToken.startsWith(jwtProperties.getTokenPrefix())) {
// 去掉token前缀(Bearer ),拿到真实token
authToken = authToken.substring(jwtProperties.getTokenPrefix().length());
// 拿到token里面的登录账号
String loginAccount = jwtProvider.getSubjectFromToken(authToken);
if (StrUtil.isNotEmpty(loginAccount) && SecurityContextHolder.getContext().getAuthentication() == null) {
// 缓存里查询用户,不存在需要重新登陆。
UserDetail userDetails = caffeineCache.get(CacheName.USER, loginAccount, UserDetail.class);
// 拿到用户信息后验证用户信息与token
if (userDetails != null && jwtProvider.validateToken(authToken, userDetails)) {
// 组装authentication对象,构造参数是Principal Credentials 与 Authorities
// 后面的拦截器里面会用到 grantedAuthorities 方法
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
// 将authentication信息放入到上下文对象中
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("JWT过滤器通过校验请求头token自动登录成功, user : {}", userDetails.getUsername());
}
}
}
chain.doFilter(request, response);
}
下面简单看看逻辑步骤:
1、拿到Authorization
请求头对应的token信息。
2、去掉token的头部(Bearer)
3、解析token,拿到存放的登录账号
4、因为已经完成过登录,所以可以直接从缓存中拿到我们的UserDetail
信息即可。
5、查看UserDetail是否为null,以及查看token是否过期,UserDetail
用户名与token
中的是否一致。
6、组装一个authentication
对象,把它放在上下文对象中,这样后面的过滤器看到我们的上下文对象中有authentication
对象,就相当于以及认证过了。
这样的话,每当一个带有正确token的请求进来之后,都会找到它的账号信息,并放在上下文对象中,可以看到使用SecurityContextHolder
很方便的拿到上下文对象中的Authentication
对象。
完成之后,启动Demo,可以看到过滤器链中有以下的过滤器,其中我们自定义的是第五个:
所以最后,我们登录完成之后,把获取到的账号信息与角色信息放入缓存中之后,当带着token的请求到来时,我们就把它从缓存中取出,再次放到缓存对象中去。
因此,逻辑链条就变成了:
登录-->
拿到token-->
请求带上token-->
JWT过滤器-->
校验token-->
将从缓存中查出来的对象放到上下文中
这样,认证逻辑就算完成了。
代码优化
认证和JWT过滤器完成后,这个JWT的项目其实已经可以运行了。但是为了代码的鲁棒性,可以增加辅助的功能代码。
1、认证失败处理器
用户未登录或者token解析失败的时候会触发这个处理器,返回一个非法访问的结果。
2、权限不足处理器
当用户本身权限不满足所访问API需要的权限的时候,触发这个处理器,返回一个权限不足的结果。
3、退出方法
用户退出一般就是清掉上下文对象和缓存就行了,也可以做附加操作,这两步是必须的。
4、token刷新
JWT的项目token刷新也是必不可少的,这里刷新token的方法放在token工具箱中,刷新完成之后把缓存重载一遍就可以了。缓存是有有效期的,重新put可以重置失效时间。
动态鉴权流程解析
认证过程主要围绕图中过滤连的绿色部分,动态鉴权则是围绕其中的橙色部分,也就是FilterSecurityInterceptor
。
1.FilterSecurityInterceptor
一个请求完成认证之后,且没有抛出异常的情况下就会到达FilterSecurityInterceptor
所负责的鉴权部分,也就是说鉴权的入口就在FilterSecurityInterceptor
。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
this.invoke(fi);
}
}
上文代码可以看出FilterSecurityInterceptor
是实现了AbstractSecurityInterceptor
,其中AbstractSecurityInterceptor
预先写好了一段很重要的代码。
FilterSecurityInterceptor
的主要方法是doFilter
方法,过滤器的特性就是请求过来之后都会执行doFilter
方法,FilterSecurityInterceptor
的doFilter
方法很简单,只有两行。
第一行创建了一个FilterInvocation
对象,这个FilterInvocation
对象你可以当作它封装了request
,它的主要工作就是拿请求中的信息,比如请求的URI。
第二行调用了自身的invoke
方法,并将FilterInvocation
对象传入。
所以显然,主要的逻辑就在invoke
方法中,可以看看:
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if (fi.getRequest() != null && this.observeOncePerRequest) {
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token