新增资源服务和token,配置上下文

This commit is contained in:
zhuyijun 2022-09-17 12:48:40 +08:00
parent 9be9ee8736
commit 1a0675643b
40 changed files with 2236 additions and 484 deletions

24
pom.xml
View File

@ -13,16 +13,10 @@
<artifactId>zyjblogs-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>zyjblogs-oauth</module>
<module>zyjblogs-rbac</module>
<module>zyjblogs-gateway</module>
<module>zyjblogs-common-spring-boot-starter</module>
<module>zyjblogs-web-spring-boot-starter</module>
</modules>
<properties>
<zyjblogs.version>1.0-SNAPSHOT</zyjblogs.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<compler.maven.plugin.version>2.3.12.RELEASE</compler.maven.plugin.version>
@ -41,7 +35,7 @@
<mybatis-plus-boot-starter.version>3.4.3</mybatis-plus-boot-starter.version>
<mysql-jdbc.version>8.0.21</mysql-jdbc.version>
<!-- spring-cloud-alibaba版本配置 -->
<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
<spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version>
<com.alibaba.transmittable.version>2.12.2</com.alibaba.transmittable.version>
<!-- orika-core实体类转换版本配置 -->
<orika-core.version>1.5.4</orika-core.version>
@ -87,6 +81,16 @@
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- ORM -->
<dependency>
<groupId>mysql</groupId>
@ -220,10 +224,6 @@
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-oauth2-authorization-server</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zyjblogs-parent</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zyjblogs-cloud-dependencies</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-common-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
</dependency>
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-oauth-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
</dependency>
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-redis-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
</dependency>
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-web-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${compler.maven.plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -6,16 +6,22 @@
<artifactId>zyjblogs-parent</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-common-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring 集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -6,14 +6,15 @@ import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.Locale;
public class BaseContextHandler {
public class BaseContext {
private static final ThreadLocal<ContextDto> CONTEXT = new TransmittableThreadLocal<ContextDto>() {
@Override
public ContextDto initialValue() {
return ContextDto.builder().build();
}
};
public BaseContextHandler() {
public BaseContext() {
}
public static ContextDto get() {
@ -32,13 +33,6 @@ public class BaseContextHandler {
return ((ContextDto)CONTEXT.get()).getUsername();
}
public static Integer getSource() {
return ((ContextDto)CONTEXT.get()).getSource();
}
public static String getTenantId() {
return ((ContextDto)CONTEXT.get()).getTenantId();
}
public static String getToken() {
return ((ContextDto)CONTEXT.get()).getToken();
@ -48,10 +42,6 @@ public class BaseContextHandler {
return (ContextDto) BeanUtils.map((ContextDto)CONTEXT.get(), ContextDto.class);
}
public static Locale getLanguage() {
return ((ContextDto)CONTEXT.get()).getLanguage();
}
public static void clear() {
CONTEXT.remove();
}

View File

@ -11,11 +11,7 @@ import java.util.Locale;
@NoArgsConstructor
@Builder
public class ContextDto {
private String tenantId;
private String userId;
private String username;
private Integer source;
private Integer isTenantCreator;
private String token;
private Locale language;
}

View File

@ -0,0 +1,34 @@
package cn.zyjblogs.starter.common.exception;
import cn.zyjblogs.starter.common.entity.response.HttpCode;
public class AbstractBusinessException extends RuntimeException{
private static final long serialVersionUID = -6583471361241853199L;
/**
* 异常码
*/
private HttpCode responseCode;
/**
* 异常描述
*/
private String message;
public AbstractBusinessException() {
}
/**
* 创建业务异常对象
*
* @param responseCode 错误码
* @param message 错误消息
*/
public AbstractBusinessException(HttpCode responseCode, String message) {
super(message);
this.responseCode = responseCode;
this.message = message;
}
}

View File

@ -1,5 +1,8 @@
package cn.zyjblogs.starter.common.exception;
/**
* @author zhuyijun
*/
public abstract class AbstractFrameworkException extends RuntimeException {
public AbstractFrameworkException(String message) {
super(message);

View File

@ -1,234 +0,0 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package cn.zyjblogs.starter.common.utils.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.DefaultClaims;
import io.jsonwebtoken.impl.DefaultClock;
import io.jsonwebtoken.impl.DefaultHeader;
import io.jsonwebtoken.impl.DefaultJws;
import io.jsonwebtoken.impl.DefaultJwsHeader;
import io.jsonwebtoken.impl.DefaultJwt;
import io.jsonwebtoken.impl.DefaultJwtParser;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import javax.crypto.spec.SecretKeySpec;
public class JwtParserUtils extends DefaultJwtParser {
private long allowedClockSkewMillis = 0L;
private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
private byte[] keyBytes;
private Key key;
private SigningKeyResolver signingKeyResolver;
private Clock clock;
private boolean checkExpired;
public JwtParserUtils(boolean checkExpired) {
this.clock = DefaultClock.INSTANCE;
this.checkExpired = checkExpired;
}
public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {
Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
String base64UrlEncodedHeader = null;
String base64UrlEncodedPayload = null;
String base64UrlEncodedDigest = null;
int delimiterCount = 0;
StringBuilder sb = new StringBuilder(128);
char[] var7 = jwt.toCharArray();
int var8 = var7.length;
for(int var9 = 0; var9 < var8; ++var9) {
char c = var7[var9];
if (c == '.') {
CharSequence tokenSeq = Strings.clean(sb);
String token = tokenSeq != null ? tokenSeq.toString() : null;
if (delimiterCount == 0) {
base64UrlEncodedHeader = token;
} else if (delimiterCount == 1) {
base64UrlEncodedPayload = token;
}
++delimiterCount;
sb.setLength(0);
} else {
sb.append(c);
}
}
if (delimiterCount != 2) {
String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
throw new MalformedJwtException(msg);
} else {
if (sb.length() > 0) {
base64UrlEncodedDigest = sb.toString();
}
if (base64UrlEncodedPayload == null) {
throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload.");
} else {
Header header = null;
CompressionCodec compressionCodec = null;
String payload;
if (base64UrlEncodedHeader != null) {
payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
Map<String, Object> m = this.readValue(payload);
if (base64UrlEncodedDigest != null) {
header = new DefaultJwsHeader(m);
} else {
header = new DefaultHeader(m);
}
compressionCodec = this.compressionCodecResolver.resolveCompressionCodec((Header)header);
}
if (compressionCodec != null) {
byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
payload = new String(decompressed, Strings.UTF_8);
} else {
payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
}
Claims claims = null;
if (payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') {
Map<String, Object> claimsMap = this.readValue(payload);
claims = new DefaultClaims(claimsMap);
}
if (base64UrlEncodedDigest != null) {
JwsHeader jwsHeader = (JwsHeader)header;
SignatureAlgorithm algorithm = null;
String object;
if (header != null) {
object = jwsHeader.getAlgorithm();
if (Strings.hasText(object)) {
algorithm = SignatureAlgorithm.forName(object);
}
}
if (algorithm == null || algorithm == SignatureAlgorithm.NONE) {
object = "JWT string has a digest/signature, but the header does not reference a valid signature algorithm.";
throw new MalformedJwtException(object);
}
if (this.key != null && this.keyBytes != null) {
throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either.");
}
if ((this.key != null || this.keyBytes != null) && this.signingKeyResolver != null) {
object = this.key != null ? "a key object" : "key bytes";
throw new IllegalStateException("A signing key resolver and " + object + " cannot both be specified. Choose either.");
}
Key key = this.key;
if (key == null) {
byte[] keyBytes = this.keyBytes;
if (Objects.isEmpty(keyBytes) && this.signingKeyResolver != null) {
if (claims != null) {
key = this.signingKeyResolver.resolveSigningKey(jwsHeader, claims);
} else {
key = this.signingKeyResolver.resolveSigningKey(jwsHeader, payload);
}
}
if (!Objects.isEmpty(keyBytes)) {
Assert.isTrue(algorithm.isHmac(), "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
}
Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed.");
String jwtWithoutSignature = base64UrlEncodedHeader + "." + base64UrlEncodedPayload;
JwtSignatureValidator validator;
try {
validator = this.createSignatureValidator(algorithm, (Key)key);
} catch (IllegalArgumentException var26) {
String algName = algorithm.getValue();
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature algorithm, but the specified signing key of type " + key.getClass().getName() + " may not be used to validate " + algName + " signatures. Because the specified signing key reflects a specific and expected algorithm, and the JWT does not reflect this algorithm, it is likely that the JWT was not expected and therefore should not be trusted. Another possibility is that the parser was configured with the incorrect signing key, but this cannot be assumed for security reasons.";
throw new UnsupportedJwtException(msg, var26);
}
if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) {
String msg = "JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.";
throw new SignatureException(msg);
}
}
boolean allowSkew = this.allowedClockSkewMillis > 0L;
if (claims != null) {
Date now = this.clock.now();
long nowTime = now.getTime();
Date exp = claims.getExpiration();
String nbfVal;
SimpleDateFormat sdf;
if (exp != null && this.checkExpired) {
long maxTime = nowTime - this.allowedClockSkewMillis;
Date max = allowSkew ? new Date(maxTime) : now;
if (max.after(exp)) {
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
String expVal = sdf.format(exp);
nbfVal = sdf.format(now);
long differenceMillis = maxTime - exp.getTime();
String msg = "JWT expired at " + expVal + ". Current time: " + nbfVal + ", a difference of " + differenceMillis + " milliseconds. Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds.";
throw new ExpiredJwtException((Header)header, claims, msg);
}
}
Date nbf = claims.getNotBefore();
if (nbf != null) {
long minTime = nowTime + this.allowedClockSkewMillis;
Date min = allowSkew ? new Date(minTime) : now;
if (min.before(nbf)) {
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
nbfVal = sdf.format(nbf);
String nowVal = sdf.format(now);
long differenceMillis = nbf.getTime() - minTime;
String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal + ", a difference of " + differenceMillis + " milliseconds. Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds.";
throw new PrematureJwtException((Header)header, claims, msg);
}
}
}
Object body = claims != null ? claims : payload;
if (base64UrlEncodedDigest != null) {
return new DefaultJws((JwsHeader)header, body, base64UrlEncodedDigest);
} else {
return new DefaultJwt((Header)header, body);
}
}
}
}
public JwtParser setSigningKey(Key key) {
Assert.notNull(key, "signing key cannot be null.");
this.key = key;
return this;
}
}

View File

@ -1,114 +1,147 @@
package cn.zyjblogs.starter.common.utils.rsa;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.stream.Collectors;
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048;
private static final String DEFAULT_ALGORITHM = "RSA";
private static final Logger log = LoggerFactory.getLogger(RsaUtils.class);
/**
* 从文件中读取公钥
*
* @param filename 公钥保存路径
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
private RsaUtils() {
}
/**
* 从文件中读取密钥
* 生成密钥对
*
* @param filename 私钥保存路径
* @return 私钥对象
* @throws Exception
* @param
* @return cn.com.hatechframework.common.entity.dto.RsaKeyDto
* @author meiji
* @date 2021/11/4 9:43
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
public static void generateKey() throws NoSuchAlgorithmException {
KeyPairGenerator generator = KeyPairGenerator.getInstance(DEFAULT_ALGORITHM);
// 密钥长度
generator.initialize(1024, new SecureRandom());
KeyPair keyPair = generator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
// 密钥 Base64 字符串化
String publicKey = Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded());
String privateKey = Base64.getEncoder().encodeToString((rsaPrivateKey.getEncoded()));
System.out.println("公钥:\r\n" + publicKey);
System.out.println("私钥:\r\n" + privateKey);
}
/**
* 公钥加密
*
* @param rawStr
* @param publicKey
* @return java.lang.String
* @author meiji
* @date 2021/11/4 9:43
*/
public static String publicKeyEncrypt(String rawStr, String publicKey) throws Exception {
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(rawStr.getBytes(StandardCharsets.UTF_8)));
}
/**
* 公钥解密
*
* @param encodeStr
* @param publicKey
* @return java.lang.String
* @author meiji
* @date 2021/11/4 9:43
*/
public static String publicKeyDecrypt(String encodeStr, String publicKey) throws Exception {
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(encodeStr.getBytes(StandardCharsets.UTF_8))));
}
/**
* 私钥加密
*
* @param rawStr
* @param privateKey
* @return java.lang.String
* @author meiji
* @date 2021/11/4 9:43
*/
public static String privateKeyEncrypt(String rawStr, String privateKey) throws Exception {
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, rsaPrivateKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(rawStr.getBytes(StandardCharsets.UTF_8)));
}
/**
* 私钥解密
*
* @param encodeStr
* @param privateKey
* @return java.lang.String
* @author meiji
* @date 2021/11/4 9:43
*/
public static String privateKeyDecrypt(String encodeStr, String privateKey) throws Exception {
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(encodeStr.getBytes(StandardCharsets.UTF_8))));
}
/**
* 获取公钥
*
* @param bytes 公钥的字节形式
* @return
* @throws Exception
* @param path 公钥路径
* @return 公钥
* @author tanyuanzhi
* @date 2021/11/9 10:06
*/
private static PublicKey getPublicKey(byte[] bytes) throws Exception {
bytes = Base64.getDecoder().decode(bytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
public static PublicKey getPublicKeyByPath(String path) {
InputStream inputStream = RsaUtils.class.getClassLoader().getResourceAsStream(path);
if (inputStream == null) {
log.error("获取公钥出错,找不到公钥文件");
return null;
}
String publicKeyStr = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining());
publicKeyStr = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
byte[] keyBytes = Base64.getMimeDecoder().decode(publicKeyStr);
// 通过X509编码的Key指令获得公钥对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
try {
KeyFactory keyFactory = KeyFactory.getInstance(DEFAULT_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
} catch (Exception e) {
log.error("获取公钥出错", e);
return null;
}
}
/**
* 获取密钥
*
* @param bytes 私钥的字节形式
* @return
* @throws Exception
*/
private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException,
InvalidKeySpecException {
bytes = Base64.getDecoder().decode(bytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 根据密文生成rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件绝对路径比如xxx/xxx/rsa_key.pub
* @param privateKeyFilename 私钥文件绝对路径,比如xxx/xxx/rsa_key
* @param secret 生成密钥的密文
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String
secret, int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
writeFile(privateKeyFilename, privateKeyBytes);
}
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String
secret) throws Exception {
generateKey(publicKeyFilename,privateKeyFilename,secret,DEFAULT_KEY_SIZE);
}
private static byte[] readFile(String fileName) throws Exception {
return Files.readAllBytes(new File(fileName).toPath());
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
File dir = dest.getParentFile();
//判断目录是否存在不在则新建
if(!dir.exists()){
dir.mkdirs();
}
//判断文件是否存在不在则新建
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
}

View File

@ -0,0 +1,35 @@
package cn.zyjblogs.starter.common.utils.web;
import cn.zyjblogs.starter.common.entity.constant.HttpHeaderConstant;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
public class RequestUtils {
private RequestUtils() {
}
/**
* 请求是否来自网关
*
* @param request 请求信息
* @return boolean
* @date 2022/3/3 14:19
*/
public static boolean requestFromGateway(HttpServletRequest request) {
return StringUtils.isNotEmpty(request.getHeader(HttpHeaderConstant.REQUEST_FROM_GATEWAY_KEY));
}
/**
* 请求是否来自feign调用
*
* @param request 请求信息
* @return boolean
* @date 2022/3/3 14:19
*/
public static boolean requestFromFeign(HttpServletRequest request) {
return StringUtils.isNotEmpty(request.getHeader(HttpHeaderConstant.REQUEST_FROM_FEIGN_KEY));
}
}

View File

@ -3,9 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zyjblogs-parent</artifactId>
<artifactId>zyjblogs-cloud-dependencies</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -31,11 +32,6 @@
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
@ -43,7 +39,6 @@
<dependency>
<artifactId>zyjblogs-web-spring-boot-starter</artifactId>
<groupId>cn.zyjblogs.starter</groupId>
<version>${zyjblogs.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
@ -51,26 +46,27 @@
</exclusion>
</exclusions>
</dependency>
<!-- 集成redis-->
<!-- <dependency>-->
<!-- <groupId>cn.zyjblogs.starter</groupId>-->
<!-- <artifactId>zyjblogs-redis-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!-- 集成nacos-->
<!-- 集成 Nacos 作为服务注册中心配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>error_prone_annotations</artifactId>
<groupId>com.google.errorprone</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 集成 Nacos 作为服务注册中心配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@ -89,22 +85,5 @@
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.security.oauth.boot</groupId>-->
<!-- <artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-oauth2</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-common-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -63,6 +63,9 @@ public class AuthFilter implements GlobalFilter {
if (isWhileList(path)) {
return chain.filter(exchange);
}
if (StringUtils.isEmpty(token)) {
return getErrorMono(response, HttpCode.UNAUTHORIZED, "无访问权限");
}
if (isExpired(token)) {
log.info("token过期");
return getErrorMono(response, HttpCode.UNAUTHORIZED, "invalid_token");
@ -90,9 +93,6 @@ public class AuthFilter implements GlobalFilter {
* @date 2021/11/15 19:17
*/
private boolean isExpired(String token) {
if (StringUtils.isEmpty(token)) {
return true;
}
if (!token.startsWith(HttpHeaderConstant.AUTHORIZATION_TYPE)) {
return true;
}

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zyjblogs-parent</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-oauth-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-common-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
</dependency>
<!-- 集成spring-boot自动装配依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 集成spring-boot配置文件属性处理依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-redis-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${compler.maven.plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,42 @@
package cn.zyjblogs.starter.oauth.config;
import cn.zyjblogs.starter.common.entity.constant.HttpHeaderConstant;
import cn.zyjblogs.starter.common.entity.context.BaseContext;
import feign.RequestInterceptor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zhuyijun
*/
@Configuration
@ConditionalOnClass(RequestInterceptor.class)
public class OauthFeignInterceptorAutoConfiguration {
@Bean
public RequestInterceptor oauthFeignRequestInterceptor() {
return template -> {
String token = BaseContext.getToken();
if (StringUtils.isNotEmpty(token)) {
token = tokenCompletion(token);
template.header(HttpHeaderConstant.AUTHORIZATION, token);
}
template.header(HttpHeaderConstant.REQUEST_FROM_FEIGN_KEY, HttpHeaderConstant.REQUEST_FROM_FEIGN_VALUE);
};
}
/**
* token信息补全,token不能为空
* @author liuweicheng
* @date 2022/03/12
* @return 补全后得token
*/
private String tokenCompletion(String token){
if(token.indexOf(HttpHeaderConstant.AUTHORIZATION_TYPE) != 0){
token = HttpHeaderConstant.AUTHORIZATION_TYPE + " " + token;
}
return token;
}
}

View File

@ -0,0 +1,17 @@
package cn.zyjblogs.starter.oauth.exception;
import cn.zyjblogs.starter.common.entity.response.HttpCode;
import cn.zyjblogs.starter.common.exception.AbstractBusinessException;
/**
* @author zhuyijun
*/
public class AuthRuntimeException extends AbstractBusinessException {
public AuthRuntimeException() {
super();
}
public AuthRuntimeException(HttpCode responseCode, String message) {
super(responseCode, message);
}
}

View File

@ -1,7 +1,8 @@
package cn.zyjblogs.rbac.config.resource;
package cn.zyjblogs.starter.oauth.resource;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
@ -19,13 +20,15 @@ import org.springframework.security.oauth2.provider.token.TokenStore;
@Configuration
@EnableResourceServer
@RequiredArgsConstructor
@RefreshScope
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID="zyjblogs-rbac";
@Value("${spring.application.name}")
private String resourceId;
private final TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
resources.resourceId(resourceId)
// 验证令牌的服务
.tokenStore(tokenStore)
.stateless(true);
@ -33,13 +36,17 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**")
.access("#oauth2.hasAnyScope('all')")
http .sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
.authorizeRequests()
.antMatchers("/**")
.authenticated()
// .anyRequest().permitAll()
// .access("#oauth2.hasAnyScope('all')")
.and()
.csrf().disable();
}
}

View File

@ -0,0 +1,38 @@
package cn.zyjblogs.starter.oauth.security;
import cn.zyjblogs.starter.common.entity.constant.ContextKeyConstant;
import cn.zyjblogs.starter.common.entity.context.BaseContext;
import cn.zyjblogs.starter.common.entity.dto.ContextDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author zhuyijun
*/
@Component
public class OauthAccessTokenConverter extends DefaultAccessTokenConverter {
private static final Logger log = LoggerFactory.getLogger(OauthAccessTokenConverter.class);
@Override
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
return super.convertAccessToken(token, authentication);
}
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
oAuth2Authentication.setDetails(map);
String userId = (String) map.get(ContextKeyConstant.USER_ID_KEY);
String username = (String) map.get(ContextKeyConstant.USERNAME_KEY);
BaseContext.set(ContextDto.builder().userId(userId).username(username).token("").build());
log.info("进入");
return oAuth2Authentication;
}
}

View File

@ -1,5 +1,6 @@
package cn.zyjblogs.rbac.config.security;
package cn.zyjblogs.starter.oauth.security;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -9,13 +10,16 @@ import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenCo
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @author zhuyijun
*/
@Configuration
@RequiredArgsConstructor
public class TokenConfig {
private String SIGNING_KEY="zyjblogs123";
private final OauthAccessTokenConverter oauthAccessTokenConverter;
/**
* 令牌存储策略
* @return
@ -29,18 +33,14 @@ public class TokenConfig {
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
String privateKey = null;
String publicKey = null;
try {
publicKey = IOUtils.toString(new ClassPathResource("public.txt").getInputStream());
privateKey = IOUtils.toString(new ClassPathResource("private.txt").getInputStream());
String publicKey = IOUtils.toString(new ClassPathResource("public.txt").getInputStream(), StandardCharsets.UTF_8);
// 公钥验签
converter.setVerifierKey(publicKey);
converter.setAccessTokenConverter(oauthAccessTokenConverter);
return converter;
} catch (final IOException e) {
throw new RuntimeException("获取不到公私密钥");
}
// 私钥签名
converter.setSigningKey(privateKey);
// 公钥验签
converter.setVerifierKey(publicKey);
return converter;
}
}

View File

@ -0,0 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.zyjblogs.starter.oauth.config.OauthFeignInterceptorAutoConfiguration,\
cn.zyjblogs.starter.oauth.resource.ResourceServerConfig,\
cn.zyjblogs.starter.oauth.security.TokenConfig,\
cn.zyjblogs.starter.oauth.security.OauthAccessTokenConverter

View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jXKxEFsDsjng2nHppqC
GTR1NQLfHJlGzc5hWalP/YgbJWIqdGXDy704Q2DuuoOe/t6KQcYI6/C7Ua9yumYp
MoKZOA5b7gmh/k0SUfsCErKwzE93DIAnLbRoT/hkGJD1Dn7V7yTzYf2BjaFoY5it
tZJ/UXM18TAqW7S1q0qCuv25Fb9NAEMh63EaX3N+DMW8rg51GBfRvtVfACbIyFo9
8PW2/wOQhppGWkxdzgJdJUwPhZ+Fo9DZ18044hapYPNuZ31ordIGptYL6pB/0VKh
kbDLk4oOnkhhWW0DmsTSFyhOiaQqtuxdrjPV7sqR1NokreZAtbUctVNezNBlYWoJ
TwIDAQAB
-----END PUBLIC KEY-----

View File

@ -3,9 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zyjblogs-parent</artifactId>
<artifactId>zyjblogs-cloud-dependencies</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -25,7 +26,6 @@
<dependency>
<artifactId>zyjblogs-web-spring-boot-starter</artifactId>
<groupId>cn.zyjblogs.starter</groupId>
<version>${zyjblogs.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -59,16 +59,6 @@
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>error_prone_annotations</artifactId>
<groupId>com.google.errorprone</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 集成 Nacos 作为服务注册中心配置 -->
<dependency>
@ -80,14 +70,16 @@
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-redis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-common-spring-boot-starter</artifactId>
<version>${zyjblogs.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,6 +1,7 @@
package cn.zyjblogs.oauth.config.security;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.CharSetUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -13,6 +14,8 @@ import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenCo
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* @author zhuyijun
@ -34,18 +37,16 @@ public class JwtTokenConfig {
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
String privateKey = null;
String publicKey = null;
try {
publicKey = IOUtils.toString(new ClassPathResource("public.txt").getInputStream());
privateKey = IOUtils.toString(new ClassPathResource("private.txt").getInputStream());
} catch (final IOException e) {
throw new RuntimeException("获取不到公私密钥");
}
String publicKey = IOUtils.toString(new ClassPathResource("public.txt").getInputStream(), StandardCharsets.UTF_8);
String privateKey = IOUtils.toString(new ClassPathResource("private.txt").getInputStream(),StandardCharsets.UTF_8);
// 私钥签名
converter.setSigningKey(privateKey);
// 公钥验签
converter.setVerifierKey(publicKey);
} catch (final IOException e) {
throw new RuntimeException("获取不到公私密钥");
}
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(oauthUserAuthenticationConverter);
converter.setAccessTokenConverter(accessTokenConverter);

View File

@ -3,9 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zyjblogs-parent</artifactId>
<artifactId>zyjblogs-cloud-dependencies</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zyjblogs.pubilc</groupId>
@ -24,19 +25,23 @@
<dependency>
<artifactId>zyjblogs-web-spring-boot-starter</artifactId>
<groupId>cn.zyjblogs.starter</groupId>
<version>${zyjblogs.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.security.oauth.boot</groupId>-->
<!-- <artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-oauth2</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-oauth-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-redis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
@ -54,16 +59,6 @@
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>error_prone_annotations</artifactId>
<groupId>com.google.errorprone</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 集成 Nacos 作为服务注册中心配置 -->
<dependency>
@ -74,6 +69,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -1,24 +0,0 @@
package cn.zyjblogs.rbac.config.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
//使HttpSecurity接收以"/login/","/oauth/"开头请求, 配置HttpSecurity不阻止swagger页面
http.authorizeRequests()
.antMatchers("/webjars/**", "/swagger-ui.html/**", "/swagger-resources/**", "/v2/api-docs/**")
.permitAll()
//以下请求必须认证通过
.antMatchers("/demo/**", "/oauth/**", "/login")
.authenticated()
.anyRequest().permitAll();
}
}

View File

@ -2,7 +2,9 @@ package cn.zyjblogs.rbac.server.user.controller;
import cn.zyjblogs.rbac.server.user.po.UserPo;
import cn.zyjblogs.rbac.server.user.service.UserService;
import cn.zyjblogs.starter.common.entity.context.BaseContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@ -15,10 +17,13 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RequiredArgsConstructor
@ResponseBody
@Log4j2
public class UserController {
private final UserService userService;
@GetMapping("/id")
public UserPo findById(String id){
log.info(BaseContext.getUserId());
log.info(BaseContext.getUsername());
return userService.getById(id);
}

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zyjblogs-parent</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-redis-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- 集成spring-boot自动装配依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 集成redis缓存依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 集成jackson 序列化redis的value值为json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 集成jackson核心注解插件依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- 集成fastjson解析DTO,VO -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- 集成lettuce pool 缓存连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${compler.maven.plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,53 @@
package cn.zyjblogs.starter.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author zhuyijun
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
/**
* Redis配置
* 针对keyvaluehashKeyhashValue做序列化
* @return org.springframework.data.redis.core.RedisTemplate<java.lang.String,java.lang.Object>
*/
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@SuppressWarnings("all")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);
//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

View File

@ -0,0 +1,9 @@
package cn.zyjblogs.starter.redis.utils.lock;
public abstract class AbstractLockExecutor<T> implements LockExecutor<T> {
protected T obtainLockInstance(boolean locked, T lockInstance) {
return locked ? lockInstance : null;
}
}

View File

@ -0,0 +1,15 @@
package cn.zyjblogs.starter.redis.utils.lock;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Collection;
@Component
public class DefaultLockKeyBuilder implements LockKeyBuilder {
@Override
public String buildKey(Collection<String> definitionKeys) {
return StringUtils.collectionToDelimitedString(definitionKeys, ".", "", "");
}
}

View File

@ -0,0 +1,40 @@
package cn.zyjblogs.starter.redis.utils.lock;
/**
* Copyright (C), 2021, 北京同创永益科技发展有限公司
*
* @author zhuyijun
* @version 3.0.0
* @description 分布式锁核心处理器
* @date 2022/5/23 10:26
*/
public interface LockExecutor<T> {
/**
* 加锁
*
* @param lockKey 锁标识
* @param lockValue 锁值
* @param expire 锁有效时间
* @param acquireTimeout 获取锁超时时间
* @return 锁信息
*/
T acquire(String lockKey, String lockValue, long expire, long acquireTimeout);
/**
* 解锁
*
* <pre>
* 为何解锁需要校验lockValue
* 客户端A加锁一段时间之后客户端A解锁在执行releaseLock之前锁突然过期了
* 此时客户端B尝试加锁成功然后客户端A再执行releaseLock方法则将客户端B的锁给解除了
* </pre>
*
* @param key 加锁key
* @param value 加锁value
* @param lockInstance 锁实例
* @return 是否释放成功
*/
boolean releaseLock(String key, String value, T lockInstance);
}

View File

@ -0,0 +1,14 @@
package cn.zyjblogs.starter.redis.utils.lock;
import java.util.Collection;
public interface LockKeyBuilder {
/**
* 构建key
*
* @param definitionKeys 定义
* @return key
*/
String buildKey(Collection<String> definitionKeys);
}

View File

@ -0,0 +1,66 @@
package cn.zyjblogs.starter.redis.utils.lock;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.util.StringUtils;
/**
* Copyright (C), 2021, 北京同创永益科技发展有限公司
*
* @author zhuyijun
* @version 3.0.0
* @description
* @date 2022/5/23 11:01
*/
@Data
@AllArgsConstructor
@Builder
public class RedisLock {
/**
* 锁名称
*/
private String lockKey;
/**
* 锁值
*/
private String lockValue;
/**
* 过期时间
*/
private Long expire;
/**
* 获取锁超时时间
*/
private Long acquireTimeout;
/**
* 获取锁次数
*/
private int acquireCount;
/**
* 锁实例
*/
private Object lockInstance;
/**
* 锁执行器
*/
private LockExecutor lockExecutor;
/***
* 解锁
* @author zhuyijun
* @Description
* @date 15:10
*/
public void unLock() {
if (lockExecutor != null && lockInstance != null && !StringUtils.isEmpty(lockKey)) {
lockExecutor.releaseLock(lockKey, lockValue, lockInstance);
}
}
}

View File

@ -0,0 +1,122 @@
package cn.zyjblogs.starter.redis.utils.lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Copyright (C), 2021, 北京同创永益科技发展有限公司
*
* @author zhuyijun
* @version 3.0.0
* @description
* @date 2022/5/23 11:02
*/
@Slf4j
@Component
public class RedisLockTemplate {
@Resource
private LockExecutor<String> RedisTemplateLockExecutor;
/**
* 默认失效时间
*/
private Long defaultExpire = 30000L;
private Long defaultAcquireTimeout = 3000L;
/**
* 获取锁失败时重试时间间隔 单位毫秒
*/
private Long retryInterval = 100L;
@Value("${zyjblogs.redis.prefix:lock}")
private String prefix;
/**
* 获取锁不推荐
*
* @param key
* @param expire
* @return
*/
public RedisLock lock(String key, long expire) {
return lock(key, expire, 0, null);
}
/**
* 加锁方法
*
* @param key 锁key 同一个key只能被一个客户端持有
* @param expire 过期时间(ms) 防止死锁
* @param acquireTimeout 尝试获取锁超时时间(ms)
* @return 加锁成功返回锁信息 失败返回null
*/
public RedisLock tryLock(String key, long expire, long acquireTimeout) {
return lock(key, expire, acquireTimeout, null);
}
/**
* 加锁方法
*
* @param key 锁key 同一个key只能被一个客户端持有
* @param expire 过期时间(ms) 防止死锁
* @param acquireTimeout 尝试获取锁超时时间(ms)
* @param executor 执行器
* @return 加锁成功返回锁信息 失败返回null
*/
public RedisLock lock(String key, long expire, long acquireTimeout, LockExecutor executor) {
key = prefix + key;
LockExecutor lockExecutor = obtainExecutor(executor);
expire = expire < 0 ? defaultExpire : expire;
long currentAcquireTimeout = acquireTimeout < 0 ? defaultAcquireTimeout : acquireTimeout;
int acquireCount = 0;
String value = UUID.randomUUID().toString();
long start = System.currentTimeMillis();
try {
do {
acquireCount++;
Object lockInstance = lockExecutor.acquire(key, value, expire, acquireTimeout);
if (null != lockInstance) {
log.debug("加锁成功 key:{}",key);
return RedisLock.builder()
.lockKey(key)
.lockValue(value)
.expire(expire)
.acquireTimeout(currentAcquireTimeout)
.acquireCount(acquireCount)
.lockInstance(lockInstance)
.lockExecutor(lockExecutor)
.build();
}
TimeUnit.MILLISECONDS.sleep(retryInterval);
} while (System.currentTimeMillis() - start < currentAcquireTimeout);
} catch (InterruptedException e) {
log.error("lock error", e);
throw new RuntimeException("加锁失败");
}
return null;
}
protected LockExecutor obtainExecutor(LockExecutor lockExecutor) {
if (null == lockExecutor) {
return RedisTemplateLockExecutor;
}
return lockExecutor;
}
/***
* 解锁
* @param redisLock
* @author zhuyijun
* @Description
* @date 11:29
*/
public boolean unLock(RedisLock redisLock) {
if (null == redisLock) {
return false;
}
return redisLock.getLockExecutor().releaseLock(redisLock.getLockKey(), redisLock.getLockValue(),
redisLock.getLockInstance());
}
}

View File

@ -0,0 +1,63 @@
package cn.zyjblogs.starter.redis.utils.lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collections;
@Slf4j
@Component
public class RedisTemplateLockExecutor extends AbstractLockExecutor<String> {
private static final RedisScript<String> SCRIPT_LOCK = new DefaultRedisScript<>("return redis.call('set',KEYS[1]," +
"ARGV[1],'NX','PX',ARGV[2])", String.class);
private static final RedisScript<String> SCRIPT_UNLOCK = new DefaultRedisScript<>("if redis.call('get',KEYS[1]) " +
"== ARGV[1] then return tostring(redis.call('del', KEYS[1])==1) else return 'false' end", String.class);
private static final String LOCK_SUCCESS = "OK";
@Resource
private RedisTemplate<String, String> redisTemplate;
/***
* 枷锁
* @param lockKey
* @param lockValue
* @param expire
* @param acquireTimeout
* @author zhuyijun
* @Description
* @date 10:26
*/
@Override
public String acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
String lock = redisTemplate.execute(SCRIPT_LOCK,
redisTemplate.getStringSerializer(),
redisTemplate.getStringSerializer(),
Collections.singletonList(lockKey),
lockValue, String.valueOf(expire));
final boolean locked = LOCK_SUCCESS.equals(lock);
return obtainLockInstance(locked, lock);
}
/***
* 解锁
* @param key
* @param value
* @param lockInstance
* @author zhuyijun
* @Description
* @date 10:26
*/
@Override
public boolean releaseLock(String key, String value, String lockInstance) {
String releaseResult = redisTemplate.execute(SCRIPT_UNLOCK,
redisTemplate.getStringSerializer(),
redisTemplate.getStringSerializer(),
Collections.singletonList(key), value);
return Boolean.parseBoolean(releaseResult);
}
}

View File

@ -0,0 +1,5 @@
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.6.3
Built-By: mitom
Build-Jdk: 11

View File

@ -0,0 +1,5 @@
#Generated by Maven
#Mon Jul 25 15:29:06 CST 2022
groupId=cn.zyjblogs.starter
artifactId=zyjblogs-redis-spring-boot-starter
version=1.0-SNAPSHOT

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zyjblogs-parent</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zyjblogs.starter</groupId>
<artifactId>zyjblogs-redis-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- 集成spring-boot自动装配依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 集成redis缓存依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 集成jackson 序列化redis的value值为json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 集成jackson核心注解插件依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- 集成fastjson解析DTO,VO -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- 集成lettuce pool 缓存连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 集成jackson 序列化redis的value值为json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${compler.maven.plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,3 @@
## Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.zyjblogs.starter.redis.config.RedisConfig

View File

@ -6,6 +6,7 @@
<artifactId>zyjblogs-parent</artifactId>
<groupId>cn.zyjblogs</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -18,11 +19,6 @@
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- Spring 集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.github.xiaoymin</groupId>-->
<!-- <artifactId>knife4j-spring-boot-starter</artifactId>-->