新增资源服务和token,配置上下文
This commit is contained in:
parent
9be9ee8736
commit
1a0675643b
24
pom.xml
24
pom.xml
@ -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>
|
||||
|
55
zyjblogs-cloud-dependencies/pom.xml
Normal file
55
zyjblogs-cloud-dependencies/pom.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
package cn.zyjblogs.starter.common.exception;
|
||||
|
||||
/**
|
||||
* @author zhuyijun
|
||||
*/
|
||||
public abstract class AbstractFrameworkException extends RuntimeException {
|
||||
public AbstractFrameworkException(String message) {
|
||||
super(message);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取密钥
|
||||
*
|
||||
* @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();
|
||||
public static PublicKey getPublicKeyByPath(String path) {
|
||||
InputStream inputStream = RsaUtils.class.getClassLoader().getResourceAsStream(path);
|
||||
if (inputStream == null) {
|
||||
log.error("获取公钥出错,找不到公钥文件");
|
||||
return null;
|
||||
}
|
||||
//判断文件是否存在,不在则新建
|
||||
if (!dest.exists()) {
|
||||
dest.createNewFile();
|
||||
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;
|
||||
}
|
||||
Files.write(dest.toPath(), bytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
68
zyjblogs-oauth-spring-boot-starter/pom.xml
Normal file
68
zyjblogs-oauth-spring-boot-starter/pom.xml
Normal 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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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-----
|
@ -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>
|
||||
|
@ -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());
|
||||
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("获取不到公私密钥");
|
||||
}
|
||||
// 私钥签名
|
||||
converter.setSigningKey(privateKey);
|
||||
// 公钥验签
|
||||
converter.setVerifierKey(publicKey);
|
||||
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
|
||||
accessTokenConverter.setUserTokenConverter(oauthUserAuthenticationConverter);
|
||||
converter.setAccessTokenConverter(accessTokenConverter);
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
71
zyjblogs-redis-spring-boot-starter/pom.xml
Normal file
71
zyjblogs-redis-spring-boot-starter/pom.xml
Normal 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>
|
@ -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配置
|
||||
* 针对key、value、hashKey、hashValue做序列化
|
||||
* @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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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, ".", "", "");
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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>
|
@ -0,0 +1,3 @@
|
||||
## Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.zyjblogs.starter.redis.config.RedisConfig
|
@ -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>-->
|
||||
|
Loading…
Reference in New Issue
Block a user