前言:
目前安全问题越来越重要,以前只需要前端权限控制,目前前端的权限控制已经无法满足当前的安全需要了,一个更细粒度的控制,也就是接口粒度的控制变得越来越重要。
思路:
权限控制还是传统的RBAC(Role-Based Access Control: 基于角色访问控制)思想,添加了给角色绑定某个菜单的某些行为的能力。
用户,角色,菜单,行为
用户和角色绑定。
角色决定 有哪些菜单的哪些行为。
比如。 拥有某个菜单的行为( 添加、删除、修改、导出、导入、打印)
每个菜单在设计时,必须指定一个id,如xxxx_A,菜单定义为常量。行为定义为枚举类,权限由菜单和行为构成。
每个接口,可以有多个权限。也就是可以归属多个菜单的多个行为。
具体操作:
菜单常量
public interface MenuConstant {
String XXXX_XXX_A = "XXXX_XXX_A";
String XXXX_XXX_B = "XXXX_XXX_B";
String XXXX_XXX_C = "XXXX_XXX_C";
}
行为枚举
public enum ActionEnum {
QUERY,
ADD,
UPDATE,
DELETE,
EXPORT,
IMPORT,
PRINT;
}
功能注解
public @interface Permission{
String menu();
ActionEnum[] actions();
}
认证注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuth {
Permission[] permissions();
}
使用举例
@RestController
@RequestMapping("/test")
public class Controller {
@PreAuth(permissions= {@Permission(menu = MenuConstant.XXXX_XXX_A, actions = {ActionEnum.ADD, ActionEnum.IMPORT})})
@RequestMapping("/methodA")
public void methodA() {
}
}
切面处理
public class UnPassPreAuthException extends Exception {
public UnPassPreAuthException() {
super();
}
public UnPassPreAuthException(String message) {
super(message);
}
}
@Aspect
@Component
public class PreAuthAspect {
@Pointcut("@annotation(com.luoq.auth.annotion.PreAuth)")
public void preAuthPointcut() {
}
@Before(value = "preAuthPointcut()")
public void aspectBefore(JoinPoint joinPoint) throws UnPassPreAuthException{
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
PreAuth preAuth = method.getAnnotation(PreAuth.class);
if (preAuth != null) {
Permission[] permissions= preAuth.permissions();
boolean hasAuth = validateHasAuth(permissions);
if (!hasAuth) {
String methodInfo = getMethodInfo(method);
YnLogger.error("PreAuth > {}, Permissions: {}, Not Pass PreAuth!", methodInfo, permissions);
throw new UnPassPreAuthException(methodInfo + " is not passing preAuth !");
}
}
private boolean validateHasAuth(Permission[] permissions){
// todo 验证当前用户是否有权限
return false;
}
private String getMethodInfo(Method method) {
String[] attr = method.toString().split(" ");
return attr[attr.length - 1];
}
}
下面的处理很灵活,根据自己系统的框架进行处理,可以是filter,可以是spring的统一异常处理。
public class PreAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception e) {
Throwable causeOne = e.getCause();
Throwable cause = null;
if (causeOne instanceof UnPassPreAuthException) {
cause = causeOne;
} else if (causeOne.getCause() instanceof UnPassPreAuthException) {
cause = causeOne.getCause();
}
if (cause != null) {
log.error("403 Access Forbidden: {}", cause.getMessage(), cause);
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setStatus(403);
response.getOutputStream().write(("403 Access Forbidden!").getBytes(StandardCharsets.UTF_8));
}
}
}
}