1. 授权框架
1.1 GrantedAuthority
String getAuthority();
AuthenticationManager
将GrantedAuthority
存入到Authentication
中, 然后被AccessDecisionManager
读取以决定是否授权.
SimpleGrantedAuthority
是GrantedAuthority
的一个实现, 它将String转换为权限.
1.2 Pre-Invocation Handling
Spring Security提供了拦截器来控制对安全对象(如方法调用或web请求)的访问。AccessDecisionManager进行调用前处理.
AccessDecisionManager由AbstractSecurityInterceptor调用,负责做出最终的访问控制决策。AccessDecisionManager接口包含三个方法:
void decide(Authentication authentication, Object secureObject,
Collection attrs) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
- 基于投票的
AccessDecisionManager
实现
使用这种方式, 通过轮询一系列AccessDecisionVoter
. 然后AccessDecisionManager
根据结果来决定是否抛出AccessDeniedException
.
// AccessDecisionVoter
int vote(Authentication authentication, Object object, Collection attrs);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
vote
方法返回int, 可能的值有: ACCESS_ABSTAIN, ACCESS_DENIED, ACCESS_GRANTED. ACCESS_ABSTAIN表示弃权, ACCESS_DENIED表示拒绝, ACCESS_GRANTED表示授权.
Spring Security提供了三个AccessDecisionManager实现来统计投票。allowIfEqualGrantedDeniedDecisions
则表示当投票相等或全部弃权时的控制行为.
- ConsensusBased:
根据ACCESS_GRANTED和ACCESS_DENIED各自的票数多少来判断. 哪种多就返回哪种结果. - AffirmativeBased:
只要有一个ACCESS_GRANTED, 则授权. - UnanimousBased:
所有的都投ACCESS_GRANTED则授权. 忽略弃权投票.
Spring Security常用投票器:
- AuthenticatedVoter
它可区别匿名用户, 完全授权用户和rememberme用户. 有的网站会限制rememberme用户的操作. 根据IS_AUTHENTICATED_FULLY , IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED_ANONYMOUSLY
来判断. - RoleVoter
判断用户是否具有相应的角色. 如果存在以ROLE_开头的属性, 则进行投票, 如果没有, 则投弃权票. - RoleHierarchyVoter
角色继承
1.3 After Invocation Handling
虽然在继续进行安全调用之前,AbstractSecurityInterceptor会调用AccessDecisionManager,但有些应用程序需要一种方法来修改安全调用返回的对象。虽然也可使用AOP来实现, 但Spring提供了一些钩子. 这个功能是由AfterInvocationManager
来实现的.
AfterInvocationManager
有一个实现AfterInvocationProviderManager
, 它会轮询AfterInvocationProvider
, 每个provider都可以修改返回对象或抛出一个AccessDeniedException
.
1.4 角色继承
ROLE_ADMIN > ROLE_STAFF
ROLE_STAFF > ROLE_USER
ROLE_USER > ROLE_GUEST
2. 使用FilterSecurityInterceptor授权HttpServletRequest
FilterSecurityInterceptor
作为一个Security Filter, 用于向所有请求提供授权.
-
FilterSecurityInterceptor
从SecurityContextHolder中获取Authentication对象. - FilterSecurityInterceptor 创建一个
FilterInvocation
对象. - 将FilterInvocation对象传递给SecurityMetadataSource以获取ConfigAttribute.
- 将Authentication, FilterInvocation, ConfigAttribute传递给AccessDecisionManager.
默认要求所有的请求都需要认证.
// java
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
);
}
//xml
自定义配置:
// java
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize
.mvcMatchers("/resources/**", "/signup", "/about").permitAll()
.mvcMatchers("/admin/**").hasRole("ADMIN")
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().denyAll()
);
}
// xml
当使用hasRole
方法时,可省略前缀.
3. 基于表达式的访问控制
表达式的计算是通过一系列root对象实现的, 其中SecurityExpressionRoot
是root对象的基类.
3.1 常见表达式
表达式 | 描述 |
---|---|
hasRole("") | 有指定角色返回true |
hasAnyRole(String… roles) | 有指定的任意一个角色返回true. hasAnyRole('admin', 'user') |
hasAuthority(String authority) | 有指定权限则返回true. hasAuthority('read') |
hasAnyAuthority(String… authorities) | 有指定权限中的一个就返回true. |
principal | 当前用户的主体对象,可在表达式中直接使用 |
authentication | authentication对象, 可直接使用 |
permitAll | 允许所有请求 |
denyAll | 拒绝所有 |
isAnonymous() | 是否为匿名用户 |
isRememberMe() | 是否为rememberme用户 |
isAuthenticated() | 如果不是匿名用户则返回true |
isFullyAuthenticated() | 是否为用户名密码登录的用户. |
hasPermission(Object target, Object permission) | hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) | hasPermission(1, 'com.example.domain.Message', 'read') |
3.2 web安全表达式
...
表达式hasIpAddress是一个额外的内置表达式,它是特定于web安全的。它由WebSecurityExpressionRoot类定义
3.3 方法安全表达式
有四种注解支持表达式属性,以允许调用前和调用后的授权检查,并支持对提交的集合参数或返回值进行过滤。它们的使用是通过global-method-security命名空间元素启用的:
- @PreAuthorize
决定方法是否被调用.
@PreAuthorize("hasRole('USER')")
public void create(Contact contact);
@PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);
可以引用方法参数. 有多种方式发现方法参数, spring security默认使用DefaultSecurityParameterNameDiscoverer
发现方法参数名.
当方法只有一个参数时, 可通过@P
注解指定参数名.
@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
当方法有多个参数时, 可用Spring Data的@Param
指定参数名
@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);
希望在调用方法之后执行访问控制检查。这可以使用@PostAuthorize注释实现。要访问方法的返回值,请在表达式中使用内置名称returnbject。
- @PreFilter and @PostFilter
Spring Security支持使用表达式过滤集合、数组、映射和流。这最好是通过优化数据检索来实现过滤.
方法安全元注解:
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}