SpringSecurity原理(三)——授权

22 篇文章 4 订阅

概述

SpringSecurity原理(一)——初探
SpringSecurity原理(二)——认证
SpringSecurity原理(三)——授权
SpringSecurity原理(四)——过滤器
SpringSecurity原理(五)——扩展与配置

前面,我们已经简单的介绍了一下校验用户名和密码的认证过程。

这里,我们来了解一下Spring Security的权限校验过程。

首先,我们还是先通过一个简单的示例,来大致了解一下Spring Security的授权是个什么操作。

示例

首先,我们还是尽量保持简单,在之前的项目之上稍作修改,项目结构基本没有变化,只是修改一下我们的UserDetailsService,添加上用户的权限,然后修改我们的测试接口,给它添加上权限限制。

项目结构

UserDetailsService

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class MyUserDetailService implements UserDetailsService {

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		String authority = "ROLE_admin,ROLE_teacher,read,write";
		List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
		return User.builder()
				.username("bob")
				.password("$2a$10$344aKAgXr17q7u.8l5i7Cu8wUJr/cxBIniLsVtf/WwFrPx0khY62K")
				.authorities(grantedAuthorities)
				.build();
	}
}

如果我们没有做特别的修改配置,Spring Security默认使用的GrantedAuthority是SimpleGrantedAuthority。

SimpleGrantedAuthority权限很简单,对得起它Simple的前缀,就只有一个字符串变量role表示角色。

权限类怎么设计是一回事,怎样校验权限又是另一回事,并且是更重要的一回事。

例如,看SimpleGrantedAuthority源码设计的目的肯定是用来表示一个用户有没有admin、editor、tech这类角色属性的。

但是和@PreAuthorize、@PostAuthorize、@Secured一起就被摆弄了十八般模样,变成:

可以校验:权限和角色

但是SimpleGrantedAuthority要守规矩:标识角色的SimpleGrantedAuthority的role必须有:ROLE_前缀,当然这也是可以反抗的,配置为其他前缀也行,但SimpleGrantedAuthority本身没啥发言权,自主权不在身上只能任人宰割。

Spring Security的User很给力,接收SimpleGrantedAuthority参数,也可以接收代表权限的字符串list,底层还是转为了SimpleGrantedAuthority列表。当然更给力的是User提供了一个roles接口,可以直接设置角色role,而不用自己去加ROLE_前缀了。

例如,向我们之前配置测试用户就用过的:

auth.inMemoryAuthentication()
				.withUser("tim")
				.password("111111")
				.roles("admin")
				.and()
				.withUser("allen")
				.password("222222")
				.roles("user");

当然,也可以像上面的示例代码一样,使用写成一个字符串,用逗号分割,然后通过工具类方法AuthorityUtils.commaSeparatedStringToAuthorityList转换。

IndexController

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/index")
public class IndexController {

    @RequestMapping("/hello")
    public String hello(){
        return "Hello World";
    }

    @PreAuthorize("hasRole('admin')")
    @RequestMapping("/admin")
    public String admin() {
        return "admin";
    }

    @PreAuthorize("hasRole('admin') && hasAuthority('update')")
    @RequestMapping("/admin-update")
    public String adminAndUpdate() {
        return "adminAndUpdate";
    }

    @PreAuthorize("hasAuthority('read')")
    @RequestMapping("/read")
    public String read() {
        return "read";
    }

    @PreAuthorize("hasAuthority('update')")
    @RequestMapping("/update")
    public String update() {
        return "update";
    }

    @Secured({"ROLE_user"})
    @RequestMapping("/user")
    public String user() {
        return "user";
    }
}

如果不去深究@PreAuthorize、@PostAuthorize、@Secured原理,只是使用非常简单,只需要添加上下面的注解,然后就可以放心使用了。

@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)

  1. @PreAuthorize是接口执行之前调用
  2. @PostAuthorize是接口执行之后调用
  3. @Secured校验角色的时候必须加上ROLE_前缀

其他添加权限控制方式

除了@PreAuthorize、@PostAuthorize、@Secured控制权限外,我们还可以通过HttpSecurity来配置权限。

