java注解浅析,java自定义注解并结合aop实现权限控制(二)

之前简单的记录了一下java的注解使用及解析java注解简述及自定义注解的简单使用,但是纸上谈兵终究不是程序员擅长的事,今天记录一下常见的权限系统使用注解实现的逻辑

归根结底,权限限制就是对比当前用户所持有的权限身份以及他即将执行的动作所需要的权限,若两者匹配,则执行逻辑,若不匹配,则返回提示。所以这里实际上只需要的两个重要参数,一个是用户持有的权限,一个是执行所需的权限。
执行权限是系统持有的,可以放在任何我们可以读取到的地方,此处我使用注解,自然是选择将这个系统权限放在注解里面
先定义一个简单的权限枚举:


public enum RoleValidEnum {
    VIP1(1,"vip1"),
    VIP2(2,"vip2"),
    VIP3(3,"vip3"),
    VIP4(4,"vip4"),
    VIP5(5,"vip5"),
    VIP6(6,"vip6"),
    SVIP(100,"svip");

    String msg;
    int vipWeight;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    RoleValidEnum(int vipWeight, String msg){
        this.vipWeight=vipWeight;
        this.msg=msg;
    }

    public static boolean hasPower(RoleValidEnum userPower,RoleValidEnum limitPower){
        return  userPower.vipWeight>=limitPower.vipWeight;
    }

    public static RoleValidEnum getPower(String msg){
        for(RoleValidEnum roleValidEnum:RoleValidEnum.values()){
            if(StringUtils.equals(msg,roleValidEnum.getMsg())){
                return roleValidEnum;
            }
        }
        return null;
    }

然后是自定义一个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleValid {
    RoleValidEnum role() default RoleValidEnum.VIP1;
}

此时我们就可以在对应的方法上灵活的配置方法所需的权限,比如

  	@Override
    @RoleValid(role=RoleValidEnum.VIP3)
    public void doVip3(Vip3Param param){
        System.out.println("vip3 办事~~~~~~~~~~~~~~~~~~~~~~~~~~~");
    }

这里就可以用来表示我这个方法的执行权限是vip3,因为我已经可以从这个方法里面读取到系统的执行权限信息了,那么接下来我们要做的还有两件事,一个是在执行方法前检测到这个注解并提取信息,第二个就是获取用户权限信息并与执行权限作对比

先来第一个,执行方法前的动作,spring提供了两个强大且易用的功能,一个是过滤器,一个是aop,由于过滤器的生效是在请求阶段,有些局限性,所以我今天选择使用aop来实现

导入aop所需依赖 我是使用的springboot2.1.4搭建的项目, maven依赖:

<!--aop依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--aop依赖-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

首先我们用最常规的思路,我们将用户信息放在方法的入参里面,入参类

public class Vip3Param {
    private String extence;
    private String userName;
    private String password;
    private String userId;
    private String power;
    get /set省去
}

关于aop的知识有很多大神提供了很好的一些技术资料,我这里只简单的分析一下我们的业务场景,首先我们需要的事方法执行前的动作,满足这个条件的就只有before前置增强和around环绕增强了,又因为around有完善的api提供了对目标方法的拦截,所以我这里选择使用@around来做这个功能,当然使用@Before也可以实现对目标方法的拦截,我会在后面放一段例子。先上正餐

@Aspect
@Component
public class RoleValidAround {
    @Autowired
    HttpSession httpSession;

    private static Gson gson = new Gson();
    private Logger logger = LoggerFactory.getLogger(RoleValidAround.class);

    @Pointcut("@annotation(com.yinyuecheng.jioencryption.DiyIntegerface.RoleValid)")
    public void RoleValidAroundPointCut(){}

    @Around("RoleValidAroundPointCut()")
    public Object validAround(ProceedingJoinPoint joinPoint){
    //先获取目标方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        //获取目标方法的注解及其属性
        RoleValid roleValid = method.getAnnotation(RoleValid.class);
        RoleValidEnum limitPower = roleValid.role();
        //获取目标方法入参并提取所需权限信息
        Object[] objs = joinPoint.getArgs();
        ConcurrentHashMap map = gson.fromJson(gson.toJson(objs[0]),ConcurrentHashMap.class);
        String user = (String)map.get("power");
        RoleValidEnum userPower = RoleValidEnum.getPower(user);
        logger.info("~~~~~~~~~~limitPower:{},~~~~~~~~~~~userPower:{}",limitPower,userPower);
        if(RoleValidEnum.hasPower(userPower,limitPower)){
            Object result = null ;
            try {
            	//注意:使用环绕增强around时如果不显示的使用proced方法的话,目标方法便不会执行
                result = joinPoint.proceed();
                return result;
            }catch(Throwable e){
                return new AuthorityException(500000,"system error");
            }
        }else{
            logger.info("~~~~~~~~~~~~~~~~~have no power");
            return null;
        }

    }

相关代码的用途已在代码中有了注解,都是spring或jdk提供的现成的方法。下面我们来测试一下

