Spring-Security主要是一个由一堆Filter组成的过滤器链,每个Filter负责做自己的事情.
spring-security内置的过滤器表:
Filter 类 | 别名 |
ChannelProcessingFilter | CHANNEL_FILTER |
SecurityContextPersistenceFilter | SECURITY_CONTEXT_FILTER |
ConcurrentSessionFilter | CONCURRENT_SESSION_FILTER |
LogoutFilter | LOGOUT_FILTER |
X509AuthenticationFilter | X509_FILTER |
AstractPreAuthenticatedProcessingFilter | PRE_AUTH_FILTER |
CasAuthenticationFilter | CAS_FILTER |
UsernamePasswordAuthenticationFilter | FORM_LOGIN_FILTER |
BasicAuthenticationFilter | BASIC_AUTH_FILTER |
SecurityContextHolderAwareRequestFilter | SERVLET_API_SUPPORT_FILTER |
JaasApiIntegrationFilter | JAAS_API_SUPPORT_FILTER |
RememberMeAuthenticationFilter | REMEMBER_ME_FILTER |
AnonymousAuthenticationFilter | ANONYMOUS_FILTER |
SessionManagementFilter | SESSION_MANAGEMENT_FILTER |
ExceptionTranslationFilter | EXCEPTION_TRANSLATION_FILTER |
FilterSecurityInterceptor | FILTER_SECURITY_INTERCEPTOR |
SwitchUserFilter | SWITCH_USER_FILTER |
一.认证过程.
1.SecurityContextPersistenceFilter
SecurityContextPersistenceFilter主要是在SecurityContextRepository中保存更新一个securityContext,并将securityContext给以后的过滤器使用.
doFilter核心犯法中主要逻辑:
①SecurityContext contextBeforeChainExecution = repo.loadContext(holder);返回一个securityContext.
②SecurityContextHolder.setContext(contextBeforeChainExecution);将securiryContext放入SecurityContextHolder中.
③chain.doFilter(holder.getRequest(), holder.getResponse());放手,然后执行后面的过滤器.
④在finally中执行:repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());执行完后面的过滤器,把securityContext 放入SecurityContextRepository中.
2.UsernamePasswordAuthenticationFilter,继承AbstractAuthenticationProcessingFilter.
attemptAuthentication核心方法逻辑:通过父类的doFilter方法进入核心方法.
①根据表单输入的username和password生成token.
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
②使用AuthenticationManager接口的authenticate(authRequest)去认证.
return this.getAuthenticationManager().authenticate(authRequest);
跟踪代码知道,AuthenticationManager接口实现类去认证时,真正去执行认证逻辑的是AuthenticationProvider接口的实现类.默认是DaoAuthenticationProvider,实际项目中,根据需要自定义XxAuthenticationProvider类实现AuthenticationProvider接口.
3.DaoAuthenticationProvider
它是AuthenticationProvider的的一个实现类,非常重要,它主要完成了两个工作,
①retrieveUser方法,从数据库获取数据(借助UserDetailsService接口,加载用户信息),封装在UserDetails实现类中.UserDetails接口和Authentication接口相似.UserDetails用户真实信息,Authentication用户提交的信息. 如: Authentication的getCredentials()与UserDetails中的getPassword()需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
②additionalAuthenticationChecks方法.retrieveUser方法执行后,得到数据库用户信息,然后进行的一些匹配操作.
③返回Authentication,return createSuccessAuthentication(principalToReturn, authentication, user);
4.返回到UsernamePasswordAuthenticationFilter---->AbstractAuthenticationProcessingFilter.
unsuccessfulAuthentication(request, response, failed);//认证失败,后操作.
successfulAuthentication(request, response, chain, authResult);//认证成功,后操作.
二.授权
FilterSecurityInterceptor:该过滤器主要用来进行授权判断工作。
① FilterSecurityInterceptor的doFilter方法.
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//FilterInvocation(里面记录包含了请求的request,response,FilterChain.
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);// 授权核心方法.
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);//SecurityContextHolder.setContext(token.getSecurityContext());恢复token不变.
}
super.afterInvocation(token, null);//调用后的处理
}
}
②父类AbstractSecurityInterceptor的真正方法.
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
//判断object是否为过滤器支持的类型,这里把FilterInvocation看做是安全对象,通过它可以获得request,通过request可以获得请求的URI。
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());
}
//获取安全对象所对应的ConfigAttribute,ConfigAtrribute实际就是访问安全所应该有的权限集。
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
//判断安全对象是否拥有权限集,没有的话说明所访问的安全对象是一个公共对象,就是任何人都可以访问的。
if (attributes == null || attributes.isEmpty()) {
if (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'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
/*
判断SecurityCntext中是否存在Authentication,不存在则说明访问着根本没登录.
调用下面的credentialsNotFound()方法则会抛出一个AuthenticationException,该异常会被ExceptionTranslationFilter捕获,并做出处理。不过默认情况下Authentication不会为null,因为AnonymouseFilter会默认注册到过滤链中,如果用户没登录的话,会将其当做匿名用户(Anonymouse User)来对待。除非你自己将AnonymouseFilter从过滤链中去掉。
*/
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
/* Autentication存在,则说明用户已经被认证(但是不表示已登录,因为匿名用户也是相当于被认证的),判断用户是否需要再次被认证,如果你配置了每次访问必须重新验证,那么就会再次调用AuthenticationManager的authenticate方法进行验证。*/
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
*/
判断用户是否有访问被保护对象的权限。 默认的AccessDesicisonManager的实现类是AffirmativeBased. AffirmativeBased采取投票的形式判断用户是否有访问安全对象的权限票就是配置的Role。AffirmativeBased采用WebExpressionVoter进行投票.*/
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (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) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
看这段代码,请明确几点。
a). beforeInvocation(Object object)中的object为安全对象,类型为FilterInvocation。安全对象就是受spring security保护的对象。虽然按道理来说安全对象应该是我们访问的url,但是FilterInvocation中封装了request,那么url也可以获取到。
b). Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object) 每个安全对象都会有对应的访问权限集(Collection<ConfigAttribute>),而且在容器启动后所有安全对象的所有权限集就已经被获取到并被放在安全元数据中(SecurityMetadataSource中),通过安全元数据可以获取到各个安全对象的权限集。因为我们每个安全对象都是登录才可以访问的(anyRequest().authenticated()),这里我们只需要知道此时每个对象的权限集只有一个元素,并且是authenticated。如果一个对象没有权限集,说明它是一个公共对象,不受spring security保护。
c). 当我们没有登录时,我们会被当做匿名用户(Anonymouse)来看待。被当做匿名用户对待是AnonymouseAuthenticationFilter来拦截封装成一个Authentication对象,当用户被认证后就会被封装成一个Authentication对象。Authentication对象中封装了用户基本信息,该对象会在认证中做详细介绍。AnonymouseAuthenticationFilter也是默认被注册的。
d). 最中进行授权判断的是AccessDecisionManager的子类AffirmativeBased的decide方法。
三、总结
现在理一下重点。
1.springSecurityFilterChain中各个过滤器怎么创建的只需了解即可。
2.重点记忆UsernamePasswordAuthenticationFilter(认证),FilterSecurityInterceptor(授权)这两个过滤器的作用及源码分析。
3.重要记忆认证中Authentication,AuthenticationManager,ProviderManager,AuthenticationProvider,UserDetailsService,UserDetails这些类的作用及源码分析。
4.重点记忆授权中FilterInvoction,SecurityMetadataSource,AccessDecisionManager的作用。
5.将这些类理解的关键是建立起关联,结合实际应用,加深理解。
参考资料:
https://www.cnblogs.com/wutianqi/p/9186645.html
https://blog.csdn.net/abcwanglinyong/article/details/80981389