Spring Security之基于方法配置权限

前言

Spring Security有两种配置方式,今天重点是绍基于方法配置的方式。

基于方法配置权限

这个主要是有一些注解提供给大家使用,今天来给大家一个demo(参考自官方sample)。

maven就不多累赘了。重点看看配置。

  • 基于角色配置
/**
 * 启用方法安全:@EnableMethodSecurity
 * 
 * 启用@secured注解: **securedEnabled = true**
 * <p>会导入配置:SecuredMethodSecurityConfiguration<p>
 * 
 * 启用@PreAuthorize@PostAuthorize@PreFilter@PostFilter:**prePostEnabled = false**
 * <p>会导入配置:PrePostMethodSecurityConfiguration</p>
 * 
 * 启用jsr250相关的安全注解:**jsr250Enabled = true**
 * <p>会导入配置:Jsr250MethodSecurityConfiguration</p>
 * <p>jsr250包括@RolesAllowed@PermitAll@DenyAll</p>
 */ 
@Configuration
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class AspectjSecurityConfig {
   

}

/**
 * 这个类的所有方法都需要有ROLE_USER角色才能执行
 */
@Service
@Secured("ROLE_USER")
public class SecuredService {
   

	public void secureMethod() {
   
		// nothing
	}

}

@Service
public class SecuredService {
   
    

	public void publicMethod() {
   
		// nothing
	}
    /**
     * 这个方法需要ROLE_USER才能执行
     * 这三种配置方式都是等价的,只是提供支持的Advice不同。
     */ 
	@Secured("ROLE_USER")
    // @RolesAllowed("ROLE_USER")
    // @PreAuthorize("hasRole('ROLE_USER')")
	public void secureMethod() {
   
		// nothing
	}

	@PreAuthorize("arguments[0] ne 'tony'")
	// @PreAuthorize("filterObject ne 'tony'")
    public void preAuthorize(@RequestParam("userCode") String userCode) {
   
        // @preAuthorize不会赋值ROOT的filterObject和returnObject,因此无法使用入参。只能使用MethodSecurityExpressionRoot的其他方法
		// 这个方法的实验现象为:不管传什么都能通过表达式
        logger.info("preAuthorize:{}", userCode);
        // nothing
    }

    /** 	
     * 这个入参会被过滤地只剩下与authentication.name一样的
     */ 
	@PreFilter("filterObject == authentication.name")
	public void preFilter(@RequestParam("userCodeList") List<String> userCodeList) {
   
		// http://localhost:8090/foo/preFilter?userCodeList=leo,tony
        logger.info("preAuthorize:{}", userCode);
	}

    /**
     * 这个方法需要ROLE_USER才能执行
     */ 
	@PostAuthorize("returnObject ne 'tony'")
	public String postAuthorize(@RequestParam("userCode") String userCode) {
   
		// 传入的是不是tony,会抛出异常: 403
        logger.info("postAuthorize:{}", userCode);
		return userCode;
	}

    @GetMapping("/preAuthorizeSpel")
    @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
    public void preAuthorizeHasRole() {
   
        logger.info("preAuthorizeSpel:{}", userCode);
        // nothing
    }

    /**
     * 这个方法需要ROLE_USER才能执行
     */ 
	@PostFilter("returnObject == authentication.name")
	public List<String> PostFilter(@RequestParam("userCodeList") List<String> userCodeList) {
   
		// 实验结果就是,传入了的参数有值,但只有跟用户名一样的才会返回
        logger.info("PostFilter:{}", userCodeList);
		return userCodeList;
	}

}

以上就是怎么使用,接着我们看下是如何实现的。

基于方法授权方式的实现原理

前面我们说过,是基于AOP实现的。那么,现在我们从源码层面来看看。

我们可以看到上面按照@EnableMethodSecurity的配置,分别对应地导入三个配置。
但是,我们可以先从AOP的角度设想一下,我们需要的是哪种类型的通知?不妨归类一下:

注解类型 通知类型 描述/备注
@PreFilter@PreAuthorize@Secured以及JSR-250的相关注解 前置通知 这些很明显都是需要先校验/过滤参数再执行目标方法
@PostFilter@PostAuthorize 后置通知 先执行目标方法,再校验/过滤结果集

接着我们来看看Spring Security的设计:

AuthorizationManagerBeforeMethodInterceptor

在执行方法之前进行鉴权,这也意味着,当权限不足时,他会抛出异常。
实际上,他是一个通用的增强,全取决于你怎么使用它。
在SpringSecurity的配置里,他可以负责@PreAuthor,也可以负责@Secured,甚至还能负责jsr250的相关注解。
需要提醒一点,一个实例对象只能一种注解哈。

但是,大家有没有想过一个问题:为什么@PreAuthor@Secured以及jsr250的相关注解,都能交给他来处理?
又或者说,他的设计是如何将这三者的处理抽象统一起来的?更具体一点,此三者有何共同之处,可以进行抽象和统一的??

要回答这个问题,我们需要先从这个三者的入手:
共同点:

都需要在执行方法前进行权限校验,校验不通过则都需要抛出异常,阻断方法调用。

异同点:

注解 配置描述 执行
@PreAuthorize 配置的是SPEL表达式 通过执行表达式来得出是否满足访问权限
@Secured 指定角色 需要校验当前用户是否拥有指定角色
jsr250的@PermitAll - 任何人都可以访问
jsr250的@DenyAll - 任何人都不能访问
jsr250的@RolesAllowed 指定角色 需要校验当前用户是否拥有指定角色

从共同点出发,本质上无非就是鉴权嘛,这不是很符合Spring Security的AuthorizationManager的职责吗?
然后就是不同点,我们发现无非就是权限的配置来源不同需要解析不同的注解咯。
有了这个思路,接下来我们翻找源码,理解起来就容易多了。

public final class AuthorizationManagerBeforeMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
   
	// 构造器,需要传入Pointcut,和AuthorizationManager<MethodInvocation>,以便校验权限。
	public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut,
			AuthorizationManager<MethodInvocation> authorizationManager) {
   
		Assert.notNull(pointcut, "pointcut cannot be null");
		Assert.notNull(authorizationManager, "authorizationManager cannot be null");
		this.pointcut = pointcut;
		this.authorizationManager = authorizationManager;
	}

	/**
	 * 创建负责处理@PreAuthorize的增强
	 */
	public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
			PreAuthorizeAuthorizationManager authorizationManager) {
   
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
				AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
		interceptor.setOrder(
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值