Spring Security认证与授权流程
2022-04-06 20:11:04 27 举报
Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。其认证流程开始于用户尝试访问受保护的资源,此时Spring Security会进行身份验证。如果用户提供的凭证有效(例如用户名和密码),则用户会被认证并被授予相应的权限。授权过程则是确定用户是否有足够的权限来访问他们试图访问的资源。这个过程通常涉及到检查用户的权限列表,以及资源的安全配置。如果用户有足够的权限,他们就可以访问资源;否则,他们将收到一个拒绝访问的错误消息。
作者其他创作
大纲/内容
UsernamePasswordAuthenticationFilter
AccessDecisionVoter
invoke(FilterInvocation fi)
accessDecisionManager() return: AccessDecisionManager
引用
static final String IS_AUTHENTICATED_FULLY: 完全登录状态,即非上面两种类型
访问授权
UserDetailsService(接口)
UsernamePasswordAuthenticationFilter() 构造方法
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) { return authentication.getAuthorities(); }
实现:.................// 获取用户名和密码 String username = obtainUsername(request); String password = obtainPassword(request);.................font color=\"#ff0000\
调用
AbstractAuthenticationProcessingFilter
AuthenticatedVoter
... for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; }try { font color=\"#ff0000\
ProviderManager 又将验证委托给了 AuthenticationProvider
final PreInvocationAuthorizationAdvice preAdvice
HttpSecurity
if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) { return true; } else { return false; }
GlobalMethodSecurityConfiguration
AccessDecisionManager 决定授权又是通过一个授权策略集合(AccessDecisionVoter )决定的,授权决定的原则是: 1. 遍历所有授权策略, 如果有其中一个返回 ACCESS_GRANTED,则同意授权。 2. 否则,等待遍历结束,统计 ACCESS_DENIED 个数,只要拒绝数大于1,则不同意授权。
访问授权主要分为两种:通过URL方式的接口访问控制和方法调用的权限控制。
对于接口访问授权,也就是 FilterSecurityInterceptor 管理的URL授权,默认对应的授权策略只有一个,就是 WebExpressionVoter,它的授权策略主要是根据 WebSecurityConfigurerAdapter 内配置的路径访问策略进行匹配,然后决定是否授权。
static final String IS_AUTHENTICATED_ANONYMOUSLY:匿名认证状态
configure(HttpSecurity http)
FormLoginConfigurer
对于我们一般所用的 DaoAuthenticationProvider 是由 UserDetailsService 专门负责获取验证信息的
UsernamePasswordAuthenticationFilter 过滤器的构造函数内绑定了 POST 类型的 /login 请求,也就是说,如果配置了 formLogin 的相关信息,那么在使用 POST 类型的 /login URL进行登录的时候就会被这个过滤器拦截,并进行登录验证
AbstractUserDetailsAuthenticationProvider
static final String IS_AUTHENTICATED_REMEMBERED:记住我登录状态
RoleVoter
PreInvocationAuthorizationAdviceVoter 解析出注解属性配置, 然后通过调用 PreInvocationAuthorizationAdvice 的前置通知方法进行授权认证,默认实现类似 ExpressionBasedPreInvocationAdvice,通知内主要进行了内容的过滤和权限表达式的匹配
在进行方法调用时,会由 MethodSecurityInterceptor 进行拦截并进行授权,MethodSecurityInterceptor 继承了 AbstractSecurityInterceptor 并实现了AOP 的 org.aopalliance.intercept.MethodInterceptor 接口, 所以可以在方法调用时进行拦截
FormLoginConfigurer 的构造函数内绑定了一个 UsernamePasswordAuthenticationFilter 过滤器。
UserDetails 提供了一个默认实现 User,主要包含用户名(username)、密码(password)、权限(authorities)和一些账号或密码状态的标识。如果默认实现满足不了你的需求,可以根据需求定制自己的 UserDetails,然后在 UserDetailsService 的 loadUserByUsername 中返回即可。
重写方法
对于方法调用授权,在全局方法安全配置类里,可以看到给 MethodSecurityInterceptor 默认配置的有 RoleVoter、AuthenticatedVoter、Jsr250Voter、和 PreInvocationAuthorizationAdviceVoter,其中 Jsr250Voter、PreInvocationAuthorizationAdviceVoter 都需要打开指定的开关,才会添加支持
findPreInvocationAttribute(Collection<ConfigAttribute> config) return: PreInvocationAttribute
formLogin().x.x 就是配置使用内置的登录验证过滤器,默认实现为 UsernamePasswordAuthenticationFilter。
从上面的登录逻辑我们可以看到,Spring Security的登录认证过程是委托给 AuthenticationManager 完成的,它先是解析出用户名和密码,然后把用户名和密码封装到一个UsernamePasswordAuthenticationToken 中,传递给 AuthenticationManager,交由 AuthenticationManager 完成实际的登录认证过程。
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; } } } }
AbstractAccessDecisionManager
.......
AccessDecisionManager
javax.servlet.Filter(接口)
WebSecurityConfigurerAdapter
....
String username = (authentication.getPrincipal() == null) ? \"NONE_PROVIDED\" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { // 子类根据自身情况从指定的地方加载认证需要的用户信息font color=\"#ff0000\
RoleVoter 是根据角色进行匹配授权的策略
AccessDecisionVoter
font color=\"#9c27b0\
@Bean public MethodInterceptor methodSecurityInterceptor() throws Exception { this.methodSecurityInterceptor = isAspectJ() ? new AspectJMethodSecurityInterceptor() : new MethodSecurityInterceptor(); methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager()); methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager()); methodSecurityInterceptor .setSecurityMetadataSource(methodSecurityMetadataSource()); RunAsManager runAsManager = runAsManager(); if (runAsManager != null) { methodSecurityInterceptor.setRunAsManager(runAsManager); } return this.methodSecurityInterceptor; }
org.aopalliance.intercept.MethodInterceptor(接口)
methodSecurityInterceptor() return: MethodInterceptor
方法调用权限
int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { // 通过各种投票策略,最终决定是否授权 font color=\"#ff0000\
AuthenticatedVoter 主要是针对有配置以下几个属性来决定授权的策略。IS_AUTHENTICATED_REMEMBERED:记住我登录状态IS_AUTHENTICATED_ANONYMOUSLY:匿名认证状态IS_AUTHENTICATED_FULLY: 完全登录状态,即非上面两种类型
font color=\"#ff0000\
登录认证
这是 AbstractAuthenticationProcessingFilter 中的一个抽象方法,包含登录主逻辑,由其子类实现具体的登录验证,如 UsernamePasswordAuthenticationFilter 是使用表单方式登录的具体实现。如果是非表单登录的方式,如JNDI等其他方式登录的可以通过继承 AbstractAuthenticationProcessingFilter 自定义登录实现。
MethodSecurityInterceptor
loadUserByUsername(String username) return: UserDetails
ProviderManager为provider管理器,管理所有AuthenticationProvider
UserDetailsService 接口只有一个方法,loadUserByUsername(String username),一般需要我们实现此接口方法,根据用户名加载登录认证和访问授权所需要的信息,并返回一个 UserDetails的实现类,后面登录认证和访问授权都需要用到此中的信息。
int result = ACCESS_ABSTAIN; for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { result = ACCESS_DENIED; // 完全登录状态 if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) { if (isFullyAuthenticated(authentication)) { return ACCESS_GRANTED; } } // 记住我登录状态 if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) { if (authenticationTrustResolver.isRememberMe(authentication) || isFullyAuthenticated(authentication)) { return ACCESS_GRANTED; } } // 匿名登录状态 if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) { if (authenticationTrustResolver.isAnonymous(authentication) || isFullyAuthenticated(authentication) || authenticationTrustResolver.isRememberMe(authentication)) { return ACCESS_GRANTED; } } } } return result;
authenticate(Authentication authentication) return: Authentication
PreInvocationAuthorizationAdviceVoter
ProviderManager
AccessDecisionVoter<>(接口)
WebExpressionVoter
UserDetails(接口)
......
for (ConfigAttribute attribute : config) { if (attribute instanceof PreInvocationAttribute) { return (PreInvocationAttribute) attribute; } } return null;
AuthenticationProvider(接口)
.........
public FormLoginConfigurer() { super(new font color=\"#ff0000\
根据验证方式的多样化,AuthenticationProvider 衍生出多种类型的实现,AbstractUserDetailsAuthenticationProvider 是 AuthenticationProvider 的抽象实现,定义了较为统一的验证逻辑,各种验证方式可以选择直接继承 AbstractUserDetailsAuthenticationProvider 完成登录认证,如 DaoAuthenticationProvider 就是继承了此抽象类,完成了从DAO方式获取验证需要的用户信息的。
invoke(MethodInvocation mi)
DaoAuthenticationProvider
AbstractSecurityInterceptor
FilterSecurityInterceptor
PreInvocationAuthorizationAdviceVoter 是针对类似 @PreAuthorize(\"hasRole('ROLE_ADMIN')\") 注解解析并进行授权的策略。
SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler()
formLogin() return: FormLoginConfigurer
WebSecurityConfig(Spring Secuity配置类)
.....
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); ... return loadedUser; ...
if (logger.isDebugEnabled()) { logger.debug(\"Authentication success. Updating SecurityContextHolder to contain: \" + authResult); } // 登录成功之后,把认证后的 Authentication 对象存储到请求线程上下文,这样在授权阶段就可以获取到此认证信息进行访问控制判断font color=\"#ff0000\
extractAuthorities(Authentication authentication) return: Collection<? extends GrantedAuthority>
如上面所述, AuthenticationProvider 通过 font color=\"#ff0000\
String rolePrefix = \"ROLE_\" // RoleVoter 默认角色名以 \"ROLE_\" 为前缀
默认实现
PreInvocationAuthorizationAdvice(接口)
... // 通过 SecurityMetadataSource 获取权限配置信息,可以定制实现自己的权限信息获取逻辑 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); ... // 确认是否经过登录认证 Authentication authenticated = authenticateIfRequired(); // Attempt authorization try { // 通过 AccessDecisionManager 完成授权认证,默认实现是 AffirmativeBased this.accessDecisionManagerfont color=\"#ff00ff\
authenticate(Authentication authentication) return: Authentication
MethodSecurityInterceptor methodSecurityInterceptor
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler()
FormLoginConfigurer() 构造方法
上面代码显示 AbstractSecurityInterceptor 又是委托授权认证器 AccessDecisionManager 完成授权认证,默认实现是 AffirmativeBased
supports(ConfigAttribute attribute) return: boolean
protected AccessDecisionManager accessDecisionManager() { List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>(); ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice(); expressionAdvice.setExpressionHandler(getExpressionHandler()); if (prePostEnabled()) { decisionVoters .add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); } if (jsr250Enabled()) { decisionVoters.add(new Jsr250Voter()); } decisionVoters.add(new RoleVoter()); decisionVoters.add(new AuthenticatedVoter()); return new AffirmativeBased(decisionVoters); }
AuthenticationManager(接口)
............
//如果请求路径及请求方法不匹配,则传递给下一个过滤器处理,细节查看源码if (font color=\"#ff0000\
实现
我们看到,MethodSecurityInterceptor 跟 FilterSecurityInterceptor 一样, 都是通过调用父类 AbstractSecurityInterceptor 的相关方法完成授权,其中 beforeInvocation 是完成权限认证的关键
User
static final long serialVersionUID = 560L;static final Log logger = LogFactory.getLog(User.class);String password;final String username;final Set<GrantedAuthority> authorities;final boolean accountNonExpired;final boolean accountNonLocked;final boolean credentialsNonExpired;final boolean enabled;
FilterSecurityInterceptor 继承了 AbstractSecurityInterceptor 并实现了 javax.servlet.Filter 接口, 所以在URL访问的时候都会被过滤器拦截
beforeInvocation(Object object) return: InterceptorStatusToken
public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher(\"/login\
AffirmativeBased
ExpressionBasedPreInvocationAdvice
接口访问权限
0 条评论
下一页