当前位置:   article > 正文

SpringBoot SpringSecurity JWT+Redis+RSA授权登录登出 验证码 前后端分离 分布式_spring-security-rsa

spring-security-rsa

目录

一、案例前的准备

1、RSA公钥私钥生成

2、JWT通过RSA算法签发

3、Redis的配置

4、FastJson配置

5、用户表与实体类

6、统一返回对象

7、常量类

8、图片验证码的使用

9、雪花算法生成唯一id

10、web渲染工具类

二、SpringSecurity的配置

1、SpringSecurity配置类

2、SpringSecurity异常处理器

三、JWT+SpringSecurity实现登录的主要步骤

1、登录图解

2、登录开始前首先创建UserDetails实体类的子类

3、创建UserDetailsService的实现类

4、登录的实现类与接口

5、认证过滤器

四、测试

1、登录测试

 2、资源访问测试

 3、退出登录

 五、案例源码链接


一、案例前的准备

1、RSA公钥私钥生成

第一步:生成密钥

通过keyTool(java提供的证书管理工具)生成
只要安装了JDK,就有这个工具。进入jdk的安装目录,就能使用该工具:

输入以下命令生成私钥:

  1. // 以下指令为一行,设置多行是方便阅读
  2. keytool -genkeypair
  3. -alias 密钥别名
  4. -keyalg 使用的算法
  5. -keypass 密钥的访问密码
  6. -keystore 生成的密钥库文件名,扩展名是jks
  7. -storepass 密钥库的访问密码,用来打开jks文件

此时私钥已生成:

 

第二步:生成公钥 

对于微服务来说,私钥放在认证服务上,其他服务只需要存公钥即可。因为其他服务只做校验,不加密JWT。私钥加密,公钥解密。

生成公钥输入以下命令:

keytool -list -rfc --keystore jks文件(包含扩展名.jks) | openssl x509 -inform pem -pubkey

复制文本,重命名文件

 

 公钥和私钥放到服务器上,私钥用于授权服务器授权token时加密,其他服务器仅需要通过公钥便可对加密内容进行解析了。

2、JWT通过RSA算法签发

这里需要添加依赖:

  1. <dependency>
  2. <groupId>io.jsonwebtoken</groupId>
  3. <artifactId>jjwt</artifactId>
  4. <version>0.9.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.security</groupId>
  8. <artifactId>spring-security-rsa</artifactId>
  9. <version>1.0.11.RELEASE</version>
  10. </dependency>

 JWT工具类:

  1. package com.dragonwu.utils;
  2. import com.dragonwu.model.OnlineUser;
  3. import io.jsonwebtoken.JwtBuilder;
  4. import io.jsonwebtoken.Jwts;
  5. import io.jsonwebtoken.SignatureAlgorithm;
  6. import org.springframework.core.io.ClassPathResource;
  7. import org.springframework.core.io.Resource;
  8. import org.springframework.security.core.context.SecurityContextHolder;
  9. import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
  10. import java.io.BufferedReader;
  11. import java.io.InputStreamReader;
  12. import java.security.KeyFactory;
  13. import java.security.KeyPair;
  14. import java.security.PrivateKey;
  15. import java.security.PublicKey;
  16. import java.security.spec.X509EncodedKeySpec;
  17. import java.util.Base64;
  18. import java.util.Date;
  19. import java.util.Map;
  20. import java.util.Objects;
  21. /**
  22. * 通过RSA非对称加密的JWT工具类
  23. *
  24. * @author DragonWu
  25. * @date 2022-10-06 17:51
  26. **/
  27. public class JwtUtil {
  28. //默认60分钟
  29. public static final Long DEFAULT_EXPIRE_MINUTES = 60L;
  30. //jwt的唯一标识,一般为username或userId
  31. public static final String JWT_PRIMARY_KEY = "userId";
  32. //私钥路径
  33. public static final String PRIVATE_KEY_PATH = "/keys/dragonwu.jks";
  34. //公钥路径
  35. public static final String PUBLIC_KEY_PATH = "/keys/public.key";
  36. //私钥
  37. public static PrivateKey privateKey;
  38. //公钥
  39. public static PublicKey publicKey;
  40. /**
  41. * 传入的数据不得含敏感信息,否则可能被盗用,私钥加密token
  42. *
  43. * @param claims 数据体
  44. * @param minutes 过期时间,分
  45. * @return String JWT的token
  46. */
  47. public static String createJwt(Map<String, Object> claims, long minutes) {
  48. //获取私钥
  49. if (Objects.isNull(privateKey)) {
  50. privateKey = getPrivateKey();
  51. }
  52. //当前系统时间
  53. long now = System.currentTimeMillis();
  54. //过期时间
  55. long exp = now + minutes * 60 * 1000;
  56. JwtBuilder jwtBuilder = Jwts.builder()
  57. //签发的算法
  58. .signWith(SignatureAlgorithm.RS256, privateKey)
  59. //设置令牌过期时间
  60. .setExpiration(new Date(exp))
  61. //设置签发时间
  62. .setIssuedAt(new Date())
  63. //设置body数据,自定义设置
  64. .setClaims(claims);
  65. return jwtBuilder.compact();
  66. }
  67. /**
  68. * 公钥解析jwt
  69. *
  70. * @param jwt jwt字符串
  71. * @return 返回null证明解析异常
  72. */
  73. public static Map<String, Object> parseJwt(String jwt) {
  74. if (Objects.isNull(publicKey)) {
  75. publicKey = getPublicKey();
  76. }
  77. try {
  78. return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody();
  79. } catch (Exception ignored) {
  80. }
  81. return null;
  82. }
  83. /**
  84. * 获取私钥
  85. *
  86. * @return 当获取异常时返回null
  87. */
  88. private static PrivateKey getPrivateKey() {
  89. try {
  90. ClassPathResource keyFileResource = new ClassPathResource(PRIVATE_KEY_PATH);
  91. //创建秘钥工厂,参数为:秘钥文件、秘钥库密码
  92. //import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
  93. //pom文件:
  94. /*
  95. 公钥私钥生成 https://blog.csdn.net/qq_37470815/article/details/123027798
  96. <dependency>
  97. <groupId>org.springframework.security</groupId>
  98. <artifactId>spring-security-rsa</artifactId>
  99. <version>1.0.11.RELEASE</version>
  100. </dependency>
  101. KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyFileResource, "密钥库密码password".toCharArray());
  102. //获取秘钥,参数为:别名,秘钥密码
  103. KeyPair keyContent = keyStoreKeyFactory.getKeyPair("alias别名", "秘钥的密码".toCharArray());
  104. */
  105. KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyFileResource, "dragonwu".toCharArray());
  106. KeyPair keyContent = keyStoreKeyFactory.getKeyPair("dragonwu", "dragonwu".toCharArray());
  107. return keyContent.getPrivate();
  108. } catch (Exception ignored) {
  109. }
  110. return null;
  111. }
  112. /**
  113. * 获取公钥
  114. *
  115. * @return 当获取异常时返回null
  116. */
  117. private static PublicKey getPublicKey() {
  118. try {
  119. Resource publicKey = new ClassPathResource(PUBLIC_KEY_PATH);
  120. InputStreamReader publicKeyIs = new InputStreamReader(publicKey.getInputStream());
  121. BufferedReader publicKeyBr = new BufferedReader(publicKeyIs);
  122. StringBuilder publicKeySb = new StringBuilder();
  123. String line;
  124. //将文件中的多行变为一行
  125. while ((line = publicKeyBr.readLine()) != null) {
  126. publicKeySb.append(line);
  127. }
  128. //将String转换成java的PublicKey对象
  129. byte[] byteKey = Base64.getDecoder().decode(publicKeySb.toString());
  130. X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(byteKey);
  131. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  132. return keyFactory.generatePublic(x509EncodedKeySpec);
  133. } catch (Exception ignored) {
  134. }
  135. return null;
  136. }
  137. /*
  138. SpringSecurity登录后获取用户信息
  139. */
  140. public static OnlineUser getUser() {
  141. return (OnlineUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  142. }
  143. }

