spring-boot aop 访问权限

使用spring-boot aop处理访问权限

spring-boot 2.0.1

1、pom.xml 添加aop-starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2、配置权限注解类 RequiresPermissions

package com.common.permission.aop.annotation;

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

import com.common.permission.aop.authz.Logical;

/**
 * 
 * Target: 注解类型,级别 Retention:RetentionPolicy.RUNTIME 运行时注解
 * 
 * @author kouht
 *
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {

	String[] value();

	Logical logical() default Logical.AND;
}

字段说明:value为权限值, logical为多个权限之间的链接关系,and和or的关系

3、Logical枚举类

package com.common.permission.aop.authz;

public enum Logical {
    AND, OR
}

4、切面处理

package com.common.permission.aop.aspect;

import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.common.permission.aop.annotation.RequiresPermissions;
import com.common.permission.aop.authz.Logical;
import com.common.permission.aop.exception.AuthenticationException;
import com.util.CookieUtils;
import com.util.JsonUtil;
import com.web.constant.FxtxConstant;
import com.web.menu.FxtxCookie;
import com.web.menu.FxtxPermission;

/**
 * 权限校验,负责对RequiresRoles、RequiresPermissions标记的controller进行权限校验
 * 他执行的时机是interceptor之后、方法执行之前 Aspect 切面标识 Component 交给spring管理
 * Order 设置aop优先级执行顺序,数字越小越先执行
 * @author kouht
 *
 */
@Order(2)
@Aspect
@Component
public class PermissionAspect {

	private static final Logger logger = LoggerFactory.getLogger(PermissionAspect.class);

	/**
	 * 定义一个切入点方法,即某方法执行时进行切入, 会拦截含有此注解的方法
	 */
	@Pointcut("@annotation(com.fxtx.oss.common.permission.aop.annotation.RequiresPermissions)")
	private void permissionAnnotation() {
		// 切入点签名
	}

	@Around("permissionAnnotation()")
	public Object executeAround(ProceedingJoinPoint jp) throws Throwable {

		// 获取RequestAttributes
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		// 从获取RequestAttributes中获取HttpServletRequest的信息
		HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
		HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
		// 获取用户所有权限
		Set<String> userPermissions = getLoginUserPermission(request);

		Signature signature = jp.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		Method targetMethod = methodSignature.getMethod();
		// 根据被代理的类对象获取要执行的方法
		Method realMethod = jp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());

		if (hasPermission(realMethod, userPermissions)) {
			// 用户拥有该方法权限时执行方法里面的内容
			return jp.proceed();
		} else {
			// 用户没有权限,则直接返回没有权限的通知
			if (realMethod.isAnnotationPresent(ResponseBody.class)) {
				// 判断是否有返回体
				response.setHeader("Content-type", "application/json; charset=UTF-8");
				OutputStream outputStream = response.getOutputStream();
				Map<String, String> resultMsg = new HashMap<>();
				resultMsg.put("msg", "权限不足,禁止访问!");
				outputStream.write(new ObjectMapper().writeValueAsString(resultMsg).getBytes("UTF-8"));
				return null;
			} else {
				throw new AuthenticationException("校验用户权限失败,权限不足");
			}
		}
	}

	/**
	 * 获取登录用户权限
	 * 
	 * @param request
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Set<String> getLoginUserPermission(HttpServletRequest request) {

		String value = CookieUtils.getCookieValue(request, FxtxConstant.FXTX_COOKIE_KEY, true);
		if (value != null) {
			FxtxCookie fxtxCookie = JsonUtil.jsonToPojo(value, FxtxCookie.class);
			String code = fxtxCookie.getCode();
			Long tenantId = fxtxCookie.getTenantId();
			String key = code + "-" + tenantId;
			List<FxtxPermission> permissionsList = (List<FxtxPermission>) request.getSession().getAttribute(key);

			if (!CollectionUtils.isEmpty(permissionsList)) {
				Set<String> permissions = new HashSet<>();
				for (int p = 0; p < permissionsList.size(); p++) {
					if (!permissionsList.get(p).getPermissionValue().isEmpty()) {
						permissions.add(permissionsList.get(p).getPermissionValue());
					}
				}
				return permissions;
			}
		}

		return new HashSet<>();
	}

	/**
	 * 判断用户是否拥有权限
	 * 
	 * @param realMethod 访问当前的方法
	 * @param permissions 已拥有的所有权限
	 * @return
	 */
	private boolean hasPermission(Method realMethod, Set<String> permissions) {

		try {
			if (realMethod.isAnnotationPresent(RequiresPermissions.class)) {
				// 获取该方法的指定注解
				RequiresPermissions requiresPermissions = realMethod.getAnnotation(RequiresPermissions.class);
				// 获取方法的权限
				String[] values = requiresPermissions.value();
				Set<String> requiresPermissionsSet = new HashSet<>();

				StringBuilder permissionStr = new StringBuilder();
				for (int i = 0; i < values.length; i++) {
					requiresPermissionsSet.add(values[i]);
					permissionStr.append(values[i] + ", ");
				}
				logger.info(realMethod + " RequiresPermissions = " + permissionStr);
				// 获取权限值的链接关系
				if (requiresPermissions.logical() == Logical.OR) {
					// 任一权限
					return hasAnyPermission(permissions, requiresPermissionsSet);
				} else {
					// 所有权限
					return hasAllPermission(permissions, requiresPermissionsSet);
				}
			}
		} catch (Exception e) {
			return false;
		}
		
		return false;
	}

	/**
	 * 是否有所有权限
	 * 
	 * @param permissionNames
	 * @return
	 */
	private boolean hasAllPermission(Set<String> permissions, Set<String> requiresPermissions) {

		return !permissions.isEmpty() && !requiresPermissions.isEmpty() && permissions.containsAll(requiresPermissions);
	}

	/**
	 * 是否有任一权限
	 * 
	 * @param permissionNames
	 * @return
	 */
	private boolean hasAnyPermission(Set<String> permissions, Set<String> requiresPermissions) {

		boolean hasAnyPermission = false;

		if (!permissions.isEmpty() && !requiresPermissions.isEmpty()) {

			for (String permission : requiresPermissions) {
				// 判断权限是否存在
				if (permissions.contains(permission)) {
					hasAnyPermission = true;
					break;
				}
			}
		}

		return hasAnyPermission;
	}

}

