码农在囧途
如果你觉得你的祖国不好,你就去建设它,如果你觉得政府不好,你就去考公务员去做官,如果你觉得人民没素质,就从你开始做一个高素质的公民,如果你觉得同胞愚昧无知,就从你开始学习并改变身边的人,而不是一昧的谩骂,抱怨,逃离。横眉冷对干夫指,俯首甘为孺子牛。
前言
权限认证是每个程序最基本也是最重要的部分,我们在软件开发过程中对接口的权限认证是必不可少的,一般我们会采用开源的框架进行认证,比如Apache Shiro,SpringSecurity等安全框架,熟悉Shrio和SpringSecurity
的同学通常会在接口上看到@RequiresPermissions("sys:user:add")
,@PreAuthorize("hasRole('admin')")
这样的注解,前一个是Shrio的,是基于操作的方式,后一种是SpringSecurity的,是基于角色的,那么我们该怎么实现一个自己的权限认证框架呢,其实实现并不难,今天我们就使用切面AOP来实现接口的权限认证。
实现步骤
我们是基于Spring的AOP实现,使用声明式注解,基于角色的方式来实现,只需要在需要认证的接口上加上注解,并指明什么角色能访问,当用户发起访问的时候,如果权限注解包含访问的用户角色,那么就放行,如果不包含访问用户的角色,则拒绝访问。
下面开始编码实现
权限注解
定义一个注解@PreAuthorize
,标注在方法上,参数为一个数组,因为同一个接口一般需要能够多个角色访问。
/**
* @Author 刘牌
* @Date 2020/3/15 0015 19:16
* @Version 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Inherited
public @interface PreAuthorize {
/**
* 用于接口上的权限验证,对象是角色,是一个数组
* @return
*/
String[] value() default {} ;
}
判断是否有访问权限
AuthFunc
类的作用是判断请求用户是否有访问权限,参数为PreAuthorize
注解和一个角色集合roles
,这个角色集合是请求用户的角色集合,如果能够访问,返回true
,否则返回false
。
/**
* @Author 刘牌
* @Date 2020/3/18 0018 23:31
* @Version 1.0
*/
public class AuthFunc {
/**
* 判断用户能否访问接口 , 返回true代表有权限 , 返回false代表没权限
* @param auth
* @return
*/
public static boolean isContain(PreAuthorize auth , List<String> roles){
return !Collections.disjoint(new ArrayList<>(Arrays.asList(auth.value())),roles);
}
}
权限切面
SecurityAspect
类是一个切面,使用注解@Aspect
进行标注,注意还需要标注@Component
注解,表示这个切面是一个Bean,不然Spring管理不到,这个切面的作用是拦截请求,因为我们在系统发起请求肯定会带上一个token
,这个token一般我们使用JWT
来生成,然后放在请求头上,token里面一般会包含用户信息,在SecurityAspect切面中我们通过
HttpServletRequest
来获取请求头token,然后解析出角色集合,然后调用AuthFunc.isContain(auth, roles)
方法判断是否有访问权限,如果有,则放行请求,如果没有,则提示没有权限信息。
/**
* @Author 刘牌
* @Date 2020/3/13 0013 18:04
* @Version 1.0
*/
@Component
@Aspect
public class SecurityAspect {
@Resource
private HttpServletRequest request;
@Pointcut("@annotation(auth)")
public void pointcutPreAuthorize(PreAuthorize auth) {
}
@Around("pointcutPreAuthorize(auth)")
public Object doPreAuthorize(ProceedingJoinPoint point, PreAuthorize auth) throws Throwable {
String token = request.getHeader(GlobalConstant.TOKEN);
List<String> roles = JWT.decode(token).getClaim(PayloadConstant.ROLE_NAME_LIST).asList(String.class);
if (AuthFunc.isContain(auth, roles)) {
return point.proceed();
} else {
throw new BusinessException("没有访问权限");
}
}
}
使用
在需要在权限认证的接口上标注@PreAuthorize
注解,如下标注了@PreAuthorize({"super_admin","admin"})
注解,代表此接口需要有"super_admin"
和"admin"
角色才能操作。
/**
* @Author 刘牌
* @Date 2020/4/22 0022 23:45
* @Version 1.0
*/
@RestController
@RequestMapping("/menu")
public class MenuController extends BaseController {
@Resource
private IMenuService menuService;
/**
* 添加菜单
* @param menu
*/
@PostMapping("/add")
@PreAuthorize({"super_admin","admin"})
public R add(@RequestBody Menu menu){
return R.success(menuService.add(menu));
}
}
结语
通过上面的操作,我们就完成了一个简单的基于角色的接口权限认证,比较简单,核心就是使用AOP,但在实际开发中,往往会有多种情况,使用基于角色的接口权限认证显得粒度有一点大,那么我们也可以实现AOP来完成基于操作(比如按钮操作,sys:user:add形式)的认证方式,只需要获取用户的操作列表,然后进行判断,通常用户的操作比较多,为了加快认证的速度,我们可以放入本地缓存或者分布式缓存中,不过我们多数都会选择线程的权限认证框架来进行开发,不过我们很有必要自己去造一下轮子。
今天的分享就到这里,感谢你的观看,我们下期见。