如何做网站结构优化/百度开户代理
授权是在用户认证通过后,对访问资源的权限进行检查的过程。
Spring Security使用标准 Filter建立了对 web请求的拦截,最终实现对资源的授权访问。
- Spring Security过滤器链加载执行流程源码分析请查看:https://blog.csdn.net/qq_42402854/article/details/122205790
关于授权,这里查看几个重要的 Filter 的处理逻辑。
一、SecurityContextPersistenceFilter
针对 ServletRequest进行了一次包装,使得 request具有更加丰富的 API。
查看 doFilter方法:
- 通过repo.loadContext从请求中获取session,然后将 session信息保存在context中,方便后面 filter直接获取当前的用户信息。
- 通过 SecurityContextHolder.setContext(contextBeforeChainExecution); 设置 context信息到contextHolder中(将context信息放在了ThreadLocal中,线程安全的)。
1)repo.loadContext方法
2)setContext方法
二、AnonymousAuthenticationFilter
Spring Security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
AnonymousAuthenticationFilter过滤器是在 UsernamePasswordAuthenticationFilter等过滤器之后,如果它前面的过滤器都没有认证成功,即认证信息为空,AnonymousAuthenticationFilter会创建一个匿名用户(一个 Authenticaiton 的匿名实现类 AnonymousAuthenticationToken
)存入到当前 SecurityContextHolder中。
查看 doFilter方法:
- 判断 SecurityContextHolder中Authentication为否为空;
- 如果空则为当前的 SecurityContextHolder中添加一个匿名的 AnonymousAuthenticationToken(
用户名为 anonymousUser
的 AnonymousAuthenticationToken)
三、ExceptionTranslationFilter
ExceptionTranslationFilter异常处理过滤器,用来处理在系统认证授权过程中抛出的异常(也就是下一个 FilterSecurityInterceptor过滤器)。
ExceptionTranslationFilter过滤器主要是拦截处理 AuthenticationException
和 AccessDeniedException
异常并添加到HTTP响应中。其他异常它会捕获 Filter处理的异常,抛给下一个 Filter去处理。
查看 doFilter方法:
四、FilterSecurityInterceptor
FilterSecurityInterceptor过滤器
是认证授权过滤器链中最后一个过滤器,获取所配置资源访问的授权信息,根据 SecurityContextHolder中存储的用户信息来决定其是否有权限。
该过滤器之后就交由 Spring MVC,访问到我们的controller方法。
查看 doFilter方法:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(request, response, chain);this.invoke(fi);}/*** doFilter实际执行的方法* @param filterInvocation 封装了request response 过滤器链的对象*/public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {// 1. 如果已经执行过该过滤器,直接放行if (isApplied(filterInvocation) && this.observeOncePerRequest) {// filter already applied to this request and user wants us to observe// once-per-request handling, so don't re-do security checkingfilterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());return;}// first time this request being called, so perform security checking// 2. 第一次调用这个请求,所以执行安全检查if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {// 3. 在request中添加__spring_security_filterSecurityInterceptor_filterApplied = true,表示执行了该过滤器filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}// 4. 前置访问控制处理InterceptorStatusToken token = super.beforeInvocation(filterInvocation);try {// 5. 放行filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());} finally {super.finallyInvocation(token);}// 6. 后置处理super.afterInvocation(token, null);}
比较重要的方法
- beforeInvocation
- finallyInvocation
- afterInvocation
1、查看 beforeInvocation方法
进入父类 AbstractSecurityInterceptor
的 beforeInvocation方法进行处理,最终返回一个 InterceptorStatusToken对象
,它就是 Spring Security处理鉴权的入口。
protected InterceptorStatusToken beforeInvocation(Object object) {// 1. 判断object是不是FilterInvocationif (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {throw new IllegalArgumentException(...);} else {// 2. 获取配置的访问控制规则 any request => authenticated ,没有配置,return nullCollection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);...// 3. 获取Authentication对象Authentication authenticated = this.authenticateIfRequired();try {// 4. 进行授权判断(重点)this.accessDecisionManager.decide(authenticated, object, attributes);} catch (AccessDeniedException var7) {this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));throw var7;}if (debug) {this.logger.debug("Authorization successful");}// 5. 发布授权成功if (this.publishAuthorizationSuccess) {this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));}// 6. 对Authentication进行再处理,这里没有处理,直接返回nullAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);if (runAs == null) {if (debug) {this.logger.debug("RunAsManager did not change Authentication object");}// 7. 返回 InterceptorStatusTokenreturn new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);} else {if (debug) {this.logger.debug("Switching to RunAs Authentication: " + runAs);}SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);// 7. 返回 InterceptorStatusTokenreturn new InterceptorStatusToken(origCtx, true, attributes, object);}} else if (this.rejectPublicInvocations) {throw new IllegalArgumentException(...);} else {if (debug) {this.logger.debug("Public object - authentication not attempted");}this.publishEvent(new PublicInvocationEvent(object));return null;}}}
1.1 AffirmativeBased授权处理器
AccessDecisionManager 是如何授权的?
Spring Security默认使用 AffirmativeBased
实现 AccessDecisionManager 的 decide 方法来实现授权。它会调用授权管理器进行决策,当失败发生异常时,会爆出异常。
调用 AccessDecisionVoter 进行vote(投票)
- 只要有投通过(ACCESS_GRANTED)票,则直接判为通过。
- 如果没有投通过则 ++deny ,
- 最后判断if(deny>0 抛出AccessDeniedException(未授权)
1.2 WebExpressionVoter.vote()
这里简单查看 WebExpressionVoter.vote()方法:
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {assert authentication != null;assert fi != null;assert attributes != null;// 1. 获取http配置项WebExpressionConfigAttribute weca = this.findConfigAttribute(attributes);// 2. 没有配置规则,弃权if (weca == null) {return 0;} else {// 3. 对EL表达式进行处理EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, fi);ctx = weca.postProcess(ctx, fi);// 5. 符合条件,则投赞成票,否则反对票return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? 1 : -1;}}
投票完成,回到 beforeInvocation方法,最后返回 InterceptorStatusToken对象。
2、查看 finallyInvocation方法
授权成功处理:没有抛出异常,则认为授权通过,FilterSecurityInterceptor会进入 finallyInvocation方法。
这个方法主要是判断需不需要重新设置 SecurityContext内容,这里没有配置,直接跳过。
3、查看 afterInvocation方法
接下来进入 afterInvocation方法,再次调用了 finallyInvocation方法,然后查询是否还有决策后置处理器,如果有,再次进行决策。
最后的最后,才代表授权成功,就交由 Spring MVC,访问到我们的 controller方法了。
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {if (token == null) {return returnedObject;} else {this.finallyInvocation(token);if (this.afterInvocationManager != null) {try {returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject);} catch (AccessDeniedException var5) {AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), var5);this.publishEvent(event);throw var5;}}return returnedObject;}}
– 求知若饥,虚心若愚。