赞
踩
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。
例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。
缺点是什么?
微服务集群中的每个服务,对外提供的都是Rest风格的接口。而Rest风格的一个最重要的规范就是:服务的无状态性,即:
带来的好处是什么呢?
无状态登录的流程:
流程图:
整个登录过程中,最关键的点是什么?
token的安全性
token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。
采用何种方式加密才是安全可靠的呢?
我们将采用JWT + RSA非对称加密
JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io
GitHub上jwt的java客户端:https://github.com/jwtk/jjwt
JWT包含三部分数据:
Header:头部,通常头部有两部分内容:
我们会对头部进行base64加密(可解密),得到第一部分数据
Payload:载荷,就是有效数据,一般包含下面信息:
这部分也会采用base64加密,得到第二部分数据
Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性
生成的数据格式:
可以看到分为3段,每段就是上面的一部分数据
流程图:
因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,完全符合了Rest的无状态规范。
不过,这个过程是不是就完美了呢?
可以发现,用户访问我们的网站,一次授权后,以后访问微服务都需要鉴权,那么每次鉴权都需要访问授权中心,一个用户请求,被分解为2次请求才能完成,效率比较低。
能不能直接在微服务的完成鉴权,不去找授权中心呢?
如果这样,就可以减少一次网络请求,效率提高了一倍。但是,微服务并没有鉴定JWT的能力
,因为鉴定需要通过密钥来完成。我们不能把密钥交给其它微服务,存在安全风险。
怎么办?
这就要用到RSA非对称加密技术了。
加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密),加密技术的要点是加密算法,加密算法可以分为三类:
RSA算法历史:
1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA
有了非对称加密,我们就可以改变签名和验签的方式了:
生成RSA密钥对,私钥存放在授权中心,公钥下发给微服务
在授权中心利用私钥对JWT签名
在微服务利用公钥验证签名有效性
因为非对称加密的特性,不用担心公钥泄漏问题,因为公钥是无法伪造签名的,但要确保私钥的安全和隐秘。
非对称加密后的授权和鉴权流程:
鉴权部分简化了非常多:
用户只需要与微服务交互,不用访问授权中心,效率大大提高!
/**
* <!--日期工具类-->
* <dependency>
* <groupId>joda-time</groupId>
* <artifactId>joda-time</artifactId>
* </dependency>
* <p>
* <!--JWT依赖-->
* <dependency>
* <groupId>io.jsonwebtoken</groupId>
* <artifactId>jjwt-api</artifactId>
* <version>0.10.5</version>
* </dependency>
* <dependency>
* <groupId>io.jsonwebtoken</groupId>
* <artifactId>jjwt-impl</artifactId>
* <version>0.10.5</version>
* <scope>runtime</scope>
* </dependency>
* <dependency>
* <groupId>io.jsonwebtoken</groupId>
* <artifactId>jjwt-jackson</artifactId>
* <version>0.10.5</version>
* <scope>runtime</scope>
* </dependency>
*/
package com.xlCai.ctx.bean;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import org.springframework.util.StringUtils;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
public class JwtUtils {
private static final String JWT_PAYLOAD_USER_KEY = "user";
private static final String SUBJECT = "ABCDEFG";
private static final String APPSECRET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz";
private static final String CLAIMOBJKEY = "CLAIMOBJKEY";
/**
* 分钟 如果要改天或者其他自己改
* 生成token,只使用jwt不使用RSA
*
* @return
*/
public static String generateTokenExpireInMinutesOnlyByJwt(String obj, int expireMinutes) {
if (StringUtils.isEmpty(obj)) {
return null;
}
return Jwts.builder().setSubject(SUBJECT)
.claim(CLAIMOBJKEY, obj)
.setIssuedAt(new Date())
.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
.signWith(SignatureAlgorithm.HS256, APPSECRET)
.compact();
}
/**
* 解析token
*
* @param token 成Claims
* @return
*/
public static Claims parserTokenOnlyByJwt(String token) {
return Jwts.parser().setSigningKey(APPSECRET).parseClaimsJws(token).getBody();
}
/**
* 解析token
*
* @param token 成对象
* @return
*/
public static <T> Object parserObjectFromTokenOnlyByJwt(String token, Class<T> type) {
Claims body = Jwts.parser().setSigningKey(APPSECRET).parseClaimsJws(token).getBody();
return body.get(CLAIMOBJKEY);
}
/**
* 私钥加密token
*
* @param info 载荷中的数据
* @param privateKey 私钥
* @param expireMinutes 过期时间,单位分钟
* @return JWT
*/
public static String generateTokenExpireInMinutes(Object info, PrivateKey privateKey, int expireMinutes) {
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(info))
.setId(createJTI())
.setIssuedAt(new Date())
.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 私钥加密token
*
* @param Info 载荷中的数据
* @param privateKey 私钥
* @param expireSeconds 过期时间,单位秒
* @return JWT
*/
public static String generateTokenExpireInSeconds(Object Info, PrivateKey privateKey, int expireSeconds) {
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(Info))
.setId(createJTI())
.setIssuedAt(new Date())
.setExpiration(DateTime.now().plusSeconds(expireSeconds).toDate())
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 公钥解析token
*
* @param token 用户请求中的token
* @param publicKey 公钥
* @return Jws<Claims>
*/
private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
/**
* 随机生成Id
*
* @return
*/
private static String createJTI() {
return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
}
/**
* 获取token中的用户信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> type) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> payload = new Payload<>();
payload.setId(body.getId());
payload.setInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), type));
payload.setExpiration(body.getExpiration());
return payload;
}
/**
* 获取token中的载荷信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> payload = new Payload<>();
payload.setId(body.getId());
payload.setExpiration(body.getExpiration());
return payload;
}
}
package cn.juan.auth.entity;
import lombok.Data;
import java.util.Date;
/**
* 载荷对象:
* <p>
* JWT中,会保存载荷数据,我们计划存储3部分:
* <p>
* - id:jwt的id
* - 用户信息:用户数据,不确定,可以是任意类型
* - 过期时间:Date
*
* @param <T>
*/
@Data
public class Payload<T> {
private String id; //tokenId
private T info; //实际存放内容
private Date expiration; //过期时间
}
package springcloud.dataencode;
import com.alibaba.fastjson.JSON;
import com.sun.istack.internal.logging.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.security.*;
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.HashMap;
import java.util.Map;
/**
* @version V1.0.0
* @author: WangQingLong
* @date: 2020/10/16 18:38
* @description:
*
* <dependency>
* <groupId>com.alibaba</groupId>
* <artifactId>fastjson</artifactId>
* <version>1.2.47</version>
* </dependency>
*
* <!--RSA加密需要的依赖-->
* <dependency>
* <groupId>org.bouncycastle</groupId>
* <artifactId>bcprov-jdk15on</artifactId>
* <version>1.51</version>
* </dependency>
*
切记: 生成两对,可以使用枚举保存,公钥加密私钥解密,交叉进行
*
*/
public final class RsaKit {
private static Logger logger = Logger.getLogger(RsaKit.class);
private static final int KEY_SIZE = 2048;
//加密算法
private static final String KEY_ALGORITHM = "RSA";
//签名算法
private static final String signature_algorithm = "MD5withRSA";
private static final Charset charset = Charset.forName("UTF-8");
private RsaKit() {
}
/**
* 生成密钥对,生成的密钥对统一放在SystemConstant ,RsA_keyPair里一份
*
* @return
*/
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM, new BouncyCastleProvider());
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
return keyPair;
} catch (Exception e) {
logger.info("生成密钥对出现异常");
throw new RuntimeException();
}
}
/**
* 获取私钥
*
* @param key
* @return
*/
public static String getPrivateKey(PrivateKey key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 获取公钥
*
* @param key
* @return
*/
public static String getPublickey(PublicKey key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 用私钥对信息生成数字签名
*
* @param data 已加密数据
* @param privatekey 私钥
* @param charset 编码
* @return
*/
public static String sign(String data, String privatekey, Charset charset) {
byte[] dataBytes = data.getBytes(charset);
return sign(dataBytes, privatekey);
}
/**
* 用私钥对信息生成数字签名
*
* @param data 已加密数据
* @param privatekey 密钥
* @return
*/
public static String sign(byte[] data, String privatekey) {
try {
byte[] keyBytes = Base64.getDecoder().decode(privatekey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(signature_algorithm);
signature.initSign(privateKey);
signature.update(data);
return Base64.getUrlEncoder().encodeToString(signature.sign());
} catch (Exception e) {
logger.info("用私钥对信息生成数字签名出现异常");
e.printStackTrace();
}
return null;
}
/**
* @param data 数据
* @param publickey 公钥
* @param sign 数据签名
* @return
*/
public static boolean verify(byte[] data, String publickey, String sign) {
try {
byte[] keyBytes = Base64.getDecoder().decode(publickey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(signature_algorithm);
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(Base64.getUrlDecoder().decode(sign));
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* @param data 数据
* @param publickey 公钥
* @param sign 签名
* @param charset 编码
* @return
*/
public static boolean verify(String data, String publickey, String sign, Charset charset) {
try {
byte[] keyBytes = Base64.getDecoder().decode(publickey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(signature_algorithm);
signature.initVerify(publicKey);
signature.update(data.getBytes(charset));
return signature.verify(Base64.getUrlDecoder().decode(sign));
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 校验数字签名
*
* @param data 数据
* @param publickey 公钥
* @param sign 签名
* @return
*/
public static boolean verify(String data, String publickey, String sign) {
return verify(data, publickey, sign, charset);
}
/**
* 私钥解密
*
* @param encrypteData 已加密数据
* @param privatekey 私钥
* @return
*/
public static byte[] decryptByPrivatekey(byte[] encrypteData, String privatekey) {
try {
byte[] keyBytes = Base64.getDecoder().decode(privatekey);
PKCS8EncodedKeySpec pkcs8keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, new BouncyCastleProvider());
RSAPrivateKey privateK = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8keySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = encrypteData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
int MAX_DECRYPT_BLOCK = privateK.getModulus().bitLength() / 8;
//读数据进行分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encrypteData, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encrypteData, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decrypteData = out.toByteArray();
out.close();
return decrypteData;
} catch (Exception e) {
logger.info("私钥解密出现异常");
e.printStackTrace();
}
return null;
}
/**
* 私钥解密
*
* @param encryptedData 已加密数据
* @param privatekey 私钥
* @param charset 编码
* @return
*/
public static String decryptByPrivatekey(String encryptedData, String privatekey, Charset charset) {
try {
byte[] bytes = RsaKit.decryptByPrivatekey(Base64.getUrlDecoder().decode(encryptedData), privatekey);
return new String(bytes, charset);
} catch (Exception e) {
logger.info("私钥解密出现异常");
e.printStackTrace();
}
return null;
}
/**
* 公钥解密
*
* @param encryteData 加密数据
* @param publickey 公钥加密
* @return
*/
public static byte[] decryptByPublickey(byte[] encryteData, String publickey) {
try {
byte[] keyBytes = Base64.getDecoder().decode(publickey);
X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, new BouncyCastleProvider());
RSAPublicKey publicK = (RSAPublicKey) keyFactory.generatePublic(x509keySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicK);
int inputLen = encryteData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
int max_decrypt_block = publicK.getModulus().bitLength() / 8;
//对数据分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > max_decrypt_block) {
cache = cipher.doFinal(encryteData, offset, max_decrypt_block);
} else {
cache = cipher.doFinal(encryteData, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * max_decrypt_block;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 公钥解密
*
* @param encryptedData 加密数据
* @param publicKey 公钥
* @param Charset 编码
* @return
*/
public static String decryptByPublicKey(String encryptedData, String publicKey, String Charset) {
try {
byte[] bytes = RsaKit.decryptByPublickey(Base64.getUrlDecoder().decode(encryptedData), publicKey);
return new String(bytes, charset);
} catch (Exception e) {
logger.info("公钥解密出现异常");
e.printStackTrace();
}
return null;
}
/**
* 公钥加密
*
* @param data 数据
* @param publicKey 公钥
* @return
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey) {
try {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, new BouncyCastleProvider());
RSAPublicKey publicK = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
int max_encrypt_block = publicK.getModulus().bitLength() / 8 - 11;
while (inputLen - offset > 0) {
if (inputLen - offset > max_encrypt_block) {
cache = cipher.doFinal(data, offset, max_encrypt_block);
} else {
cache = cipher.doFinal(data, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * max_encrypt_block;
}
byte[] encrypteData = out.toByteArray();
out.close();
return encrypteData;
} catch (Exception e) {
logger.info("公钥加密出现异常");
e.printStackTrace();
}
return null;
}
/**
* 公钥加密
*
* @param data 数据
* @param publicKey 公钥
* @param charset 编码
* @return
*/
public static String encryptByPublicKey(String data, String publicKey, Charset charset) {
try {
byte[] dataBytes = data.getBytes(charset);
byte[] encrypt = encryptByPublicKey(dataBytes, publicKey);
return Base64.getUrlEncoder().encodeToString(encrypt);
} catch (Exception e) {
logger.info("公钥加密出现异常");
e.printStackTrace();
}
return null;
}
/**
* 私钥加密
*
* @param data
* @param privateKey
* @return
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey) {
try {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, new BouncyCastleProvider());
RSAPrivateKey privateK = (RSAPrivateKey) keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
int max_encrypt_block = privateK.getModulus().bitLength() / 8 - 11;
while (inputLen - offset > 0) {
if (inputLen - offset > max_encrypt_block) {
cache = cipher.doFinal(data, offset, max_encrypt_block);
} else {
cache = cipher.doFinal(data, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * max_encrypt_block;
}
byte[] encrypteData = out.toByteArray();
out.close();
return encrypteData;
} catch (Exception e) {
logger.info("私钥加密出现异常");
e.printStackTrace();
}
return null;
}
public static String encryptByPrivateKey(String data, String privateKey, Charset charset) {
try {
byte[] dataBytes = data.getBytes(charset);
byte[] encrypt = encryptByPublicKey(dataBytes, privateKey);
return Base64.getUrlEncoder().encodeToString(encrypt);
} catch (Exception e) {
logger.info("私钥加密出现异常");
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
KeyPair keyPair = generateKeyPair();
PublicKey aPublic = keyPair.getPublic();
String publickey = getPublickey(aPublic);
System.out.println("publickey = " + publickey);
PrivateKey aPrivate = keyPair.getPrivate();
String privateKey = getPrivateKey(aPrivate);
System.out.println("privateKey = " + privateKey);
HashMap<Object, Object> map = new HashMap<>();
map.put("张三", "李四");
String s = JSON.toJSONString(map);
String s1 = encryptByPublicKey(s, publickey, charset);
System.out.println("加密数据为:"+s1);
String s2 = decryptByPrivatekey(s1, privateKey, charset);
System.out.println("解密之后数据为:"+s2);
Map map1 = JSON.parseObject(s2, Map.class);
System.out.println("map1 = " + map1);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。