赞
踩
最近在整合完单点登录后,又来了一个新活,好在这个任务已经比较成熟,实现的方式也比较多,也有比较成熟的框架已经实现了它,它就是权限管理。
实现权限管理的方式有很多,我提出我通过查阅资料学习所知道的,如果有更多欢迎分享:
其中,1、2点都是基于在类或方法上加上一个自定义注解实现的,在通知或拦截器中通过获取类或方法上的注解的value值,来比较判断用户的权限,再决定是否放行执行我们的核心业务。
考虑到公司架构和其他原因,我使用到的是使用AOP进行鉴权认证。
实现过程如下:
RBAC是基于角色的访问控制,是用户通过角色与权限进行关联,为什么不直接给用户分配权限呢?简单来说,一个用户拥有多个角色,每个角色拥有若干权限。这样就构成了“用户-角色-权限”的授权模型。在这个模型中,用户与角色、角色与权限之间是多对多的关系。
根据角色授权的思想,我们需要设计五张表
用户表(user)
角色表(role)
权限表(permission)
这三个表之间都是多对多的关系,所以衍生出两个关联表如下
用户角色表(user_role)
角色资源表(role_permission)
1.定义注解
- @Retention(value = RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD,ElementType.TYPE})
- @Inherited
- @Documented
- public @interface PreAuthorize {
- String value();
- }
注解的value值代表的是方法所需的条件或权限
2.AuthorizeAspect切面定义
-
- @Aspect
- @Component
- public class AuthorizeAspect {
-
- @Autowired
- private ZsjScadaUserService userService;
-
- @Autowired
- private ZsjScadaUserRoleService userRoleService;
-
- @Autowired
- private ZsjScadaRolePermissionService rolePermissionService;
-
- @Autowired
- private ZsjScadaPermissionService permissionService;
-
- //定义切点
- @Pointcut("@annotation(com.metastar.vip.scada.service.annotation.PreAuthorize)")
- public void logPointCut(){
-
- }
- //鉴权通知
- //环绕通知选择原因:取消方法执行:在环绕通知中,我们可以选择不执行目标方法(JointPoint),从而取消方法的执行。这在鉴权过程中非常有用,如果用户没有相应的权限,我们可以直接返回错误信息,而不必执行目标方法。
- @Around("logPointCut()")
- public Object authAround(ProceedingJoinPoint joinPoint) throws Throwable{
-
- // TODO: 2023/8/31 获取目标方法中的HttpRequest -> 获取请求头携带的Cookie
- Object[] args = joinPoint.getArgs();
- String name = joinPoint.getSignature().getName();
- HttpServletRequest request = (HttpServletRequest) args[0];
- Object proceed = null;
- HttpSession session = request.getSession(false);
- if (session == null){
-
- return proceed = Result.fail("请先登录");
- }
- // TODO: 2023/8/31 根据cookie获取当前会话信息 -> 获取当前登录用户
- Assertion assertion = (Assertion) session.getAttribute("_const_cas_assertion_");
- Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
- String user = attributes.get("USER").toString();
- JSONObject userJsonObject = JSON.parseObject(user);
- String userId = userJsonObject.getString("userId");
- // TODO: 2023/8/31 根据用户ID查询该用户角色
- QueryWrapper<UserDO> queryWrapper = new QueryWrapper<>();
- queryWrapper.eq("id",userId).eq("in_use",1);
- UserDO userDO = userService.getOne(queryWrapper);
- if (user == null){
- throw new RuntimeException("该用户不存在");
- }
- // TODO: 2023/8/31 根据该角色去查询所拥有的权限
- QueryWrapper<UserRoleDO> userRoleDOQueryWrapper = new QueryWrapper<>();
- userRoleDOQueryWrapper.eq("user_id",userDO.getId());
- UserRoleDO one = userRoleService.getOne(userRoleDOQueryWrapper);
- Long roleId = one.getRoleId();
- QueryWrapper<RolePermissionDO> rolePermissionDOQueryWrapper = new QueryWrapper<>();
- rolePermissionDOQueryWrapper.eq("role_id",roleId);
- List<Long> permissionIds = rolePermissionService.list(rolePermissionDOQueryWrapper).stream().map(RolePermissionDO::getPermissionId).collect(Collectors.toList());
- // TODO: 2023/8/31 查看该权限所能访问的资源uri
- // TODO: 2023/8/31 查看当前目标类+方法的URI是否存在与当前用户权限所对应的资源uri列表中
- // TODO: 2023/8/31 存在->继续指定控制器中的方法 不存在->返回错误信息
- Signature signature = joinPoint.getSignature();
- MethodSignature methodSignature = (MethodSignature) signature;
- Object target = joinPoint.getTarget();
- //获取注解标注的类(鉴权以类为单位)
- // PreAuthorize annotation1 = target.getClass().getAnnotation(PreAuthorize.class);
- // String value = annotation1.value();
- //获取注解标注的方法(鉴权以方法为单位)
- Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
- //通过方法获取注解
- PreAuthorize annotation = method.getAnnotation(PreAuthorize.class);
- //获取注解中的值
- String permissionValue = annotation.value();
- Integer count = 0;
- for (Long permissionId : permissionIds) {
-
- String permissionName = permissionService.getById(permissionId).getPermissionName();
- if (permissionName.equals(permissionValue)){
- //该角色含有该权限 访问方法
- proceed = joinPoint.proceed(args);
- break;
- }
- count++;
- }
- if (count == permissionIds.size()){
- //没有该权限 抛出异常
- return proceed = Result.fail("权限不足,无法访问");
-
- }
- return proceed;
- }
-
- }
3.测试
- @RestController
- @RequestMapping("/auth")
- public class authTestController {
-
- @PostMapping("/test1")
- @PreAuthorize(value = "用户模块")
- public Result Test1(HttpServletRequest request,String userName){
-
- return Result.ok(userName);
- }
- }
Postman访问该url后,拦截后执行环绕通知进行鉴权,通过后访问该控制器中的Test1方法响应结果。
1.同样是定义自定义注解
- @Target({ElementType.TYPE, ElementType.METHOD}) //注解的作用域,即此注解应该被用在什么地方
- @Retention(RetentionPolicy.RUNTIME) //注解的生命周期,即注解在什么范围内有效
- @Inherited //标识注解,允许子类继承
- @Documented //标识注解,生成javadoc文档时,会包含此注解
- public @interface RoleNum {
- // int value(); //default 1;
- String value();
- }
2.定义拦截器
- @Component
- public class LoginInterceptor implements HandlerInterceptor {
-
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
-
- /**
- * 目标方法执行之前(Controller方法调用之前)
- * @param request
- * @param response
- * @param handler
- * @return
- * @throws Exception
- */
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-
- // TODO: 2023/7/21 获取前端Authorization的token值 访问redis
- // TODO: 2023/7/21 命中 放行 刷新redis中token的TTL
- // TODO: 2023/7/21 未命中(过期) 重新向登录页面 拦截
- // TODO: 2023/7/24 在验证到相应登陆的Token后 需要查看当前用户Role值 检验是否有该路径的访问权限
- String token = request.getHeader("Authorization");
- Long size = stringRedisTemplate.opsForHash().size(RedisConstant.LOGIN_INFO + token);
- if (size == 0){
- //重定向到登录页面
- // response.sendRedirect("/login");
- //拦截
- response.getWriter().write("{\"code\":-1,\"msg\":\"please login first\",\"data\":null}");
- return false;
- }else {
- String role = (String) stringRedisTemplate.opsForHash().get(RedisConstant.LOGIN_INFO + token, "role");
- if (hasPermission(handler,role)) {
- //权限足够
- //放行 并 刷新 用户信息token TTL
- stringRedisTemplate.expire(RedisConstant.LOGIN_INFO + token,RedisConstant.LOGIN_TTL, TimeUnit.MINUTES);
- return true;
- } else {
- //权限不足
- response.getWriter().write("{\"code\":-1,\"msg\":\"privilege is not enough\",\"data\":null}");
- //放行 并 刷新 用户信息token TTL
- stringRedisTemplate.expire(RedisConstant.LOGIN_INFO + token,RedisConstant.LOGIN_TTL, TimeUnit.MINUTES);
- return false;
- }
- }
- }
-
- /**
- * 验证权限是否足够
- * @param handler
- * @param role
- * @return
- */
- private boolean hasPermission(Object handler, String role) {
-
- if (handler instanceof HandlerMethod){
- HandlerMethod handlerMethod = (HandlerMethod) handler;
- //获取方法上的注解
- RoleNum roleNum = handlerMethod.getMethod().getAnnotation(RoleNum.class);
- //如果方法上的注解为空 则获取类的注解
- if (roleNum == null){
- roleNum = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RoleNum.class);
- }
- //如果标记了注解,则判断权限
- if (roleNum != null){
- if (roleNum.value().equals("2")){ //该通用身份即可访问
- // System.err.println("权限足够,正在访问");
- return true;
- }
- if (roleNum.value().equals("1")) { //Normal 0
- if (role.equals("1")){
- return true;
- }else if (role.equals("0")){
- return true;
- }
- }
- if (roleNum.value().equals("0")){
- if (role.equals("0")){
- return true;
- }else {
- return false;
- }
- }
- }
- }
-
- return false;
- }
注意:上述用户的角色权限信息是存储在Redis中的。
现在鉴权模块已经非常成熟,以上仅为个人心得和实践,还有很多改进的地方欢迎评论,后续还会补充通过Shiro框架实现的鉴权这一模块。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。