5、异常处理

package com.common.permission.aop.exception;

public class PermissionsException extends RuntimeException {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
     * Creates a new PermissionsException.
     */
    public PermissionsException() {
        super();
    }

    /**
     * Constructs a new PermissionsException.
     *
     * @param message the reason for the exception
     */
    public PermissionsException(String message) {
        super(message);
    }

    /**
     * Constructs a new PermissionsException.
     *
     * @param cause the underlying Throwable that caused this exception to be thrown.
     */
    public PermissionsException(Throwable cause) {
        super(cause);
    }

    /**
     * Constructs a new PermissionsException.
     *
     * @param message the reason for the exception
     * @param cause   the underlying Throwable that caused this exception to be thrown.
     */
    public PermissionsException(String message, Throwable cause) {
        super(message, cause);
    }

}
package com.common.permission.aop.exception;

public class AuthenticationException extends PermissionsException {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
     * Creates a new AuthenticationException.
     */
    public AuthenticationException() {
        super();
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     */
    public AuthenticationException(String message) {
        super(message);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param cause the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(Throwable cause) {
        super(cause);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     * @param cause   the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(String message, Throwable cause) {
        super(message, cause);
    }
    
}

6、处理权限异常处理跳转,权限异常跳转到403页面

package com.web;

import org.springframework.web.bind.annotation.ExceptionHandler;

import com.common.permission.aop.exception.AuthenticationException;

public abstract class BaseController {
	
	/**
	 * 授权登录异常
	 */
	@ExceptionHandler({AuthenticationException.class})
    public String authenticationException() {  
        return "error/403";
    }

}

7、所需拦截方法添加权限拦截控制

package com.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.common.permission.aop.annotation.RequiresPermissions;
import com.common.permission.aop.authz.Logical;
import com.web.BaseController;


@Controller
@RequestMapping("/test")
public class Controller extends BaseController {

	
	/**
	 * 房东信息
	 * @return
	 */
	@RequiresPermissions("employee:view")
	@RequestMapping(value = {"/view" }, method = RequestMethod.GET)
	public String landlordManagementIndex() {
		
		return "view.html";
	}
	

	@RequiresPermissions(value={"employee:create", "employee:hello"}, logical=Logical.AND)
	@GetMapping("hello")
	@ResponseBody
	public String hello() {
		
		return "{\"ret\":\"0\",\"data\":\"你好aop\"}";
	}
}

@RequiresPermissions(value={"employee:create", "employee:hello"}, logical=Logical.AND)

表示访问需要有employee:create和employee:hello这两个权限,logical=Logical.AND表示这两个权限都必须有才可以访问

logical=Logical.OR表示任何一个权限都可以访问

 

注意: 问如果访问aop不生效,需要看切面的文件目录是否和主程序目录是否在相同目录下,spring-boot主程序启动默认扫描主程序同目录及同目录下的所有文件,如果不在同目录,则扫描不到切面,这个aop不生效,如果需要生效,解决方法两个:

1、将切面方放主程序目录下

2、主程序添加配置 @ComponentScan("aop目录")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值