SpringSecurity 原理初探(四)

1、 AuthenticationManagerBuilder

用于主要用于构建AuthenticationManager(ProviderManager)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
ProviderManager providerManager = new ProviderManager(this.authenticationProviders,
this.parentAuthenticationManager);
if (this.eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
}
if (this.eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(this.eventPublisher);
}
providerManager = postProcess(providerManager);
return providerManager;
}

SecurityConfig

在SecurityConfig配置类中如果configure(AuthenticationManagerBuilder auth)方法则以此方法中的配置优先

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//通过这两种方式都能给内存中存放一个用户信息,但生效的只有configure(AuthenticationManagerBuilder auth)方法中的用户信息
//此配置不生效
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
InMemoryUserDetailsManager in = new InMemoryUserDetailsManager();
in.createUser(User.withUsername("aaaa").password("{noop}123456").authorities(new ArrayList<>()).build());
CachingUserDetailsService ca = new CachingUserDetailsService(in);
return ca;
}

//如果重写了此配置,则以此配置配置的规则优先
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin")
.password("{noop}123456")
.authorities(new ArrayList<>());

}

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//获得ProviderManager对象
//如果configure方法没有被重写就采用方式一,重写了就采用方式二
protected AuthenticationManager authenticationManager() throws Exception {
if (!this.authenticationManagerInitialized) {
//如果这个方法被子类重写了则调用子类重写的方法
configure(this.localConfigureAuthenticationBldr);
//如果没有重写这个configure()方法,则会将disableLocalConfigureAuthenticationBldr设置为true
if (this.disableLocalConfigureAuthenticationBldr) {
//获得ProviderManager方式一,这种方式会使用容器中的UserDetailsService
this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
}
else {
//获得ProviderManager方式二
this.authenticationManager = this.localConfigureAuthenticationBldr.build();
}
this.authenticationManagerInitialized = true;
}
return this.authenticationManager;
}

2、Filter

image-20231031170330536

登录认证:会访问/login post请求。不会走完所有过滤器,它按顺序走前面的过滤器一直走完 UsernamePasswordAuthenticationFilter 过滤器就直接返回,不再继续往下走

权限校验 请求会走完最后一个过滤器,判断有没有权限,最终是否能否访问后台接口资源

/login get 请求经过DefaultLoginPageGeneratingFilter后过滤器直接返回,给前端响应一个登录页面

3、权限校验

3.1、FilterSecurityInterceptor

源码分析

过滤器拦截器是过滤器链中最后一个Filter,负责权限校验

1
2
3
4
5
6
7
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.invoke(new FilterInvocation(request, response, chain));
}
....
}

进入FilterSecurityInterceptor中的invoke方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
//检查是否已经应用了安全性过滤器
if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} else {
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {

//如果尚未应用安全性过滤器则于标记已经应用了安全性过滤器。
filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);

}
//开始进行安全性检查
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);

try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}

super.afterInvocation(token, (Object)null);
}
}

进入父类beforeInvocation方法

AbstractSecurityInterceptor.class

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
protected InterceptorStatusToken beforeInvocation(Object object) {
....
//获取当前request请求所能匹配中的权限Spel表达式
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
....
//得到认证成功后返回的用户信息
Authentication authenticated = authenticateIfRequired();


//授权
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();
SecurityContext newCtx = SecurityContextHolder.createEmptyContext();
newCtx.setAuthentication(runAs);
SecurityContextHolder.setContext(newCtx);


// 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, attributes, authenticated);

1
2
3
4
5
6
7
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
Authentication authenticated) {
...
this.accessDecisionManager.decide(authenticated, object, attributes);
..

}

decide(authenticated, object, attributes); 根据投票授予访问权限

AffirmativeBased.class

投票结果为1表示可以访问直接返回,投票结果为-1表示拒绝访问,向上抛拒绝访问异常,投票器为WebExpressionVoter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
//voter:WebExpressionVoter
//进行投票
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();
}