3、Redis的配置

引入依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

redis配置类:

  1. @Configuration
  2. public class RedisConfig {
  3. @Bean
  4. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
  5. // 创建 RedisTemplate 对象
  6. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  7. // 设置连接工厂
  8. redisTemplate.setConnectionFactory(connectionFactory);
  9. // 设置 Key 的序列化 - String 序列化 RedisSerializer.string() => StringRedisSerializer.UTF_8
  10. redisTemplate.setKeySerializer(RedisSerializer.string());
  11. redisTemplate.setHashKeySerializer(RedisSerializer.string());
  12. // 设置 Value 的序列化 - JSON 序列化 RedisSerializer.json() => GenericJackson2JsonRedisSerializer
  13. redisTemplate.setValueSerializer(RedisSerializer.json());
  14. redisTemplate.setHashValueSerializer(RedisSerializer.json());
  15. // 返回
  16. return redisTemplate;
  17. }
  18. }

redis工具类:

  1. @Component
  2. public class RedisUtil {
  3. @Autowired
  4. private RedisTemplate<String, Object> redisTemplate;
  5. /*
  6. 设置对象
  7. */
  8. public void setObject(String key, Object object, long time, TimeUnit timeUnit) {
  9. try {
  10. redisTemplate.opsForValue().set(key, object, time, timeUnit);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. /*
  16. 获取对象
  17. */
  18. public Object getObject(String key) {
  19. try {
  20. return redisTemplate.opsForValue().get(key);
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. return null;
  24. }
  25. }
  26. /*
  27. 删除对象
  28. */
  29. public void deleteObject(String key) {
  30. try {
  31. redisTemplate.delete(key);
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }

4、FastJson配置

引入依赖:

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>2.0.9</version>
  5. </dependency>

fastJson配置类:

  1. @Configuration
  2. public class MyFastJsonConfig {
  3. @Bean
  4. public HttpMessageConverters fastJsonHttpMessageConverters() {
  5. FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
  6. FastJsonConfig fastJsonConfig = new FastJsonConfig();
  7. fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
  8. fastConverter.setFastJsonConfig(fastJsonConfig);
  9. return new HttpMessageConverters((HttpMessageConverter<?>) fastConverter);
  10. }
  11. }

5、用户表与实体类

  1. CREATE TABLE `sys_user` (
  2. `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  3. `user_name` varchar(30) NOT NULL COMMENT '用户账号',
  4. `password` varchar(100) DEFAULT '' COMMENT '密码'
  5. PRIMARY KEY (`user_id`) USING BTREE
  6. ) ;

实体类:

  1. @Data
  2. @TableName("sys_user")
  3. public class SysUser implements Serializable {
  4. @TableId(value = "user_id")
  5. private Long userId;
  6. private String userName;
  7. //对该字段不进行序列化
  8. @JSONField(serialize = false)
  9. private String password;
  10. }

登录成功返回该前端的对象:

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class LoginVo implements Serializable {
  5. //用户对象
  6. private SysUser sysUser;
  7. //用户权限
  8. private List<String> authorities;
  9. //JWT token
  10. private String token;
  11. }

6、统一返回对象

  1. package com.dragonwu.model;
  2. import com.dragonwu.constant.Constants;
  3. import java.io.Serializable;
  4. public class R<T> implements Serializable {
  5. private static final long serialVersionUID = 1L;
  6. /**
  7. * 成功
  8. */
  9. public static final int SUCCESS = Constants.SUCCESS;
  10. /**
  11. * 失败
  12. */
  13. public static final int FAIL = Constants.FAIL;
  14. private int code;//状体码
  15. private String msg;//信息
  16. private T data;//数据
  17. public static <T> R<T> ok() {
  18. return restResult(null, SUCCESS, null);
  19. }
  20. public static <T> R<T> ok(T data) {
  21. return restResult(data, SUCCESS, null);
  22. }
  23. public static <T> R<T> ok(T data, String msg) {
  24. return restResult(data, SUCCESS, msg);
  25. }
  26. public static <T> R<T> fail() {
  27. return restResult(null, FAIL, null);
  28. }
  29. public static <T> R<T> fail(String msg) {
  30. return restResult(null, FAIL, msg);
  31. }
  32. public static <T> R<T> fail(T data) {
  33. return restResult(data, FAIL, null);
  34. }
  35. public static <T> R<T> fail(T data, String msg) {
  36. return restResult(data, FAIL, msg);
  37. }
  38. public static <T> R<T> fail(int code, String msg) {
  39. return restResult(null, code, msg);
  40. }
  41. public static <T> R<T> restResult(T data, int code, String msg) {
  42. R<T> apiResult = new R<>();
  43. apiResult.setCode(code);
  44. apiResult.setData(data);
  45. apiResult.setMsg(msg);
  46. return apiResult;
  47. }
  48. public int getCode() {
  49. return code;
  50. }
  51. public void setCode(int code) {
  52. this.code = code;
  53. }
  54. public String getMsg() {
  55. return msg;
  56. }
  57. public void setMsg(String msg) {
  58. this.msg = msg;
  59. }
  60. public T getData() {
  61. return data;
  62. }
  63. public void setData(T data) {
  64. this.data = data;
  65. }
  66. }

7、常量类

  1. package com.dragonwu.constant;
  2. /**
  3. * @author DragonWu
  4. * @since 2022-10-07 19:21
  5. **/
  6. public interface Constants {
  7. /*
  8. 成功
  9. */
  10. Integer SUCCESS = 200;
  11. /*
  12. 失败
  13. */
  14. Integer FAIL = 500;
  15. /*
  16. redis的统一前缀
  17. */
  18. String REDIS_PRE = "DragonWu:";
  19. /*
  20. redis的登录用户前缀
  21. */
  22. String ONLINE_PRE = REDIS_PRE + "online:";
  23. /*
  24. 登录
  25. */
  26. String LOGIN_OK = "登录成功";
  27. String LOGIN_FAIL = "登录失败";
  28. String LOGOUT_OK = "登出成功";
  29. String LOGOUT_FAIL = "登出失败";
  30. String LOGIN_ERROR = "用户名或密码错误";
  31. /*
  32. 获取资源
  33. */
  34. String GET_RESOURCE_OK = "获取成功";
  35. String GET_RESOURCE_FAIL = "获取失败";
  36. }

8、图片验证码的使用

依赖引入:

  1. <!--验证码-->
  2. <dependency>
  3. <groupId>com.github.whvcse</groupId>
  4. <artifactId>easy-captcha</artifactId>
  5. <version>1.6.2</version>
  6. </dependency>

服务层接口:

  1. package com.dragonwu.service;
  2. import com.dragonwu.constant.Constants;
  3. import java.io.IOException;
  4. import java.util.Map;
  5. /**
  6. * 验证码类
  7. * https://github.com/whvcse/EasyCaptcha
  8. */
  9. public interface EasyCaptchaService {
  10. /*
  11. 验证码宽度
  12. */
  13. Integer CAPTCHA_WIDTH = 130;
  14. /*
  15. 验证码高度
  16. */
  17. Integer CAPTCHA_HEIGHT = 48;
  18. /*
  19. 验证码长度
  20. */
  21. Integer CAPTCHA_LENGTH = 4;
  22. /*
  23. 验证码类型: 默认图片
  24. */
  25. int CAPTCHA_TYPE_DEFAULT_PNG = 10;
  26. /*
  27. 验证码类型: 动态GIF
  28. */
  29. final int CAPTCHA_TYPE_GIF = 20;
  30. /*
  31. 验证码类型: 中文
  32. */
  33. final int CAPTCHA_TYPE_CHINESE = 30;
  34. /*
  35. 验证码类型: 动态中文
  36. */
  37. final int CAPTCHA_TYPE_GIF_CHINESE = 40;
  38. /*
  39. 验证码类型: 算术
  40. */
  41. final int CAPTCHA_TYPE_ARITHMETIC = 50;
  42. /*
  43. 验证码缓存key前缀
  44. */
  45. String CAPTCHA_KEY_PRE = Constants.REDIS_PRE + "captcha:";
  46. /*
  47. 验证码图片键名
  48. */
  49. String IMAGE = "image";
  50. /*
  51. 验证码UUID键名
  52. */
  53. String UUID = "uuid";
  54. /*
  55. 验证码缓存时间,分
  56. */
  57. Integer CAPTCHA_CACHE_TIME = 3;
  58. /**
  59. * 生成普通图片验证码
  60. *
  61. * @param type 验证码类型
  62. * @return Map 验证码值,和base64转换的图片
  63. */
  64. Map<String, String> outputCaptchaImg(Integer type) throws IOException;
  65. /**
  66. * 校验图片验证码
  67. *
  68. * @param uuid 存入redis的key
  69. * @param captchaCode 验证码
  70. * @throws RuntimeException 验证失败抛出错误
  71. */
  72. void verifyCaptchaCode(String uuid, String captchaCode) throws RuntimeException;
  73. }

服务实现类:

  1. package com.dragonwu.service.impl;
  2. import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  3. import com.dragonwu.service.EasyCaptchaService;
  4. import com.dragonwu.utils.RedisUtil;
  5. import com.dragonwu.utils.SnowflakeIdGen;
  6. import com.wf.captcha.ArithmeticCaptcha;
  7. import com.wf.captcha.ChineseCaptcha;
  8. import com.wf.captcha.ChineseGifCaptcha;
  9. import com.wf.captcha.GifCaptcha;
  10. import com.wf.captcha.SpecCaptcha;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Service;
  13. import java.io.IOException;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. import java.util.concurrent.TimeUnit;
  17. @Service
  18. public class EasyCaptchaServiceImpl implements EasyCaptchaService {
  19. /*
  20. redis工具
  21. */
  22. @Autowired
  23. private RedisUtil redisUtil;
  24. /*
  25. 雪花算法生成唯一key
  26. */
  27. @Autowired
  28. private SnowflakeIdGen snowflakeIdGen;
  29. @Override
  30. public Map<String, String> outputCaptchaImg(Integer type) throws IOException {
  31. String codeValue = "";
  32. Map<String, String> map = new HashMap<>();
  33. switch (type) {
  34. case CAPTCHA_TYPE_DEFAULT_PNG:
  35. SpecCaptcha specCaptcha = new SpecCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
  36. codeValue = specCaptcha.text().toLowerCase();
  37. map.put(EasyCaptchaService.IMAGE, specCaptcha.toBase64());
  38. break;
  39. case CAPTCHA_TYPE_GIF:
  40. GifCaptcha gifCaptcha = new GifCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
  41. codeValue = gifCaptcha.text().toLowerCase();
  42. map.put(EasyCaptchaService.IMAGE, gifCaptcha.toBase64());
  43. break;
  44. case CAPTCHA_TYPE_CHINESE:
  45. ChineseCaptcha chineseCaptcha = new ChineseCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
  46. codeValue = chineseCaptcha.text();
  47. map.put(EasyCaptchaService.IMAGE, chineseCaptcha.toBase64());
  48. break;
  49. case CAPTCHA_TYPE_GIF_CHINESE:
  50. ChineseGifCaptcha chineseGifCaptcha = new ChineseGifCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
  51. codeValue = chineseGifCaptcha.text();
  52. map.put(EasyCaptchaService.IMAGE, chineseGifCaptcha.toBase64());
  53. break;
  54. case CAPTCHA_TYPE_ARITHMETIC:
  55. ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT);
  56. codeValue = arithmeticCaptcha.text();
  57. map.put(EasyCaptchaService.IMAGE, arithmeticCaptcha.toBase64());
  58. break;
  59. default:
  60. throw new IOException("类型不存在");
  61. }
  62. //生成redis键名
  63. String uuid = String.valueOf(snowflakeIdGen.nextId());
  64. //存入redis
  65. redisUtil.setObject(EasyCaptchaService.CAPTCHA_KEY_PRE + uuid, codeValue, EasyCaptchaService.CAPTCHA_CACHE_TIME, TimeUnit.MINUTES);
  66. map.put(EasyCaptchaService.UUID, uuid);
  67. return map;
  68. }
  69. @Override
  70. public void verifyCaptchaCode(String uuid, String captchaCode) throws RuntimeException {
  71. String cacheCaptchaCode = (String) redisUtil.getObject(EasyCaptchaService.CAPTCHA_KEY_PRE + uuid);
  72. if (StringUtils.isBlank(cacheCaptchaCode)) {
  73. throw new RuntimeException("验证码已过期,请重新获取");
  74. }
  75. if (!cacheCaptchaCode.equals(captchaCode.toLowerCase())) {
  76. throw new RuntimeException("验证码错误");
  77. }
  78. }
  79. }

9、雪花算法生成唯一id

  1. package com.dragonwu.utils;
  2. import org.springframework.stereotype.Component;
  3. /**
  4. * 采用twitter的雪花算法,生成有一定顺序且不重复的id,结果类型为64位的long型
  5. */
  6. @Component
  7. public class SnowflakeIdGen {
  8. //集群id
  9. private final long dataCenterId;
  10. //机器id
  11. private final long workerId;
  12. //集群id的bit位数
  13. private final long dataCenterIdBits = 5L;
  14. //机器id的bit位数
  15. private final long workerIdBits = 5L;
  16. //序列号
  17. private long sequenceId;
  18. //上一次生成id使用的timestamp ,以毫秒为单位
  19. private long lastTimestamp = 1L;
  20. /**
  21. * 若没有指定集群id和机器id,则默认均为0
  22. */
  23. public SnowflakeIdGen() {
  24. this(0, 0);
  25. }
  26. /**
  27. * 指定集群id和机器id
  28. */
  29. public SnowflakeIdGen(long dataCenterId, long workerId) {
  30. //集群id的最大编号
  31. long maxdataCenterId = ~(-1L << dataCenterIdBits);
  32. if (dataCenterId < 0 || dataCenterId > maxdataCenterId) {
  33. throw new RuntimeException(String.format("dataCenterId greater than %d or less than 0", maxdataCenterId));
  34. }
  35. //机器id的最大编号
  36. long maxWorkerId = ~(-1L << workerIdBits);
  37. if (workerId < 0 || workerId > maxWorkerId) {
  38. throw new RuntimeException(String.format("workerId greater than %d or less than 0", maxWorkerId));
  39. }
  40. this.dataCenterId = dataCenterId;
  41. this.workerId = workerId;
  42. }
  43. /**
  44. * 生成全局唯一的id
  45. */
  46. public synchronized long nextId() {
  47. long timestamp = System.currentTimeMillis();
  48. if (timestamp < lastTimestamp) { //出现这种情况,通常是由于机器时间出问题了
  49. throw new RuntimeException("machine time error");
  50. }
  51. //同一时刻生成的id号
  52. //序列号的bit位数
  53. long sequenceIdBits = 12L;
  54. if (timestamp == lastTimestamp) {
  55. //序列号的掩码
  56. long sequenceIdMask = ~(-1L << sequenceIdBits);
  57. sequenceId = (sequenceId + 1) & sequenceIdMask;
  58. if (sequenceId == 0) { //说明当前毫秒的序列号用完了,需从下个毫秒数开始重新计数
  59. timestamp = nextTimestamp(lastTimestamp);
  60. }
  61. } else {
  62. //否则序列号从0开始
  63. sequenceId = 0L;
  64. }
  65. lastTimestamp = timestamp;
  66. //生成最终结果时,集群id需移动的bit位数
  67. long timestampShiftBits = sequenceIdBits + workerIdBits + dataCenterIdBits;
  68. //生成最终结果时,集群id需移动的bit位数
  69. long dataCenterIdShiftBits = sequenceIdBits + workerIdBits;
  70. //生成最终结果时,机器id需移动的bit位数
  71. //去掉过去的时间,即从指定时间(本例以2017-10-12 00:00:00)开始算,
  72. // 大约可用69.5年(41位的时间位,最大值换成毫秒,再换算成年,大约69.5年)
  73. //1507737600000为从1970-01-01 00:00:00到2017-10-12 00:00:00经过的毫秒数
  74. long pastMills = 1507737600000L;
  75. return ((timestamp - pastMills) << timestampShiftBits)
  76. | (dataCenterId << dataCenterIdShiftBits)
  77. | (workerId << sequenceIdBits)
  78. | sequenceId;
  79. }
  80. /**
  81. * 获取上次取数毫秒的下一时刻
  82. */
  83. long nextTimestamp(long lastTimestamp) {
  84. long timestamp = System.currentTimeMillis();
  85. while (timestamp <= lastTimestamp) {
  86. timestamp = System.currentTimeMillis();
  87. }
  88. return timestamp;
  89. }
  90. }

10、web渲染工具类

  1. package com.dragonwu.utils;
  2. import javax.servlet.http.HttpServletResponse;
  3. import java.io.IOException;
  4. /**
  5. * web渲染工具类
  6. *
  7. * @author DragonWu
  8. * @since 2022-10-07 15:36
  9. **/
  10. public class WebUtil {
  11. /**
  12. * @param response 渲染对象
  13. * @param jsonString 待渲染的字符串
  14. */
  15. public static void renderString(HttpServletResponse response, String jsonString) {
  16. try {
  17. response.setStatus(200);
  18. response.setContentType("application/json");
  19. response.setCharacterEncoding("utf-8");
  20. response.getWriter().write(jsonString);
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }

二、SpringSecurity的配置

依赖引入:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>

1、SpringSecurity配置类

  1. package com.dragonwu.config;
  2. import com.dragonwu.filter.TokenAuthenticationFilter;
  3. import com.dragonwu.handler.AccessDeniedHandlerImpl;
  4. import com.dragonwu.handler.AuthenticationEntryPointImpl;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.security.authentication.AuthenticationManager;
  9. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  10. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  11. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  13. import org.springframework.security.config.http.SessionCreationPolicy;
  14. import org.springframework.security.core.userdetails.UserDetailsService;
  15. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  16. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  17. import org.springframework.web.cors.CorsConfiguration;
  18. import org.springframework.web.cors.CorsConfigurationSource;
  19. import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  20. import java.util.Arrays;
  21. import java.util.Collections;
  22. /**
  23. * @author DragonWu
  24. * @since 2022-10-06 21:14
  25. **/
  26. @Configuration
  27. @EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限访问注解
  28. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  29. //允许匿名访问的
  30. private static final String[] ALLOW_ASK = {
  31. "/login",
  32. "/captcha"
  33. };
  34. //总数允许访问的
  35. private static final String[] ALWAYS_ALLOW_ASK = {
  36. "/v2/api-docs",
  37. "/swagger-resources/configuration/ui",//用来获取支持的动作
  38. "/swagger-resources",//用来获取api-docs的URI
  39. "/swagger-resources/configuration/security",//安全选项
  40. "/webjars/**",
  41. "/swagger-ui.html",//以上为api文档接口访问路径
  42. "/static/**",//放行静态资源
  43. "**/open" //开放访问的资源
  44. };
  45. //自定义登录
  46. @Autowired
  47. private UserDetailsService userDetailsService;
  48. //token过滤器
  49. @Autowired
  50. private TokenAuthenticationFilter tokenAuthenticationFilter;
  51. //禁止访问异常处理器
  52. @Autowired
  53. private AccessDeniedHandlerImpl accessDeniedHandler;
  54. //认证异常处理器
  55. @Autowired
  56. private AuthenticationEntryPointImpl authenticationEntryPoint;
  57. /*
  58. 密码加密器
  59. */
  60. @Bean
  61. public BCryptPasswordEncoder bCryptPasswordEncoder() {
  62. return new BCryptPasswordEncoder();
  63. }
  64. /*
  65. 设置自定义登录逻辑
  66. */
  67. @Override
  68. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  69. auth.userDetailsService(userDetailsService);
  70. }
  71. /*
  72. 自定义登录,用户认证
  73. */
  74. @Bean
  75. @Override
  76. protected AuthenticationManager authenticationManager() throws Exception {
  77. return super.authenticationManager();
  78. }
  79. /*
  80. 页面资源授权
  81. */
  82. @Override
  83. protected void configure(HttpSecurity http) throws Exception {
  84. http
  85. //关闭csrf
  86. .csrf().disable()
  87. //不通过Session获取SecurityContext
  88. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  89. .and()
  90. .authorizeRequests()
  91. //对应登录接口允许匿名访问
  92. .antMatchers(ALWAYS_ALLOW_ASK).permitAll()
  93. .antMatchers(ALLOW_ASK).anonymous()
  94. //除上面接口全都需要鉴权访问
  95. .anyRequest().authenticated();
  96. //添加过滤器
  97. http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
  98. //添加异常处理器
  99. http.exceptionHandling()
  100. //认证失败的处理器
  101. .authenticationEntryPoint(authenticationEntryPoint)
  102. //授权失败处理器
  103. .accessDeniedHandler(accessDeniedHandler);
  104. //跨域配置
  105. http.cors()
  106. .configurationSource(corsConfigurationSource());
  107. }
  108. /*
  109. 跨域配置对象
  110. */
  111. @Bean
  112. public CorsConfigurationSource corsConfigurationSource() {
  113. CorsConfiguration configuration = new CorsConfiguration();
  114. //配置允许访问的服务器域名
  115. configuration.setAllowedOrigins(Collections.singletonList("*"));
  116. configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
  117. configuration.setAllowedHeaders(Collections.singletonList("*"));
  118. configuration.setAllowCredentials(true);
  119. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  120. source.registerCorsConfiguration("/**", configuration);
  121. return source;
  122. }
  123. }

2、SpringSecurity异常处理器

认证异常处理器:

  1. @Component
  2. public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
  3. @Override
  4. public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  5. R<String> result = R.restResult(null, HttpStatus.UNAUTHORIZED.value(), "认证失败");
  6. String json = JSON.toJSONString(result);
  7. //处理异常,返回结果
  8. WebUtil.renderString(httpServletResponse, json);
  9. }
  10. }

禁止访问异常处理器:

  1. @Component
  2. public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
  3. @Override
  4. public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
  5. R<String> result = R.restResult(null, HttpStatus.FORBIDDEN.value(), "禁止访问");
  6. String json = JSON.toJSONString(result);
  7. //处理异常,返回结果
  8. WebUtil.renderString(httpServletResponse, json);
  9. }
  10. }

异常处理器可以将异常自定义处理。

三、JWT+SpringSecurity实现登录的主要步骤

1、登录图解

2、登录开始前首先创建UserDetails实体类的子类

  1. package com.dragonwu.model;
  2. import com.alibaba.fastjson.annotation.JSONField;
  3. import com.dragonwu.model.entity.SysUser;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. import org.apache.tomcat.util.buf.StringUtils;
  7. import org.springframework.security.core.GrantedAuthority;
  8. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  9. import org.springframework.security.core.userdetails.UserDetails;
  10. import java.util.Collection;
  11. import java.util.List;
  12. import java.util.Objects;
  13. import java.util.stream.Collectors;
  14. /**
  15. * @author DragonWu
  16. * @since 2022-10-06 20:29
  17. **/
  18. @Data
  19. @NoArgsConstructor
  20. public class LoginUser implements UserDetails {
  21. private SysUser sysUser;
  22. //用户角色权限
  23. private List<String> jurisdiction;
  24. //将不会被序列化到redis里
  25. @JSONField(serialize = false)
  26. private List<SimpleGrantedAuthority> authorities;
  27. public LoginUser(SysUser sysUser, List<String> jurisdiction) {
  28. this.sysUser = sysUser;
  29. this.jurisdiction = jurisdiction;
  30. }
  31. public String getJurisdictionToString() {
  32. return StringUtils.join(jurisdiction, ',');
  33. }
  34. @Override
  35. public Collection<? extends GrantedAuthority> getAuthorities() {
  36. //把jurisdiction里的权限信息字符串封装成SimpleGrantedAuthority对象
  37. if (!Objects.isNull(authorities)) {
  38. return authorities;
  39. }
  40. authorities = jurisdiction.stream()
  41. .map(SimpleGrantedAuthority::new)
  42. .collect(Collectors.toList());
  43. return authorities;
  44. }
  45. @Override
  46. public String getPassword() {
  47. return sysUser.getPassword();
  48. }
  49. @Override
  50. public String getUsername() {
  51. return sysUser.getUserName();
  52. }
  53. @Override
  54. public boolean isAccountNonExpired() {
  55. return true;
  56. }
  57. @Override
  58. public boolean isAccountNonLocked() {
  59. return true;
  60. }
  61. @Override
  62. public boolean isCredentialsNonExpired() {
  63. return true;
  64. }
  65. @Override
  66. public boolean isEnabled() {
  67. return true;
  68. }
  69. }

3、创建UserDetailsService的实现类

通过UserDetailsService的实现可对用户对象进行自定义返回

  1. package com.dragonwu.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.dragonwu.model.entity.SysUser;
  4. import com.dragonwu.mapper.SysUserMapper;
  5. import com.dragonwu.model.LoginUser;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.security.core.userdetails.UserDetailsService;
  9. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  10. import org.springframework.stereotype.Service;
  11. import java.util.ArrayList;
  12. import java.util.Arrays;
  13. import java.util.List;
  14. import java.util.Objects;
  15. /**
  16. * @author DragonWu
  17. * @since 2022-10-06 20:47
  18. **/
  19. @Service
  20. public class UserDetailsServiceImpl implements UserDetailsService {
  21. @Autowired
  22. private SysUserMapper sysUserMapper;
  23. @Override
  24. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  25. //查询用户信息
  26. SysUser user = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName,username));
  27. //如果没有查询到用户就抛出异常
  28. if (Objects.isNull(user)) {
  29. throw new UsernameNotFoundException("用户名不存在!");
  30. }
  31. //查询对应的权限信息和角色信息
  32. // 这一般需要查询role表和jurisdiction表的权限,这里略过
  33. List<String> jurisdiction=new ArrayList<>(Arrays.asList("admin","normal","ROLE_manager"));
  34. //将数据封装到UserDetail返回
  35. return new LoginUser(user, jurisdiction);
  36. }
  37. }

4、登录的实现类与接口

接口:

  1. package com.dragonwu.controller;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.dragonwu.constant.Constants;
  4. import com.dragonwu.model.R;
  5. import com.dragonwu.model.param.LoginParam;
  6. import com.dragonwu.model.vo.LoginVo;
  7. import com.dragonwu.service.LoginService;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.web.bind.annotation.GetMapping;
  11. import org.springframework.web.bind.annotation.PostMapping;
  12. import org.springframework.web.bind.annotation.RequestBody;
  13. import org.springframework.web.bind.annotation.RestController;
  14. import java.util.Map;
  15. @RestController
  16. @Slf4j
  17. public class LoginController {
  18. @Autowired
  19. private LoginService loginService;
  20. @PostMapping("/login")
  21. public R<LoginVo> login(@RequestBody LoginParam loginParam) {
  22. try {
  23. return R.ok(loginService.login(loginParam), Constants.LOGIN_OK);
  24. } catch (RuntimeException msg) {
  25. //返回失败结果
  26. return R.fail(null, msg.getMessage());
  27. } catch (Exception e) {
  28. log.error("后台登录出现异常:" + JSONObject.toJSONString(loginParam), e);
  29. return R.fail(null, Constants.LOGIN_FAIL);
  30. }
  31. }
  32. @GetMapping("/logout.do")
  33. public R<String> logout() {
  34. try {
  35. loginService.logout();
  36. return R.ok(null, Constants.LOGOUT_OK);
  37. } catch (RuntimeException msg) {
  38. return R.fail(null, msg.getMessage());
  39. } catch (Exception e) {
  40. log.error("退出登录出现异常:" + e);
  41. return R.fail(null, Constants.LOGOUT_FAIL);
  42. }
  43. }
  44. @GetMapping("/captcha")
  45. public R<Map<String, String>> captcha() {
  46. try {
  47. return R.ok(loginService.getCaptcha(), Constants.GET_RESOURCE_OK);
  48. } catch (RuntimeException msg) {
  49. return R.fail(null, msg.getMessage());
  50. } catch (Exception e) {
  51. log.error("获取验证码出现异常:", e);
  52. return R.fail(null, Constants.GET_RESOURCE_FAIL);
  53. }
  54. }
  55. }

服务层接口:

  1. public interface LoginService {
  2. /**
  3. * 用户登录
  4. * @param loginParam 登录参数
  5. * @return 登录返回对象
  6. */
  7. LoginVo login(LoginParam loginParam);
  8. /**
  9. * 退出登录
  10. */
  11. void logout() throws RuntimeException;
  12. /**
  13. * 获取验证码
  14. * @return Map UUID验证码对应redis里的key,image的base64编码
  15. */
  16. Map<String,String> getCaptcha() throws IOException;
  17. }

服务层实现类:

  1. package com.dragonwu.service.impl;
  2. import com.dragonwu.constant.Constants;
  3. import com.dragonwu.model.LoginUser;
  4. import com.dragonwu.model.OnlineUser;
  5. import com.dragonwu.model.param.LoginParam;
  6. import com.dragonwu.model.vo.LoginVo;
  7. import com.dragonwu.service.EasyCaptchaService;
  8. import com.dragonwu.service.LoginService;
  9. import com.dragonwu.utils.JwtUtil;
  10. import com.dragonwu.utils.RedisUtil;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.security.authentication.AuthenticationManager;
  13. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  14. import org.springframework.security.core.Authentication;
  15. import org.springframework.security.core.context.SecurityContextHolder;
  16. import org.springframework.stereotype.Service;
  17. import java.io.IOException;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.Objects;
  21. import java.util.concurrent.TimeUnit;
  22. /**
  23. * @author DragonWu
  24. * @since 2022-10-07 19:02
  25. **/
  26. @Service
  27. public class LoginServiceImpl implements LoginService {
  28. @Autowired
  29. private AuthenticationManager authenticationManager;
  30. @Autowired
  31. private RedisUtil redisUtil;
  32. @Autowired
  33. private EasyCaptchaService easyCaptchaService;
  34. @Override
  35. public LoginVo login(LoginParam loginParam) throws RuntimeException {
  36. //判断验证码是否正确
  37. easyCaptchaService.verifyCaptchaCode(loginParam.getUuid(), loginParam.getCaptcha());
  38. //AuthenticationManager authenticate进行用户认证
  39. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginParam.getUsername(), loginParam.getPassword());
  40. Authentication authenticate = authenticationManager.authenticate(authenticationToken);
  41. //如果认证没通过给出相应的提示
  42. if (Objects.isNull(authenticate)) {
  43. throw new RuntimeException(Constants.LOGIN_ERROR);
  44. }
  45. //如果认证通过了,获取UserDetailService返回过期的UserDetail对象
  46. LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
  47. //使用primaryKey生成JWT
  48. String primaryKey = loginUser.getSysUser().getUserId().toString();
  49. Map<String, Object> claims = new HashMap<>();
  50. claims.put(JwtUtil.JWT_PRIMARY_KEY, primaryKey);
  51. String token = JwtUtil.createJwt(claims, JwtUtil.DEFAULT_EXPIRE_MINUTES);
  52. //把用户信息存入redis
  53. OnlineUser onlineUser = new OnlineUser(loginUser.getSysUser(), loginUser.getJurisdictionToString());
  54. redisUtil.setObject(Constants.ONLINE_PRE + primaryKey, onlineUser, JwtUtil.DEFAULT_EXPIRE_MINUTES, TimeUnit.MINUTES);
  55. //将返回结果封装达到返回对象
  56. return new LoginVo(loginUser.getSysUser(), loginUser.getJurisdiction(), token);
  57. }
  58. @Override
  59. public void logout() throws RuntimeException {
  60. try {
  61. //获取SecurityContextHolder里的用户id
  62. UsernamePasswordAuthenticationToken authentication =
  63. (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
  64. OnlineUser onlineUser = (OnlineUser) authentication.getPrincipal();
  65. String primaryKey = String.valueOf(onlineUser.getSysUser().getUserId());
  66. //删除redis中的值
  67. redisUtil.deleteObject(Constants.ONLINE_PRE + primaryKey);
  68. } catch (Exception e) {
  69. throw new RuntimeException(Constants.LOGOUT_FAIL);
  70. }
  71. }
  72. @Override
  73. public Map<String, String> getCaptcha() throws IOException {
  74. return easyCaptchaService.outputCaptchaImg(EasyCaptchaService.CAPTCHA_TYPE_GIF);
  75. }
  76. }

这里我们便实现了登录逻辑。

5、认证过滤器

判断用户是否已登录

  1. package com.dragonwu.filter;
  2. import com.alibaba.fastjson.JSON;
  3. import com.dragonwu.constant.Constants;
  4. import com.dragonwu.model.OnlineUser;
  5. import com.dragonwu.model.R;
  6. import com.dragonwu.utils.JwtUtil;
  7. import com.dragonwu.utils.RedisUtil;
  8. import com.dragonwu.utils.WebUtil;
  9. import org.junit.platform.commons.util.StringUtils;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.http.HttpStatus;
  12. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  13. import org.springframework.security.core.authority.AuthorityUtils;
  14. import org.springframework.security.core.context.SecurityContextHolder;
  15. import org.springframework.stereotype.Component;
  16. import org.springframework.web.filter.OncePerRequestFilter;
  17. import javax.servlet.FilterChain;
  18. import javax.servlet.ServletException;
  19. import javax.servlet.http.HttpServletRequest;
  20. import javax.servlet.http.HttpServletResponse;
  21. import java.io.IOException;
  22. import java.util.Map;
  23. import java.util.Objects;
  24. /**
  25. * token验证过滤器
  26. *
  27. * @author DragonWu
  28. * @since 2022-10-07 19:02
  29. **/
  30. @Component
  31. public class TokenAuthenticationFilter extends OncePerRequestFilter {
  32. public static final String HEADER = "Authorization";
  33. public static final String PARAM = "token";
  34. @Autowired
  35. private RedisUtil redisUtil;
  36. @Override
  37. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
  38. //获得前端请求中的token
  39. String token = request.getHeader(HEADER);
  40. if (StringUtils.isBlank(token)) {
  41. token = request.getParameter(PARAM);
  42. }
  43. //如果token为空,放行,验证失败
  44. if (StringUtils.isBlank(token)) {
  45. chain.doFilter(request, response);
  46. return;
  47. }
  48. //解析token
  49. Map<String, Object> claims = JwtUtil.parseJwt(token);
  50. if (Objects.isNull(claims)) {//解析异常
  51. R<String> result = R.restResult(null, HttpStatus.FORBIDDEN.value(), "token非法");
  52. String json = JSON.toJSONString(result);
  53. //返回解析错误的json数据
  54. WebUtil.renderString(response, json);
  55. return;
  56. }
  57. //获取用户主键,以便到redis中查询
  58. String primaryKey = (String) claims.get(JwtUtil.JWT_PRIMARY_KEY);
  59. //从redis中获取token对应的数据
  60. String redisKey = Constants.ONLINE_PRE + primaryKey;
  61. //获取用户信息
  62. OnlineUser onlineUser = (OnlineUser) redisUtil.getObject(redisKey);
  63. if (Objects.isNull(onlineUser)) {
  64. R<String> result = R.restResult(null, HttpStatus.UNAUTHORIZED.value(), "认证失败");
  65. String json = JSON.toJSONString(result);
  66. //返回请求失败的json数据
  67. WebUtil.renderString(response, json);
  68. return;
  69. }
  70. //存入SecurityContextHolder中
  71. UsernamePasswordAuthenticationToken authenticationToken =
  72. new UsernamePasswordAuthenticationToken(onlineUser, null,
  73. AuthorityUtils.commaSeparatedStringToAuthorityList(onlineUser.getAuthorities()));
  74. //获取权限信息封装到authenticationToken里
  75. SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  76. //放行
  77. chain.doFilter(request, response);
  78. }
  79. }

四、测试

1、登录测试

首先访问验证码接口获取验证码的图片和uuid:

 

 服务器通过用户输入的验证码结果和redis中的缓存进行比较得出是否验证码正确。

访问登录接口:

 登录成功:

 2、资源访问测试

未携带JWT:

 携带token就是登录的状态:

携带错误的token测试:可以看到错误的token会被服务器警告。

 

 3、退出登录

退出登录后,redis中缓存的用户信息将被删除。

 五、案例源码链接

SpringCloudAlibaba各技术栈实现的案例代码集 我的笔记: SpringCloudAlibaba各技术栈实现的案例代码集 我的笔记 - Gitee.com

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

闽ICP备14008679号