Spring-Security-文档笔记之授权

1. 授权框架

1.1 GrantedAuthority
String getAuthority();

AuthenticationManagerGrantedAuthority存入到Authentication中, 然后被AccessDecisionManager读取以决定是否授权.
SimpleGrantedAuthorityGrantedAuthority的一个实现, 它将String转换为权限.

1.2 Pre-Invocation Handling

Spring Security提供了拦截器来控制对安全对象(如方法调用或web请求)的访问。AccessDecisionManager进行调用前处理.
AccessDecisionManager由AbstractSecurityInterceptor调用,负责做出最终的访问控制决策。AccessDecisionManager接口包含三个方法:

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);
  • 基于投票的AccessDecisionManager实现
    image.png

    使用这种方式, 通过轮询一系列AccessDecisionVoter. 然后AccessDecisionManager根据结果来决定是否抛出AccessDeniedException.
// AccessDecisionVoter 
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> 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来实现的.

image.png

AfterInvocationManager有一个实现AfterInvocationProviderManager, 它会轮询AfterInvocationProvider, 每个provider都可以修改返回对象或抛出一个AccessDeniedException.

1.4 角色继承
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
    <constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy"
        class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
    <property name="hierarchy">
        <value>
            ROLE_ADMIN > ROLE_STAFF
            ROLE_STAFF > ROLE_USER
            ROLE_USER > ROLE_GUEST
        </value>
    </property>
</bean>

2. 使用FilterSecurityInterceptor授权HttpServletRequest

FilterSecurityInterceptor作为一个Security Filter, 用于向所有请求提供授权.

image.png

  1. FilterSecurityInterceptor从SecurityContextHolder中获取Authentication对象.
  2. FilterSecurityInterceptor 创建一个FilterInvocation对象.
  3. 将FilterInvocation对象传递给SecurityMetadataSource以获取ConfigAttribute.
  4. 将Authentication, FilterInvocation, ConfigAttribute传递给AccessDecisionManager.

默认要求所有的请求都需要认证.

// java
protected void configure(HttpSecurity http) throws Exception {
    http
        // ...
        .authorizeRequests(authorize -> authorize
            .anyRequest().authenticated()
        );
}

//xml
<http>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

自定义配置:

// 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
<http> 
    <intercept-url pattern="/resources/**" access="permitAll"/>
    <intercept-url pattern="/signup" access="permitAll"/>
    <intercept-url pattern="/about" access="permitAll"/>

    <intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> 
    <intercept-url pattern="/db/**" access="hasRole('ADMIN') and hasRole('DBA')"/> 
    <intercept-url pattern="/**" access="denyAll"/> 
</http>

当使用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安全表达式
<http use-expressions="true">
    <intercept-url pattern="/admin*"
        access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
    ...
</http>

表达式hasIpAddress是一个额外的内置表达式,它是特定于web安全的。它由WebSecurityExpressionRoot类定义

3.3 方法安全表达式

有四种注解支持表达式属性,以允许调用前和调用后的授权检查,并支持对提交的集合参数或返回值进行过滤。它们的使用是通过global-method-security命名空间元素启用的:

<global-method-security pre-post-annotations="enabled"/>
  1. @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。

  1. @PreFilter and @PostFilter
    Spring Security支持使用表达式过滤集合、数组、映射和流。这最好是通过优化数据检索来实现过滤.

方法安全元注解:

@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容