上篇文章主要讲了用户的认证,就是UsernamePasswordAuthenticationFilter 这个Filter,这里顺便提一下上篇博文没有提到的AnonymousAuthenticationFilter ,这个Filter其实和UsernamePasswordAuthenticationFilter 基本上一样,只不过UsernamePasswordAuthenticationFilter 需要到数据库认证用户信息,而AnonymousAuthenticationFilter 里面默认创建了一个匿名用户,用户的角色是“ROLE_ANONYMOUS”,不需要再去数据库认证了,这个过滤器默认是不开启的,如果有需要场景,可以开启这个Filter。
下面进入正题,主要讲一下用户的权限FilterSecurityInterceptor 这个Filter。
直接上源码
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
this.invoke(fi);
}
啥也没干,直接调的invoke(fi)方法,继续看这个方法
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, (Object)null);
}
}
super.beforeInvocation(fi),这个是父类的方法,这个方法处理权限,看看他们都干了什么
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
boolean debug = this.logger.isDebugEnabled();
if (!this.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: " + this.getSecureObjectClass());
} else {
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes != null && !attributes.isEmpty()) {
if (debug) {
this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
Authentication authenticated = this.authenticateIfRequired();
try {
//这个方法处理权限
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");
}
if (this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs == null) {
if (debug) {
this.logger.debug("RunAsManager did not change Authentication object");
}
return 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);
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
} else if (this.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'");
} else {
if (debug) {
this.logger.debug("Public object - authentication not attempted");
}
this.publishEvent(new PublicInvocationEvent(object));
return null;
}
}
}
关键方法this.accessDecisionManager.decide(authenticated, object, attributes),这个方法是处理权限的,我们追源码直线先墨迹几句,AccessDecisionManager,这个是决定角色是否有权限访问资源的类,他是个接口,实现它的是个抽象类AbstractAccessDecisionManager,然后通过3个类来实现,
AffirmativeBased 默认的实现,一票通过
ConsensusBased 一票否决
UnanimousBased 半数以上通过
默认的是AffirmativeBased ,直接上AffirmativeBased 的源码
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
Iterator var5 = this.getDecisionVoters().iterator();
while(var5.hasNext()) {
AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
//权限校验
int result = voter.vote(authentication, object, configAttributes);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Voter: " + voter + ", returned: " + result);
}
switch(result) {
case -1:
++deny;
break;
case 1:
return;
}
}
if (deny > 0) {
throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
} else {
this.checkAllowIfAllAbstainDecisions();
}
}
关键方法int result = voter.vote(authentication, object, configAttributes); 通过返回值判断有没有权限,AccessDecisionVoter 同样是一个接口,vote方法在实现类里,默认的是WebExpressionVoter类
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
WebExpressionConfigAttribute weca = this.findConfigAttribute(attributes);
if (weca == null) {
return 0;
} else {
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, fi);
ctx = weca.postProcess(ctx, fi);
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? 1 : -1;
}
}
到这里以后实在是看不懂了,不过索性我们知道判断权限的方法是哪个,我们从判断权限的方法入手,还记得this.accessDecisionManager.decide(authenticated, object, attributes)这个方法吧?是在FilterSecurityInterceptor 这个Filter的父类的beforeInvocation()方法里,那么我们重新实现以下这个方法,写一个实现AccessDecisionManager接口的实现类
package com.springcloud.security;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Iterator;
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
// decide 方法是判定是否拥有权限的决策方法,
//authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
//object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,
// 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {
return;
}
ConfigAttribute configAttribute;
String needRoleId;
for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
configAttribute = iter.next();
needRoleId = configAttribute.getAttribute();//权限
for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
String[] roleArr = ga.getAuthority().split(",");
for(String roleId:roleArr) {
if (needRoleId.trim().equals(roleId)) {
return;
}
}
}
}
throw new AccessDeniedException("no right");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
我们说说这3个参数是什么,第一个authenticated这个是用户信息里的角色或者资源(这里存的是角色,因为资源太多了),从SecurityContextHolder里得到的,也就是登录的时候放进去的(如果是jwt格式的token登录的话,是每次验证token的时候放到SecurityContextHolder里的),第二个object这个是本次访问的信息(request,response),第三个attributes是本次访问的资源也就是rui地址所对应的角色信息。整个逻辑就是通过第一个参数获取觉得id,然后和第三个参数(访问资源所需要的角色)去比较,有就放行,没有抛异常,第三个参数的获取方法也需要自己实现一下,下面是源码
@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
@Autowired
ISysRoleResourceService roleResourceService;
@Autowired
ISysResourceService resourceService;
//此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
String uri = request.getRequestURI();
Map<String, Object> condition = new HashMap<String, Object>();
condition.put("url",uri);
List<SysResource> resourceList = resourceService.selectByMap(condition);//查出资源ID
if(resourceList != null && resourceList.size() > 0 ) {
Map<String, Object> condition1 = new HashMap<String, Object>();
condition1.put("resource_id",resourceList.get(0).getId());
List<SysRoleResource> roleResourceList = roleResourceService.selectByMap(condition1);
Collection<ConfigAttribute> array = new ArrayList<>();
ConfigAttribute configAttribute;
for (SysRoleResource roleResource : roleResourceList) {
configAttribute = new SecurityConfig(roleResource.getRoleId().toString());
array.add(configAttribute);
}
return array;
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
最后还需要重新写一下FilterSecurityInterceptor 这个Filter,直接实现它的父接口就行,新建一个MyFilterSecurityInterceptor,代码如下
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
这个和原来的FilterSecurityInterceptor 基本一样,只是设置了我们自己的AccessDecisionManager和FilterInvocationSecurityMetadataSource
最后别忘记在配置文件上添加一条Filter
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
这里最后还留了一个问题,就是没法把原来的Filter删除掉,就是FilterSecurityInterceptor 还生效,哪位大神能帮忙告诉一下怎么去掉原来的Filter,感激不尽