当前位置:   article > 正文

SpringBoot整合Security+Redis+RBAC权限管理_springsecurity jpa rbac

springsecurity jpa rbac

介绍

参照链接:https://github.com/xkcoding/spring-boot-demo

知识储备

用户权限管理数据库设计(RBAC)

在这里插入图片描述
在这里插入图片描述
参考链接:https://www.cnblogs.com/myseries/p/10871633.html

SpringDataJpa技术

把SQL全部封装到注解的方式,或者直接使用方法拼接查询。
SpringBoot整合SpringDataJPA入门案例

JpaRepository<T, ID>

该方法封装了常用的增删改查。

JpaSpecificationExecutor< T>

封装了查询一条数据、条件查询、分页查询、排序查询、计数查询。

@EntityGraph、@ManyToMany

@EntityGraph:解决懒加载的查询N+1问题,提升查询效率。
@ManyToMany:一对多
注意:防止无限递归,可以使用toString来解决。

@Query

实现自定义的sql。

Security技术

SpringBoot整合Security无数据库版本入门
SpringBoot整合Security数据库版本入门
后续会更新微服务版本、整合OAuth2+JWT版本。

Redis技术

Redis基本命令入门
常用的获取key,删除key,以及批量删除key。

@Component
@Slf4j
public class RedisUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 分页获取指定格式key,使用 scan 命令代替 keys 命令,在大数据量的情况下可以提高查询效率
     *
     * @param patternKey  key格式
     * @param currentPage 当前页码
     * @param pageSize    每页条数
     * @return 分页获取指定格式key
     */
    public PageResult<String> findKeysForPage(String patternKey, int currentPage, int pageSize) {
        ScanOptions options = ScanOptions.scanOptions()
                .match(patternKey)
                .build();
        RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory();
        RedisConnection rc = factory.getConnection();
        Cursor<byte[]> cursor = rc.scan(options);

        List<String> result = Lists.newArrayList();

        long tmpIndex = 0;
        int startIndex = (currentPage - 1) * pageSize;
        int end = currentPage * pageSize;
        while (cursor.hasNext()) {
            String key = new String(cursor.next());
            if (tmpIndex >= startIndex && tmpIndex < end) {
                result.add(key);
            }
            tmpIndex++;
        }

        try {
            cursor.close();
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            log.warn("Redis连接关闭异常,", e);
        }

        return new PageResult<>(result, tmpIndex);
    }

    /**
     * 删除 Redis 中的某个key
     *
     * @param key 键
     */
    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }

    /**
     * 批量删除 Redis 中的某些key
     *
     * @param keys 键列表
     */
    public void delete(Collection<String> keys) {
        stringRedisTemplate.delete(keys);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
/**
 * 通用分页参数返回
 * @param <T>
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> implements Serializable {
    private static final long serialVersionUID = 3420391142991247367L;

    /**
     * 当前页数据
     */
    private List<T> rows;

    /**
     * 总条数
     */
    private Long total;

    public static <T> PageResult of(List<T> rows, Long total) {
        return new PageResult<>(rows, total);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

Lombok技术

@Data

实现bean 的get/set。

@NoArgsConstructor

实现bean空构造函数。

@AllArgsConstructor

实现可能会用到构造函数。

@Builder

建造者模式下的bean。

@ToString

toString()方法。

@Slf4j

log日志。

JWT技术

token鉴权,token就是各种协议和加密字符串集成。权限验证的方式。

@Configuration
@Slf4j
public class JwtUtil {

	/**
	 * redis工具类
	 */
	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	/**
	 * 创建JWT
	 *
	 * @param authentication 用户认证信息
	 * @param rememberMe     记住我
	 * @return JWT
	 */
	public String createJWT(Authentication authentication, Boolean rememberMe) {
		UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
		return createJWT(rememberMe, userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities());
	}

	/**
	 * 创建JWT
	 *
	 * @param rememberMe  记住我
	 * @param id          用户id
	 * @param subject     用户名
	 * @param roles       用户角色
	 * @param authorities 用户权限
	 * @return JWT
	 */
	public String createJWT(Boolean rememberMe, Long id, String subject, List<String> roles, Collection<? extends GrantedAuthority> authorities) {
		Date now = new Date();
		//生成JWT的时间
		long nowMillis = System.currentTimeMillis();
		// 生成加密key
		SecretKey key = generalKey();
		// 为payload添加各种标准声明和私有声明了
		JwtBuilder builder = Jwts.builder()
				// 设置jti(JWT ID):是JWT的唯一标识,从而回避重放攻击。
				.setId(id.toString())
				// sub代表这个JWT的主体,即它的所有人。
				.setSubject(subject)
				// jwt签收者
				.setIssuedAt(now)
				// 设置签名使用的签名算法和签名使用的秘钥
				.signWith(SignatureAlgorithm.HS256, key)
				.claim("roles", roles) // 创建Payload
				.claim("authorities", authorities);

		// 设置过期时间
		Long ttlMillis = rememberMe ? CommonConstant.JWT_REMEMBER : CommonConstant.JWT_TTL;
		if (ttlMillis > 0) {
			long expMillis = nowMillis + ttlMillis;
			Date exp = new Date(expMillis);
			builder.setExpiration(exp);
		}

		String jwt = builder.compact();
		// 将生成的JWT保存至Redis
		stringRedisTemplate.opsForValue()
				.set(CommonConstant.REDIS_JWT_KEY_PREFIX + subject, jwt, ttlMillis, TimeUnit.MILLISECONDS);
		return jwt;
	}


	/**
	 * 从 request 的 header 中获取 JWT
	 *
	 * @param request 请求
	 * @return JWT
	 */
	public String getJwtFromRequest(HttpServletRequest request) {
		String bearerToken = request.getHeader(CommonConstant.JWT_HEADER);
		if (!StringUtils.isEmpty(bearerToken) && bearerToken.startsWith(CommonConstant.JWT_BEARER)) {
			return bearerToken.substring(7);
		}
		return null;
	}

	/**
	 * 从令牌获取用户名
	 *
	 * @param token 令牌
	 * @return 用户名
	 */
	public String getUsernameFromToken(String token) {
		// 从令牌中获取用户名
		final Claims claims = getClaimsFromToken(token);
		// 从要求中获取主题(用户名)
		return claims.getSubject();
	}

	/**
	 * 解析JWT
	 *
	 * @param token JWT
	 * @return {@link Claims}
	 */
	private Claims getClaimsFromToken(String token) {
		try {
			// 签名秘钥,和生成的签名的秘钥一模一样
			SecretKey key = generalKey();
			Claims claims = Jwts.parser() // 得到DefaultJwtParser
					.setSigningKey(key)  // 设置签名的秘钥
					.parseClaimsJws(token).getBody(); // 设置需要解析的jwt

			// 获取用户名
			String username = claims.getSubject();
			// 申明redis存储key
			String redisKey = CommonConstant.REDIS_JWT_KEY_PREFIX + username;

			// 校验redis中的JWT是否存在
			Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);
			if (Objects.isNull(expire) || expire <= 0) {
				throw new SecurityException(HttpStatusCodeEnum.TOKEN_EXPIRED);
			}

			// 校验redis中的JWT是否与当前的一致,不一致则代表用户已注销/用户在不同设备登录,均代表JWT已过期
			String redisToken = stringRedisTemplate.opsForValue()
					.get(redisKey);
			if (!token.equals(redisToken)) {
				throw new SecurityException(HttpStatusCodeEnum.TOKEN_OUT_OF_CTRL);
			}
			return claims;
		} catch (ExpiredJwtException e) {
			log.error("Token 已过期");
			throw new SecurityException(HttpStatusCodeEnum.TOKEN_EXPIRED);
		} catch (UnsupportedJwtException e) {
			log.error("不支持的 Token");
			throw new SecurityException(HttpStatusCodeEnum.TOKEN_PARSE_ERROR);
		} catch (MalformedJwtException e) {
			log.error("Token 无效");
			throw new SecurityException(HttpStatusCodeEnum.TOKEN_PARSE_ERROR);
		} catch (SignatureException e) {
			log.error("无效的 Token 签名");
			throw new SecurityException(HttpStatusCodeEnum.TOKEN_PARSE_ERROR);
		} catch (IllegalArgumentException e) {
			log.error("Token 参数不存在");
			throw new SecurityException(HttpStatusCodeEnum.TOKEN_PARSE_ERROR);
		}
	}

	/**
	 * 由字符串生成加密key
	 * @return
	 */
	public SecretKey generalKey(){
		// signature签证信息生成, header (base64后的)payload (base64后的) secret
		byte[] encodedKey = Base64.decodeBase64(CommonConstant.JWT_KEY);//本地的密码解码[B@152f6e2
		// 根据给定的字节数组使用AES加密算法构造一个密钥
		SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
		return key;
	}

	/**
	 * 设置JWT过期
	 *
	 * @param request 请求
	 */
	public void invalidateJWT(HttpServletRequest request) {
		String jwt = getJwtFromRequest(request);
		String username = getUsernameFromToken(jwt);
		// 从redis中清除JWT
		stringRedisTemplate.delete(CommonConstant.REDIS_JWT_KEY_PREFIX + username);
	}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
public class JwtUtil3 {

	/**
	 * 创建一个 header 为JWT ,算法为 HS256
	 *      payload
	 */
	@Test
	public void createJwtToken() {
		Map<String,Object> headerMap = new HashMap<>();
		headerMap.put("typ","JWT");
		headerMap.put("alg","HS256");
		JwtBuilder builder = Jwts.builder()
				.setHeader(headerMap) // 设置请求头
				.setIssuer("hikktn")
				.setAudience("xiaohong")
				.setId("999")   //设置唯一编号
				.setSubject("小白")//设置主题  可以是JSON数据
				.setIssuedAt(new Date())//设置签发日期
				.signWith(SignatureAlgorithm.HS256, "hahaha");//设置签名 使用HS256算法,并设置SecretKey(字符串)
		//构建 并返回一个字符串
		System.out.println(builder.compact());
	}

	@Test
	public void parseJWT(){
		String compactJwt ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJoaWtrdG4iLCJhdWQiOiJ4aWFvaG9uZyIsImp0aSI6Ijk5OSIsInN1YiI6IuWwj-eZvSIsImlhdCI6MTYyNTk5MTc4N30.wcYmhw7-OyZJYPWIMoc9AQtVhQZHdBiQaDfPsFFo50E";
		JwtParser parser = Jwts.parser();
		JwtParser jwtParser = parser.setSigningKey("hahaha");
		Claims claims = jwtParser.parseClaimsJws(compactJwt).getBody();
		System.out.println(claims);
		JwsHeader header = jwtParser.parseClaimsJws(compactJwt).getHeader();
		System.out.println(header);
		String signature = jwtParser.parseClaimsJws(compactJwt).getSignature();
		System.out.println(signature);
	}

	/**
	 * Header(请求头)
	 * {“typ”:“JWT”,“alg”:“HS256”}
	 * 类型:JWT,算法:HS256
	 */


	/**
	 * Payload(负载) 参数自定义
	 * 例子:{“sub”:“123”,“name”:“Tom”,“admin”:true}
	 * 可以选择使用的参数:
	 *      iss(issuer): jwt签发者
	 *      sub(subject): jwt所面向的用户,主题
	 *      aud(audience): 接收jwt的一方
	 *      exp(expiration time): jwt的过期时间,这个过期时间必须要大于签发时间
	 *      nbf(Not Before): 生效时间
	 *      iat(Issued At): jwt的签发时间
	 *      jti(JWT ID): jwt的唯一身份标识,主要用来作为一次性token。
	 * 这组数据被定为claim(要求)使用
	 */

	/**
	 * signature(签名)
	 * header和payload以JSON格式转换为base64,逗号拼接后根据header里的算法加密后,得到的签名
	 * 组成部分:
	 *      header (base64后的)
	 *      payload (base64后的)
	 *      secret(盐)  自定义
	 * 盐就是你的私钥,可以进行自定义
	 */

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

具体实现

思路和问题

我的学习旅程,拿到一个项目,里面没有任何文档说明,只能硬着头皮理解代码,而想要理解代码,就必须掌握很多作者的技术,关键是我没有使用或学习过的经验,这之前为了消化这个项目,我的选择是先收集一些专门争对这个项目的技术入门,完成一到二个demo。
然后按照自己的思路结合前面的demo,进行改进项目,一边思考一边学习和理解这个项目,过程中却是痛苦的。
各种因为我的代码和作者的代码不同,出现各种错误异常抛出,先后出现了不下五种异常,加之这个有bug代码移植到新的机子上,又出现了各种版本引起的环境问题。
说说我遇到卡人的问题把!
先是用WinMerge比较两个文件差异,可是并没有解决问题。
重新安装软件redis和mysql。

问题一:Authentication 获取null

首先debug到源码中,一直抛出这个异常,后面一步步检查代码,发现我是用的方法获取到空值。
AbstractUserDetailsAuthenticationProvider
在这里插入图片描述

在这里插入图片描述
就是这里我之前的代码判断是没有非空,该死!这个方法替换的时候,写错了。

问题二:DisabledException: User is disabled

获取的用户一直为null,经过盘查后,发现UserPrincipal类,一旦继承了User类,那么你就得自己重新创建属性,并且类型和父类一样,而传值也是一个一个传,千万不要写一个User类,构造函数后传入UserPrincipal类,那样你就会和我一样愚蠢,一些数据就是传不进去。
没遇到这个异常前,我根本不理解为什么要这么写,遇到这个问题后,才理解了。
还有一个问题,在isEnabled()方法,我一开始写的return false,导致验证用户直接被禁用。
先放开验证。

	@Override
	public boolean isEnabled() {
		return true;
	}
  • 1
  • 2
  • 3
  • 4

开始

数据表和数据准备

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 80026
 Source Host           : localhost:3306
 Source Schema         : sso_demo

 Target Server Type    : MySQL
 Target Server Version : 80026
 File Encoding         : 65001

 Date: 23/08/2021 01:05:33
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sec_permission
-- ----------------------------
DROP TABLE IF EXISTS `sec_permission`;
CREATE TABLE `sec_permission`  (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名',
  `url` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类型为页面时,代表前端路由地址,类型为按钮时,代表后端接口地址',
  `type` int NOT NULL COMMENT '权限类型,页面-1,按钮-2',
  `permission` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限表达式',
  `method` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '后端接口访问方式',
  `sort` int NOT NULL COMMENT '排序',
  `parent_id` bigint NOT NULL COMMENT '父级id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sec_permission
-- ----------------------------
INSERT INTO `sec_permission` VALUES (1072806379288399872, '测试页面', '/test', 1, 'page:test', NULL, 1, 0);
INSERT INTO `sec_permission` VALUES (1072806379313565696, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072806379288399872);
INSERT INTO `sec_permission` VALUES (1072806379330342912, '测试页面-添加', '/**/test', 2, 'btn:test:insert', 'POST', 2, 1072806379288399872);
INSERT INTO `sec_permission` VALUES (1072806379342925824, '监控在线用户页面', '/monitor', 1, 'page:monitor:online', NULL, 2, 0);
INSERT INTO `sec_permission` VALUES (1072806379363897344, '在线用户页面-查询', '/**/api/monitor/online/user', 2, 'btn:monitor:online:query', 'GET', 1, 1072806379342925824);
INSERT INTO `sec_permission` VALUES (1072806379384868864, '在线用户页面-踢出', '/**/api/monitor/online/user/kickout', 2, 'btn:monitor:online:kickout', 'DELETE', 2, 1072806379342925824);

-- ----------------------------
-- Table structure for sec_role
-- ----------------------------
DROP TABLE IF EXISTS `sec_role`;
CREATE TABLE `sec_role`  (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名',
  `description` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  `create_time` date NOT NULL COMMENT '创建时间',
  `update_time` date NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sec_role
-- ----------------------------
INSERT INTO `sec_role` VALUES (1072806379208708096, '管理员', '超级管理员', '2021-08-22', '2021-08-22');
INSERT INTO `sec_role` VALUES (1072806379238068224, '普通用户', '普通用户', '2021-08-22', '2021-08-22');

-- ----------------------------
-- Table structure for sec_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sec_role_permission`;
CREATE TABLE `sec_role_permission`  (
  `role_id` bigint NOT NULL COMMENT '角色主键',
  `permission_id` bigint NOT NULL COMMENT '权限主键',
  PRIMARY KEY (`role_id`, `permission_id`) USING BTREE,
  INDEX `FKpin61ltb1uniw17shihq1cove`(`permission_id`) USING BTREE,
  CONSTRAINT `FK2btquksd4tgtj9a8pn6qyeubl` FOREIGN KEY (`role_id`) REFERENCES `sec_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FKpin61ltb1uniw17shihq1cove` FOREIGN KEY (`permission_id`) REFERENCES `sec_permission` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限关系表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sec_role_permission
-- ----------------------------
INSERT INTO `sec_role_permission` VALUES (1072806379208708096, 1072806379288399872);
INSERT INTO `sec_role_permission` VALUES (1072806379238068224, 1072806379288399872);
INSERT INTO `sec_role_permission` VALUES (1072806379208708096, 1072806379313565696);
INSERT INTO `sec_role_permission` VALUES (1072806379238068224, 1072806379313565696);
INSERT INTO `sec_role_permission` VALUES (1072806379208708096, 1072806379330342912);
INSERT INTO `sec_role_permission` VALUES (1072806379208708096, 1072806379342925824);
INSERT INTO `sec_role_permission` VALUES (1072806379208708096, 1072806379363897344);
INSERT INTO `sec_role_permission` VALUES (1072806379208708096, 1072806379384868864);

-- ----------------------------
-- Table structure for sec_user
-- ----------------------------
DROP TABLE IF EXISTS `sec_user`;
CREATE TABLE `sec_user`  (
  `id` bigint NOT NULL COMMENT '主键',
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `birthday` bigint NULL DEFAULT NULL COMMENT '生日',
  `sex` int NULL DEFAULT NULL COMMENT '性别,男-1,女-2',
  `status` int NOT NULL DEFAULT 1 COMMENT '状态,启用-1,禁用-0',
  `create_time` date NOT NULL COMMENT '创建时间',
  `update_time` date NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`username`) USING BTREE,
  UNIQUE INDEX `phone`(`phone`) USING BTREE,
  UNIQUE INDEX `email`(`email`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sec_user
-- ----------------------------
INSERT INTO `sec_user` VALUES (1072806377661009920, 'admin', '$2a$10$2xC9NWUJxgCWwjJtADT3mOY3uekARVMbRiXWntKZSFsSf5ugP9K2G', '管理员', '17300000000', 'admin@xkcoding.com', 785433600000, 1, 1, '2021-08-22', '2021-08-22');
INSERT INTO `sec_user` VALUES (1072806378780889088, 'user', '$2a$10$OUDl4thpcHfs7WZ1kMUOb.ZO5eD4QANW5E.cexBLiKDIzDNt87QbO', '普通用户', '17300001111', 'user@xkcoding.com', 785433600000, 1, 1, '2021-08-22', '2021-08-22');

-- ----------------------------
-- Table structure for sec_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sec_user_role`;
CREATE TABLE `sec_user_role`  (
  `user_id` bigint NOT NULL COMMENT '用户主键',
  `role_id` bigint NOT NULL COMMENT '角色主键',
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE,
  INDEX `FKfowkd8vw5qarh8b8y9noaf4et`(`role_id`) USING BTREE,
  CONSTRAINT `FK835bbyiy6majrolcov7bp0yo0` FOREIGN KEY (`user_id`) REFERENCES `sec_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FKfowkd8vw5qarh8b8y9noaf4et` FOREIGN KEY (`role_id`) REFERENCES `sec_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色关系表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sec_user_role
-- ----------------------------
INSERT INTO `sec_user_role` VALUES (1072806377661009920, 1072806379208708096);
INSERT INTO `sec_user_role` VALUES (1072806378780889088, 1072806379238068224);

SET FOREIGN_KEY_CHECKS = 1;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139

思路

接下来,只讲解代码的核心思路,不会把全部代码贴出来,当然代码还是会分享出来,并且这么测试也会放出来。
首先我想玩玩JPA多对多,所以尝试了@ManyToMany。
在这里插入图片描述
上面就是我改动的代码,下面就是原本的代码。
这里是上面代码的主要逻辑。

«interface» UserDao +findByUsernameOrEmailOrPhone(username,email,phone) SecUser +List<SecRole> roleInfo; SecRole +List<SecPermission> permissionInfo; SecPermission 调用roleInfo

下面写Security主要的业务逻辑。

AuthController +login(username,password,rememberme) «abstract» WebSecurityConfigurerAdapter SpringSecurityConfig +configure(http) RbacAuthorityService +hasPermission(request,authentication) SecurityException +SecurityException(code,msg) GlobalExceptionHandler +handlerException(e) JwtAuthenticationFilter +doFilterInternal(request,response,filterChain) CustomAccessDeniedHandler +handle() CustomUserDetailsService +loadUserByUsername(username) UserPrincipal +create(user,roleInfo,permissionInfo) 依赖 继承 调用hasPermission()检验请求和权限 调用checkRequest(request)校验请求是否存在 全局异常拦截返回 url校验通过 调用doFilterInternal() 调用handle()无权限响应内容 调用loadUserByUsername()查询用户和密码 调用create(user,roleInfo,permissionInfo) 回调,返回结果

以及重要的url校验过程

SecurityConfig +CustomConfig customConfig +configure(web) CustomConfig -IgnoreConfig ignores IgnoreConfig - pattern -GET -POST ... 调用getIgnores()读取application.yml配置的放行URL和校验的URL 调用每个请求方式,设置请求方式,例如:get/post请求

源码分享

链接:https://pan.baidu.com/s/1w6Ui4zogDZagfYM4mSemlA
提取码:21y4

测试

使用Postman测试
在这里插入图片描述
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/181838
推荐阅读
相关标签
  

闽ICP备14008679号