一、 关键词
Authentication:鉴权,我理解为身份认证,是权限验证中的一个特殊的分支。
Authorization:授权,表示为权限验证。
AuthenticationProvider:认证处理,对每一个支持的认证对象的身份进行认证。
AuthenticationManager:认证管理器,管理多个AuthenticationProvider,实现允许多个认证处理去执行不同来源的身份认证。
AccessDecisionManager:验证管理器,主要是对Security表达式(表达式数量可以是多个)进行权限验证处理。例如:authenticated()、hasRole()、hasAuthority()、access(表达式)
等方法进行权限的验证。其中authenticated()
表示对用户是否拥有验明的身份权,即为身份认证。
权限通过:AccessDecisionManager管理有三个实现类,分别对应三种权限验证通过的方式。权限验证通过是通过类似投票方式决定,有三种投票机制
ACCESS_GRANTED:赞成票
ACCESS_ABSTAIN:弃权票
ACCESS_DENIED:否决票
通过方式 | 实现类 | 解释 |
---|---|---|
非否决通过 | AffirmativeBased | 1.有赞成票即可通过 2.全部弃权即可通过 |
少数服从多数 | ConsensusBased | 1.赞成票大于否决票则通过 2.赞成票和否决票相同,allowIfEqualGrantedDeniedDecisions=true则通过 3.全部弃权,allowIfEqualGrantedDeniedDecisions=true则通过 |
一致通过 | UnanimousBased | 1.有赞成票,没有反对票则通过 2.全部否决,allowIfAllAbstainDecisions=true则通过 |
二、 基本流程
1.从@EnableWebSecurity说起
源码如下
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class, // 导入WebSecurity配置,是针对于Web方面的一些注册
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication // 允许全局定义认证机制,包含一些认证的注册配置等等
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
在WebSecurityConfiguration类中有setFilterChainProxySecurityConfigurer()
方法,这个方法将会导入关于SpringSecurity的所有配置器对象(实现WebSecurityConfigurer
接口的实例),方法如下:
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
// 在本处需要导入系统中关于WebSecurityConfigurer配置器
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
进入@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()
方法进行调试。如图
从图中可知,Spring将会注入
SpringBootWebSecurityConfiguration
对象查看
SpringBootWebSecurityConfiguration
源码SpringBootWebSecurityConfiguration
从图中可知:
- 根据
@ConditionalOnClass
和@ConditionalOnMissingBean
这两个条件来自定义创建WebSecurityConfigurerAdapter对象,从而实现注入。 - 因此,通常自定义Security配置的话,都是继承WebSecurityConfigurerAdapter,并注入到Spring容器中就好。
-
如果配置多个Security配置器,则如图
2.那么注册配置器后有什么用呢?
根据Spring官方文档中描述如图
SpringBoot的自启动实现三点:
- 启用Spring Security的默认配置,该配置将Servlet过滤器创建为名为springSecurityFilterChain的bean。该bean负责应用程序中的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。
- 创建一个UserDetailsService bean,其中包含用户名user和随机生成的密码,该密码将记录到控制台。
-
对于每个请求,使用Servlet容器向名为springSecurityFilterChain的bean注册过滤器。
注册名为springSecurityFilterChain的过滤器,实际上注册的是FilterChainProxy对象,该对象中保存有两个WebSecurityConfigurer配置器的配置过滤器链。而整个请求的过滤都将通过这一系列过滤器进行过滤。如图
3.查看FilterChainProxy类源码,分析其作用
// FilterChainProxy类的doFilter方法
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 配置每一个请求只过滤一次。
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
可以看出最终将都将会执行doFilterInternal方法。继续查看该方法源码
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
// 通过自定义过滤器执行链来执行每一个过滤器
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
进入List<Filter> filters = getFilters(fwRequest);
方法
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
该方法表明将从所有的WebSecurityConfiguration配置对象所生成的过滤器链集合中找出与当前请求所匹配的过滤器链,并返回过滤器链中的所有过滤器。
请求匹配的过滤链的所有过滤器
所有的过滤器如下:
- 1.
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
- 2.
org.springframework.security.web.context.SecurityContextPersistenceFilter
- 3.
org.springframework.security.web.header.HeaderWriterFilter
- 4.
org.springframework.security.web.csrf.CsrfFilter
- 5.
org.springframework.security.web.authentication.logout.LogoutFilter
- 6.
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
- 7.
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
- 8.
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
- 9.
org.springframework.security.web.authentication.www.BasicAuthenticationFilter
- 10.
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
- 11.
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
- 12.
org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
- 13.
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
- 14.
org.springframework.security.web.session.SessionManagementFilter
- 15.
org.springframework.security.web.access.ExceptionTranslationFilter
- 16.
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
- 17.
org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor
(注:并未出现在过滤链中,只是可以归为一类型理解处理)
4.在WebSecurityConfiguration配置中,过滤器是如何加入到请求过滤链中的
默认配置自定义配置增加rememberMe()方法,使过滤器链中加入RememberMeAuthenticationFilter
自定义配置
添加全局方法注解,会使用代理MethodSecurityInterceptor处理业务方法
三、 粗略分析每个过滤器
前提:解释一下OncePerRequestFilter过滤器,因为有一些过滤器都会继承OncePerRequestFilter,所以在此先看一下该过滤器的作用
0.OncePerRequestFilter
源码如下:
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
// 是否被过滤
String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (!this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
// 通过此处控制每个请求只被过滤一次
if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
this.doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
filterChain.doFilter(request, response);
} else {
// 设置已经被过滤
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 主要的过滤方法
this.doFilterInternal(httpRequest, httpResponse, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
} else {
filterChain.doFilter(request, response);
}
} else {
throw new ServletException("OncePerRequestFilter just supports HTTP requests");
}
}
作用:大致表现为每一个请求只能被过滤器过滤一次。过滤时调用doFilterInternal方法,因此每一个子类只需要看doFilterInternal方法即可。
1. WebAsyncManagerIntegrationFilter
名称:Web异步管理器集成过滤器,继承OncePerRequestFilter
源码:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
作用:提供SecurityContext和SpringWeb的集成
- doFilter前:进行SecurityContext和SpringWeb集成
- doFilter后:无
2. SecurityContextPersistenceFilter
名称:SecurityContext持久化过滤器
源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// 防止重复过滤 ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
// 提前创建session,如果SecurityContext使用HttpSession管理。作用暂时不清楚
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
// 实际上就是讲request和response封装为一个对象,然后传入SecurityContext持久库中加载SecurityContext。
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
// 从SecurityContext持久库中加载SecurityContext
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
// 将加载后的SecurityContext设置到SecurityContextHolder中,方便以后使用
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// 清空SecurityContextHolder中的SecurityContext
SecurityContextHolder.clearContext();
// 将经过一系列修改的SecurityContext保存至SecurityContext库中。
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
作用:对SecurityContext进行持久化储存
- doFilter前:尝试从SecurityContext持久库中调用loadContext方法加载SecurityContext,并设置到SecurityContextHolder中方便之后使用
-
doFilter后:
- 清空SecurityContextHolder中的SecurityContext
- 将经过一系列修改的SecurityContext保存至SecurityContext库中。
其他:关于SecurityContextRepository的载入和自定义。
- 加载SecurityContextRepository:调用http. securityContext(),进入后直接调用SecurityContextConfigurer的configure(H http)方法。
public void configure(H http) {
// 从sharedObject中拿SecurityContextRepository对象
SecurityContextRepository securityContextRepository = http
.getSharedObject(SecurityContextRepository.class);
// 如果没拿到,则新建一个session管理的SecurityContextRepository对象
if (securityContextRepository == null) {
securityContextRepository = new HttpSessionSecurityContextRepository();
}
SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
securityContextRepository);
// 拿到session管理配置
SessionManagementConfigurer<?> sessionManagement = http
.getConfigurer(SessionManagementConfigurer.class);
// 从session管理配置中拿到创建策略
SessionCreationPolicy sessionCreationPolicy = sessionManagement == null ? null
: sessionManagement.getSessionCreationPolicy();
// 如果是ALWAYS策略,则允许securityContextFilter提前创建session
if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
securityContextFilter.setForceEagerSessionCreation(true);
}
// 对securityContextFilter过滤器后置处理
// 其会调用http.objectPostProcessor()方法提供的后置处理器。
securityContextFilter = postProcess(securityContextFilter);
http.addFilter(securityContextFilter);
}
-
因此:有两种自定义SecurityContextRepository的方法。
- 一是http.setSharedObject(SecurityContextRepository.class, obj)将自定义的库对象放入到SharedObject中
- 二是http.securityContext(Customizer<SecurityContextConfigurer<HttpSecurity>> securityContextCustomizer)重新自定义SecurityContextPersistenceFilter过滤器
3. HeaderWriterFilter
名称:请求头填写过滤器
继承OncePerRequestFilter
源码:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.shouldWriteHeadersEagerly) {
doHeadersBefore(request, response, filterChain);
} else {
doHeadersAfter(request, response, filterChain);
}
}
作用:为请求头添加headers
- doFilter前:如果属性shouldWriteHeadersEagerly为true,则会在过滤前添加请求头。否则,过滤前不会添加。
- doFilter后:如果属性shouldWriteHeadersEagerly为false,则会在过滤后添加请求头。否则,过滤后不会添加。
- shouldWriteHeadersEagerly默认为false
4. CsrfFilter
名称:Csrf过滤器,继承OncePerRequestFilter
作用:开启Csrf防御
- doFilter前:验证是否csrf安全
- doFilter后:无
5. LogoutFilter
名称:登出过滤器
源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是否为登出请求
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
// 登出的处理
this.handler.logout(request, response, auth);
// 登出成功后处理
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
作用:处理登出请求
- doFilter前:如果是登出请求,则进行登出处理和登出成功后处理。通过
http.logout().addLogoutHandler().logoutSuccessHandler()
可以增加处理机制 - doFilter后:无
6. UsernamePasswordAuthenticationFilter
名称:用户认证过滤器
继承AbstractAuthenticationProcessingFilter
,是基于浏览器的基于HTTP的身份验证请求的抽象处理器
源码:
// AbstractAuthenticationProcessingFilter的doFilter方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是否为登录请求
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// 尝试去获取认证对象
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
// session处理机制处理认证对象,默认为NullAuthenticatedSessionStrategy不进行处理
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// 认证成功后判断是否继续执行其他过滤器
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//
successfulAuthentication(request, response, chain, authResult);
}
// UsernamePasswordAuthenticationFilter的attemptAuthentication方法
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 从请求中获取参数名为'username','password'的值
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 通过用户名密码生成一个未经验证的认证对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// 通过认证管理器对该认证对象进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
作用:判断用户是否为登录请求,如果是登录请求,提取其中的用户名和密码并保存认证对象到SecurityContextHolder中
- doFilter前:如果不是登录请求,跳过当前过滤。如果是登录请求,则尝试去获取认证对象。认证对象主要是通过认证管理器下认证机制对包含用户名和密码的对象进行认证并返回。
- 如果continueChainBeforeSuccessfulAuthentication属性为false,则不执行后置过滤。如果为true。执行后置过滤
- doFilter后:如果不执行后置过滤或者执行完后置过滤后去执行一些成功登录的处理,包括保存认证对象、rememberMe记录、成功后的自定义处理器处理
7. DefaultLoginPageGeneratingFilter
名称:生成默认登录页过滤器
作用:生成默认的登录页
- doFilter前:生成登录页
- doFilter后:无
8. DefaultLogoutPageGeneratingFilter
名称:生成默认登出页过滤器,继承OncePerRequestFilter
作用:生成默认的登出页
- doFilter前:如果匹配默认/logout的GET请求,则渲染登出页。如果不匹配,则跳过该过滤器。
- doFilter后:无
9. BasicAuthenticationFilter
名称:Http基本认证过滤器
源码:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
try {
// 判断是否包含请求头Authorization,并且该值以BASIC开头,将其值朱哪位认证对象
UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
if (authRequest == null) {
chain.doFilter(request, response);
return;
}
String username = authRequest.getName();
if (debug) {
this.logger
.debug("Basic Authentication Authorization header found for user '"
+ username + "'");
}
// 需要对从请求头中获取的认证对象进行认证处理器进行处理
if (authenticationIsRequired(username)) {
// 认证管理器对该认证对象进行认证
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
// 保存认证对象到SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(authResult);
// rememberMe记录
this.rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
if (debug) {
this.logger.debug("Authentication request for failed: " + failed);
}
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}
return;
}
chain.doFilter(request, response);
}
作用:对Http请求进行基本认证,是对请求头Authorization的认证
- doFilter前:判断是否包含请求头Authorization,并且该值以BASIC开头。如果该值不可以转换为认证对象,跳过该过滤器。如果可以转换为认证对象,如果需要对改对象进行认证处理,则使用认证管理器对其进行处理,然后依次保存认证对象、rememberMe记录、成功后处理器处理
- doFilter后:无
10. RequestCacheAwareFilter
名称:请求缓存过滤器
源码:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 尝试去获取缓存的请求
HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
(HttpServletRequest) request, (HttpServletResponse) response);
chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
response);
}
作用:缓存当前的请求,可以用作重新登录后的再次请求使用
- doFilter前:尝试去获取缓存的请求,如果缓存请求不为null,则doFilter中传入缓存的请求,否则传入原有的请求。
- doFilter后:无
11. SecurityContextHolderAwareRequestFilter
名称:SecurityContextHolder包装请求过滤器
源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
(HttpServletResponse) res), res);
}
作用:将request包装,更适用于SecurityContext
- doFilter前:将request和rolePrefix(角色前缀)、authenticationEntryPoint(认证异常处理)、authenticationManager(认证管理器)、logoutHandlers(退出处理)、trustResolver(身份信任处理)绑定。
- doFIlter后:无
12. RememberMeAuthenticationFilter
名称:rememberMe认证过滤器
源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 本身是登录的状态,则不需要对rememberMe的对象进行认证
if (SecurityContextHolder.getContext().getAuthentication() == null) {
// 通过服务类从请求的rememerMe中拿到认证对象
// rememberMeServices通常
// 1.如果有PersistentTokenRepository token持久化库,则会PersistentTokenBasedRememberMeServices服务,从持久库中拿
// 2.如果没有持久化库,则使用基本的rememberMe服务,TokenBasedRememberMeServices
Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
response);
if (rememberMeAuth != null) {
// 尝试去通过认证管理器来认证rememberMe对象
try {
rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
// 保存至SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
onSuccessfulAuthentication(request, response, rememberMeAuth);
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder populated with remember-me token: '"
+ SecurityContextHolder.getContext().getAuthentication()
+ "'");
}
// Fire event
if (this.eventPublisher != null) {
eventPublisher
.publishEvent(new InteractiveAuthenticationSuccessEvent(
SecurityContextHolder.getContext()
.getAuthentication(), this.getClass()));
}
if (successHandler != null) {
// 认证成功后的处理
successHandler.onAuthenticationSuccess(request, response,
rememberMeAuth);
return;
}
}
catch (AuthenticationException authenticationException) {
if (logger.isDebugEnabled()) {
logger.debug(
"SecurityContextHolder not populated with remember-me token, as "
+ "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
+ rememberMeAuth
+ "'; invalidating remember-me token",
authenticationException);
}
rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response,
authenticationException);
}
}
chain.doFilter(request, response);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(request, response);
}
}
作用:认证rememberMe的对象
- doFilter前:如果非登录状态,并且可以从请求中rememberMe相关中拿到认证对象。那么就使用认证管理器进行认证,之后成功后进行成功认证处理。否则,则跳过该过滤器
- doFilter后:无
其他:关于remember的一些配置。可以使用http.rememberMe()方法获取配置对象,进行一些配置。
例如:
- tokenRepository()配置rememberMe对象获取服务
- rememberMeParameter()登录时表示remememberMe的参数
- rememberMeCookieName()配置rememberMe的cookie名称
- 可以参考RememberMeConfigurer类的源码
13. AnonymousAuthenticationFilter
名称:匿名认证过滤器
源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
chain.doFilter(req, res);
}
作用:检测SecurityContextHolder中是否有认证对象,如果没有,就创建一个匿名访问对象。
- doFilter前:检测SecurityContextHolder中是否有认证对象,如果没有,就创建一个匿名访问对象
- doFilter后:无
14. SessionManagementFilter
名称:会话管理过滤器
源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 过滤器只执行一次
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
// 如果SecurityContext持久库中包含本次请求的context。不包含则进行一系列处理
if (!securityContextRepository.containsContext(request)) {
// 本次请求的认证对象
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
// 认证对象不为null且不是匿名对象
if (authentication != null && !trustResolver.isAnonymous(authentication)) {
// The user has been authenticated during the current request, so call the
// session strategy
try {
// session处理
sessionAuthenticationStrategy.onAuthentication(authentication,
request, response);
}
catch (SessionAuthenticationException e) {
// The session strategy can reject the authentication
logger.debug(
"SessionAuthenticationStrategy rejected the authentication object",
e);
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
// Eagerly save the security context to make it available for any possible
// re-entrant
// requests which may occur before the current request completes.
// SEC-1396.
// 重新再保存至SecurityContext持久库中
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
}
else {
// 如果认证对象不存在,则检查session是否过期
if (request.getRequestedSessionId() != null
&& !request.isRequestedSessionIdValid()) {
if (logger.isDebugEnabled()) {
logger.debug("Requested session ID "
+ request.getRequestedSessionId() + " is invalid.");
}
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
}
}
}
chain.doFilter(request, response);
}
作用:执行与会话相关的一些设置
-
doFilter前:如果持久库中存在本次请求的securityContext,跳过该过滤器。如果不存在,则需要做session处理。
- 匿名对象,检测session是否过期
- 其他认证对象,通过SessionAuthenticationStrategy的实际对象做出相应的处理,然后将处理后的认证对象重新保存在SecurityContext持久库中
- doFilter后:无
15. ExceptionTranslationFilter
名称:异常事务过滤器
源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
作用:对过滤器中抛出的异常进行处理
- doFilter前:无
-
doFilter后:
- 如果是AuthenticationException异常,需要AuthenticationEntryPoint对象处理
- 如果是AccessDeniedException异常,需要AccessDeniedHandler对象处理
- 如果是其他异常,继续向上抛
其他:可以通过http. exceptionHandling().authenticationEntryPoint().accessDeniedHandler()
来进行添加。
16. FilterSecurityInterceptor
名称:安全拦截器
源码:
// FilterSecurityInterceptor过滤方法
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 包装request, response, chain
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
// request不为null AND 并且不是第一次过滤 AND observeOncePerRequest(每一个请求观察一次,默认为true)
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// 控制请求被检查一次
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 过滤前的处理
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
// 再次保存SecurityContext至SecurityContextHodler中
super.finallyInvocation(token);
}
// 后置处理,使用后置管理器(AfterInvocationManager)处理
super.afterInvocation(token, null);
}
}
// AbstractSecurityInterceptor的beforeInvocation方法
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
// 获取验证方式的spel表达式集合
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
// 如果认证对象仍然需要认证,则使用认证管理器进行认证
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 对权限进行验证,即对access()、authenticated()、hasRole()、hasAuthority()进行验证。
// 验证处理方式是使用验证管理器进行验证。
// 1.AffirmativeBased 非否决通过
// 2.ConsensusBased 少数服从多数
// 3.UnanimousBased 一致通过
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
// 如果认证对象已经得到认证 并且 !alwaysReauthenticate(不是一定需要认证)
if (authentication.isAuthenticated() && !alwaysReauthenticate) {
if (logger.isDebugEnabled()) {
logger.debug("Previously Authenticated: " + authentication);
}
return authentication;
}
// 认证管理器认证
authentication = authenticationManager.authenticate(authentication);
// We don't authenticated.setAuthentication(true), because each provider should do
// that
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
作用:拦截之前过滤器过滤后的认证对象,然后对其进行最终的身份认证或者是权限验证。
- doFilter前:获取匹配该请求的权限验证spel表达式,如果任然需要认证,可以使用认证管理器再次进行认证。然后执行权限验证,验证表达式是否通过。(通过有三种模式:一票通过,少数服从多数,全票通过)
- doFilter后:使用后置管理器进行处理。
17. MethodSecurityInterceptor
名称:方法拦截器
源码:
public Object invoke(MethodInvocation mi) throws Throwable {
InterceptorStatusToken token = super.beforeInvocation(mi);
Object result;
try {
result = mi.proceed();
}
finally {
super.finallyInvocation(token);
}
return super.afterInvocation(token, result);
}
作用:如果配置文件中开启@EnableGlobalMethodSecurity,并且开了prePostEnabled或securedEnabled或jsr250Enabled等就会使用到该拦截器,通过切面的方式,在业务方法前后进行认证或验证。
四、 认证管理器和验证管理器
1. 认证管理器—AuthenticationManager
源码:
public interface AuthenticationManager {
/**
* 主要是进行身份认证,认证成功返回认证后的对象,认证不成功则抛出相应的异常AuthenticationException
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
实现类:ProviderManager
源码:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
// 获取所有的认证处理
for (AuthenticationProvider provider : getProviders()) {
// 判断当前的认证处理是否支持对当前的认证对象的认证
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
// 执行认证处理的认证方法
result = provider.authenticate(authentication);
// 认证后的结果不为null的话,则认为返回是通过认证的认证对象
if (result != null) {
// 如果认证后的对象没有细节内容,则将原有的认证对象的细节内容填充至认证后的对象中
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
// 经过当前提供的一系列认证处理后仍然返回null 并且 该认证管理器具有父级处理认证的方式(需要新建实例时传入)
if (result == null && parent != null) {
// Allow the parent to try.
try {
// 那么就可以使用父级认证管理器来对当前的认证对象进行认证
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
作用:通过配置多套认证处理(AuthenticationProvider),以应对不同情况下的认证情形,是对认证的扩展。
认证处理类:AuthenticationProvider
源码:
public interface AuthenticationProvider {
/**
* 对认证对象进行认证
* 返回认证后的对象表示已经被认证
* 返回null表示不做处理
* 抛出异常,表示认证失败
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
/**
* 支持的认证对象实体类
* 返回true表示支持
* 返回false表示不支持,将不进行authenticate认证
*/
boolean supports(Class<?> authentication);
}
作用:认证处理,对每一个支持的认证对象的身份进行认证。
关于AuthenticationManager的配置
- 使用http. authenticationProvider()方法直接添加认证处理。
public void configure(HttpSecurity http) throws Exception {
http
.authenticationProvider(new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证000");
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
})
.authorizeRequests()
.antMatchers("/test").access("hasAnyRole('ADMIN', 'USER')")
.anyRequest().authenticated()
.and()
// .formLogin().and()
.rememberMe().and() // rememberMe过滤器 -- RememberMeAuthenticationFilter
.httpBasic();
}
说明:该AuthenticationProvider是直接插入到默认的认证管理器中。与下列的几个配置方法不冲突
- 继承WebSecurityConfigurerAdapter类时覆写authenticationManager()方法
class CustomizeWebSecurityConfigurer extends WebSecurityConfigurerAdapter{
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/test").access("hasAnyRole('ADMIN', 'USER')")
.anyRequest().authenticated()
.and()
// .formLogin().and()
.rememberMe().and() // rememberMe过滤器 -- RememberMeAuthenticationFilter
.httpBasic();
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
AuthenticationProvider provider = new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证111");
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
};
ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
return providerManager;
}
}
说明:该AuthenticationManager是被注册到FilterSecurityInterceptor的默认AuthenticationManager的parent认证管理器中
- 直接向Spring中注册AuthenticationManager的Bean
@Bean
public AuthenticationManager authenticationManager() {
AuthenticationProvider provider = new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证333");
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
};
ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
return providerManager;
}
说明:该AuthenticationManager是被注册到FilterSecurityInterceptor的默认AuthenticationManager的parent认证管理器中,也会注册到MethodSecurityInterceptor认证管理器中。
- 可以向Spring注册AuthenticationProvider
@Bean
public AuthenticationProvider authenticationProvider() {
return new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证222");
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
};
}
说明:注入AuthenticationProvider只能提供一个对象的注册,并且该AuthenticationProvider是注册到FilterSecurityInterceptor的默认AuthenticationManager的parent认证管理器中,也会注册到MethodSecurityInterceptor认证管理器中。
FilterSecurityInterceptor优先等级如下:
- http. authenticationProvider最优先使用。
-
继承覆写 > AuthenticationProvider > AuthenticationManager
源码:
@Bean
public AuthenticationProvider authenticationProvider() {
return new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证222");
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
};
}
/**
* 传入自定义的认证管理器
* @return
*/
@Bean
public AuthenticationManager authenticationManager() {
AuthenticationProvider provider = new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证333");
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
};
ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
return providerManager;
}
@Order(1)
class CustomizeWebSecurityConfigurer extends WebSecurityConfigurerAdapter{
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authenticationProvider(new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证000");
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
})
.authorizeRequests()
.antMatchers("/test").access("hasAnyRole('ADMIN', 'USER')")
.anyRequest().authenticated()
.and()
// .formLogin().and()
.rememberMe().and() // rememberMe过滤器 -- RememberMeAuthenticationFilter
.httpBasic();
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
AuthenticationProvider provider = new AuthenticationProvider(){
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("认证111");
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
};
ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
return providerManager;
}
}
说明:当前情况下先执行”认证000”,由于返回null,所以执行”认证111”,如果去掉覆写,执行”认证222”,如果去掉AuthenticationProvider注册,执行”认证333”
2. 验证管理器—AccessDecisionManager
关于验证管理器的配置:
- 使用http. authorizeRequests().accessDecisionManager()方法配置
class OtherWebSecurityConfigurer extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().accessDecisionManager(new AffirmativeBased(Arrays.asList(new WebExpressionVoter())))
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
}
五、 全局方法的注解权限验证和过滤
1. @EnableGlobalMethodSecurity注解
开启全局的验证方法,下面解释一下常用的注解
2. prePostEnabled
说明:设置true时,将可以使用@ PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter注解
-
PreAuthorize:前置权限验证。
-
PostAuthorize:后置权限验证。
-
PreFilter:前置过滤。
-
PostFilter:后置过滤
3. securedEnabled
说明:设置true时,允许使用SpringSecurity内置的方法验证。可以使用@Secured
参考:开启Secured
4. jsr250Enabled
说明:设置true时,允许使用JSR250注解进行验证。
六、 总结
1. 通常情况下
在FilterSecurityInterceptor过滤器之前,SecurityContextHolder中存在认证对象,即算通过认证。
2. 权限验证
authenticated()
验证通过的是非匿名对象,并不是使用Authentication
认证对象的isAuthenticated()
方法进行验证。
3. 注解@EnableGlobalMethodSecurity
使用该注解后,会使用代理模式。通过MethodSecurityInterceptor
的invoke()
方法来对业务方法进行验证等操作
4. 过滤器参考图
SpringSecurity过滤流程5. 其他
SpringSecurity参考文档:参考文档
SpringSecurity中文参考文档:中文参考文档
核心源码及注释下载:网盘链接 提取码: bwpv
关于Spring源码调试及注释:文章链接
网友评论