    @Test
    public void testForbid() throws Exception {
        Vip3Param param = new Vip3Param();
        param.setPower(RoleValidEnum.VIP2.getMsg());
        basicInfoService.doVip3(param);
    }

输出结果

2019-05-30 15:04:13 013 [main] INFO  com.yinyuecheng.jioencryption.aop.RoleValidAround - ~~~~~~~~~~limitPower:VIP3,~~~~~~~~~~~userPower:VIP2
2019-05-30 15:04:13 013 [main] INFO  com.yinyuecheng.jioencryption.aop.RoleValidAround - ~~~~~~~~~~~~~~~~~have no power
2019-05-30 15:04:13 013 [Thread-2] INFO  o.s.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'

Process finished with exit code 0

而当我们更改用户vip信息后

    @Test
    public void testForbid() throws Exception {
        Vip3Param param = new Vip3Param();
        param.setPower(RoleValidEnum.VIP3.getMsg());
        basicInfoService.doVip3(param);
    }

此处权限更改为vip3,输出结果

2019-05-30 15:06:37 037 [main] INFO  com.yinyuecheng.jioencryption.aop.RoleValidAround - ~~~~~~~~~~limitPower:VIP3,~~~~~~~~~~~userPower:VIP3
vip3 办事~~~~~~~~~~~~~~~~~~~~~~~~~~~
2019-05-30 15:06:37 037 [Thread-2] INFO  o.s.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'

Process finished with exit code 0

一直到这里,一切看起来都是那么美好,但是我们看一下切面的这段代码

        Object[] objs = joinPoint.getArgs();//这里获取了所有的入参信息
        ConcurrentHashMap map = gson.fromJson(gson.toJson(objs[0]),ConcurrentHashMap.class);//这里是取的第一个参数
        String user = (String)map.get("power");//取名为power的参数

这么一看简直到处都是漏洞,首先方法如果不只一个入参并且携带权限信息的参数不在第一个,就挂掉了。其次虽说需要执行权限校验的逻辑带上权限信息是必须的,但是也不一定就叫power,又挂掉了。所以,我们可以再改进一下。
既然做权限的校验就必须带上权限信息,那我们何不把这部分信息独立出来呢

public class UserInfo {
    private String userName;
    private String password;
    private String userId;
    private String power;
}

然后我们只需要在其余的参数中继承这个类就可以了

public class Vip3Param  extends UserInfo{
    private String extence;
}

切面的代码也需要优化一下

        Object[] objs = joinPoint.getArgs();
        Class [] paramClasses = method.getParameterTypes(); //获取目标方法的所有参数类型
        UserInfo userInfo = null;
        for(int i=0;i<paramClasses.length;i++){
            if(paramClasses[i].newInstance() instanceof UserInfo){
                userInfo = gson.fromJson(gson.toJson(objs[i]),UserInfo.class);
            }
        }
        if(null==userInfo){
            System.out.println("无可检索的权限信息,请检查入参是否规范");
            return null;
        }
        String user = userInfo.getPower();

我们更改一下目标方法,让他拥有两个参数

    @Override
    @RoleValid(role=RoleValidEnum.VIP3)
    public void doVip3(String test,Vip3Param param){
        System.out.println("vip3 办事~~~~~~~~~~~~~~~~~~~~~~~~~~~");
    }

测试代码

    @Test
    public void testForbid() throws Exception {
        Vip3Param param = new Vip3Param();
        param.setPower(RoleValidEnum.VIP2.getMsg());
        basicInfoService.doVip3("test",param);
    }

运行结果

2019-05-30 15:32:20 020 [main] INFO  com.yinyuecheng.jioencryption.aop.RoleValidAround - ~~~~~~~~~~limitPower:VIP3,~~~~~~~~~~~userPower:VIP2
2019-05-30 15:32:20 020 [main] INFO  com.yinyuecheng.jioencryption.aop.RoleValidAround - ~~~~~~~~~~~~~~~~~have no power
2019-05-30 15:32:20 020 [Thread-2] INFO  o.s.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'

Process finished with exit code 0

到了这里,代码风险已经相对可控了,我们只需要告知对应的开发人员在使用这个注解的时候注意入参继承一下就可以了,在某些场景下可能确实只能做到这样,但是我们的权限系统不是,我们回想一下,我们的用户系统在用户登陆之后做的事,其中一个很重要的就是把用户信息放到session里面,而我们可以在整个请求流程里面获取到这个session并提取他的信息
由于我们的切面也是交给spring管理的,所以我们可以直接使用@Autowired来获取session,这样获取用户信息也变得异常简单

@Autowired
    HttpSession httpSession;
    
        UserInfo userInfo = gson.fromJson(gson.toJson(httpSession.getAttribute("userInfo")),UserInfo .class);

并且这么做我们完全不需要关心方法本身的入参,业务逻辑的跟权限逻辑完全分开,分锅分的更清晰。

到此aop结合自定义注解实现权限控制圆满完成
另外关于使用@before前置增强阻断目标方法执行的法子就是抛异常,并且一定要是运行时异常 RuntimeException 或其子类,不然系统会抛出一个上层反射异常导致无法接收处理我们自定义的异常信息,部分代码如下:

  if(ageIn<forbidAge){
            System.out.printf("年龄太小,不让过");
            throw new  AuthorityException(400101,"年龄太小,不让过");
        }

异常类如下

public class AuthorityException extends RuntimeException {
    private int code;
    private String msg;
    public AuthorityException() {
        super();
    }
    public AuthorityException(int code,String msg) {
        super(msg);
        this.code=code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值