Springboot权限管理

一、前言

最近在整合完单点登录后,又来了一个新活,好在这个任务已经比较成熟,实现的方式也比较多,也有比较成熟的框架已经实现了它,它就是权限管理。

二、实现过程

实现权限管理的方式有很多,我提出我通过查阅资料学习所知道的,如果有更多欢迎分享:

  1. 拦截器做鉴权
  2. AOP做鉴权
  3. shiro框架
  4. springsecurity框架

其中,1、2点都是基于在类或方法上加上一个自定义注解实现的,在通知或拦截器中通过获取类或方法上的注解的value值,来比较判断用户的权限,再决定是否放行执行我们的核心业务。

考虑到公司架构和其他原因,我使用到的是使用AOP进行鉴权认证。

实现过程如下:

2.1数据库设计   

2.1.1RBAC权限模型

RBAC是基于角色的访问控制,是用户通过角色与权限进行关联,为什么不直接给用户分配权限呢?简单来说,一个用户拥有多个角色,每个角色拥有若干权限。这样就构成了“用户-角色-权限”的授权模型。在这个模型中,用户与角色、角色与权限之间是多对多的关系。

根据角色授权的思想,我们需要设计五张表

​ 用户表(user)

​ 角色表(role)

​ 权限表(permission)

这三个表之间都是多对多的关系,所以衍生出两个关联表如下

 用户角色表(user_role)

​ 角色资源表(role_permission)

2.2.选择方案

2.2.1基于AOP的实现

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方法响应结果。

2.2.2基于拦截器的实现

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框架实现的鉴权这一模块。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
Spring Boot 提供了多种权限管理方式,如使用 Spring Security 或自定义实现。下面简单介绍两种实现方式: 1. 使用 Spring Security Spring Security 是 Spring 生态中用于安全认证和授权的框架,可以非常方便地实现基于角色或权限的访问控制。在 Spring Boot 中集成 Spring Security 非常简单,只需要添加依赖和配置文件即可。 首先,在 `pom.xml` 中添加 Spring Security 的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 然后,在 `application.properties` 文件中配置用户名和密码: ``` spring.security.user.name=admin spring.security.user.password=123456 ``` 最后,在 Spring Boot 应用程序的启动类上添加 `@EnableWebSecurity` 注解,启用 Spring Security: ```java @SpringBootApplication @EnableWebSecurity public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 现在,你可以使用 `@PreAuthorize` 或 `@Secured` 注解来限制方法的访问: ```java @RestController public class UserController { @GetMapping("/users") @PreAuthorize("hasRole('ADMIN')") public List<User> getUsers() { // ... } @PostMapping("/users") @PreAuthorize("hasAuthority('CREATE_USER')") public void createUser(@RequestBody User user) { // ... } } ``` 在上面的例子中,`@PreAuthorize` 注解会检查用户是否具有指定的角色或权限。如果用户没有权限,访问将被拒绝。 2. 自定义实现 如果你不想使用 Spring Security,或者需要更细粒度的控制,可以自定义权限管理实现。下面是一个简单的例子: 首先,定义一个 `Permission` 类表示权限: ```java public enum Permission { READ_USER, CREATE_USER, UPDATE_USER, DELETE_USER } ``` 然后,定义一个 `User` 类表示用户信息: ```java public class User { private String username; private String password; private List<Permission> permissions; // getter and setter } ``` 接着,在 `UserController` 中添加一个 `checkPermission` 方法来检查用户是否具有指定的权限: ```java @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/users") public List<User> getUsers() { checkPermission(Permission.READ_USER); return userService.getUsers(); } @PostMapping("/users") public void createUser(@RequestBody User user) { checkPermission(Permission.CREATE_USER); userService.createUser(user); } private void checkPermission(Permission permission) { User user = getCurrentUser(); if (user == null || !user.getPermissions().contains(permission)) { throw new RuntimeException("Access denied"); } } private User getCurrentUser() { // 获取当前用户信息 // ... } } ``` 在上面的例子中,`checkPermission` 方法会检查当前用户是否具有指定的权限。如果用户没有权限,访问将被拒绝。 最后,在 `UserService` 中添加一个 `getCurrentUser` 方法来获取当前用户信息: ```java @Service public class UserService { public User getCurrentUser() { // 获取当前用户信息 // ... } } ``` 在实际应用中,你可能需要更复杂的权限管理逻辑,如基于角色的访问控制、动态授权等。这些都可以通过自定义实现来实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值