进入vote

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int vote(Authentication authentication, FilterInvocation filterInvocation, Collection<ConfigAttribute> attributes) {
Assert.notNull(authentication, "authentication must not be null");
Assert.notNull(filterInvocation, "filterInvocation must not be null");
Assert.notNull(attributes, "attributes must not be null");
WebExpressionConfigAttribute webExpressionConfigAttribute = this.findConfigAttribute(attributes);
if (webExpressionConfigAttribute == null) {
this.logger.trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");
return 0;
} else {
EvaluationContext ctx = webExpressionConfigAttribute.postProcess(this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);
boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);
if (granted) {
return 1;
} else {
this.logger.trace("Voted to deny authorization");
return -1;
}
}
}

最后调用evaluateAsBoolean

1
2
3
4
5
6
7
8
9
public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
try {
return expr.getValue(ctx, Boolean.class);
}
catch (EvaluationException ex) {
throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'",
ex);
}
}

image-20231031180853086

3.2、授权实现

FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。判断当前用户是否拥有访问当前资源所需的权限。

在SecurityConfig中添加EnableGlobalMethodSecurity注解

1
2
3
4
5
6
7
8
9
10
11
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)

/*
确定是否应启用Spring Security的发布前注释。默认值为false。
*/
boolean prePostEnabled() default false;

/*
确定是否应启用Spring Security的Secured注释。
*/
boolean securedEnabled() default false;

在controller中添加 ,@PreAuthorize注解,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping("/hello")
@PreAuthorize("hasAuthority('test')") //表示拥有test权限即可访问
public String hello1(){
return "hello";
}


public @interface PreAuthorize {

/**
调用受保护方法之前要评估的Spring EL表达式
* @return the Spring-EL expression to be evaluated before invoking the protected
* method
*/
String value();
}

SecurityConfig

1
2
3
4
5
6
7
8
9
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin")
.password("{noop}123456")
.authorities("test");//指定用户信息的访问权限

}

如果添加了@PreAuthoriz注解 最后会经过这个投票器进行投票处理

PreInvocationAuthorizationAdviceVoter.class

1
2
3
4
5
6
7
8
9
10
11
@Override
public int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) {
// Find prefilter and preauth (or combined) attributes
// if both null, abstain else call advice with them
PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
if (preAttr == null) {
// No expression based metadata, so abstain
return ACCESS_ABSTAIN;
}
return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED;
}

3.3、权限表达式

权限表达式(ExpressionUrlAuthorizationConfigurer) 说明 Spel表达式 Spel表达式实际执行方法(SecurityExpressionOperations)
permitAll() 表示允许所有,永远返回true permitAll permitAll()
denyAll() 表示拒绝所有,永远返回false denyAll denyAll()
anonymous() 当前用户是anonymous时返回true anonymous isAnonymous()
rememberMe() 当前用户是rememberMe用户时返回true rememberMe isRememberMe()
authenticated() 当前用户不是anonymous时返回true authenticated isAuthenticated()
fullyAuthenticated() 当前用户既不是anonymous也不是rememberMe用户时返回true fullyAuthenticated isFullyAuthenticated()
hasRole(“BUYER”) 用户拥有指定权限时返回true hasRole(‘ROLE_BUYER’) hasRole(String role)
hasAnyRole(“BUYER”,“SELLER”) 用于拥有任意一个角色权限时返回true hasAnyRole (‘ROLE_BUYER’,‘ROLE_BUYER’) hasAnyRole(String… roles)
hasAuthority(“BUYER”) 同hasRole hasAuthority(‘ROLE_BUYER’) hasAuthority(String role)
hasAnyAuthority(“BUYER”,“SELLER”) 同hasAnyRole hasAnyAuthority (‘ROLE_BUYER’,‘ROLE_BUYER’) hasAnyAuthority(String… authorities)
hasIpAddress(‘192.168.1.0/24’) 请求发送的Ip匹配时返回true hasIpAddress(‘192.168.1.0/24’) hasIpAddress(String ipAddress),该方法在WebSecurityExpressionRoot类中
access(“@rbacService.hasPermission(request, authentication)”) 可以自定义Spel表达式 @rbacService.hasPermission (request, authentication) hasPermission(request, authentication) ,该方法在自定义的RbacServiceImpl类中