编码
编写生成公钥密钥的测试类,创建 一些我们常用的VO对象 用来储存我们常用的一些变量,比如用户信息,公钥,密钥,一些常用的属性 放进 VO的模型里
@Slf4j @SpringBootTest @RunWith(SpringRunner.class) /** * * @author : 冷环渊 * @date : 2021/12/5 * @context: RSA 非对称 加密算法 * @params : null * @return : * @return : null */ public class RSATest { @Test public void generateKeyBytes() throws Exception { /*获取到 RSA算法实例*/ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); /* 这里最小是 2048 低于的话 是会报错的*/ keyPairGenerator.initialize(2048); /* * 生成公钥对 * */ KeyPair keyPair = keyPairGenerator.generateKeyPair(); /*获取 公钥和私钥对象*/ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); log.info("private key:[{}]", Base64.encode(privateKey.getEncoded())); log.info("public key:[{}]", Base64.encode(publicKey.getEncoded())); } }
创建VO对象保存 我们常用且不会变化的值和对象
存储私钥 应为是私钥 所以只对鉴权中心 暴露 于是我们在鉴权服务中创建Constant包创建这个AuthotityConstant类保存信息
/** * @author : 冷环渊 * @date : 2021/12/5 * @context: 鉴权的常量 * @params : null * @return : * @return : null */ public class AuthorCanstant { /*私钥 只暴露给 鉴权中心 不暴露给任何的其他服务*/ public static final String PRIVATE_KEY = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBA" + "QCMXrQCudalKHJlH16YHr9mI5/xyYnkp5u2gAbMFf2xAHAyykYmixJP3CqG2a8tUwiJjjTIJXP+79Jzgjgg" + "VbBaTakrvjeFXz9HNP1D4XD6Li+sRVjnN1iBUwIFRxiFN2EOJflA9bqeQLAge/LgAu06y3jdLLleJF7yDRuMH" + "YedqPl9AJa5RdJmt0OgCoVOqacB7oGkFCFISm0Cwjfgq06nyiiULGZNVt8uhDxZAE4Pi2lmf3yggXCBH9AtU/2" + "XdyxU9caQJOAbYGxd/mART/NivBjSqo60wcBnktI+booUbDKRBbWRxvfYqKWEwPOwxlJUB3l3pcLZm866Xl3qtVM" + "XAgMBAAECggEADCGjLRkik+OK/3JWmo8Nu6YYjKz+XeSecIdgDwNXiZSgHcOdjHc4fe5pPn5RxXkHo9vGdAXIoJ/Z" + "cGIwt5qwQx2zITSvV7eDoIPT36n8OaMEO79Cj7kYzRR/eDVMyTagDLj7ccHK/yJYFnaf5vxZxFsRdwwGeTxreD" + "/pwZJLxjRSz1W57v5yUJNPPimNB229EogNYHIhQ8+Z7OGiilbtBIL9r6lqlz2hUAVBzXl4kOXFVI+vEodLuV2" + "rtQXXrpO1+AgH5lZJ7ahShKbqHt/Q6uJSTKAhbsfv/iadcPjmYp2F7nnYBLf66Jln6AWUwnXrJ7XETOf/+Qcib" + "q/5m6RjAQKBgQDruxn+kaDr5uYQMVSHog+CBRBJghJ4JklhY7ZDYJ2wN2KNHOd3mW/wUVDihVIyRFniIzsWU" + "0lnI+4OLqNLAZOBaQB5VrjyH4fxn5b26t0xLO1d5EWcOYI8ZRhwWDWaZipe2dUMeqVVMYFeDdTdNsyGrf8x" + "L+OVyRDiH4s4pBIs7QKBgQCYcIVFgDbrmwsP7lA9/dU9kClutY3gjEUgB2IJp2Y8S4Xhfi4NC8GqRQoMUyuqg" + "vPHKEiTCa1EojGHS/+r4JVcSg9Wsv64SpGZ+gANxRhfYFPrbkjU4YOMaZeCGUfKR2QnD20c3I4gdQ9kU5nK52n+Y" + "JEkAFUejg1Mhb6Fp6HDkwKBgAHYYBa3CxxtnUVpLXE2Woq5AWyh4QUhv5dMkYOrgPB9Ln9OR52PDOpDqK9tP" + "bx4/n8fqXm+QyfUhyuDP/H5XC86JC/O9vmmN4kzp5ndMsgMwvrmK4lShet1GyDd/+VqgVBmwh0r5JlrHske" + "sJjesfEn8YRwDIcCoOg0OQHDfwTtAoGAQfE61YvXNihFqsiOkaKCYjVAlxGWpDJJnMdU05REl4ScD6WDy" + "kTxq/RdmmNIGmS3i8mTS3f+Khh3kG2B1ho6wkePRxP7OEGZpqAM8ef22RtUch2tB9neDBmJXtAMzCYB3xu/O" + "aL3IHdDB0Va2/krUsz3PDmgmK0ed6HLfwm64l0CgYB+iGkMAQEwqYmcCEXKK825Q9y/u8PE9y8uaMGfsZQzDo6v" + "V5v+reOhmZRrk5BnX+pgztbE28sS6c2vYR0RYoR90aD2GXungCPXWEMDQudHFxvSsNTCYkDynjTSlnzu9aDcfqw1" + "UIzHog2zCquSro7tnbOMsvV5UdsLBq+WNQGgAw=="; /*默认的 token 超时时间,一天*/ public static final Integer DEFAULT_EXPIRE_DAY = 1; }
之后是创建一些公共常用的VO模型 e-commerce-common
保存 公钥到公用包 以后我们的服务 需要做授权都需要使用到
/** * @author : 冷环渊 * @date : 2021/12/5 * @context: 通用模块的常量定义 * @params : null * @return : * @return : null */ public class CommonCanstant { /* RSA 公钥*/ public static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjF60ArnWpShyZ" + "R9emB6/ZiOf8cmJ5KebtoAGzBX9sQBwMspGJosST9wqhtmvLVMIiY40yCVz/u/Sc4I4IFWwWk2pK743hV8/RzT9Q+F" + "w+i4vrEVY5zdYgVMCBUcYhTdhDiX5QPW6nkCwIHvy4ALtOst43Sy5XiRe8g0bjB2Hnaj5fQCWuUXSZrdDoAqFTqmnA" + "e6BpBQhSEptAsI34KtOp8oolCxmTVbfLoQ8WQBOD4tpZn98oIFwgR/QLVP9l3csVPXGkCTgG2BsXf5gEU/zYrwY0qqO" + "tMHAZ5LSPm6KFGwykQW1kcb32KilhMDzsMZSVAd5d6XC2ZvOul5d6rVTFwIDAQAB"; /* JWT 中 存储用户信息到 key*/ public static final String JWT_USER_INFO_KEY = "e-commerce-user"; /*授权中心的 service-id*/ public static final String AUTHORITY_CENTER_SERVICE_ID = "e-commerce-authity-center"; }
用户信息的常用VO对象
/** * @author : 冷环渊 * @date : 2021/12/5 * @context: 授权中心 鉴权 之后给客户端的token * @params : null * @return : * @return : null */ @Data @NoArgsConstructor @AllArgsConstructor public class JwtToken { /* JWT*/ private String token; }
@Data @NoArgsConstructor @AllArgsConstructor public class LoginUserinfo { /*用户 id*/ private Long id; /*用户名*/ private String username; }
/** * @author : 冷环渊 * @date : 2021/12/5 * @context:用户名和密码 * @params : null * @return : * @return : null */ @Data @AllArgsConstructor @NoArgsConstructor public class UsernameAndPassword { /*用户名 */ private String username; /*密码*/ private String password; }
授权服务编写
首先创建一个 接口 IJWTService
定义我们需要实现的授权方法
/** * @author : 冷环渊 * @date : 2021/12/5 * @context: JWT 相关服务接口定义 * @params : null * @return : * @return : null */ public interface IJWTService { /* * 生成 token 使用默认的超时时间 * */ String generateToken(String username, String password) throws Exception; /* * 生成 JWT Token 可以设置超时时间 单位是天 * */ String generateToken(String username, String password, Integer expireTime) throws Exception; /* * 注册用户并且生成 token 返回 * */ String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) throws Exception; }
授权方法实现类
这里我们有三个方法实现
默认超时时间的 生成 token
自定义超时时间的设置生成token
注册新用户并且生成的token返回
JWT对象生成细节:
1) 我们需要设置需要传递的对象
2)我们需要设置一个不重复的 id
3)我们需要设置超时时间
4)设置我们的加密签名
5)完成设置返回字符串对象
Jwts.builder() //这里 claim 其实就是 jwt 的 payload 对象 --> KV .claim(CommonCanstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserinfo)) // jwt id 表示是 jwt的id .setId(UUID.randomUUID().toString()) //jwt 的过期时间 .setExpiration(expireDate) // 这里是设置加密的私钥和加密类型 .signWith(getPrivateKey(), SignatureAlgorithm.RS256) //生成 jwt信息 返回的是一个字符串类型 .compact(); }
完整代码
@Service @Slf4j @Transactional(rollbackFor = Exception.class) public class IJWTServiceIpml implements IJWTService { @Autowired private EcommerceUserDao ecommerceUserDao; @Override public String generateToken(String username, String password) throws Exception { return generateToken(username, password, 0); } @Override public String generateToken(String username, String password, Integer expireTime) throws Exception { //首先需要验证用户是否通过授权校验,即 输入的用户名和密码能否寻找到匹配数据表的记录 EcommerceUser ecommerceUser = ecommerceUserDao.findByUsernameAndPassword(username, password); if (ecommerceUser == null) { log.error("can not find user:[{}],[{}]", username, password); return null; } //Token 中塞入对象, 即 JWT中 储存的对象,后端拿到这些信息 就可以知道那个用户在操作 LoginUserinfo loginUserinfo = new LoginUserinfo( ecommerceUser.getId(), ecommerceUser.getUsername() ); if (expireTime <= 0) { expireTime = AuthorCanstant.DEFAULT_EXPIRE_DAY; } //计算超时时间 ZonedDateTime zdt = LocalDate.now().plus(expireTime, ChronoUnit.DAYS) .atStartOfDay(ZoneId.systemDefault()); Date expireDate = Date.from(zdt.toInstant()); return Jwts.builder() //这里 claim 其实就是 jwt 的 payload 对象 --> KV .claim(CommonCanstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserinfo)) // jwt id 表示是 jwt的id .setId(UUID.randomUUID().toString()) //jwt 的过期时间 .setExpiration(expireDate) // 这里是设置加密的私钥和加密类型 .signWith(getPrivateKey(), SignatureAlgorithm.RS256) //生成 jwt信息 返回的是一个字符串类型 .compact(); } @Override public String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) throws Exception { //先去校验 用户名是否存在 如果存在 不能重复注册 EcommerceUser oldUser = ecommerceUserDao.findByUsername(usernameAndPassword.getUsername()); if (null != oldUser) { log.error("username is registered:[{}]", oldUser.getUsername()); return null; } EcommerceUser ecommerceUser = new EcommerceUser(); ecommerceUser.setUsername(usernameAndPassword.getUsername()); ecommerceUser.setPassword(usernameAndPassword.getPassword()); //MD5 编码以后 ecommerceUser.setExtraInfo("{}"); //注册一个新用户 写到一个 记录表中 ecommerceUser = ecommerceUserDao.save(ecommerceUser); log.info("regiter user success:[{}],[{}]", ecommerceUser.getUsername()); //生成 token 并且返回 return generateToken(ecommerceUser.getUsername(), ecommerceUser.getPassword()); } /* * 根据本地储存的私钥获取到 PrivateKey对象 * */ private PrivateKey getPrivateKey() throws Exception { //使用给定的编码密钥创建一个新的PKCS8EncodedKeySpec。 PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(AuthorCanstant.PRIVATE_KEY)); // 设置生成新密钥的工厂加密方式 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); //返回生成好的密钥 return keyFactory.generatePrivate(priPKCS8); } }
之后我们的授权都会使用到以上的方法