项目中需要用到权限管理,但是角色非常少,觉得使用Spring Security会比较重。所以,打算自己写一个简单的角色检查,实现原理就是:
把用户的信息(一定要包含角色ID)放到Session里,然后在Controller方法上加注解,该注解会标记哪些角色可以访问,通过AOP反射解析注解,同时从Session里拿到请求用户的角色ID进行比较。
由于使用到了AspectJ,所以要在Spring的配置文件里声明下:
<aop:aspectj-autoproxy/>
先定义一个注解,它是角色ID被检查的载体:
/**
* 这个注解用来确定哪些用户可以做哪些操作,目前我们只有3个角色,管理员,高级用户,普通用户。
* 其中普通用户只能查看,管理员可以操作一切,高级用户只比管理员少了管理用户的功能。
*/
@Documented//文档
@Retention(RetentionPolicy.RUNTIME)//在运行时可以获取
@Target(ElementType.METHOD)//作用到类,方法,接口上等
public @interface RolesAllowed {
public String value() default "";
}
再定义AOP拦截器,内部通过反射解析注解(顺带可以检查用户是否登录过):
/**
* 这个类用来拦截请求,如果用户没有登录,则被要求先登录。
* 任何请求访问的Controller的方法都会被检测,看是否包含
* RolesAllowed注解,如果有就看当前登录用户是否有权限操作这个方法。
*/
@Component
@Aspect
public class RoleControlAspect {
@Before("execution(* com.hebta.vinci.controller.*Controller.*(..))")
public void logBefore(JoinPoint joinPoint) throws Exception {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
if (!(session != null || request.getRequestURI().contains("login")
|| request.getRequestURI().contains("logout"))){
throw new RuntimeException("用户尚未登录。");
}
User user = new User();
try {
user = (User)session.getAttribute(VinciConstants.SESSION_ATTR_USER);
} catch (Exception e) {
throw new RuntimeException("用户没有登录过,请先登录。");
}
int roleId = 0;
if (user != null){
roleId = user.getRoleId();
}
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(RolesAllowed.class)){
RolesAllowed annotation = method.getAnnotation(RolesAllowed.class);
if (annotation != null){
String rolesStr = annotation.value();
for (String role : rolesStr.split(",")){
if (Integer.parseInt(role) == roleId){
return;
}
}
throw new RuntimeException("用户权限不够。");
}
}
}
}
这个类有几个需要注意的地方:
1. 必须加上@Component 注解,不然Spring的<mvc:annotation-driven/>扫描不到;
2. 必须使用 MethodSignature,不然无法获得Method 类,也就没办法获得定义其上的 RolesAllowed 注解。
最后就是在 Controller的方法上加上 RolesAllowed 注解,里面标上哪些角色可以访问,为了简单,我直接使用逗号分隔角色ID:
@RolesAllowed("6")
@RequestMapping(value="{id}",method=RequestMethod.DELETE)
public BaseResponse deleteUser(@PathVariable Integer id, HttpServletRequest request){
BaseResponse resp = new BaseResponse<>(RESPONSE_STATUS.SUCCESS);
int result = userService.deleteUser(id);
if (result <= 0){
resp.setStatus(RESPONSE_STATUS.FAIL);
resp.setMsg("标记用户不可用失败。");
} else {
resp.setMsg("成功标记用户不可用。");
}
return resp;
}
稍微补充下,可以定义一个专门拦截Controller 的拦截器,Spring 有个 @ControllerAdvice 注解,使用它就可以拦截所有Controller的事件,下面的代码只是拦截 Controller 里面抛出的异常。我们项目组开发是前后端分离的,所以只需要返回一个JSON字符串即可:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = Logger.getLogger(GlobalExceptionHandler.class);
@ResponseBody
@ExceptionHandler(value = Throwable.class)
public BaseResponse<String> defaultErrorHandler(HttpServletResponse response, Throwable ex) throws Exception {
BaseResponse<String> resp = new BaseResponse<>(RESPONSE_STATUS.FAIL);
resp.setMsg(ex.getMessage());
logger.debug("error occurs: ", ex);
return resp;
}
}