SpringAOP对自定义注解类进行权限控制

HarryZhang 2019年07月06日 133次浏览

需求

权限控制:
在某些场景中可能会对某些操作进行一个权限的控制,比如: 后台相关的操作只有某个权限以上的人才能使用。如何实现呢? 突然想到用自定义注解(annotation)加上Spring中AOP对自定义的权限注解做一个@before的拦截判断是否有权限进行该操作。然后再通过全局异常处理对应的异常。

具体实现步骤:

  1. 自定义一个注解类@Authorize
  2. 自定义一个权限异常AuthorityException,在验证用户没有权限时抛出该异常
  3. 自定义一个SecurityAdvice通知,使用AOP前置通知拦截用户请求,判断用户是否登录已经是有具有访问当前Controller方法的权限,如果没有权限则抛出AuthorityException异常
  4. 定义一个处理AuthorityException的全局异常处理器AuthorityExceptionHandler,当发生AuthorityException异常时进行某些操作

首先自定义一个权限注解

package security;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface Authorize {
    String[] rules() default {};
}

@Target:用于描述注解的使用范围,超出范围时编译失败

  1. CONSTRUCTOR:用于描述构造器
  2. FIELD:用于描述域(成员变量)
  3. LOCAL_VARIABLE:用于描述局部变量
  4. METHOD:用于描述方法 5 .PACKAGE:用于描述包
  5. PARAMETER:用于描述参数
  6. TYPE:用于描述类、接口(包括注解类型) 或enum声明

自定义权限异常

package exception;

import org.springframework.http.HttpStatus;

/**
 * Insufficient permissions
 * @author HarryZhang
 */
public class AuthorityException extends RuntimeException {
    public AuthorityException(String message) {
        super(message);
    }

    public AuthorityException(String message, Throwable cause) {
        super(message, cause);
    }

    public HttpStatus getStatus() {
        return HttpStatus.UNAUTHORIZED; //401
    }
}

自定义一个SecurityAdvice通知

package aop;

import exception.AuthorityException;
import model.entity.User;
import model.entity.UserRole;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import security.Authorize;
import service.UserRoleService;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Aspect
public class SecurityAdvice {

    private UserRoleService userRoleService;

    @Autowired
    public SecurityAdvice(UserRoleService userRoleService) {
        this.userRoleService = userRoleService;
    }

    @Before("@within(security.Authorize)")
    public void checkAuthority(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        User loginUser = (User) request.getSession().getAttribute("loginUser");
        if (loginUser == null) {
            throw new AuthorityException("请先登录后操作哦");
        }
        //当前用户权限
        UserRole userRole = userRoleService.fetchById(loginUser.getUserRoleId());
        Signature signature = joinPoint.getSignature();
        // 获取切入的 Method
        MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
        Method method = joinPointObject.getMethod();

        boolean flag = method.isAnnotationPresent(Authorize.class);
        String[] rules = null;
        if (flag) {
            Authorize annotation = method.getAnnotation(Authorize.class);
            rules = annotation.rules();
        } else {
            // 如果方法上没有注解,则搜索类上是否有注解
            Authorize classAnnotation = AnnotationUtils.findAnnotation(joinPointObject.getMethod().getDeclaringClass(), Authorize.class);
            rules = classAnnotation.rules();
        }
        //判断权限
        if (!Arrays.asList(rules).contains(userRole.getName())) {
            throw new AuthorityException("你的权限不足!");
        }
    }
}

这里只是为了演示,所以登录成功后吧user放入了session方便判断

注意

  • @within(全限定类名)
  • 如果你想要再类上顶注释让整个controller下的方法全部被拦截的话你必须这么写
@Before("@within(security.Authorize)")
  • 如果你只是单单想要对某个方法拦截
@Before("@annotation(security.Authorize)")

全局异常处理

package controller.base;

import exception.AuthorityException;
import exception.BadRequestException;
import model.support.BaseResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@ControllerAdvice
public class ExceptionHandlerController {
    /**
     * 权限不足异常
     * @param model 向界面回推的信息
     * @param e exception
     * @return 返回视图
     */
    @ExceptionHandler(AuthorityException.class)
    public String handleAuthorityException(Model model,AuthorityException e){
        model.addAttribute("message",e.getMessage());
        return "error";
    }
}

到了这里基本就完成了。 下面看看使用的效果:

1

AdminController上顶一个给定管理员才能进行访问 或者在方法上指定权限 我下面用一个普通用户对AdminController进行访问

2

可以看到效果跟我预期的相同吧。 如果有哪里错误的地方,欢迎指出!