Spring Security OAuth2.0认证授权知识概括
安全框架基本概念
什么是认证:
- 进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码 登录微信的过程就是认证。
- 系统为什么要认证?
①认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。 - 认证 :
①用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。
②常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。
什么是会话:
- 用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
- 基于session的认证方式如下图:
①它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id 存放到 cookie 中。
②这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。
- 基于token方式如下图:
①它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。
- 总结:
①基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie;
②基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。
什么是授权:
- 还拿微信来举例子,微信登录成功后用户即可使用微信的功能,比如,发红包、发朋友圈、添加好友等,没有绑定银行卡的用户是无法发送红包的,绑定银行卡的用户才可以发红包,发红包功能、发朋友圈功能都是微信的资源即功能资源,用户拥有发红包功能的权限才可以正常使用发送红包功能,拥有发朋友圈功能的权限才可以使用发朋友圈功能,这个根据用户的权限来控制用户使用资源的过程就是授权。
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源。
授权: 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有 权限则拒绝访问。
授权的数据模型:
- 如何进行授权即如何对用户访问资源进行控制,首先需要学习授权相关的数据模型。
- 授权可简单理解为Who对What(which)进行How操作,包括如下:
①Who,即主体(Subject),主体一般是指用户,也可以是程序,需要访问系统中的资源。 What,即资源(Resource),如系统菜单、页面、按钮、代码方法、系统商品信息、系统订单信息等。
②系统菜单、页面、按钮、代码方法都属于系统功能资源,对于web系统每个功能资源通常对应一个URL;系统商品信息、系统订单信息都属于实体资源(数据资源),实体资源由资源类型和资源实例组成,比如商品信息为资源类型,商品编号 为001的商品为资源实例。 How,权限/许可(Permission),规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个代码方法的调用权限、编号为001的用户的修改权限等,通过权限可知用户对哪些资源都有哪些操作许可。
③主体、资源、权限关系如下图:
④主体、资源、权限相关的数据模型如下:
<1>主体(用户id、账号、密码、…)
<2>资源(资源id、资源名称、访问地址、…)
<3>权限(权限id、权限标识、权限名称、资源id、…)
<4>角色(角色id、角色名称、…)
<5>角色和权限关系(角色id、权限id、…)
<6>主体(用户)和角色关系(用户id、角色id、…)
⑤主体(用户)、资源、权限关系如下图:
⑥通常企业开发中将资源和权限表合并为一张权限表,如下:
<1>资源(资源id、资源名称、访问地址、…)
<2>权限(权限id、权限标识、权限名称、资源id、…)
⑦合并为:
<1>权限(权限id、权限标识、权限名称、资源名称、资源访问地址、…)
<2>修改后数据模型之间的关系如下图:
RBAC:
- 如何实现授权?
①业界通常基于RBAC实现授权。
- 基于角色的访问控制
①RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
②根据上图中的判断逻辑,授权代码可表示如下:
<1>根据下边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。
if(主体.hasRole("总经理角色id")){
查询工资
}
如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是总经理或部门经理”,修改代码如下:
if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id")){
查询工资
}
- 基于资源的访问控制
①RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权,比如:用户必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
②优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统可扩展性强。
③根据上图中的判断,授权代码可以表示为:
if(主体.hasPermission("查询工资权限标识")){
查询工资
}
总结来说就是基于资源比基于角色拥有更小的粒度,因此更好扩展。
基于Session的认证方式
认证流程:
- 基于Session认证方式的流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话),而发给客户端的sesssion_id 存放到 cookie 中,这样用客户端请求时带上 session_id 就可以验证服务器端是否存在 session数据,以此完成用户的合法校验。当用户退出系统或session过期销毁时,客户端的session_id也就无效了。
- 下图是session认证方式的流程图:
- 基于Session的认证机制由Servlet规范定制,Servlet容器已实现,用户通过HttpSession的操作方法即可实现,如下是HttpSession相关的操作API。
方法 | 含义 |
---|---|
HttpSession | getSession(Boolean create) 获取当前HttpSession对象 |
void | setAttribute(String name,Object value) 向session中存放对象 |
object | getAttribute(String name) 从session中获取对象 |
void | removeAttribute(String name); 移除session中对象 |
void | invalidate() 使HttpSession失效 |
略 | … |
- Demo要点:
①实现会话功能:首先在UserDto中定义一个SESSION_USER_KEY,作为Session中存放登录用户信息的key。
②授权功能思路:
<1>匿名用户(未登录用户)访问拦截:禁止匿名用户访问某些资源。
<2>登录用户访问拦截:根据用户的权限决定是否能访问某些资源。
③实现授权功能:为了实现这样的功能,我们需要在UserDto里增加权限属性( Set<String>类型),用于表示该登录用户所拥有的权限。
Spring Security简介
Spring Security介绍:
- Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是Spring生态系统中的一员,因此它伴随着整个Spring生态系统不断修正、升级,在spring boot项目中加入spring security更是十分简单,使用Spring Security 减少了为企业系统安全控制编写大量重复代码的工作。
- 引入以下依赖:在security-springmvc的基础上增加spring-security的依赖:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring‐security‐web</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring‐security‐config</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
- Spring Security初始化,这里有两种情况:
①如果您不使用 Spring 或 Spring MVC,则需要将WebSecurityConfig传递到超类中,以确保配置被接受。(写一个类然后继承超类进行添加)
②如果我们在应用程序的其他地方使用 Spring,则可能已经有一个WebApplicationInitializer用来加载 Spring 配置。如果我们使用以前的配置,将会得到一个错误。相反,我们应该使用现有的ApplicationContext注册 Spring Security。(在已有的类上进行添加)
--------不使用 Spring 或 Spring MVC--------
public class SpringSecurityApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SpringSecurityApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
--------使用 Spring--------
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {
WebSecurityConfig.class };
}
// ... other overrides ...
}
- WebSecurityConfig详解:
①WebSecurityConfigurerAdapter在configure(HttpSecurity http)方法中提供了默认配置:
<1>确保对我们应用程序的任何请求都需要对用户进行身份验证
<2>允许用户使用基于表单的登录进行身份验证
<3>允许用户使用 HTTP Basic 身份验证进行身份验证
②使用and()方法表示等效于关闭 XML 标记的 Java 配置,该方法允许我们 continue 配置父级。
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { }
- springSecurity默认提供认证页面(
登录,登出
),不需要额外开发。当然我们也可以自定义配置(大部分)
:
①使用WebSecurityConfigurerAdapter时,将自动应用登录功能。默认设置是访问 URL/login
将通过以下方式登录用户。
②使用WebSecurityConfigurerAdapter时,将自动应用注销功能。默认设置是访问 URL/logout
将通过以下方式注销用户。
③除此之外还可以配置登录成功的页面,登录失败的页面以及注销成功的页面。
- 授权请求:
我们可以通过将多个子级添加到http.authorizeRequests()方法中来为 URL 指定自定义要求:
(1) http.authorizeRequests()方法有多个子级,每个匹配器均按声明 Sequences 考虑。
(2) 我们指定了任何用户都可以访问的多个 URL 模式。具体来说,如果 URL 以“/resources /”开头,等于“/signup”或等于“/about”,则任何用户都可以访问请求。
(3) 任何以“/admin /”开头的 URL 都将限于角色为“ ROLE_ADMIN”的用户。您将注意到,由于我们正在调用hasRole方法,因此无需指定“ ROLE_”前缀。
(4) 任何以“/db /”开头的 URL 都要求用户同时具有“ ROLE_ADMIN”和“ ROLE_DBA”。您会注意到,由于我们使用的是hasRole表达式,因此无需指定“ ROLE_”前缀。
(5) antMatchers("/r/r2").hasAuthority(“p2”)表示:访问/r/r2资源的 url需要拥有p2权限。
(6) 尚未匹配的任何 URL 仅要求对用户进行身份验证
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() (1)
.antMatchers("/resources/**", "/signup", "/about").permitAll() (2)
.antMatchers("/admin/**").hasRole("ADMIN") (3)
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") (4)
.antMatchers("/r/r2").hasAuthority("p2") (5)
.anyRequest().authenticated() (6)
.and()
// ...
.formLogin();
}
Authentication认证:
①内存中身份验证:从内存中查
②JDBC 身份验证:从数据库中查
③LDAP 认证:从配置文件中查
//配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}
密码编码:
Spring Security 的PasswordEncoder接口用于执行密码的单向转换,以允许安全地存储密码。鉴于PasswordEncoder是一种单向转换,因此在密码转换需要采用两种方式(即存储用于向数据库进行身份验证的凭据)时并不适用。通常,PasswordEncoder用于存储需要与认证时用户提供的密码进行比较的密码。
//密码加密器
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
SpringSecurity集成SpringBoot:
- Spring Boot是一套Spring的快速开发框架,基于Spring 4.0设计,使用Spring Boot开发可以避免一些繁琐的工程搭建和配置,同时它集成了大量的常用框架,快速导入依赖包,避免依赖包的冲突。Spring Boot提供spring-boot-starter-security用于开 发Spring Security应用。
- 由于Spring boot starter自动装配机制,这里无需使用@EnableWebSecurity,WebSecurityConfig内容如下:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//内容跟Spring security入门程序一致
}
SpringSecurity详解
结构总览:
-
Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,
可以通过Filter或AOP等技术来实现,SpringSecurity对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。
-
当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain的Servlet过滤器,类型为
org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图: -
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了
认证管理器(AuthenticationManager)
和决策管理器 (AccessDecisionManager)
进行处理,下图是FilterChainProxy相关类的UML图示。 -
spring Security功能的实现主要是由一系列过滤器链相互配合完成。
-
下面介绍过滤器链中主要的几个过滤器及其作用:
①SecurityContextPersistenceFilter
这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截 器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好 的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
②UsernamePasswordAuthenticationFilter
用于处理来自表单提交的认证。该表单必须提供对应的用户名和密 码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;
③FilterSecurityInterceptor
是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前 面已经详细介绍过了;
④ExceptionTranslationFilter
能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常: AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
认证流程:
- 用户提交用户名、密码被SecurityFilterChain中的 过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
- 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
- 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,实例。身份信息,细节信息,但密码通常会被移除)
- SecurityContextHolder 安全上下文容器将第3步填充了信息的,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。
①可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。
AuthenticationProvider(认证流程提供者):
- 通过前面的Spring Security认证流程我们得知,认证管理器(AuthenticationManager)委托AuthenticationProvider完成认证工作。
- AuthenticationProvider是一个接口,定义如下:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> var1);
}
- authenticate()方法定义了认证的实现过程,它的参数是一个Authentication,里面包含了登录用户所提交的用户、密码等。而返回值也是一个Authentication,这个Authentication则是在认证成功后,将用户的权限及其他信息重新组装后生成。
- Spring Security中维护着一个列表,存放多种认证方式,不同的认证方式使用不同的AuthenticationProvider。如使用用户名密码登录时,使用AuthenticationProvider1,短信登录时使用AuthenticationProvider2等等这样的例子很多。
- 每个AuthenticationProvider需要实现supports()方法来表明自己支持的认证方式,如我们使用表单方式认证,在提交请求时Spring Security会生成UsernamePasswordAuthenticationToken,它是一个Authentication,里面封装着用户提交的用户名、密码信息。而对应的,哪个AuthenticationProvider来处理它?
- 我们在DaoAuthenticationProvider的基类AbstractUserDetailsAuthenticationProvider发现以下代码:
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
- 也就是说当web表单提交用户名密码时,Spring Security由DaoAuthenticationProvider处理。
- 最后,我们来看一下Authentication(认证信息)的结构,它是一个接口,我们之前提到的 UsernamePasswordAuthenticationToken就是它的实现之一:
(1)
Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个getName()方法。
(2)
getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系 列字符串。
(3)
getCredentials(),凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
(4)
getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地 址和sessionId的值。
(5)
getPrincipal(),身份信息,大部分情况下返回的是UserDetails接口的实现类,UserDetails代表用户的详细信息,那从Authentication中取出来的UserDetails就是当前登录用户信息,它也是框架中的常用接口之一。
public interface Authentication extends Principal, Serializable {
(1)
Collection<? extends GrantedAuthority>
getAuthorities(); (2)
Object getCredentials(); (3)
Object getDetails(); (4)
Object getPrincipal(); (5)
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
UserDetailsService(身份信息):
- 现在咱们现在知道DaoAuthenticationProvider处理了web表单的认证逻辑,认证成功后既得到一个Authentication(UsernamePasswordAuthenticationToken实现),里面包含了身份信息(Principal)。这个身份信息就是一个Object,,大多数情况下它可以被强转为UserDetails对象。
- DaoAuthenticationProvider中包含了一个UserDetailsService实例,它负责根据用户名提取用户信息UserDetails(包含密码),而后DaoAuthenticationProvider会去对比UserDetailsService提取的用户密码与用户提交公开为spring bean来定 的密码是否匹配作为认证成功的关键依据,因此可以通过将自定义的UserDetailsService 公开为spring bean来定义自定义身份验证。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
- 很多人把DaoAuthenticationProvider和UserDetailsService的职责搞混淆,其实UserDetailsService只负责从特定 的地方(通常是数据库)加载用户信息,仅此而已。而DaoAuthenticationProvider的职责更大,它完成完整的认 证流程,同时会把UserDetails填充至Authentication。
- 上面一直提到UserDetails是用户信息,咱们看一下它的真面目:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled(); }
- 它和Authentication接口很类似,比如它们都拥有username,authorities。Authentication的getCredentials()与UserDetails中的getPassword()需要被区分对待,前者是用户提交的密码凭证,后者是用户实际存储的密码,认证其实就是对这两者的比对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递而形 成的。还记得Authentication接口中的getDetails()方法吗?其中的UserDetails用户详细信息便是经过了 AuthenticationProvider认证之后被填充的。通过实现UserDetailsService和UserDetails,我们可以完成对用户信息获取方式以及用户信息字段的扩展。
SpringSecurity提供的InMemoryUserDetailsManager(内存认证),JdbcUserDetailsManager(jdbc认证)就是 UserDetailsService的实现类,主要区别无非就是从内存还是从数据库加载用户。
PasswordEncoder(密码加解密器):
- DaoAuthenticationProvider认证处理器通过UserDetailsService获取到UserDetails后,它是如何与请求Authentication中的密码做对比呢?
- 在这里Spring Security为了适应多种多样的加密类型,又做了抽象,DaoAuthenticationProvider通过PasswordEncoder接口的matches方法进行密码的对比,而具体的密码对比细节取决于实现:
public interface PasswordEncoder {
String encode(CharSequence var1);
boolean matches(CharSequence var1, String var2);
default boolean upgradeEncoding(String encodedPassword){
return false;
}
}
- 而SpringSecurity提供很多内置的PasswordEncoder,能够开箱即用,使用某种PasswordEncoder只需要进行如下声明即可,如下:
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
- NoOpPasswordEncoder采用字符串匹配方法,不对密码进行加密比较处理,密码比较流程如下:
①用户输入密码(明文 )
②DaoAuthenticationProvider获取UserDetails(其中存储了用户的正确密码)
③DaoAuthenticationProvider使用PasswordEncoder对输入的密码和正确的密码进行校验,密码一致则校验通 过,否则校验失败。NoOpPasswordEncoder的校验规则拿 输入的密码和UserDetails中的正确密码进行字符串比较,字符串内容一致 则校验通过,否则校验失败。 - 实际项目中推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder,SCryptPasswordEncoder等,感兴趣 的大家可以看看这些PasswordEncoder的具体实现。
- Demo:使用BCryptPasswordEncoder:
①配置BCryptPasswordEncoder 在安全配置类中定义:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
授权流程:
- 通过快速上手我们知道,Spring Security可以通过 http.authorizeRequests() 对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
- Spring Security的授权流程如下:
- 分析授权流程:
①拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的 FilterSecurityInterceptor 的子类拦截。
②获取资源访问策略,FilterSecurityInterceptor会从 SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource获取要访问当前资源所需要的权限Collection<ConfigAttribute>。
<1>SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则,读取访问策略如下:
③最后,FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资 源,否则将禁止访问。
http.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
...
- AccessDecisionManager(访问决策管理器)的核心接口如下:
①这里着重说明一下decide的参数:
<1>authentication:要访问资源的访问者的身份
<2>object:要访问的受保护资源,web请求对应FilterInvocation
<3>configAttributes:是受保护资源的访问策略,通过<4>SecurityMetadataSource获取。
②decide接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。
public interface AccessDecisionManager {
/**
* 通过传递的参数来决定用户是否有访问对应受保护资源的权限
*/
void decide(Authentication authentication,
Object object,
Collection<ConfigAttribute> configAttributes
)
throws AccessDeniedException, InsufficientAuthenticationException;
//略..
}
授权决策:
- AccessDecisionManager采用投票的方式来确定是否能够访问受保护资源。
- 通过上图可以看出,AccessDecisionManager中包含的一系列AccessDecisionVoter将会被用来对Authentication 是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。AccessDecisionVoter是一个接口,其中定义有三个方法,具体结构如下所示:
①vote()方法的返回结果会是AccessDecisionVoter中定义的三个常量之一。
<1>ACCESS_GRANTED表示同意, ACCESS_DENIED表示拒绝,ACCESS_ABSTAIN表示弃权。
<2>如果一个AccessDecisionVoter不能判定当前 Authentication是否拥有访问对应受保护对象的权限,则其vote()方法的返回值应当为弃权ACCESS_ABSTAIN。
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = ‐1;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
int vote(Authentication var1, S var2, Collection<ConfigAttribute> var3); }
- Spring Security内置了三个基于投票的AccessDecisionManager实现类如下,它们分别是AffirmativeBased、ConsensusBased和UnanimousBased。
- AffirmativeBased的逻辑是:
①只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
②如果全部弃权也表示通过;
③如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。 Spring security默认使用的是AffirmativeBased。 - ConsensusBased的逻辑是:
①如果赞成票多于反对票则表示通过。
②反过来,如果反对票多于赞成票则将抛出AccessDeniedException。
③如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。
④如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值 为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。 - UnanimousBased的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传