使用Spring AOP实现接口权限认证

2022-07-26 17:14:24 浏览数 (1)

码农在囧途

如果你觉得你的祖国不好,你就去建设它,如果你觉得政府不好,你就去考公务员去做官,如果你觉得人民没素质,就从你开始做一个高素质的公民,如果你觉得同胞愚昧无知,就从你开始学习并改变身边的人,而不是一昧的谩骂,抱怨,逃离。横眉冷对干夫指,俯首甘为孺子牛。

前言

权限认证是每个程序最基本也是最重要的部分,我们在软件开发过程中对接口的权限认证是必不可少的,一般我们会采用开源的框架进行认证,比如Apache Shiro,SpringSecurity等安全框架,熟悉Shrio和SpringSecurity的同学通常会在接口上看到@RequiresPermissions("sys:user:add"),@PreAuthorize("hasRole('admin')")这样的注解,前一个是Shrio的,是基于操作的方式,后一种是SpringSecurity的,是基于角色的,那么我们该怎么实现一个自己的权限认证框架呢,其实实现并不难,今天我们就使用切面AOP来实现接口的权限认证。

实现步骤

我们是基于Spring的AOP实现,使用声明式注解,基于角色的方式来实现,只需要在需要认证的接口上加上注解,并指明什么角色能访问,当用户发起访问的时候,如果权限注解包含访问的用户角色,那么就放行,如果不包含访问用户的角色,则拒绝访问。

下面开始编码实现

权限注解

定义一个注解@PreAuthorize,标注在方法上,参数为一个数组,因为同一个接口一般需要能够多个角色访问。

代码语言:javascript复制
/**
 * @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

代码语言:javascript复制
/**
 * @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)方法判断是否有访问权限,如果有,则放行请求,如果没有,则提示没有权限信息。

代码语言:javascript复制
/**
 * @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"角色才能操作。

代码语言:javascript复制
/**
 * @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形式)的认证方式,只需要获取用户的操作列表,然后进行判断,通常用户的操作比较多,为了加快认证的速度,我们可以放入本地缓存或者分布式缓存中,不过我们多数都会选择线程的权限认证框架来进行开发,不过我们很有必要自己去造一下轮子。

今天的分享就到这里,感谢你的观看,我们下期见。

0 人点赞