从零搭建基于SpringCloud Alibaba 鉴权中心服务(详细教程)
cac55 2024-10-01 07:49 26 浏览 0 评论
原文来源于:程序员恰恰
如有侵权,联系删除
鉴权中心服务
认识JWT
json web token 是一个开放的标准 ,它定义了一个种紧凑的,自包含的方式,用于作为json对象在各方之间安全的传输信息
- 服务器鉴权完成之后 会生成 json 对象 发送给客户端,之后客户端和服务端传输数据都需要带上这个对象,服务器完全通过这个json对象认定客户端身份,为了防止篡改数据,服务端在生成的时候都会加上签名(加密的意思),服务器不保存session数据也就是无状态,更适合实现扩展
- 那些环境可以考虑使用jwt呢?用户授权 ,信息交换
JWT组成部分
- Header :头部信息
Header 由两部分组成(Token类型,加密算法的名称),并且使用的是base64的编码
- Payload:我们想要传递的数据
Payload KV形式的诗数据 ,这里就是我们想要传递的信息(授权的话就是Token信息)
- Signature :签名
Signature 为了得到签名 首先我们得有编码过的Header 编码过的payload 和一个密钥。签名用的算法就是header中指定的那个,之后就会对他们签名
我们需要一个签名公式
HMACSHA245(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
产生一个签名,返回一个字符串,返回给客户端,之后客户端每次访问都要带上这个字符串,进行鉴权
JWT使用.号来连接 HHH.PPPP.SSSS
授权,鉴权设计
这里我们先不考虑 gateway 网关,后续会搭建,我们的重点放在中间和右边部分
鉴权部分,我们独立实现公共的工具类,为什么?以下三点
- JWT本质上是通过算法算出的加密字符串,也可以通过算法反向解析出来,他不依赖任何的框架,所以这个功能又可以单独提取出来的前提
- 我们的电商系统包含多个微服务,很显然我们每个服务都需要鉴权,于是我们把这个方法提取出来,方便复用
- 高性能鉴权,为什么不在授权中心做鉴权,首先他回头过http请求等一系列操作,我们在本地只用java的话 少去了很多步骤,性能得到倍数的增长
授权编码实现
我们创建新的一个服务来编写我们的鉴权中心
e-commerce-authority-center
导入相关的依赖
<dependencies> <!-- spring cloud alibaba nacos discovery 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.3.RELEASE</version> </dependency> <!-- Java Persistence API, ORM 规范 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL 驱动, 注意, 这个需要与 MySQL 版本对应 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.12</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.hyc.ecommerce</groupId> <artifactId>e-commerce-mvc-config</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.5.0.RELEASE</version> </dependency> <!-- screw 生成数据库文档 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> <dependency> <groupId>cn.smallbun.screw</groupId> <artifactId>screw-core</artifactId> <version>1.0.3</version> </dependency></dependencies>
导入好依赖之后我们 编写对应的配置,如注册到naocs 加入adminserver的监管,配置数据源等 这里我们使用jpa 来做orm
- 配置编写
server: port: 7000 servlet: context-path: /ecommerce-authority-centerspring: application: name: e-commerce-authority-center cloud: nacos: discovery: enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可 server-addr: 127.0.0.1:8848 # Nacos 服务器地址 # server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址 namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6 metadata: management: context-path: ${server.servlet.context-path}/actuator jpa: show-sql: true hibernate: ddl-auto: none properties: hibernate.show_sql: true hibernate.format_sql: true open-in-view: false datasource: # 数据源 url: jdbc:mysql://127.0.0.1:3306/imooc_e_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC username: root password: root type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver # 连接池 hikari: maximum-pool-size: 8 minimum-idle: 4 idle-timeout: 30000 connection-timeout: 30000 max-lifetime: 45000 auto-commit: true pool-name: ImoocEcommerceHikariCP kafka: bootstrap-servers: 127.0.0.1:9092 producer: retries: 3 consumer: auto-offset-reset: latest zipkin: sender: type: kafka # 默认是 web base-url: http://127.0.0.1:9411/> 基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能>> * 项目地址:<https://gitee.com/zhijiantianya/ruoyi-vue-pro>> * 视频教程:<https://doc.iocoder.cn/video/># 暴露端点management: endpoints: web: exposure: include: '*' endpoint: health: show-details: always
配置完成之后,编写主启动类 @EnableJpaAuditing因为我们用到 自动加入创建时间和修改时间,所以我们需要打开 jpa的自动审计功能,不然会报错
@EnableJpaAuditing //允许 jpa 的自动审计@SpringBootApplication@EnableDiscoveryClientpublic class AuthorityApplication { public static void main(String[] args) { SpringApplication.run(AuthorityApplication.class, args); }}
test包下就测试环境是否正确
/** * 授权中心测试入口 * 验证授权中心 环境可用性 */@SpringBootTest@RunWith(SpringRunner.class)public class AuthorityCenterApplicationTest { @Test public void conetextLoad() { }}
环境ok之后
我们去测试 数据库操作是否可用
编写实体类ecommerceUser
/* * 用户表实体类定义 * */@Entity@EntityListeners(AuditingEntityListener.class)@Table(name = "t_ecommerce_user")@Data@NoArgsConstructor@AllArgsConstructorpublic class EcommerceUser { /* 自增组件*/ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private long id; /*用户名*/ @Column(name = "username", nullable = false) private String username; /* MD5 密码*/ @Column(name = "password", nullable = false) private String password; /*额外的信息 json 字符串存储*/ @Column(name = "extra_info", nullable = false) private String extraInfo; /*自动加入创建时间 需要主启动类的注解*/ @CreatedDate @Column(name = "create_time", nullable = false) private Date createTime; /*自动加入更新时间 需要主启动类的注解*/ @CreatedDate @Column(name = "update_time", nullable = false) private Date updateTime;}
有了实体类我们需要有数据操作的实现 于是编写Dao 接口
其实当我们创建接口的时候jpa就已经有了对应的基础增删改查的方法
这里我们实现两个自定义查询方法
/** * EcommerceUserDao 接口定义 */public interface EcommerceUserDao extends JpaRepository<EcommerceUser, Long> { /* * 根据用户名查询 EcommerceUser 对象 * 等于 select * form t_ecommerce_user where username=? * */ EcommerceUser findByUsername(String name); /* * 根据用户名查询 EcommerceUser 对象 * 等于 select * form t_ecommerce_user where username=? and password=? * */ EcommerceUser findByUsernameAndPassword(String name, String password);}
之后创建 test service
/** * @author : 冷环渊 * @date : 2021/12/4 * @context: EcommerceUser 相关测试 * @params : null * @return : * @return : null */@SpringBootTest@RunWith(SpringRunner.class)@Slf4jpublic class EcommerUserTest { @Autowired EcommerceUserDao ecommerceUserDao; /*测试 新增一个用户数据 */ @Test public void createUserRecord() { EcommerceUser ecommerceUser = new EcommerceUser(); //设置要插入的信息 ecommerceUser.setUsername("hyc@qq.com"); ecommerceUser.setPassword(MD5.create().digestHex("123456")); ecommerceUser.setExtraInfo("{}"); //日志打印返回结果 log.info("server user:[{}]", JSON.toJSON(ecommerceUserDao.save(ecommerceUser))); } /*测试 我们编写的自定义方法 查询 刚才创建的新角色*/ @Test public void SelectUserInfo() { String username = "hyc@qq.com"; log.info("select userinof:[{}]", JSON.toJSON(ecommerceUserDao.findByUsername(username))); }}
测试相关的 方法 新增用户啊 或者是 按条件查询用户 ,测试均通过
生成RSA256的公钥 和 私钥 非对称加密算法
他通过 私钥加密 公钥解密来完成验证,目前很多的鉴权 都是 JWTRSA256的算法来加密鉴权的,如果了解不多,就是用RSA256就可以了
- 编码
编写生成公钥密钥的测试类,创建 一些我们常用的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对象
JwtToken
/** * @author : 冷环渊 * @date : 2021/12/5 * @context: 授权中心 鉴权 之后给客户端的token * @params : null * @return : * @return : null */@Data@NoArgsConstructor@AllArgsConstructorpublic class JwtToken { /* JWT*/ private String token;}
LoginUserinfo
@Data@NoArgsConstructor@AllArgsConstructorpublic class LoginUserinfo { /*用户 id*/ private Long id; /*用户名*/ private String username;}
UsernameAndPassword
/** * @author : 冷环渊 * @date : 2021/12/5 * @context:用户名和密码 * @params : null * @return : * @return : null */@Data@AllArgsConstructor@NoArgsConstructorpublic 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); }}
之后我们的授权都会使用到以上的方法
- Controller
我们需要给注册用户和生成token 一个程序的入口
就是我们的 AuthorityController,这里可以用到我们之前使用的注解@IgnoreResponseAdvice我们为啥那么不让他封装呢,我们需要验证,单纯的 JwtToken对象就可以了,不需要封装和转化
@Slf4j@RestController@RequestMapping("/authority")public class AuthorityConroller { private final IJWTService ljwtService; public AuthorityConroller(IJWTService ljwtService) { this.ljwtService = ljwtService; } /* * 从授权中心 获取 token (其实就是登陆功能) 且返回信息中没有统一响应的包装 * */ @IgnoreResponseAdvice @PostMapping("/token") public JwtToken token(@RequestBody UsernameAndPassword usernameAndPassword) throws Exception { //通常 日志里不会答打印用户的信息 防止泄露,我们这本身就是一个授权服务器,本身就不对外开放,所以我们可以打印用户信息到日志方便查看 log.info("request to get token with param:[{}]", JSON.toJSONString(usernameAndPassword)); return new JwtToken(ljwtService.generateToken( usernameAndPassword.getUsername(), usernameAndPassword.getPassword())); } /*注册用户并且返回注册当前用户的token 就是通过授权中心常见用户*/ @IgnoreResponseAdvice @PostMapping("/register") public JwtToken register(@RequestBody UsernameAndPassword usernameAndPassword) throws Exception { log.info("register user with param:[{}]", JSON.toJSONString(usernameAndPassword)); return new JwtToken(ljwtService.registerUserAndGenerateToken(usernameAndPassword)); }}
鉴权编码实现
这里我们打鉴权 放到公共模块里 为什么呢,这里我们不止是鉴权中心还有其他的服务也要用到鉴权服务,秉着封装的思想,我们提取公共的方法放到 Common里面
创建JWT Token解析类TokenParseUtil
/** * @author : 冷环渊 * @date : 2021/12/5 * @context: JWT Token 解析工具类 * @params : null * @return : * @return : null */public class TokenParseUtil { public static LoginUserinfo parseUserInfoFromToken(String token) throws Exception { if (null == token) { return null; } Jws<Claims> claimsJws = parseToken(token, getPublicKey()); Claims body = claimsJws.getBody(); //如果 Token 已经过期返回 null if (body.getExpiration().before(Calendar.getInstance().getTime())) { return null; } // 返回 Token中保存的用户信息 return JSON.parseObject( body.get(CommonCanstant.JWT_USER_INFO_KEY).toString(), LoginUserinfo.class ); } /* * 通过公钥去解析 JWT Token * */ private static Jws<Claims> parseToken(String token, PublicKey publicKey) { // 用设置签名公钥,解析claims信息 token return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token); } /* * 根据本地存储的公钥获取到 getPublicKey * */ public static PublicKey getPublicKey() throws Exception { //解码器 我们设置解码器 将公钥放进去 X509EncodedKeySpec keySpec = new X509EncodedKeySpec( new BASE64Decoder().decodeBuffer(CommonCanstant.PUBLIC_KEY) ); //创建 RSA 实例 通过示例生成公钥对象 return KeyFactory.getInstance("RSA").generatePublic(keySpec); }}
这里是涉及到一个问题 ,token要是传输的不是jwt token对象,会跑出异常,没有兜底,
其实这里这问题其实也不成立,应为你没有传入token对象,我们这里抛出异常是正确的,也不会影响其他服务,之后搭配sentinel和豪猪哥 可以实现异常重启等等,这里我们就先不编写兜底方法,以解析jwt token为主。
验证鉴权授权
我们写一个 test 类来测试 授权和鉴权拿到对象,是否有效
/** * @author : 冷环渊 * @date : 2021/12/5 * @context: JWT 相关测试类 * @params : null * @return : * @return : null */@Slf4j@SpringBootTest@RunWith(SpringRunner.class)public class JWTServiceTest { @Autowired private IJWTService ijwtService; @Test public void testGenerateAndParseToken() throws Exception { String jwtToken = ijwtService.generateToken( "hyc@qq.com", "e10adc3949ba59abbe56e057f20f883e" ); log.info("jwt token is:[{}]", jwtToken); LoginUserinfo userinfo = TokenParseUtil.parseUserInfoFromToken(jwtToken); log.info("userinfo by jwt prase token :[{}]", JSON.toJSONString(userinfo)); }}
启动测试查看结果
eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjExLFwidXNlcm5hbWVcIjpcImh5Y0BxcS5jb21cIn0iLCJqdGkiOiIzNDgwNjdjMi00MTBlLTQ3MjItYmM3ZS02NWQyYmNmYTRkN2MiLCJleHAiOjE2Mzg3MjAwMDB9.ZbFl81MkIipJSULZLf4F2X2Fb0q1TwhHIMT7nyZsZVwUxXyZnK54RlzoGM_b-kMUdKO_Tab-qEeOT6Jn--FiKmbOziWXiBx3a-k5ipthMJx0Fez-X8Acty-Pg7zukNalugiLxGb5ophQoVQWRTDmv2hytGHqiV71HVyErznkJa36QQr6QsjXqlJleo3BBt-6BFzdTFPLUmdTEJ4XsmZBa_acUDGBhY0_tU2gYtKBWhwvMCknuyCcV-_GVI5EvgMIKRpeFSZrWfTsDG2y1MFcyzjKE6jnzek-YwT3XkzQ8eGzUbiOlaU_Zx5OJah-UtrKwqlAw9WbO71pNgEBefdsYw
这是封装好的 JWT Token 这里我们可以看到三个点分别分割 了 header和payload以及签名,和我们之前讲的 结构一模一样,
userinfo by jwt prase token :[{"id":11,"username":"hyc@qq.com"}]
获取到的我们放在 jwt 里面需要传递的对象
验证对外提供的接口是否好用
这里我们编写 http脚本来测试对外题提供的接口是否有用
- Token 方法
> 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能>> * 项目地址:<https://gitee.com/zhijiantianya/yudao-cloud>> * 视频教程:<https://doc.iocoder.cn/video/># 获取 Token -- 登录功能实现POST http://127.0.0.1:7000/ecommerce-authority-center/authority/tokenContent-Type: application/json{ "username": "hyc@qq.com", "password": "e10adc3949ba59abbe56e057f20f883e"}POST http://127.0.0.1:7000/ecommerce-authority-center/authority/tokenHTTP/1.1 200 Content-Type: application/jsonTransfer-Encoding: chunkedDate: Sun, 05 Dec 2021 15:35:52 GMTKeep-Alive: timeout=60Connection: keep-alive{ "token": "eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjExLFwidXNlcm5hbWVcIjpcImh5Y0BxcS5jb21cIn0iLCJqdGkiOiIxNDU1M2FjZi1lZmE5LTQ4OTgtOTliYS1hNzA4NWI4MjU4MzAiLCJleHAiOjE2Mzg3MjAwMDB9.AlOpo6uf97R20ZLojXeun-3MK8DpSYlWxEygvDrtQeWaM9R0iKx-iW1VXnK6WoEntvqPxIrmPA7khjl3dXPa8kQHtdq-LVO7BDuZZDiQyZ64ZS7A9jWZr5JReSWBUSR1YUnsOvBRMkx4JVcAF3_W7nHwd722FFzOZRCr72hLHQIKpsugKtqjMEtaiEW0vcqphCYRJTAO_rQx1Lb1eVVg_Ufur0qSlKkV5dSJ0x3x9mc9UZRckwN0rrP7wQxZcrxJvKTfX7CkRRSO-CxZbG4WLokSaMtaGBMWU-7KGq7HSCZ0yuOgbbLdouHncsp6VD2tNLFdWSdJ_whCIbZxfX8R7w"}
获取 token 成功
这里他没有被响应包裹,证明我们之前的选择屏蔽注解也生效了,很符合我们的预期
- 验证如果记录数据表没有是否会返回null
# 获取 Token -- 登录功能实现POST http://127.0.0.1:7000/ecommerce-authority-center/authority/tokenContent-Type: application/json# 随便写的id{"username": "hyc1111@qq.com","password": "e10adc3949ba59abbe56e057f20f883e"}
返回结果 也符合我们预期 是 null
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/tokenHTTP/1.1 200 Content-Type: application/jsonTransfer-Encoding: chunkedDate: Sun, 05 Dec 2021 15:40:44 GMTKeep-Alive: timeout=60Connection: keep-alive{ "token": null}
- register
# 注册用户并返回 Token -- 注册功能实现POST http://127.0.0.1:7000/ecommerce-authority-center/authority/registerContent-Type: application/json{ "username": "hyc@qq.com", "password": "e10adc3949ba59abbe56e057f20f883e"}
这个用户之前是注册过的,我们来看一下是否会返回我们预期的处理
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/registerHTTP/1.1 200 Content-Type: application/jsonTransfer-Encoding: chunkedDate: Sun, 05 Dec 2021 15:42:00 GMTKeep-Alive: timeout=60Connection: keep-alive{ "token": null}
- 现在我们去注册一个新的用户
# 注册用户并返回 Token -- 注册功能实现POST http://127.0.0.1:7000/ecommerce-authority-center/authority/registerContent-Type: application/json{ "username": "hyc11@qq.com", "password": "e10adc3949ba59abbe56e057f20f883e"}
符合预期结果,创建了我们预期的对象,这个时候我们去看一下数据表
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/registerHTTP/1.1 200 Content-Type: application/jsonTransfer-Encoding: chunkedDate: Sun, 05 Dec 2021 15:42:57 GMTKeep-Alive: timeout=60Connection: keep-alive{ "token": "eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEyLFwidXNlcm5hbWVcIjpcImh5YzExQHFxLmNvbVwifSIsImp0aSI6IjMxNDc0NmIwLTMyOGYtNDZkNS05ZTIwLTg3YjI0OWY1ZjZkOCIsImV4cCI6MTYzODcyMDAwMH0.MKxk-Q4BG5kaYFAsLiy13trtk_gDFmCKORpdE4EAwgSVecXFQcYfT1VvqSAKvoQLFsSlQAxOR5elV8CFOoKwAomwqdyyghZp63NKJ2smRbg3Y-4jWBzFVsUgcjOY2fwh7oNTdHEsWmLBYAh5r0hm_MysZsUEsE-cwb3sw8NSMk1OZp0J6tcRras7V1Uw5xXH8OnCoq2cUfdynJMHS29EzJT1TFPb8unVQ_A1RWodsHdK3n1Bl4wFbJjMtnHx7vzOeAUSNJx1XpAGdo0xYHK6HBpS9E1KBS3x1AnYFONM0DKd4-_QxMkBW1kkg2uWrRpf3GYZF20FKxXgmBAPHGZhew"}
对象生成,功能验证一切正常
鉴权服务中心总结
对比基于Token与基于服务器的身份认证
传统:
- 最为传统的做法,客户端储存 cookie 一般是 Session id 服务器存储 Session
- Session 是每次用户认证通过以后 ,服务器需要创建一条记录保存用户信息,通常是在内存中(也可以放在redis中),随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大
- 不同域名之前切换的时候,请求可能会被禁止,即跨越问题
基于token:
- JWT与Session的差异相同点是,他们都是存储用户信息。然而Session是在服务器端的,而JWT是在客户端的
- JWT方式将用户状态分散到了客户端中,可以明显减轻请服务器的内存压力,服务端只需要用算法解析客户端的token就可以得到信息
- 两者优缺点的对比
- 解析方法:JWT使用算法直接解析得到用户信息;Session需要额外的数据映射。实现匹配
- 管理方法:JWT只有过期时间的限制,Session 数据保存在服务器,可控性更强
- 跨平台:JWT就是一段字符串,可以任意传播,Session跨平台需要有统一的解析平台,较为繁琐
- 时效性:JWT一旦生成 独立存在,很难做到特殊的控制;Session时效性完全由服务端的逻辑说了算
TIPS :各自都有优缺点,都是登陆和授权的解决方案
相关推荐
- 服务器用的CPU和个人电脑用的CPU有什么区别?一篇文章告诉你!
-
服务器cpu和普通cpu的区别你的电脑CPU是‘短跑健将’,服务器CPU却是‘铁人三项选手’——它不追求瞬间爆发力,而要7×24小时扛住千军万马的数据洪流!想知道为什么企业机房敢收天价服务费?答案全藏...
- “吃鸡”新版本第1天,玩家进入游戏点击“立即更新”,后悔了!
-
欢迎诸位小伙伴们来到天哥开讲的《和平精英》“精英小课堂”~每逢两三个月,这款游戏就会迎来一次大版本迭代更新,很多朋友会在第一时间更新版本,前往全新的主题模式里一探究竟。不过也有一些老玩家并不会立刻更新...
- 中关村在线·aigo存储杯《无畏契约》全国高校争霸赛招募启事
-
以青春之名,燃电竞之火1赛事背景与宗旨在金秋送爽的9月,芊芊学子们即将回归校园生活。为了给精彩的校园生活锦上添花,由中关村在线与aigo存储联合主办的《无畏契约》全国高校争霸赛正式启幕,旨在为全国高...
- 【生肖狗】9.7-9.10提醒:人算不如天算,转变即是转机
-
九月上旬的风,带着秋意的清爽,也带着几分不可捉摸的变数。对于生肖狗的朋友们来说,9月7日到9月10日这四天,格外需要留意“计划与变化”的碰撞——你们向来习惯提前规划,做事稳妥周全...
- 转转客服IM系统的WebSocket集群架构设计和部署方案
-
本文由转转技术李帅分享,原题“转转客服IM的WebSocket集群部署方案”,下文有修订和重新排版。1、引言转转作为国内头部的二手闲置交易平台,拥有上亿的用户。用户在使用转转app遇到问题时,一般可以...
- 上线3天Steam好评率86%,《时间旅者:重生曙光》开启生存恐怖新篇章
-
这里究竟发生了什么?末日降临,真正的故事悄然启幕。目前,生存恐怖类游戏《时间旅者:重生曙光(Cronos:TheNewDawn)》已在PC(Steam、EpicGamesStore)、P...
- 什么神仙洗衣机让我一天有28小时?拆开松下「大四洗」藏了啥秘密
-
说起家庭洗衣的烦恼,想必很多人都有过类似的经历:贴身内衣要单独洗,宝宝的口水巾得小心呵护,宠物玩具怕藏污纳垢,床单被套又体积庞大,把这些东西混在一起洗担心越洗越脏,分开洗又得反复操作,洗完烘、烘完再洗...
- 爆料人挖出GTA6注册的奇葩域名 延续经典讽刺风格
-
等待《侠盗猎车手6》的日子跨越了数个春秋,在游戏圈期盼着这部可能成为史上最重磅游戏的过程中,每过一段时间就会有些许消息浮出水面。最新线索来自数据挖掘者Tez2在GTA论坛的发现,他可能偶然发现了关于...
- 跟着故事去旅行——读《驼峰间:旅行、探险与征服》
-
作者:郭冰茹《驼峰间》记录了旅行家伊本·白图泰有生之年流传的一则寓言,说一对父子被关进了监狱,有一天儿子问父亲他们每天吃的都是些什么肉,父亲说有牛、羊和骆驼,并且详细地描述了每种动物的特点。但不管父亲...
- 前端工程师需要熟悉的Linux服务器(SSH 终端操作)指令
-
在Linux服务器管理中,SSH(SecureShell)是远程操作的核心工具。以下是SSH终端操作的常用命令和技巧,涵盖连接、文件操作、系统管理等场景:一、SSH连接服务器1.基本连接...
- 跳票6年后,「丝之歌」首发把Steam服务器干爆了 | 玩点好的
-
文丨果脯樱花隧道昨天晚上22点,「鸽」了6年的《空洞骑士:丝之歌》终于上线,算是了却不少玩家的执念。毕竟,这款游戏实在让人等了太多太多年,而且曾有过多次定档后跳票的「案底」,不知道把多少人都整出了P...
- 对标魔兽失败!腾讯版“魔兽”运营一年多后,宣布国际服凉凉
-
大家好,这里是正惊游戏,我是正惊小弟。有很多游戏都想干掉《魔兽世界》,但是大部分魔兽杀手都知道自己不是魔兽的对手,不过是想蹭一下人气而已。腾讯也有一款曾经想对标魔兽的大作,可是上线才一年半国际服就宣布...
- 408 Request Timeout:服务器等待客户端发送请求的时间过长。
-
408RequestTimeout是HTTP状态码之一,表示客户端在发送请求时,服务器等待的时间过长,最终放弃了处理该请求。此问题通常与网络延迟、客户端配置、服务器设置或者应用程序的性能有关...
- 梦幻西游:9.9维护解读,全新时间服锁定129级
-
梦幻西游:9.9维护解读,全新时间服锁定129级9月9日维护解读。1、教师节活动开启,一共7天。挂机,答题,收笔墨纸砚,收海马,搞起来。或者是提前收点家具,教师节期间体力珍贵,家具会涨价。又或者是教师...
- 只是拆掉一面墙,空间就立马大变样,这种设计思路,值得学习
-
你有没有过这样的经历?刚买的房子户型图看起来方方正正,装修完却发现——玄关鞋柜只能塞在角落,进门就撞墙;餐厅正好在过道中间,吃饭像走流程;明明有四个房间,却有一个空着没用,像块食之无味的鸡肋;客餐厅之...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 服务器用的CPU和个人电脑用的CPU有什么区别?一篇文章告诉你!
- “吃鸡”新版本第1天,玩家进入游戏点击“立即更新”,后悔了!
- 中关村在线·aigo存储杯《无畏契约》全国高校争霸赛招募启事
- 【生肖狗】9.7-9.10提醒:人算不如天算,转变即是转机
- 转转客服IM系统的WebSocket集群架构设计和部署方案
- 上线3天Steam好评率86%,《时间旅者:重生曙光》开启生存恐怖新篇章
- 什么神仙洗衣机让我一天有28小时?拆开松下「大四洗」藏了啥秘密
- 爆料人挖出GTA6注册的奇葩域名 延续经典讽刺风格
- 跟着故事去旅行——读《驼峰间:旅行、探险与征服》
- 前端工程师需要熟悉的Linux服务器(SSH 终端操作)指令
- 标签列表
-
- 如何绘制折线图 (52)
- javaabstract (48)
- 新浪微博头像 (53)
- grub4dos (66)
- s扫描器 (51)
- httpfile dll (48)
- ps实例教程 (55)
- taskmgr (51)
- s spline (61)
- vnc远程控制 (47)
- 数据丢失 (47)
- wbem (57)
- flac文件 (72)
- 网页制作基础教程 (53)
- 镜像文件刻录 (61)
- ug5 0软件免费下载 (78)
- debian下载 (53)
- ubuntu10 04 (60)
- web qq登录 (59)
- 笔记本变成无线路由 (52)
- flash player 11 4 (50)
- 右键菜单清理 (78)
- cuteftp 注册码 (57)
- ospf协议 (53)
- ms17 010 下载 (60)