我们之前讲解的各种过滤器,现在我们主要讲解的是:AnonymousAuthenticationFilter(匿名认证过滤器),他处于我们上面绿色过滤器最后,不管前面有各种过滤器,后面都会到AnonymousAuthenticationFilter(匿名认证过滤器):看代码之后,主要看其里面的逻辑是判断当前SecurityContextHolder里面是否有:Authentication;也就是说:前面过滤器是否成功进行了成功地身份认证。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { if (SecurityContextHolder.getContext().getAuthentication() == null) { SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req)); if (this.logger.isDebugEnabled()) { this.logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } } else if (this.logger.isDebugEnabled()) { this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } chain.doFilter(req, res); }
我们之前说过,认证成功之后,我们返回的是一个:Authentication.我们查看:AnonymousAuthenticationFilter的创建:Authentication 我们知道:里面的用户信息:principal是:anonymousUser,如果前面的过滤器没有认证成功,那么此时 SecurityContextHolder.getContext()的认证信息:Authentication就是AnonymousAuthenticationFilter自己创建的。
protected Authentication createAuthentication(HttpServletRequest request) { AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(this.key, this.principal, this.authorities); auth.setDetails(this.authenticationDetailsSource.buildDetails(request)); return auth; } public AnonymousAuthenticationFilter(String key) { this(key, "anonymousUser", AuthorityUtils.createAuthorityList(new String[]{"ROLE_ANONYMOUS"})); } public AnonymousAuthenticationFilter(String key, Object principal, List<GrantedAuthority> authorities) { this.authenticationDetailsSource = new WebAuthenticationDetailsSource(); Assert.hasLength(key, "key cannot be null or empty"); Assert.notNull(principal, "Anonymous authentication principal must be set"); Assert.notNull(authorities, "Anonymous authorities must be set"); this.key = key; this.principal = principal; this.authorities = authorities; }
我们现在主要关注:FilterSecurityInterceptor和ExceptionTranslationFilter,首先我们观察下spring security中和授权相关的类接口以及调用关系。
其中核心的类和接口就只有3个:
FilterSecurityInterceptor、AccessDecisionManager、AccessDecisionVoter。
我们看两个流程:一个是:用户没登录时候,被拒绝访问流程,另一个是用户登录以后,访问通过流程。
我们访问: http://127.0.0.1 :8088/
断点:FilterSecurityInterceptor
//封装请求、响应、过滤器链到FilterInvocation FilterInvocation fi = new FilterInvocation(request, response, chain);
//判断:此请求是否经过FilterSecurityInterceptor处理,经过的话直接返回到下一个Filter中。 if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
//处理授权的核心逻辑,就是我们FilterSecurityInterceptor判断是否可以访问RESTAPI,授权主要也在这里,如果授权不通过,则会抛出异常。 InterceptorStatusToken token = super.beforeInvocation(fi);
protected InterceptorStatusToken beforeInvocation(Object object)方法中
//读取我们的SecurityConfig将其封装成一个对象。 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
DefaultFilterInvocationSecurityMetadataSource:
我们可以看到每一个url,所具备的权限。
这个map就是根据我们WebSecurityConfig里面protected void configure(HttpSecurity http) throws Exception()生成的。
//判断SecurityContextHolder.getContext()里面是否有授权信息,因为授权信息我们就算没有认证成功,也会在AnonymousAuthenticationFilter生成,如果确实为空,则有异常了。 if (SecurityContextHolder.getContext().getAuthentication() == null) { this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); }
通过decide方法决定是不是让其通过.
未有授权通过就会抛出异常:异常先从:最先调用的AffirmativeBased抛出异常,然后抛出异常到父类,再次到接口,最后抛出异常到: FilterSecurityInterceptor;然后往前抛到FilterSecurityInterceptor前面的异常转换过滤器(ExceptionTranslationFilter);
然后进入: this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
把我发出去去认证。
把我发出去去认证,其实就是跳到了我们之前在:WebSecurityConfig配置里面loginPage所配置的。
之前是用户未登录时候的授权流程,我们现在开始登陆。
登录完成之后,我们再次访问:
http://127.0.0.1:8088/user/1
这个hasRole('ROLE_ADMIN')是怎么来的呢?
我们在配置文件总配置了:ADMIN
然后进入源码跟踪:ExpressionUrlAuthorizationConfigurer的:
public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry hasRole(String role) { return this.access(ExpressionUrlAuthorizationConfigurer.hasRole(role)); }
然后进入:ExpressionUrlAuthorizationConfigurer
private static String hasRole(String role) { Assert.notNull(role, "role cannot be null"); if (role.startsWith("ROLE_")) { throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'"); } else { return "hasRole('ROLE_" + role + "')"; } }
说明返回的字符串会拼接前缀返回:"hasRole('ROLE_" + role + "')"。
所以我们授权时候也需要给:ROLE_ADMIN
上面我们拿到了系统配置和Authentication信息,现在我们进入投票Voter,结果如下:
最后拿到了我们的接口返回信息: