赞
踩
目录
第一步:生成密钥
通过keyTool(java提供的证书管理工具)生成
只要安装了JDK,就有这个工具。进入jdk的安装目录,就能使用该工具:
输入以下命令生成私钥:
- // 以下指令为一行,设置多行是方便阅读
- keytool -genkeypair
- -alias 密钥别名
- -keyalg 使用的算法
- -keypass 密钥的访问密码
- -keystore 生成的密钥库文件名,扩展名是jks
- -storepass 密钥库的访问密码,用来打开jks文件
此时私钥已生成:
第二步:生成公钥
对于微服务来说,私钥放在认证服务上,其他服务只需要存公钥即可。因为其他服务只做校验,不加密JWT。私钥加密,公钥解密。
生成公钥输入以下命令:
keytool -list -rfc --keystore jks文件(包含扩展名.jks) | openssl x509 -inform pem -pubkey
复制文本,重命名文件
公钥和私钥放到服务器上,私钥用于授权服务器授权token时加密,其他服务器仅需要通过公钥便可对加密内容进行解析了。
这里需要添加依赖:
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.security</groupId>
- <artifactId>spring-security-rsa</artifactId>
- <version>1.0.11.RELEASE</version>
- </dependency>
JWT工具类:
- package com.dragonwu.utils;
-
- import com.dragonwu.model.OnlineUser;
- import io.jsonwebtoken.JwtBuilder;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
- import org.springframework.core.io.ClassPathResource;
- import org.springframework.core.io.Resource;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
-
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.security.KeyFactory;
- import java.security.KeyPair;
- import java.security.PrivateKey;
- import java.security.PublicKey;
- import java.security.spec.X509EncodedKeySpec;
- import java.util.Base64;
- import java.util.Date;
- import java.util.Map;
- import java.util.Objects;
-
- /**
- * 通过RSA非对称加密的JWT工具类
- *
- * @author DragonWu
- * @date 2022-10-06 17:51
- **/
- public class JwtUtil {
-
- //默认60分钟
- public static final Long DEFAULT_EXPIRE_MINUTES = 60L;
-
- //jwt的唯一标识,一般为username或userId
- public static final String JWT_PRIMARY_KEY = "userId";
-
- //私钥路径
- public static final String PRIVATE_KEY_PATH = "/keys/dragonwu.jks";
- //公钥路径
- public static final String PUBLIC_KEY_PATH = "/keys/public.key";
-
- //私钥
- public static PrivateKey privateKey;
- //公钥
- public static PublicKey publicKey;
-
- /**
- * 传入的数据不得含敏感信息,否则可能被盗用,私钥加密token
- *
- * @param claims 数据体
- * @param minutes 过期时间,分
- * @return String JWT的token
- */
- public static String createJwt(Map<String, Object> claims, long minutes) {
- //获取私钥
- if (Objects.isNull(privateKey)) {
- privateKey = getPrivateKey();
- }
-
- //当前系统时间
- long now = System.currentTimeMillis();
- //过期时间
- long exp = now + minutes * 60 * 1000;
-
- JwtBuilder jwtBuilder = Jwts.builder()
- //签发的算法
- .signWith(SignatureAlgorithm.RS256, privateKey)
- //设置令牌过期时间
- .setExpiration(new Date(exp))
- //设置签发时间
- .setIssuedAt(new Date())
- //设置body数据,自定义设置
- .setClaims(claims);
- return jwtBuilder.compact();
- }
-
- /**
- * 公钥解析jwt
- *
- * @param jwt jwt字符串
- * @return 返回null证明解析异常
- */
- public static Map<String, Object> parseJwt(String jwt) {
- if (Objects.isNull(publicKey)) {
- publicKey = getPublicKey();
- }
-
- try {
- return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody();
- } catch (Exception ignored) {
- }
- return null;
- }
-
- /**
- * 获取私钥
- *
- * @return 当获取异常时返回null
- */
- private static PrivateKey getPrivateKey() {
- try {
- ClassPathResource keyFileResource = new ClassPathResource(PRIVATE_KEY_PATH);
- //创建秘钥工厂,参数为:秘钥文件、秘钥库密码
- //import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
- //pom文件:
- /*
- 公钥私钥生成 https://blog.csdn.net/qq_37470815/article/details/123027798
- <dependency>
- <groupId>org.springframework.security</groupId>
- <artifactId>spring-security-rsa</artifactId>
- <version>1.0.11.RELEASE</version>
- </dependency>
- KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyFileResource, "密钥库密码password".toCharArray());
- //获取秘钥,参数为:别名,秘钥密码
- KeyPair keyContent = keyStoreKeyFactory.getKeyPair("alias别名", "秘钥的密码".toCharArray());
- */
- KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyFileResource, "dragonwu".toCharArray());
- KeyPair keyContent = keyStoreKeyFactory.getKeyPair("dragonwu", "dragonwu".toCharArray());
- return keyContent.getPrivate();
- } catch (Exception ignored) {
- }
- return null;
- }
-
- /**
- * 获取公钥
- *
- * @return 当获取异常时返回null
- */
- private static PublicKey getPublicKey() {
- try {
- Resource publicKey = new ClassPathResource(PUBLIC_KEY_PATH);
- InputStreamReader publicKeyIs = new InputStreamReader(publicKey.getInputStream());
- BufferedReader publicKeyBr = new BufferedReader(publicKeyIs);
- StringBuilder publicKeySb = new StringBuilder();
- String line;
- //将文件中的多行变为一行
- while ((line = publicKeyBr.readLine()) != null) {
- publicKeySb.append(line);
- }
-
- //将String转换成java的PublicKey对象
- byte[] byteKey = Base64.getDecoder().decode(publicKeySb.toString());
- X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(byteKey);
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- return keyFactory.generatePublic(x509EncodedKeySpec);
- } catch (Exception ignored) {
- }
- return null;
- }
-
- /*
- SpringSecurity登录后获取用户信息
- */
- public static OnlineUser getUser() {
- return (OnlineUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- }
-
- }

引入依赖:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
redis配置类:
- @Configuration
- public class RedisConfig {
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
- // 创建 RedisTemplate 对象
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- // 设置连接工厂
- redisTemplate.setConnectionFactory(connectionFactory);
- // 设置 Key 的序列化 - String 序列化 RedisSerializer.string() => StringRedisSerializer.UTF_8
- redisTemplate.setKeySerializer(RedisSerializer.string());
- redisTemplate.setHashKeySerializer(RedisSerializer.string());
- // 设置 Value 的序列化 - JSON 序列化 RedisSerializer.json() => GenericJackson2JsonRedisSerializer
- redisTemplate.setValueSerializer(RedisSerializer.json());
- redisTemplate.setHashValueSerializer(RedisSerializer.json());
- // 返回
- return redisTemplate;
- }
- }

redis工具类:
- @Component
- public class RedisUtil {
-
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
-
- /*
- 设置对象
- */
- public void setObject(String key, Object object, long time, TimeUnit timeUnit) {
- try {
- redisTemplate.opsForValue().set(key, object, time, timeUnit);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /*
- 获取对象
- */
- public Object getObject(String key) {
- try {
- return redisTemplate.opsForValue().get(key);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /*
- 删除对象
- */
- public void deleteObject(String key) {
- try {
- redisTemplate.delete(key);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- }

引入依赖:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>2.0.9</version>
- </dependency>
fastJson配置类:
- @Configuration
- public class MyFastJsonConfig {
- @Bean
- public HttpMessageConverters fastJsonHttpMessageConverters() {
- FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
- FastJsonConfig fastJsonConfig = new FastJsonConfig();
- fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
- fastConverter.setFastJsonConfig(fastJsonConfig);
- return new HttpMessageConverters((HttpMessageConverter<?>) fastConverter);
- }
-
- }
- CREATE TABLE `sys_user` (
- `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
- `user_name` varchar(30) NOT NULL COMMENT '用户账号',
- `password` varchar(100) DEFAULT '' COMMENT '密码'
- PRIMARY KEY (`user_id`) USING BTREE
- ) ;
实体类:
- @Data
- @TableName("sys_user")
- public class SysUser implements Serializable {
- @TableId(value = "user_id")
- private Long userId;
-
- private String userName;
-
- //对该字段不进行序列化
- @JSONField(serialize = false)
- private String password;
- }
登录成功返回该前端的对象:
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class LoginVo implements Serializable {
- //用户对象
- private SysUser sysUser;
-
- //用户权限
- private List<String> authorities;
-
- //JWT token
- private String token;
- }
- package com.dragonwu.model;
-
- import com.dragonwu.constant.Constants;
-
- import java.io.Serializable;
-
- public class R<T> implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- /**
- * 成功
- */
- public static final int SUCCESS = Constants.SUCCESS;
-
- /**
- * 失败
- */
- public static final int FAIL = Constants.FAIL;
-
- private int code;//状体码
-
- private String msg;//信息
-
- private T data;//数据
-
- public static <T> R<T> ok() {
- return restResult(null, SUCCESS, null);
- }
-
- public static <T> R<T> ok(T data) {
- return restResult(data, SUCCESS, null);
- }
-
- public static <T> R<T> ok(T data, String msg) {
- return restResult(data, SUCCESS, msg);
- }
-
- public static <T> R<T> fail() {
- return restResult(null, FAIL, null);
- }
-
- public static <T> R<T> fail(String msg) {
- return restResult(null, FAIL, msg);
- }
-
- public static <T> R<T> fail(T data) {
- return restResult(data, FAIL, null);
- }
-
- public static <T> R<T> fail(T data, String msg) {
- return restResult(data, FAIL, msg);
- }
-
- public static <T> R<T> fail(int code, String msg) {
- return restResult(null, code, msg);
- }
-
- public static <T> R<T> restResult(T data, int code, String msg) {
- R<T> apiResult = new R<>();
- apiResult.setCode(code);
- apiResult.setData(data);
- apiResult.setMsg(msg);
- return apiResult;
- }
-
- public int getCode() {
- return code;
- }
-
- public void setCode(int code) {
- this.code = code;
- }
-
- public String getMsg() {
- return msg;
- }
-
- public void setMsg(String msg) {
- this.msg = msg;
- }
-
- public T getData() {
- return data;
- }
-
- public void setData(T data) {
- this.data = data;
- }
- }

- package com.dragonwu.constant;
-
- /**
- * @author DragonWu
- * @since 2022-10-07 19:21
- **/
- public interface Constants {
- /*
- 成功
- */
- Integer SUCCESS = 200;
-
- /*
- 失败
- */
- Integer FAIL = 500;
-
- /*
- redis的统一前缀
- */
- String REDIS_PRE = "DragonWu:";
-
- /*
- redis的登录用户前缀
- */
- String ONLINE_PRE = REDIS_PRE + "online:";
-
- /*
- 登录
- */
- String LOGIN_OK = "登录成功";
- String LOGIN_FAIL = "登录失败";
- String LOGOUT_OK = "登出成功";
- String LOGOUT_FAIL = "登出失败";
- String LOGIN_ERROR = "用户名或密码错误";
-
- /*
- 获取资源
- */
- String GET_RESOURCE_OK = "获取成功";
- String GET_RESOURCE_FAIL = "获取失败";
-
-
- }

依赖引入:
- <!--验证码-->
- <dependency>
- <groupId>com.github.whvcse</groupId>
- <artifactId>easy-captcha</artifactId>
- <version>1.6.2</version>
- </dependency>
服务层接口:
- package com.dragonwu.service;
-
- import com.dragonwu.constant.Constants;
-
- import java.io.IOException;
- import java.util.Map;
-
- /**
- * 验证码类
- * https://github.com/whvcse/EasyCaptcha
- */
- public interface EasyCaptchaService {
- /*
- 验证码宽度
- */
- Integer CAPTCHA_WIDTH = 130;
-
- /*
- 验证码高度
- */
- Integer CAPTCHA_HEIGHT = 48;
- /*
- 验证码长度
- */
- Integer CAPTCHA_LENGTH = 4;
-
- /*
- 验证码类型: 默认图片
- */
- int CAPTCHA_TYPE_DEFAULT_PNG = 10;
- /*
- 验证码类型: 动态GIF
- */
- final int CAPTCHA_TYPE_GIF = 20;
- /*
- 验证码类型: 中文
- */
- final int CAPTCHA_TYPE_CHINESE = 30;
- /*
- 验证码类型: 动态中文
- */
- final int CAPTCHA_TYPE_GIF_CHINESE = 40;
- /*
- 验证码类型: 算术
- */
- final int CAPTCHA_TYPE_ARITHMETIC = 50;
-
- /*
- 验证码缓存key前缀
- */
- String CAPTCHA_KEY_PRE = Constants.REDIS_PRE + "captcha:";
- /*
- 验证码图片键名
- */
- String IMAGE = "image";
- /*
- 验证码UUID键名
- */
- String UUID = "uuid";
- /*
- 验证码缓存时间,分
- */
- Integer CAPTCHA_CACHE_TIME = 3;
-
- /**
- * 生成普通图片验证码
- *
- * @param type 验证码类型
- * @return Map 验证码值,和base64转换的图片
- */
- Map<String, String> outputCaptchaImg(Integer type) throws IOException;
-
- /**
- * 校验图片验证码
- *
- * @param uuid 存入redis的key
- * @param captchaCode 验证码
- * @throws RuntimeException 验证失败抛出错误
- */
- void verifyCaptchaCode(String uuid, String captchaCode) throws RuntimeException;
- }

服务实现类:
- package com.dragonwu.service.impl;
-
- import com.baomidou.mybatisplus.core.toolkit.StringUtils;
- import com.dragonwu.service.EasyCaptchaService;
- import com.dragonwu.utils.RedisUtil;
- import com.dragonwu.utils.SnowflakeIdGen;
- import com.wf.captcha.ArithmeticCaptcha;
- import com.wf.captcha.ChineseCaptcha;
- import com.wf.captcha.ChineseGifCaptcha;
- import com.wf.captcha.GifCaptcha;
- import com.wf.captcha.SpecCaptcha;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.concurrent.TimeUnit;
-
- @Service
- public class EasyCaptchaServiceImpl implements EasyCaptchaService {
- /*
- redis工具
- */
- @Autowired
- private RedisUtil redisUtil;
-
- /*
- 雪花算法生成唯一key
- */
- @Autowired
- private SnowflakeIdGen snowflakeIdGen;
-
- @Override
- public Map<String, String> outputCaptchaImg(Integer type) throws IOException {
- String codeValue = "";
- Map<String, String> map = new HashMap<>();
- switch (type) {
- case CAPTCHA_TYPE_DEFAULT_PNG:
- SpecCaptcha specCaptcha = new SpecCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
- codeValue = specCaptcha.text().toLowerCase();
- map.put(EasyCaptchaService.IMAGE, specCaptcha.toBase64());
- break;
- case CAPTCHA_TYPE_GIF:
- GifCaptcha gifCaptcha = new GifCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
- codeValue = gifCaptcha.text().toLowerCase();
- map.put(EasyCaptchaService.IMAGE, gifCaptcha.toBase64());
- break;
- case CAPTCHA_TYPE_CHINESE:
- ChineseCaptcha chineseCaptcha = new ChineseCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
- codeValue = chineseCaptcha.text();
- map.put(EasyCaptchaService.IMAGE, chineseCaptcha.toBase64());
- break;
- case CAPTCHA_TYPE_GIF_CHINESE:
- ChineseGifCaptcha chineseGifCaptcha = new ChineseGifCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, CAPTCHA_LENGTH);
- codeValue = chineseGifCaptcha.text();
- map.put(EasyCaptchaService.IMAGE, chineseGifCaptcha.toBase64());
- break;
- case CAPTCHA_TYPE_ARITHMETIC:
- ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT);
- codeValue = arithmeticCaptcha.text();
- map.put(EasyCaptchaService.IMAGE, arithmeticCaptcha.toBase64());
- break;
- default:
- throw new IOException("类型不存在");
- }
- //生成redis键名
- String uuid = String.valueOf(snowflakeIdGen.nextId());
- //存入redis
- redisUtil.setObject(EasyCaptchaService.CAPTCHA_KEY_PRE + uuid, codeValue, EasyCaptchaService.CAPTCHA_CACHE_TIME, TimeUnit.MINUTES);
- map.put(EasyCaptchaService.UUID, uuid);
- return map;
- }
-
- @Override
- public void verifyCaptchaCode(String uuid, String captchaCode) throws RuntimeException {
- String cacheCaptchaCode = (String) redisUtil.getObject(EasyCaptchaService.CAPTCHA_KEY_PRE + uuid);
- if (StringUtils.isBlank(cacheCaptchaCode)) {
- throw new RuntimeException("验证码已过期,请重新获取");
- }
- if (!cacheCaptchaCode.equals(captchaCode.toLowerCase())) {
- throw new RuntimeException("验证码错误");
- }
- }
- }

- package com.dragonwu.utils;
-
- import org.springframework.stereotype.Component;
-
- /**
- * 采用twitter的雪花算法,生成有一定顺序且不重复的id,结果类型为64位的long型
- */
- @Component
- public class SnowflakeIdGen {
- //集群id
- private final long dataCenterId;
- //机器id
- private final long workerId;
- //集群id的bit位数
- private final long dataCenterIdBits = 5L;
- //机器id的bit位数
- private final long workerIdBits = 5L;
- //序列号
- private long sequenceId;
- //上一次生成id使用的timestamp ,以毫秒为单位
- private long lastTimestamp = 1L;
-
- /**
- * 若没有指定集群id和机器id,则默认均为0
- */
- public SnowflakeIdGen() {
- this(0, 0);
- }
-
- /**
- * 指定集群id和机器id
- */
- public SnowflakeIdGen(long dataCenterId, long workerId) {
- //集群id的最大编号
- long maxdataCenterId = ~(-1L << dataCenterIdBits);
- if (dataCenterId < 0 || dataCenterId > maxdataCenterId) {
- throw new RuntimeException(String.format("dataCenterId greater than %d or less than 0", maxdataCenterId));
- }
- //机器id的最大编号
- long maxWorkerId = ~(-1L << workerIdBits);
- if (workerId < 0 || workerId > maxWorkerId) {
- throw new RuntimeException(String.format("workerId greater than %d or less than 0", maxWorkerId));
- }
- this.dataCenterId = dataCenterId;
- this.workerId = workerId;
- }
-
- /**
- * 生成全局唯一的id
- */
- public synchronized long nextId() {
- long timestamp = System.currentTimeMillis();
- if (timestamp < lastTimestamp) { //出现这种情况,通常是由于机器时间出问题了
- throw new RuntimeException("machine time error");
- }
-
- //同一时刻生成的id号
- //序列号的bit位数
- long sequenceIdBits = 12L;
- if (timestamp == lastTimestamp) {
- //序列号的掩码
- long sequenceIdMask = ~(-1L << sequenceIdBits);
- sequenceId = (sequenceId + 1) & sequenceIdMask;
- if (sequenceId == 0) { //说明当前毫秒的序列号用完了,需从下个毫秒数开始重新计数
- timestamp = nextTimestamp(lastTimestamp);
- }
- } else {
- //否则序列号从0开始
- sequenceId = 0L;
- }
-
- lastTimestamp = timestamp;
- //生成最终结果时,集群id需移动的bit位数
- long timestampShiftBits = sequenceIdBits + workerIdBits + dataCenterIdBits;
- //生成最终结果时,集群id需移动的bit位数
- long dataCenterIdShiftBits = sequenceIdBits + workerIdBits;
- //生成最终结果时,机器id需移动的bit位数
- //去掉过去的时间,即从指定时间(本例以2017-10-12 00:00:00)开始算,
- // 大约可用69.5年(41位的时间位,最大值换成毫秒,再换算成年,大约69.5年)
- //1507737600000为从1970-01-01 00:00:00到2017-10-12 00:00:00经过的毫秒数
- long pastMills = 1507737600000L;
- return ((timestamp - pastMills) << timestampShiftBits)
- | (dataCenterId << dataCenterIdShiftBits)
- | (workerId << sequenceIdBits)
- | sequenceId;
- }
-
- /**
- * 获取上次取数毫秒的下一时刻
- */
- long nextTimestamp(long lastTimestamp) {
- long timestamp = System.currentTimeMillis();
- while (timestamp <= lastTimestamp) {
- timestamp = System.currentTimeMillis();
- }
- return timestamp;
- }
- }

- package com.dragonwu.utils;
-
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- /**
- * web渲染工具类
- *
- * @author DragonWu
- * @since 2022-10-07 15:36
- **/
- public class WebUtil {
-
- /**
- * @param response 渲染对象
- * @param jsonString 待渲染的字符串
- */
- public static void renderString(HttpServletResponse response, String jsonString) {
- try {
- response.setStatus(200);
- response.setContentType("application/json");
- response.setCharacterEncoding("utf-8");
- response.getWriter().write(jsonString);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }

依赖引入:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- package com.dragonwu.config;
-
- import com.dragonwu.filter.TokenAuthenticationFilter;
- import com.dragonwu.handler.AccessDeniedHandlerImpl;
- import com.dragonwu.handler.AuthenticationEntryPointImpl;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.config.http.SessionCreationPolicy;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
- import org.springframework.web.cors.CorsConfiguration;
- import org.springframework.web.cors.CorsConfigurationSource;
- import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-
- import java.util.Arrays;
- import java.util.Collections;
-
- /**
- * @author DragonWu
- * @since 2022-10-06 21:14
- **/
- @Configuration
- @EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限访问注解
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- //允许匿名访问的
- private static final String[] ALLOW_ASK = {
- "/login",
- "/captcha"
- };
- //总数允许访问的
- private static final String[] ALWAYS_ALLOW_ASK = {
- "/v2/api-docs",
- "/swagger-resources/configuration/ui",//用来获取支持的动作
- "/swagger-resources",//用来获取api-docs的URI
- "/swagger-resources/configuration/security",//安全选项
- "/webjars/**",
- "/swagger-ui.html",//以上为api文档接口访问路径
- "/static/**",//放行静态资源
- "**/open" //开放访问的资源
- };
-
- //自定义登录
- @Autowired
- private UserDetailsService userDetailsService;
-
- //token过滤器
- @Autowired
- private TokenAuthenticationFilter tokenAuthenticationFilter;
-
- //禁止访问异常处理器
- @Autowired
- private AccessDeniedHandlerImpl accessDeniedHandler;
-
- //认证异常处理器
- @Autowired
- private AuthenticationEntryPointImpl authenticationEntryPoint;
-
- /*
- 密码加密器
- */
- @Bean
- public BCryptPasswordEncoder bCryptPasswordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
- /*
- 设置自定义登录逻辑
- */
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userDetailsService);
- }
-
- /*
- 自定义登录,用户认证
- */
- @Bean
- @Override
- protected AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
- }
-
- /*
- 页面资源授权
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- //关闭csrf
- .csrf().disable()
- //不通过Session获取SecurityContext
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .authorizeRequests()
- //对应登录接口允许匿名访问
- .antMatchers(ALWAYS_ALLOW_ASK).permitAll()
- .antMatchers(ALLOW_ASK).anonymous()
- //除上面接口全都需要鉴权访问
- .anyRequest().authenticated();
-
- //添加过滤器
- http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
-
- //添加异常处理器
- http.exceptionHandling()
- //认证失败的处理器
- .authenticationEntryPoint(authenticationEntryPoint)
- //授权失败处理器
- .accessDeniedHandler(accessDeniedHandler);
-
- //跨域配置
- http.cors()
- .configurationSource(corsConfigurationSource());
-
- }
-
- /*
- 跨域配置对象
- */
- @Bean
- public CorsConfigurationSource corsConfigurationSource() {
- CorsConfiguration configuration = new CorsConfiguration();
- //配置允许访问的服务器域名
- configuration.setAllowedOrigins(Collections.singletonList("*"));
- configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
- configuration.setAllowedHeaders(Collections.singletonList("*"));
- configuration.setAllowCredentials(true);
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**", configuration);
- return source;
- }
- }

认证异常处理器:
- @Component
- public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
-
- @Override
- public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
- R<String> result = R.restResult(null, HttpStatus.UNAUTHORIZED.value(), "认证失败");
- String json = JSON.toJSONString(result);
- //处理异常,返回结果
- WebUtil.renderString(httpServletResponse, json);
- }
- }
禁止访问异常处理器:
- @Component
- public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
- @Override
- public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
- R<String> result = R.restResult(null, HttpStatus.FORBIDDEN.value(), "禁止访问");
- String json = JSON.toJSONString(result);
- //处理异常,返回结果
- WebUtil.renderString(httpServletResponse, json);
- }
- }
异常处理器可以将异常自定义处理。
- package com.dragonwu.model;
-
- import com.alibaba.fastjson.annotation.JSONField;
- import com.dragonwu.model.entity.SysUser;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import org.apache.tomcat.util.buf.StringUtils;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.authority.SimpleGrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
-
- import java.util.Collection;
- import java.util.List;
- import java.util.Objects;
- import java.util.stream.Collectors;
-
- /**
- * @author DragonWu
- * @since 2022-10-06 20:29
- **/
- @Data
- @NoArgsConstructor
- public class LoginUser implements UserDetails {
-
- private SysUser sysUser;
-
- //用户角色权限
- private List<String> jurisdiction;
-
- //将不会被序列化到redis里
- @JSONField(serialize = false)
- private List<SimpleGrantedAuthority> authorities;
-
- public LoginUser(SysUser sysUser, List<String> jurisdiction) {
- this.sysUser = sysUser;
- this.jurisdiction = jurisdiction;
- }
-
- public String getJurisdictionToString() {
- return StringUtils.join(jurisdiction, ',');
- }
-
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- //把jurisdiction里的权限信息字符串封装成SimpleGrantedAuthority对象
- if (!Objects.isNull(authorities)) {
- return authorities;
- }
- authorities = jurisdiction.stream()
- .map(SimpleGrantedAuthority::new)
- .collect(Collectors.toList());
- return authorities;
- }
-
- @Override
- public String getPassword() {
- return sysUser.getPassword();
- }
-
- @Override
- public String getUsername() {
- return sysUser.getUserName();
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
- }

通过UserDetailsService的实现可对用户对象进行自定义返回
- package com.dragonwu.service.impl;
-
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.dragonwu.model.entity.SysUser;
- import com.dragonwu.mapper.SysUserMapper;
- import com.dragonwu.model.LoginUser;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Service;
-
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Objects;
-
- /**
- * @author DragonWu
- * @since 2022-10-06 20:47
- **/
- @Service
- public class UserDetailsServiceImpl implements UserDetailsService {
-
- @Autowired
- private SysUserMapper sysUserMapper;
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- //查询用户信息
- SysUser user = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName,username));
- //如果没有查询到用户就抛出异常
- if (Objects.isNull(user)) {
- throw new UsernameNotFoundException("用户名不存在!");
- }
- //查询对应的权限信息和角色信息
- // 这一般需要查询role表和jurisdiction表的权限,这里略过
- List<String> jurisdiction=new ArrayList<>(Arrays.asList("admin","normal","ROLE_manager"));
- //将数据封装到UserDetail返回
- return new LoginUser(user, jurisdiction);
- }
- }

接口:
- package com.dragonwu.controller;
-
- import com.alibaba.fastjson.JSONObject;
- import com.dragonwu.constant.Constants;
- import com.dragonwu.model.R;
- import com.dragonwu.model.param.LoginParam;
- import com.dragonwu.model.vo.LoginVo;
- import com.dragonwu.service.LoginService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RestController;
-
- import java.util.Map;
-
- @RestController
- @Slf4j
- public class LoginController {
-
- @Autowired
- private LoginService loginService;
-
- @PostMapping("/login")
- public R<LoginVo> login(@RequestBody LoginParam loginParam) {
- try {
- return R.ok(loginService.login(loginParam), Constants.LOGIN_OK);
- } catch (RuntimeException msg) {
- //返回失败结果
- return R.fail(null, msg.getMessage());
- } catch (Exception e) {
- log.error("后台登录出现异常:" + JSONObject.toJSONString(loginParam), e);
- return R.fail(null, Constants.LOGIN_FAIL);
- }
- }
-
- @GetMapping("/logout.do")
- public R<String> logout() {
- try {
- loginService.logout();
- return R.ok(null, Constants.LOGOUT_OK);
- } catch (RuntimeException msg) {
- return R.fail(null, msg.getMessage());
- } catch (Exception e) {
- log.error("退出登录出现异常:" + e);
- return R.fail(null, Constants.LOGOUT_FAIL);
- }
- }
-
- @GetMapping("/captcha")
- public R<Map<String, String>> captcha() {
- try {
- return R.ok(loginService.getCaptcha(), Constants.GET_RESOURCE_OK);
- } catch (RuntimeException msg) {
- return R.fail(null, msg.getMessage());
- } catch (Exception e) {
- log.error("获取验证码出现异常:", e);
- return R.fail(null, Constants.GET_RESOURCE_FAIL);
- }
- }
- }

服务层接口:
- public interface LoginService {
-
- /**
- * 用户登录
- * @param loginParam 登录参数
- * @return 登录返回对象
- */
- LoginVo login(LoginParam loginParam);
-
- /**
- * 退出登录
- */
- void logout() throws RuntimeException;
-
- /**
- * 获取验证码
- * @return Map UUID验证码对应redis里的key,image的base64编码
- */
- Map<String,String> getCaptcha() throws IOException;
-
- }

服务层实现类:
- package com.dragonwu.service.impl;
-
- import com.dragonwu.constant.Constants;
- import com.dragonwu.model.LoginUser;
- import com.dragonwu.model.OnlineUser;
- import com.dragonwu.model.param.LoginParam;
- import com.dragonwu.model.vo.LoginVo;
- import com.dragonwu.service.EasyCaptchaService;
- import com.dragonwu.service.LoginService;
- import com.dragonwu.utils.JwtUtil;
- import com.dragonwu.utils.RedisUtil;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.stereotype.Service;
-
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Objects;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @author DragonWu
- * @since 2022-10-07 19:02
- **/
- @Service
- public class LoginServiceImpl implements LoginService {
-
- @Autowired
- private AuthenticationManager authenticationManager;
-
- @Autowired
- private RedisUtil redisUtil;
-
- @Autowired
- private EasyCaptchaService easyCaptchaService;
-
-
- @Override
- public LoginVo login(LoginParam loginParam) throws RuntimeException {
- //判断验证码是否正确
- easyCaptchaService.verifyCaptchaCode(loginParam.getUuid(), loginParam.getCaptcha());
-
- //AuthenticationManager authenticate进行用户认证
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginParam.getUsername(), loginParam.getPassword());
- Authentication authenticate = authenticationManager.authenticate(authenticationToken);
- //如果认证没通过给出相应的提示
- if (Objects.isNull(authenticate)) {
- throw new RuntimeException(Constants.LOGIN_ERROR);
- }
-
- //如果认证通过了,获取UserDetailService返回过期的UserDetail对象
- LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
- //使用primaryKey生成JWT
- String primaryKey = loginUser.getSysUser().getUserId().toString();
- Map<String, Object> claims = new HashMap<>();
- claims.put(JwtUtil.JWT_PRIMARY_KEY, primaryKey);
- String token = JwtUtil.createJwt(claims, JwtUtil.DEFAULT_EXPIRE_MINUTES);
-
- //把用户信息存入redis
- OnlineUser onlineUser = new OnlineUser(loginUser.getSysUser(), loginUser.getJurisdictionToString());
- redisUtil.setObject(Constants.ONLINE_PRE + primaryKey, onlineUser, JwtUtil.DEFAULT_EXPIRE_MINUTES, TimeUnit.MINUTES);
-
- //将返回结果封装达到返回对象
- return new LoginVo(loginUser.getSysUser(), loginUser.getJurisdiction(), token);
- }
-
- @Override
- public void logout() throws RuntimeException {
- try {
- //获取SecurityContextHolder里的用户id
- UsernamePasswordAuthenticationToken authentication =
- (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
- OnlineUser onlineUser = (OnlineUser) authentication.getPrincipal();
- String primaryKey = String.valueOf(onlineUser.getSysUser().getUserId());
- //删除redis中的值
- redisUtil.deleteObject(Constants.ONLINE_PRE + primaryKey);
- } catch (Exception e) {
- throw new RuntimeException(Constants.LOGOUT_FAIL);
- }
- }
-
- @Override
- public Map<String, String> getCaptcha() throws IOException {
- return easyCaptchaService.outputCaptchaImg(EasyCaptchaService.CAPTCHA_TYPE_GIF);
- }
- }

这里我们便实现了登录逻辑。
判断用户是否已登录
- package com.dragonwu.filter;
-
- import com.alibaba.fastjson.JSON;
- import com.dragonwu.constant.Constants;
- import com.dragonwu.model.OnlineUser;
- import com.dragonwu.model.R;
- import com.dragonwu.utils.JwtUtil;
- import com.dragonwu.utils.RedisUtil;
- import com.dragonwu.utils.WebUtil;
- import org.junit.platform.commons.util.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.http.HttpStatus;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.authority.AuthorityUtils;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.stereotype.Component;
- import org.springframework.web.filter.OncePerRequestFilter;
-
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.Map;
- import java.util.Objects;
-
- /**
- * token验证过滤器
- *
- * @author DragonWu
- * @since 2022-10-07 19:02
- **/
- @Component
- public class TokenAuthenticationFilter extends OncePerRequestFilter {
-
- public static final String HEADER = "Authorization";
- public static final String PARAM = "token";
-
- @Autowired
- private RedisUtil redisUtil;
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
- //获得前端请求中的token
- String token = request.getHeader(HEADER);
- if (StringUtils.isBlank(token)) {
- token = request.getParameter(PARAM);
- }
- //如果token为空,放行,验证失败
- if (StringUtils.isBlank(token)) {
- chain.doFilter(request, response);
- return;
- }
-
- //解析token
- Map<String, Object> claims = JwtUtil.parseJwt(token);
- if (Objects.isNull(claims)) {//解析异常
- R<String> result = R.restResult(null, HttpStatus.FORBIDDEN.value(), "token非法");
- String json = JSON.toJSONString(result);
- //返回解析错误的json数据
- WebUtil.renderString(response, json);
- return;
- }
-
- //获取用户主键,以便到redis中查询
- String primaryKey = (String) claims.get(JwtUtil.JWT_PRIMARY_KEY);
- //从redis中获取token对应的数据
- String redisKey = Constants.ONLINE_PRE + primaryKey;
- //获取用户信息
- OnlineUser onlineUser = (OnlineUser) redisUtil.getObject(redisKey);
- if (Objects.isNull(onlineUser)) {
- R<String> result = R.restResult(null, HttpStatus.UNAUTHORIZED.value(), "认证失败");
- String json = JSON.toJSONString(result);
- //返回请求失败的json数据
- WebUtil.renderString(response, json);
- return;
- }
-
- //存入SecurityContextHolder中
- UsernamePasswordAuthenticationToken authenticationToken =
- new UsernamePasswordAuthenticationToken(onlineUser, null,
- AuthorityUtils.commaSeparatedStringToAuthorityList(onlineUser.getAuthorities()));
- //获取权限信息封装到authenticationToken里
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
-
- //放行
- chain.doFilter(request, response);
-
- }
- }

首先访问验证码接口获取验证码的图片和uuid:
服务器通过用户输入的验证码结果和redis中的缓存进行比较得出是否验证码正确。
访问登录接口:
登录成功:
未携带JWT:
携带token就是登录的状态:
携带错误的token测试:可以看到错误的token会被服务器警告。
退出登录后,redis中缓存的用户信息将被删除。
SpringCloudAlibaba各技术栈实现的案例代码集 我的笔记: SpringCloudAlibaba各技术栈实现的案例代码集 我的笔记 - Gitee.com
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。