认证控制

springsecurity 的认证过程的处理是通过过滤器进行拦截处理的,在请求前判断是否有具体的权限来做一些控制;

基本流程是通过过滤器 拿到请求信息,交给访问决策管理器(AccessDecisionManager) 判断是否有权限,

而 访问决策管理器(AccessDecisionManager) 通过投票机制判断是否有权限,对应的实现类聚合了多个 AccessDecisionVoter (访问投票器),

对于一个请求 所有的投票器可以投出自己的一票 通过 ,拒绝 或弃权,根据投票的最终结果得出是否允许访问成功;

首先先认识几个重要的组件;

访问决策管理器 (AccessDecisionManager)

AccessDecisionManager 来处理关于访问的判断控制,在过滤器拦截后 会交给此管理器来判断是否有访问权限
核心方法是 decide 方法;

对应的实现类管理了多个 AccessDecisionVoter (访问投票器) 由多个投票器进行投票;

AbstractAccessDecisionManager

AbstractAccessDecisionManager 是公共的抽象实现,封装了公共逻辑

针对不同的投票如何处理有3个处理类;

  • AffirmativeBased 任何一个投票者 通过即为通过
  • UnanimousBased 必须所有的投票者全部通过
  • ConsensusBased 多数的投票者通过即为通过
1
2
3
4
5
6
7
8
9
10

void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException;


boolean supports(ConfigAttribute attribute);



boolean supports(Class<?> clazz);

访问投票器 AccessDecisionVoter

根据当前请求和权限信息决定是否通过,拒绝或弃权;

1
2
3
4
5
6
int ACCESS_GRANTED = 1;

int ACCESS_ABSTAIN = 0;

int ACCESS_DENIED = -1;

有多个实现类处理不同的权限投票逻辑;

  • RoleVoter 基于角色的投票
  • WebExpressionVoter 表达式的投票
  • RoleHierarchyVoter 继承 RoleVoter ,处理角色的层级结构返回给 RoleVoter
  • AuthenticatedVoter 其主要用来区分匿名用户、通过Remember-Me认证的用户和完全认证的用户。完全认证的用户是指由系统提供的登录入口进行成功登录认证的用户。
  • PreInvocationAuthorizationAdviceVoter 处理@pre 相关注解的投票认证

以RoleVoter 来说明,主要做了角色的code的比对处理返回对应的投票状态;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

@Override
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}

授权投票相关的类

流程

AbstractSecurityInterceptor

AbstractSecurityInterceptor 是授权拦截器的抽象类

有多个实现类

  • FilterSecurityInterceptor
  • MethodSecurityInterceptor
    方法级别的权限过滤器
  • AspectJMethodSecurityInterceptor
    MethodSecurityInterceptor 的子类 对 joinpoint 使用 MethodInvocationAdapter做了包装

FilterSecurityInterceptor

首先看此类的 invoke(FilterInvocation filterInvocation) 方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
//如果已经请求过来,并且观察只有一次请求就放过
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 checking
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// first time this request being called, so perform security checking

//为请求加上已经请求过的标记
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}



//调用执行逻辑
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}



beforeInvocation(Object object) 调用了 父类 (AbstractSecurityInterceptor) 的方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
protected InterceptorStatusToken beforeInvocation(Object object) {

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());
}


//获取 SecurityMetadataSource 用于获取配置的属性 根据此getAttributes 获取到当前请求的 方法 的 对应的配置属性信息
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);


//没有对应的配置信息的处理
if (CollectionUtils.isEmpty(attributes)) {
Assert.isTrue(!this.rejectPublicInvocations,
() -> "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 (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized public object %s", object));
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}




if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"), object, attributes);
}

//是否授权前执行认证逻辑
Authentication authenticated = authenticateIfRequired();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
}


//真正的获取授权的方法
attemptAuthorization(object, attributes, authenticated);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes));
}
if (this.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) {
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);

if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs));
}
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null");
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);

}



attemptAuthorization(Object object, Collectionattributes,
Authentication authenticated) 方法用于执行授权逻辑,最终调用 accessDecisionManager 的decide方法;
默认的实现类是 AffirmativeBased 既只要有任何一票同意既可通过请求;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
Authentication authenticated) {
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object,
attributes, this.accessDecisionManager));
}
else if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));
}
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));
throw ex;
}
}


AffirmativeBased#decide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {

//遍历投票当只要有一个拒接,将抛出异常
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(
this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained

//如果所有都弃权了,根据是否允许弃权决定是否抛出异常
checkAllowIfAllAbstainDecisions();
}


SecurityMetadataSource (权限元数据源)

是通过当前执行的对象来获取配置的类;

比如默认的实现类 DefaultFilterInvocationSecurityMetadataSource

通过当前对象的请求信息和配置的url 进行匹配返回;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

@Override
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
int count = 0;
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : this.requestMap.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
else {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Did not match request to %s - %s (%d/%d)", entry.getKey(),
entry.getValue(), ++count, this.requestMap.size()));
}
}
}
return null;
}


自定义投票器

可以通过自定义投票者来灵活的控制权限

参考: https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time

  1. 新建自己的投票器

这里只根据请求参数是否有 auth 是 true 的参数来决定如何投票,实际使用中自己定义真实逻辑;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public class MyAccessDecisionVoter implements AccessDecisionVoter<Object> {

@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}

@Override
public boolean supports(Class clazz) {
return true;
}

@Override
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
FilterInvocation filterInvocation = (FilterInvocation)object;
HttpServletRequest httpRequest = filterInvocation.getHttpRequest();
String auth = httpRequest.getParameter("auth");
if("true".equalsIgnoreCase(auth)){
return 1;
}
return 0;
}
}



  1. 定义 AccessDecisionManager 并将多个投票器设置进去包含自己定义的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@Bean
public AccessDecisionManager accessDecisionManager(){
List<AccessDecisionVoter<?>> accessDecisionVoters = new ArrayList<>();
AccessDecisionVoter myAccessDecisionVoter = new MyAccessDecisionVoter();
AccessDecisionVoter roleVoter = new RoleVoter();
AccessDecisionVoter authenticatedVoter = new AuthenticatedVoter();
AccessDecisionVoter webExpressionVoter = new WebExpressionVoter();
accessDecisionVoters.add(myAccessDecisionVoter);
accessDecisionVoters.add(roleVoter);
accessDecisionVoters.add(authenticatedVoter);
accessDecisionVoters.add(webExpressionVoter);
return new AffirmativeBased(accessDecisionVoters);
}


  1. 配置使用自己定义的 访问管理器 AccessDecisionManager

在 http.authorizeRequests() 处配置

1
2
3
4

.accessDecisionManager(accessDecisionManager())


至此 我们自定义的投票器都可以起作用了;