从零开始,手打一个权限管理系统(第五章 权限控制)


前言

这章主要通过SpringSecurity来实现对权限的控制,权限粒度是到每个方法。


一、token验证

第四章登录我们获取到了token,每次请求的时候都必须验证这个token是否合法、是否过期,所以我们需要一个拦截器来拦截每一次的请求;这里我们可以通过继承OncePerRequestFilter来实现我们对token的验证;当然并不是所有请求都需要拦截,所以还需要一个白名单,来配置不需要被拦截的请求。

@Slf4j
@Configuration
@ConfigurationProperties(prefix = "security.white")
public class PermitUrlProperties {

    @Getter
    @Setter
    private List<String> urls = new ArrayList<>();

}

yml配置:

security:
  white:
    urls:
      - /login
      - /logout

JwtAuthenticationTokenFilter

/**
 * token拦截验证
 */
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JWTUtil jwtUtil;

    @Autowired
    private PermitUrlProperties permitUrlProperties;

    @Override
    protected void initFilterBean() throws ServletException {
        System.out.println("JwtAuthenticationTokenFilter初始化...");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String requestUrl = httpServletRequest.getRequestURI();
        log.info("请求url:{}", requestUrl);
        // 白名单url放过
        if (filterWhiteUrl(requestUrl)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        String authToken = httpServletRequest.getHeader(SecurityConstants.AUTHORIZATION);
        if (StrUtil.isBlank(authToken)) {
            Result<String> result = Result.fail();
            result.setMsg("未登录");
            ResponseUtil.response(httpServletResponse, result);
            return;
        }
        boolean checkToken = jwtUtil.checkToken(authToken);
        if (checkToken) {
            Result<String> result = Result.fail();
            result.setMsg("会话已过期,请重新登录");
            httpServletResponse.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
            ResponseUtil.response(httpServletResponse, result);
            return;
        }

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            //Context中的认证为空,进行token验证
            Claims claims = jwtUtil.getClaimsFromToken(authToken);
            //从jwt中恢复用户信息和权限
            String id = claims.get(JWTUtil.ID, String.class);
            String orgId = claims.get(JWTUtil.ORGID, String.class);
            String username = claims.get(JWTUtil.USERNAME, String.class);
            String authorities = claims.get(JWTUtil.AUTHORITIES, String.class);
            List<String> list = JSON.parseObject(authorities, new TypeReference<List<String>>() {
            });
            JwtUser jwtUser = new JwtUser(id, orgId, username, "", AuthorityUtils.createAuthorityList(list.toArray(new String[0])));
            //如username不为空,并且能够在数据库中查到
            JwtAuthenticationToken jwtAuthenticationToken =
                    new JwtAuthenticationToken(jwtUser.getAuthorities(), jwtUser, null);
            //将authentication放入SecurityContextHolder中
            SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    /**
     * 过滤表名单的url
     *
     * @param url
     * @return
     */
    private boolean filterWhiteUrl(String url) {
        List<String> whiteList = permitUrlProperties.getUrls();
        if (CollectionUtil.isNotEmpty(whiteList)) {
            PathMatcher matcher = new AntPathMatcher();
            for (String releaseUrl : whiteList) {
                boolean match = matcher.match(releaseUrl, url);
                if (match) {
                    return true;
                }
            }
        }
        return false;
    }
}

更新下SpringSecurityConfigurer,将JwtAuthenticationTokenFilter加入配置中,部分代码如下:

	http.addFilterAfter(jwtAuthenticationTokenFilter(),UsernamePasswordAuthenticationFilter.class);

 	@Bean
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
        return new JwtAuthenticationTokenFilter();
    }

经过一系列的编译调试后,启动项目验证:

1、获取token
在这里插入图片描述
2、不带token访问主页
在这里插入图片描述
3、带token访问主页
在这里插入图片描述
4、token错误和过期访问主页
在这里插入图片描述

二、权限验证

1、开启全局安全配置

在SpringSecurityConfigurer这个文件上加上@EnableGlobalMethodSecurity(prePostEnabled = true)就可以了,他会解锁 @PreAuthorize 和 @PostAuthorize 两个注解,@PreAuthorize 会在方法执行前进行验证, @PostAuthorize 会在方法执行后进行验证。

2、标记需要校验的方法

我们在IndexController上面加上权限校验,即@PreAuthorize(“hasAuthority(‘sys:index’)”)
在这里插入图片描述

3、自定义未授权处理器

