[TOC]
1.1、了解微服务状态
微服务集群中的每个服务,对外提供的都是Rest风格的接口,而Rest风格的一个最重要的规范就是:服务的无状态性。
什么是无状态?
1.服务端不保存任何客户端请求者信息
2.客户端的每次请求必须自备描述信息,通过这些信息识别客户端身份
无状态,在微服务开放中,优势是?
1.客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
2.服务端的是否集群对客户端透明
3.服务端可以任意的迁移和伸缩
4.减小服务端储存压力
1.2、无状态登录实现原理
- 服务器端生产唯一标识(注意:最终需要进行校验)
- 方案1:UUID,数据单一,不能包含种类过多的信息。
- 方案2:RAS加密,数据多样,需要使用算法,有一定的理解难度。【使用】
- 浏览器储存和自动携带数据
- 方案1:使用cookie,有很多局限性(大小,个数)
- 方案2:请求参数,get请求URL有长度限制,每一个路径都需要处理比较麻烦。
- 方案3:浏览器localStroage存储,请求头携带。【使用】
2.1、RAS工具
服务与服务之间共享数据,采用JWT先生成数据,在另一个服务中解析数据,为了保证数据安全性,使用RAS对数据进行加密。
使用RAS加密保证token数据在传输过程中不会被篡改
- RAS:非对称加密算法
- 特点
- 同时生产一对密钥:公钥和私钥
- 公钥秘钥:用于加密
- 私钥秘钥:用于解密
- 特点
工具类RasUtils
package com.czxy.utils; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * @author 庭前云落 * @Date 2019/12/13 22:01 * @description */ public class RasUtils { /** * 从文件中读取公钥 * * @param filename 公钥保存路径,相对于classpath * @return 公钥对象 * @throws Exception */ public static PublicKey getPublicKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPublicKey(bytes); } /** * 从文件中读取密钥 * * @param filename 私钥保存路径,相对于classpath * @return 私钥对象 * @throws Exception */ public static PrivateKey getPrivateKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPrivateKey(bytes); } /** * 获取公钥 * * @param bytes 公钥的字节形式 * @return * @throws Exception */ public static PublicKey getPublicKey(byte[] bytes) throws Exception { X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(spec); } /** * 获取密钥 * * @param bytes 私钥的字节形式 * @return * @throws Exception */ public static PrivateKey getPrivateKey(byte[] bytes) throws Exception { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(spec); } /** * 根据密文,生存rsa公钥和私钥,并写入指定文件 * * @param publicKeyFilename 公钥文件路径 * @param privateKeyFilename 私钥文件路径 * @param secret 生成密钥的密文 * @throws Exception */ public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom secureRandom = new SecureRandom(secret.getBytes()); keyPairGenerator.initialize(1024, secureRandom); KeyPair keyPair = keyPairGenerator.genKeyPair(); // 获取公钥并写出 byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); writeFile(publicKeyFilename, publicKeyBytes); // 获取私钥并写出 byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); writeFile(privateKeyFilename, privateKeyBytes); } 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); //创建父文件夹 if(!dest.getParentFile().exists()){ dest.getParentFile().mkdirs(); } //创建需要的文件 if (!dest.exists()) { dest.createNewFile(); } Files.write(dest.toPath(), bytes); } }
2.1.1使用工具
//生成公钥和私钥 RasUtils.generateKey(公钥位置,私钥位置,密码); RasUtils.generateKey(pubKeyPath,priKeyPath,"234"); //获得公钥 RasUtils.getPublicKey(pubKeyPath); //获得私钥 RasUtils.getPrivateKey(priKeyPath);
package com.czxy; import com.czxy.utils.RasUtils; import org.junit.Test; import java.security.PrivateKey; import java.security.PublicKey; /** * @author 庭前云落 * @Date 2019/12/13 22:07 * @description */ public class TestRAS { private static final String pugbKeyPath="D:\\ras\\ras.pub"; private static final String priKeyPath="D:\\ras\\ras.pri"; @Test public void testRas() throws Exception { //生产公钥和私钥 RasUtils.generateKey(pugbKeyPath,priKeyPath,"234"); } @Test public void testGetRas() throws Exception { //获得公钥和私钥 PublicKey publicKey = RasUtils.getPublicKey(pugbKeyPath); PrivateKey privateKey = RasUtils.getPrivateKey(priKeyPath); System.out.println(publicKey.toString()); System.out.println(privateKey.toString()); } }
3、JWT工具
3.1概述
JWT,全称是JSON Web Token,是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权:官网:https://jwt.io
- JWT基于JSON的认证规范。(Json Web Token)
- 使用JWT目的:生成数据、解析数据
3.2、使用JWT
pom
<properties> <jwt.jjwt.version>0.9.0</jwt.jjwt.version> <jwt.joda.version>2.9.7</jwt.joda.version> <lombok.version>1.16.20</lombok.version> <beanutils.version>1.9.3</beanutils.version> </properties> <dependencies> <!--网关依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!--添加eureka客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!--jwt依赖--> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>${beanutils.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jwt.jjwt.version}</version> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>${jwt.joda.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> </dependencies>
导入工具类
工具类:JwtUtils
package com.czxy.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.commons.beanutils.BeanUtils; import org.joda.time.DateTime; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.security.PrivateKey; import java.security.PublicKey; /** * @author 庭前云落 * @Date 2019/12/13 22:01 * @description */ public class JwtUtils { /** * 私钥加密token * @param data 需要加密的数据(载荷内容) * @param expireMinutes 过期时间,单位:分钟 * @param privateKey 私钥 * @return */ public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception { //1 获得jwt构建对象 JwtBuilder jwtBuilder = Jwts.builder(); //2 设置数据 if( data == null ) { throw new RuntimeException("数据不能为空"); } BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass()); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 获得属性名 String name = propertyDescriptor.getName(); // 获得属性值 Object value = propertyDescriptor.getReadMethod().invoke(data); if(value != null) { jwtBuilder.claim(name,value); } } //3 设置过期时间 jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate()); //4 设置加密 jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey); //5 构建 return jwtBuilder.compact(); } /** * 通过公钥解析token * @param token 需要解析的数据 * @param publicKey 公钥 * @param beanClass 封装的JavaBean * @return * @throws Exception */ public static <T> T getObjectFromToken(String token, PublicKey publicKey,Class<T> beanClass) throws Exception { //1 获得解析后内容 Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody(); //2 将内容封装到对象JavaBean T bean = beanClass.newInstance(); BeanInfo beanInfo = Introspector.getBeanInfo(beanClass); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 获得属性名 String name = propertyDescriptor.getName(); // 通过属性名,获得对应解析的数据 Object value = body.get(name); if(value != null) { // 将获得的数据封装到对应的JavaBean中 BeanUtils.setProperty(bean,name,value); } } return bean; } }
时间处理工具:DateTime
//当前时间 DateTime.now().toDate().toLocaleString() //当前时间加5分钟 DateTime.now().plusMinutes(5).toDate().toLocaleString() //当前时间减5分钟 DateTime.now().minusMinutes(5).toDate().toLocaleString()
3.3、测试
//生成数据, UserInfo --> String(加密) //JwtUtils.generateToken(数据,过期时间(分钟), 私钥) String token = JwtUtils.generateToken(userInfo,30, RasUtils.getPrivateKey(priKeyPath)); //解析数据, String(加密) --> UserInfo // JwtUtils.getObjectFromToken(加密数据, 公钥, 封装对象.class); UserInfo userInfo = JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(pubKeyPath), UserInfo.class);
- 生产Token
package com.czxy; import com.czxy.utils.RasUtils; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.joda.time.DateTime; import org.junit.Test; /** * @author 庭前云落 * @Date 2019/12/13 22:32 * @description */ public class TestJWT { private static final String pugbKeyPath="D:\\ras\\ras.pub"; private static final String priKeyPath="D:\\ras\\ras.pri"; @Test public void testGenerateToken() throws Exception { String str = Jwts.builder() .claim("test","庭前云落") .setExpiration(DateTime.now().plusMinutes(60).toDate()) .signWith(SignatureAlgorithm.RS256, RasUtils.getPrivateKey(priKeyPath)) .compact(); System.out.println(str); } }
"庭前云落":Token ---》eyJhbGciOiJSUzI1NiJ9.eyJ0ZXN0Ijoi5bqt5YmN5LqR6JC9IiwiZXhwIjoxNTc2MjkzMTEyfQ.a32GamgbG6F1xC-4NtEJNNLX8mcV6Ycyc2bf7_7wX6_xa4LzimqO5ZH9d4bSii-IixYudSreurJ2Rjq72aXvv3nv_VsZasmODeLkBMLtBGhKDztKW3hNQM7rcRLIxL4PFP48xjosJl48F-hXSgEWqYXuC6Voexlk8W4eonRcGqg
- 解析Token
@Test public void testParseToken() throws Exception { String token="\n" + "eyJhbGciOiJSUzI1NiJ9.eyJ0ZXN0Ijoi5bqt5YmN5LqR6JC9IiwiZXhwIjoxNTc2MjkzMTEyfQ.a32GamgbG6F1xC-4NtEJNNLX8mcV6Ycyc2bf7_7wX6_xa4LzimqO5ZH9d4bSii-IixYudSreurJ2Rjq72aXvv3nv_VsZasmODeLkBMLtBGhKDztKW3hNQM7rcRLIxL4PFP48xjosJl48F-hXSgEWqYXuC6Voexlk8W4eonRcGqg"; Claims claims = Jwts.parser().setSigningKey(RasUtils.getPublicKey(pubKeyPath)). parseClaimsJws(token).getBody(); String text = claims.get("test",String.class); System.out.println(text); }
4、生产token和校验token
编写测试对象UserInfo
package com.czxy.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author 庭前云落 * @Date 2019/12/13 21:55 * @description */ @Data @AllArgsConstructor @NoArgsConstructor public class UserInfo { private Long id; private String username; }
测试
package com.czxy; import com.czxy.domain.UserInfo; import com.czxy.utils.JwtUtils; import com.czxy.utils.RasUtils; import org.junit.Test; /** * @author 庭前云落 * @Date 2019/12/13 22:32 * @description */ public class TestJWT { private static final String pubKeyPath="D:\\ras\\ras.pub"; private static final String priKeyPath="D:\\ras\\ras.pri"; @Test public void testToken() throws Exception { UserInfo userInfo = new UserInfo(); userInfo.setId(10L); userInfo.setUsername("庭前云落"); String token = JwtUtils.generateToken(userInfo, 30, RasUtils.getPrivateKey(priKeyPath)); System.out.println(token); } @Test public void testParserToken() throws Exception { String token="eyJhbGciOiJSUzI1NiJ9.eyJjbGFzcyI6ImNvbS5jenh5LmRvbWFpbi5Vc2VySW5mbyIsImlkIjoxMCwidXNlcm5hbWUiOiLluq3liY3kupHokL0iLCJleHAiOjE1NzYyOTI1Mzd9.LlyCCBeW4f7fjU3LmE7cA8W7aNB1BXp23Yv9WQJouCRCtoD46GiXQAHn2kezuzuPfp2u5G0OXOIeahHtnvRMSDjtQFJ6s-cZcKNupJPOPK8BzuEnladx0ilcrSr5TeWNxujg-svSz5EJRwWj8KbRKhQluohpAg0VhERjJjD5wTY"; UserInfo userInfo = JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(pubKeyPath), UserInfo.class); System.out.println(userInfo); } }
5、JWT token组成
JWT的token包含三部分数据:头部、载荷、签名。
名称 | 描述 | 组成部分 |
---|---|---|
头部(Header) | 通常头部有两部分信息 | 1. 声明类型,这里是JW2. 加密算法,自定义 |
载荷(Payload) | 就是有效数据 | 1. 用户身份信息2. 注册声明 |
签名(Signature) | 整个数据的认证信息 | 一般根据前两步的数据,再加上服务的的密钥(secret),通过加密算法生成。用于验证整个数据完整和可靠性 |
- 生成的数据格式
来源:http://www.1994july.club/seo/
来源:https://www.cnblogs.com/1994jinnan/p/12038589.html