在SecurityConfig(继承了WebSecurityConfigurerAdapter)中添加:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/home", "/js/*","/img/*").permitAll()
            .antMatchers("/admin/**","/upload/**").hasAnyRole("admin")
            .antMatchers("/order/**").hasAnyRole("user","admin")
            .antMatchers("/tech/**").hasAnyRole("tech","admin")
            .anyRequest().authenticated();
    http.formLogin();
}

HttpSecurity还可以配置很多东西,我们后面再一点一点介绍。

上面我们简单的了解了一下Spring Security为我们提供的权限控制与校验,一般也就够用了。

但是,如果我们要:

  1. 设计自己的权限体系
  2. 修改权限校验的规则
  3. 自定义权限和自定义校验

那又该如何?

下面,我们就来简单了解一下Spring Security的权限与鉴权的流程。

GrantedAuthority

首先还是GrantedAuthority,它是用户权限的抽象,例如拥有什么角色role,有什么标识principal之类。

Spring Security为我们提供了3个GrantedAuthority实现类:

  1. SimpleGrantedAuthority:最重要的权限是角色
  2. SwitchUserGrantedAuthority:存储用户切换原用户的Authentication,像Windows用户切换用户操作
  3. JaasGrantedAuthority:除了角色,还多了Principal

JaasGrantedAuthority中的JASS(Java Authentication and Authorization Service),Java认证与授权服务,这个类更多是设计为JaasAuthenticationProvider与DefaultJaasAuthenticationProvider服务的,但是需要自己实现AuthorityGranter。

怎么设计GrantedAuthority很灵活,根据实际情况设计即可,问题的关键在于怎样校验。

在Spring Security授权体系中有一个非常重要的类FilterSecurityInterceptor,它虽然是以Interceptor结尾,但是它是一个拦截器。它控制了授权流程,为了过于算乱,跑遍了,我们在后面文章的过滤器中再介绍。

这里,我们先介绍一下和权限具体判定有关的投票者(AccessDecisionVoter)和投票管理器(AccessDecisionManager)。

AccessDecisionVoter

投票者有3个选项,已经固化在了它们的身体中:

// 同意
int ACCESS_GRANTED = 1;
// 弃权
int ACCESS_ABSTAIN = 0;
// 拒绝
int ACCESS_DENIED = -1;

Spring Security已经为我们提供了很多默认的投票者:
投票者

RoleVoter的投票方式就是,如果权限authority为空就拒绝,如果权限中有一个角色role和检查权限equals就同意。

RoleHierarchy表示角色继承,例如角色admin可以访问角色tech的所有权限,就可以表示为:

@Bean
protected RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_admin > ROLE_tech");
    return hierarchy;
}

WebExpressionVoter就是根据SpEL表达式的计算值是否为true来决定同意还是拒绝。

我们前面介绍的@PreAuthorize、@PostAuthorize、@Secured就是使用的WebExpressionVoter。

如果对Spring EL不熟悉,可以看一下:

Spring EL小记一
Spring EL小记二

AccessDecisionManager

AccessDecisionManager是投票管理器,一次投票一般不会只有一个投票者,投票管理器的作用就是持有投票者的list,然后根据投票者的投票,最终统计出一个结果。

这里就涉及不同投票统计的策略了,所以Spring Security为我们提供了3个具体实现类:

  1. AffirmativeBased,霸权模式,表示一票通过
  2. ConsensusBased,民主模式,表示少数服从多数
  3. UnanimousBased,投资人模式,表示一票否决

在使用@PreAuthorize、@PostAuthorize、@Secured的时候,默认就是AffirmativeBased,只要有一票就通过。

TIP:和AccessDecisionManager比较像的是AfterInvocationManager,它管理一些AfterInvocationProvider,用于决定调用返回值。

权限校验SpEL

最后,在说一点,我们使用的的SpEL的校验:

@PreAuthorize(“hasRole(‘admin’) && hasAuthority(‘update’)”)

实际使用了MethodSecurityExpressionRoot,它继承了SecurityExpressionRoot,最顶层接口是SecurityExpressionOperations,整个继承体系是:

在这里插入图片描述

正真执行是在SecurityExpressionRoot的hasAnyAuthorityName方法:

private boolean hasAnyAuthorityName(String prefix, String... roles) {
    Set<String> roleSet = getAuthoritySet();
    for (String role : roles) {
        String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
        if (roleSet.contains(defaultedRole)) {
            return true;
        }
    }
    return false;
}

文档

Spring Security官方文档

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值