实现AccessDeniedHandler的handle接口即可

JwtAccessDeniedHandler

/**
 * 未授权访问处理
 */
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {

        Result<String> result = Result.fail(e.getMessage());
        httpServletResponse.setStatus(HttpStatus.HTTP_FORBIDDEN);
        ResponseUtil.response(httpServletResponse, result);
    }
}

把这个加到SpringSecurityConfigurer里面,新增代码如下:

	.exceptionHandling((execption) -> execption
                        // 未授权异常处理
                        .accessDeniedHandler(new JwtAccessDeniedHandler()));

测试未授权

在这里插入图片描述

测试已授权
在JwtUserDetailsServiceImpl的权限列表中加入我们刚刚注解的权限标记sys:index

在这里插入图片描述

重新登录,获取新的token,并请求主页,发现能够正常访问

在这里插入图片描述


3、通过数据库配置权限

前面都是写死的权限,实际项目都是从数据库中查询的,这个项目我们采用RBAC 基于角色的访问控制,将所有权限都赋给角色,将角色赋给具体的用户。

3.1、表设计

用户表sys_user,用来存放用户名、密码等基础信息

CREATE TABLE `sys_user`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '主键ID',
  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '电话',
  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '头像',
  `org_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '机构ID',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '1' COMMENT '1-正常,0-锁定',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '1' COMMENT '逻辑删除标记(1:显示;0:删除)',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `uk_username`(`username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户表' ROW_FORMAT = Dynamic;

组织机构表sys_org

CREATE TABLE `sys_org`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `parent_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `sort` int NULL DEFAULT 1 COMMENT '排序',
  `type` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '机构类型',
  `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '机构编码',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机构名称',
  `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '电话',
  `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
  `address` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '地址',
  `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
  `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '逻辑删除标记(1:显示;0:删除)',
  `status` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '1:正常,0:锁定',
	`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '机构管理' ROW_FORMAT = Dynamic;

菜单表sys_menu,存放对应的菜单和权限标识

CREATE TABLE `sys_menu`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单ID',
  `title` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单名称',
  `permission` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标识',
  `parent_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '父菜单ID',
  `sort` int NOT NULL DEFAULT 0 COMMENT '排序值',
  `type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单类型 (0菜单 1按钮)',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '逻辑删除标记(1:显示;0:删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;

角色表sys_role

CREATE TABLE `sys_role`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '主键',
  `role_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '角色名',
  `role_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '角色编码',
  `role_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '角色描述',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '1' COMMENT '逻辑删除标记(1:显示;0:删除)',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `role_id_role_code`(`role_code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '系统角色表' ROW_FORMAT = Dynamic;

角色菜单关系表sys_role_menu,一个角色拥有哪些菜单的权限

CREATE TABLE `sys_role_menu`  (
  `role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
  `menu_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单ID',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色菜单表' ROW_FORMAT = Dynamic;

用户角色表sys_user_role,一个用户拥有哪些角色

CREATE TABLE `sys_user_role`  (
  `user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户ID',
  `role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
	`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色表' ROW_FORMAT = Dynamic;

用户和角色是一对多的关系,角色和菜单也是一对多的关系
在这里插入图片描述

3.2、创建实体和实现CRUD

写这些类其实是一个重复的工作,把这个项目写完了,一定要做一个代码生成器,一个一个地敲太费时费力了!!

在这里插入图片描述

4、测试验证

4.1、初始化数据

之前我们开启了权限验证,现在初始化数据的时候先关一下;只需要注释掉SpringSecurityConfigurer上的@EnableGlobalMethodSecurity(prePostEnabled = true)这个注解即可。

接口文档

初始化菜单数据

初始化菜单数据

取消注释,发送登录请求,可以发现权限信息已经全部写进去了,大家会发现新生成的token会比之前大很多,因为写入了权限信息,具体代码可看JWTUtil的createToken方法

在这里插入图片描述

测试访问没有权限的主页

在这里插入图片描述

测试有权限的用户新增

在这里插入图片描述

看看能不能登录

在这里插入图片描述

到这里,这个系统的基本功能大部分都完成了,接下来我将继续完善和优化细节!!!

当前版本:1.0.4
代码仓库


三、 体验地址

后台数据库只给了部分权限,报错属于正常!
想学的老铁给点点关注吧!!!
后期会开源前后端所有代码!!!

我是阿咕噜,一个从互联网慢慢上岸的程序员,如果喜欢我的文章,记得帮忙点个赞哟,谢谢!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值