授权

Spring Security中的高级授权功能代表了其受欢迎程度的最令人信服的原因之一

授权体系结构

Authorities

GrantedAuthority对象由AuthenticationManager插入到Authentication对象中,并在以后做出授权决策时由AccessDecisionManager读取。 GrantedAuthority是只有一种方法的接口:

public interface GrantedAuthority extends Serializable {
	String getAuthority();
}

此方法使AccessDecisionManager可以获取GrantedAuthority的精确String表示形式, 如果GrantedAuthority无法精确地表示为String ,则GrantedAuthority被视为”复杂”,并且getAuthority()必须返回null

Spring提供GrantedAuthority的实现 SimpleGrantedAuthority,将String转化成GrantedAuthority

Pre-Invocation Handling

Spring Security提供了拦截器,用于控制对安全对象的访问,例如方法调用或Web请求. AccessDecisionManager做出关于是否允许进行调用的预调用决定

AccessDecisionManager

AccessDecisionManagerAbstractSecurityInterceptor调用,它负责做出最终的访问控制决策。AccessDecisionManager接口包含三个方法:

void decide(Authentication authentication, Object secureObject,
    Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);
  • decide为传递的参数解析访问控制决策。
  • 在启动时, AbstractSecurityInterceptor将调用supports(ConfigAttribute)方法,以确定AccessDecisionManager可以处理传递的ConfigAttribut
  • 安全拦截器实现调用supports(Class)方法,以确保配置的AccessDecisionManager支持安全拦截器将显示的安全对象的类型.

AccessDecisionVoter

Spring Security提供几种基于投票的AccessDecisionManager实现 使用这种方法,可以对一系列AccessDecisionVoter实现进行授权决策轮询。然后,AccessDecisionManager根据对投票的评估决定是否抛出AccessDeniedException异常。

AccessDecisionVoter

int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

vote具体的实现返回一个int,可能的值反映在AccessDecisionVoter静态字段ACCESS_ABSTAIN ACCESS_DENIED ACCESS_GRANTED

Spring Security提供了三个具体的AccessDecisionManager来对选票进行汇总

  • ConsensusBased ACCESS_GRANTED投票数大于ACCESS_DENIED投票数则判为通过,小于则不通过,投票数相等时根据allowIfEqualGrantedDeniedDecisions判断是否通过,默认通过(true)
  • AffirmativeBased 若有ACCESS_GRANTED投票直接通过,否则若有 ACCESS_DENIED投票则不通过,两者投票都没有时根据allowIfAllAbstainDecisions判断
  • UnanimousBased 若有 ACCESS_DENIED投票直接不通过,否则若有ACCESS_GRANTED投票则通过,两者投票都没有时根据allowIfAllAbstainDecisions判断

RoleVoter

Spring Security提供的最常用的AccessDecisionVoter是简单的RoleVoter ,它将配置属性视为简单的角色名称和投票,以在用户分配了该角色后授予访问权限.

如果任何ConfigAttribute以前缀ROLE_开头,它将进行投票. 如果有GrantedAuthority返回一个String表示(通过getAuthority()方法),该String表示完全等于一个或多个以前缀ROLE_开头的ConfigAttributes ,它将投票授予访问权限. 如果没有以ROLE_开头的ConfigAttribute完全匹配,则RoleVoter会投票拒绝访问. 如果没有ConfigAttributeROLE_ ,则投票者将弃权.

AuthenticatedVoter

我们隐式看到的另一个投票者是AuthenticatedVoter ,它可用于区分匿名,完全认证和记住我的认证用户. 许多站点允许使用”记住我”身份验证进行某些受限访问,但是要求用户通过登录以进行完全访问来确认其身份。 当我们使用属性IS_AUTHENTICATED_ANONYMOUSLY授予匿名访问权限时,此属性已由AuthenticatedVoter处理

自定义AccessDecisionVoter

After Invocation Handling

虽然在进行安全对象调用之前AbstractSecurityInterceptor会调用AccessDecisionManager ,但某些应用程序需要一种方法来修改安全对象调用实际返回的对象. 尽管您可以轻松实现自己的AOP问题来实现这一点,但Spring Security提供了一个方便的挂钩,该挂钩具有一些与其ACL功能集成的具体实现. 像Spring Security的许多其他部分一样, AfterInvocationManager有一个具体的实现AfterInvocationProviderManager ,它轮询AfterInvocationProvider的列表. 每个AfterInvocationProvider都可以修改返回对象或引发AccessDeniedException . 实际上,由于前一个提供程序的结果将传递到列表中的下一个,因此多个提供程序可以修改该对象.

Hierarchical Roles

使用角色层次结构,可以配置哪些角色(或权限)应包括其他角色. Spring Security的RoleVoter的扩展版本RoleHierarchyVoter配置有RoleHierarchy

FilterSecurityInterceptor

FilterSecurityInterceptorHttpServletRequest提供授权 . 它作为安全筛选器之一插入到FilterChainProxy中.

  1. 首先, FilterSecurityInterceptorSecurityContextHolder获得认证 .
  2. 其次,FilterSecurityInterceptor根据传递给FilterSecurityInterceptorHttpServletRequestHttpServletResponseFilterChain创建一个FilterInvocation.
  3. 它将FilterInvocation传递给SecurityMetadataSource以获取ConfigAttribute .
  4. 最后,它将AuthenticationFilterInvocationConfigAttribute传递给AccessDecisionManager .
  5. 如果授权被拒绝,则抛出AccessDeniedException . 在这种情况下, ExceptionTranslationFilter处理AccessDeniedException .
  6. 如果授予访问权限, FilterSecurityInterceptor继续使用FilterChain ,该链接允许应用程序正常处理.

Expression-Based Access Control

Spring Security 3.0引入了使用Spring EL表达式作为授权机制的能力,此外还可以简单地使用配置属性和访问决定投票器. 基于表达式的访问控制基于相同的体系结构,但是允许将复杂的布尔逻辑封装在单个表达式中.

Built-In Expressions

ExpressionDescription
hasRole(String role)如果当前主体具有指定角色,则返回true . 例如, hasRole(‘admin’)。默认情况下,如果提供的角色不是以” ROLE_“开头,则会添加该角色. 这可以通过修改DefaultWebSecurityExpressionHandler上的defaultRolePrefix进行自定义.
hasAnyRole(String… roles)如果当前主体具有提供的任何角色(以逗号分隔的字符串列表形式),则返回true .
hasAuthority(String authority)如果当前主体具有指定的权限,则返回true . 例如, hasAuthority(‘read’)
principal允许直接访问代表当前用户的主体对象
authentication允许直接访问从SecurityContext获得的当前Authentication对象
permitAll始终评估为true
denyAll始终评估为false
isAnonymous()如果当前主体是匿名用户,则返回true
isRememberMe()如果当前主体是”记住我”用户,则返回true
isAuthenticated()如果用户不是匿名的,则返回true
isFullyAuthenticated()如果用户不是匿名用户或”记住我”用户,则返回true
hasPermission(Object target, Object permission)如果用户可以访问给定权限的给定目标,则返回true . 例如, hasPermission(domainObject, ‘read’)
hasPermission(Object targetId, String targetType, Object permission)如果用户可以访问给定权限的给定目标,则返回true . 例如, hasPermission(1, ‘com.example.domain.Message’, ‘read’)

Web Security Expressions

在表达式中引用Bean

http
    .authorizeRequests(authorize -> authorize
        .antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
        ...
    )

引用路径变量/user/{userId}

http
    .authorizeRequests(authorize -> authorize
        .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
        ...
    );

Method Security Expressions

有四个注释支持表达式属性,以允许调用前和调用后的授权检查,还支持过滤提交的集合参数或返回值. @PreAuthorize @PreFilter@PostAuthorize @PostFilter

@PreAuthorize("hasRole('USER')")
public void create(Contact contact);
@PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);
@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);
@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);

hasPermission()表达式委托给PermissionEvaluator的实例. 它旨在在表达式系统和Spring Security的ACL系统之间架起桥梁

boolean hasPermission(Authentication authentication, Object targetDomainObject,
                            Object permission);

boolean hasPermission(Authentication authentication, Serializable targetId,
                            String targetType, Object permission);

使用@PreFilter和@PostFilter进行过滤,如果要过滤大型集合并删除许多条目,则效率可能很低.

@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();

不必在所有地方重复此操作,我们可以创建一个可以用作替代的元注释.

@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}

Secure Object Implementations

方法安全性是通过使用MethodSecurityInterceptor来实现的,它保护方法位置的安全。根据配置方法的不同,拦截器可能特定于单个bean,也可能在多个bean之间共享。拦截器使用MethodSecurityMetadataSource实例来获取应用于特定方法调用的配置属性。mapbasedmethodsecuritymetadasource用于存储由方法名(可以是通配符)键入的配置属性,并且当使用< intercept-methods >或< protect-point >元素在应用程序上下文中定义属性时,将在内部使用该属性。其他实现将用于处理基于注释的配置。

AOP Alliance (MethodInvocation) Security Interceptor

AspectJ (JoinPoint) Security Interceptor

Method Security

@EnableGlobalMethodSecurity可以配置多个参数:

  • prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]
  • secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用
  • jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
方法授权类型声明方式JSR标准允许SpEL表达式
@PreAuthorize @PostAuthorize注解NoYes
@RolesAllowed @PermitAll @DenyAll注解YesNO
@Secure注解NONO
protect-pointcutXMLNONO

Updated: