从0搭建公司SpringCloud Alibaba 微服務鉴权中心服务(详细教程)

article/2025/10/4 18:34:40

大家好,我是宝哥!

鉴权中心服务

认识JWT

json web token 是一个开放的标准 ,它定义了一个种紧凑的,自包含的方式,用于作为json对象在各方之间安全的传输信息

  • 服务器鉴权完成之后 会生成 json 对象 发送给客户端,之后客户端和服务端传输数据都需要带上这个对象,服务器完全通过这个json对象认定客户端身份,为了防止篡改数据,服务端在生成的时候都会加上签名(加密的意思),服务器不保存session数据也就是无状态,更适合实现扩展

  • 那些环境可以考虑使用jwt呢?用户授权 ,信息交换

JWT组成部分

  • Header :头部信息

Header 由两部分组成(Token类型,加密算法的名称),并且使用的是base64的编码

6ce45c1fd5ac82e10af9d241ae8c54d6.png
  • Payload:我们想要传递的数据

Payload KV形式的诗数据 ,这里就是我们想要传递的信息(授权的话就是Token信息)

  • Signature :签名

Signature 为了得到签名 首先我们得有编码过的Header 编码过的payload 和一个密钥。签名用的算法就是header中指定的那个,之后就会对他们签名

我们需要一个签名公式

HMACSHA245(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

产生一个签名,返回一个字符串,返回给客户端,之后客户端每次访问都要带上这个字符串,进行鉴权

JWT使用.号来连接 HHH.PPPP.SSSS

授权,鉴权设计

这里我们先不考虑 gateway 网关,后续会搭建,我们的重点放在中间和右边部分

7b5995e9f57519356f756983824de497.png

鉴权部分,我们独立实现公共的工具类,为什么?以下三点

  • 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: 7000servlet:context-path: /ecommerce-authority-centerspring:application:name: e-commerce-authority-centercloud: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-695c25bc0ac6metadata:management:context-path: ${server.servlet.context-path}/actuatorjpa:show-sql: truehibernate:ddl-auto: noneproperties:hibernate.show_sql: truehibernate.format_sql: trueopen-in-view: falsedatasource:# 数据源url: jdbc:mysql://127.0.0.1:3306/imooc_e_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTCusername: rootpassword: roottype: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver# 连接池hikari:maximum-pool-size: 8minimum-idle: 4idle-timeout: 30000connection-timeout: 30000max-lifetime: 45000auto-commit: truepool-name: ImoocEcommerceHikariCPkafka:bootstrap-servers: 127.0.0.1:9092producer:retries: 3consumer:auto-offset-reset: latestzipkin:sender:type: kafka # 默认是 webbase-url: http://127.0.0.1:9411/# 暴露端点
management:endpoints:web:exposure:include: '*'endpoint:health:show-details: always

配置完成之后,编写主启动类 @EnableJpaAuditing因为我们用到 自动加入创建时间和修改时间,所以我们需要打开 jpa的自动审计功能,不然会报错

@EnableJpaAuditing //允许 jpa 的自动审计
@SpringBootApplication
@EnableDiscoveryClient
public class AuthorityApplication {public static void main(String[] args) {SpringApplication.run(AuthorityApplication.class, args);}
}

test包下就测试环境是否正确

/*** 授权中心测试入口* 验证授权中心 环境可用性*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class AuthorityCenterApplicationTest {@Testpublic void conetextLoad() {}
}

环境ok之后

我们去测试 数据库操作是否可用

编写实体类ecommerceUser

/** 用户表实体类定义* */
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "t_ecommerce_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public 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就已经有了对应的基础增删改查的方法

4a1a6261396759b1d237413f57cf2bee.png

这里我们实现两个自定义查询方法

/*** 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)
@Slf4j
public class EcommerUserTest {@AutowiredEcommerceUserDao ecommerceUserDao;/*测试  新增一个用户数据 */@Testpublic 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)));}/*测试 我们编写的自定义方法 查询 刚才创建的新角色*/@Testpublic void SelectUserInfo() {String username = "hyc@qq.com";log.info("select userinof:[{}]", JSON.toJSON(ecommerceUserDao.findByUsername(username)));}
}

测试相关的 方法 新增用户啊 或者是 按条件查询用户 ,测试均通过

7b4432917fe8357919fc86005e9b3b2e.png

生成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 {@Testpublic 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
@AllArgsConstructor
public class JwtToken {/* JWT*/private String token;}

LoginUserinfo

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUserinfo {/*用户 id*/private Long id;/*用户名*/private String username;
}

UsernameAndPassword

/*** @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 {@Autowiredprivate EcommerceUserDao ecommerceUserDao;@Overridepublic String generateToken(String username, String password) throws Exception {return generateToken(username, password, 0);}@Overridepublic 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();}@Overridepublic 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 已经过期返回 nullif (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信息 tokenreturn 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 {@Autowiredprivate IJWTService ijwtService;@Testpublic 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));}
}

启动测试查看结果

e3f1de9fe6bc0a86f498c62ce04e176c.png
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 方法

### 获取 Token -- 登录功能实现
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/token
Content-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/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:35:52 GMT
Keep-Alive: timeout=60
Connection: 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/token
Content-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/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:40:44 GMT
Keep-Alive: timeout=60
Connection: keep-alive{"token": null
}
  • register

### 注册用户并返回 Token -- 注册功能实现
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/register
Content-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/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:42:00 GMT
Keep-Alive: timeout=60
Connection: keep-alive{"token": null
}
  • 现在我们去注册一个新的用户

### 注册用户并返回 Token -- 注册功能实现
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/register
Content-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/json
Transfer-Encoding: chunked
Date: Sun, 05 Dec 2021 15:42:57 GMT
Keep-Alive: timeout=60
Connection: keep-alive{"token": "eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEyLFwidXNlcm5hbWVcIjpcImh5YzExQHFxLmNvbVwifSIsImp0aSI6IjMxNDc0NmIwLTMyOGYtNDZkNS05ZTIwLTg3YjI0OWY1ZjZkOCIsImV4cCI6MTYzODcyMDAwMH0.MKxk-Q4BG5kaYFAsLiy13trtk_gDFmCKORpdE4EAwgSVecXFQcYfT1VvqSAKvoQLFsSlQAxOR5elV8CFOoKwAomwqdyyghZp63NKJ2smRbg3Y-4jWBzFVsUgcjOY2fwh7oNTdHEsWmLBYAh5r0hm_MysZsUEsE-cwb3sw8NSMk1OZp0J6tcRras7V1Uw5xXH8OnCoq2cUfdynJMHS29EzJT1TFPb8unVQ_A1RWodsHdK3n1Bl4wFbJjMtnHx7vzOeAUSNJx1XpAGdo0xYHK6HBpS9E1KBS3x1AnYFONM0DKd4-_QxMkBW1kkg2uWrRpf3GYZF20FKxXgmBAPHGZhew"
}
d54f2c92ce819dec29f6b80348a956de.png

对象生成,功能验证一切正常

鉴权服务中心总结

对比基于Token与基于服务器的身份认证

传统:
  • 最为传统的做法,客户端储存 cookie 一般是 Session id 服务器存储 Session

  • Session 是每次用户认证通过以后 ,服务器需要创建一条记录保存用户信息,通常是在内存中(也可以放在redis中),随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大

  • 不同域名之前切换的时候,请求可能会被禁止,即跨越问题

14c0c1cada4f61f8e79218d961b6e781.png
基于token:
  • JWT与Session的差异相同点是,他们都是存储用户信息。然而Session是在服务器端的,而JWT是在客户端的

  • JWT方式将用户状态分散到了客户端中,可以明显减轻请服务器的内存压力,服务端只需要用算法解析客户端的token就可以得到信息

f76fc69e26f893d114134c442da7ec64.png
  • 两者优缺点的对比

  • 解析方法:JWT使用算法直接解析得到用户信息;Session需要额外的数据映射。实现匹配

  • 管理方法:JWT只有过期时间的限制,Session 数据保存在服务器,可控性更强

  • 跨平台:JWT就是一段字符串,可以任意传播,Session跨平台需要有统一的解析平台,较为繁琐

  • 时效性:JWT一旦生成 独立存在,很难做到特殊的控制;Session时效性完全由服务端的逻辑说了算

TIPS :各自都有优缺点,都是登陆和授权的解决方案

来源:blog.csdn.net/doomwatcher/article/

details/121743887

精彩推荐:
JWT 实现登录认证 + Token 自动续期方案,这才是正确的使用姿势!别用XShell了,这款SSH工具绝对惊艳,还支持网页版.....大文件上传下载实现思路,分片、断点续传代码实现SpringBoot+Vue实现微信扫码支付、退款功能我的mybatis-plus用法,被全公司同事开始悄悄模仿了!

http://chatgpt.dhexx.cn/article/lP1oJ7VO.shtml

相关文章

腾讯云开源业界微服管理框架 Femas

导读 企业数字化向云原生演进过程面临诸多痛点&#xff0c;微服务框架不统一、协议多样化、语言异构&#xff0c;纷繁复杂的微服务技术栈&#xff0c;基础组件之间像一座座孤岛&#xff0c;各个基础组件的控制面不能互联&#xff0c;让用户的体验非常割裂&#xff0c;各种历史包…

基于Ant DesignPro Vue + SpringBoot 前后端分离 - 后端微服化 + 接口网关 + Nacos + Sentinel

基于Ant DesignPro Vue SpringBoot 前后端分离 - 后端微服化 接口网关 Nacos Sentinel 通过Ant DesignPro Vue SpringBoot 搭建的后台管理系统后&#xff0c;实现了前后端分离&#xff0c;并实现了登录认证&#xff0c;认证成功后返回该用户相应权限范围内可见的菜单。 后…

分布式/微服务必配APM系统,SkyWalking让你不迷路

前言 如今分布式、微服务盛行&#xff0c;面对拆分服务比较多的系统&#xff0c;如果线上出现异常&#xff0c;需要快速定位到异常服务节点&#xff0c;假如还用传统的方式排查肯定效率是极低的&#xff0c;因为服务之间的各种通信会让定位更加繁琐&#xff1b;所以就急需一个…

大开眼界,Jenkins 结合 SpringCloud+K8S,打通微服一条龙技术讲解

Jenkins 是目前最常用的持续集成工具&#xff0c;拥有近 50%的市场份额&#xff0c;他还是很多技术团队的第一个使用的自动化工具。由此可见他的重要性&#xff01; 这份 Jenkins 宝典从入门介绍到结合 DockerSpringCloudKubernetes&#xff0c;打通一条龙技术讲解&#xff0c;…

【微服】单体、SOA、微服务

单体架构 将所有的功能都集中在一个模块中&#xff08;WAR包&#xff09;开发、部署、迭代&#xff0c;牵一发而动全身&#xff0c;局部低效率拖垮整个服务。 SOA 按服务对项目拆分&#xff0c;通过对外提供接口的方式提供服务&#xff0c;缓解了单体的单服务低效率拖垮整个服…

微服架构基础设施环境平台搭建 -(三)Docker+Kubernetes集群搭建

微服架构基础设施环境平台搭建 -&#xff08;三&#xff09;DockerKubernetes集群搭建 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、测试…

微服架构基础设施环境平台搭建 -(二)Docker私有仓库Harbor服务搭建

微服架构基础设施环境平台搭建 -&#xff08;二&#xff09;Docker私有仓库Harbor服务搭建 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、…

SpringCloud微服架构

微服务架构 1&#xff09;单体应用架构 高效开发&#xff1a;项目前期开发节奏快&#xff0c;团队成员少的时候能够快替代 架构简单&#xff1a;mvc架构&#xff0c;只需要借助Ide开发&#xff0c;调试即可 易于测试&#xff1a;只要通过单元测试或者浏览器完成 易于部署&…

微服架构

首先我们看看为什么要考虑使用微服务。 开发单体式应用 假设你正准备开发一款与Uber和Hailo竞争的出租车调度软件&#xff0c;经过初步会议和需求分析&#xff0c;你可能会手动或者使用基于Rails、Spring Boot、Play或者Maven的生成器开始这个新项目&#xff0c;它的六边形架构…

微服架构基础设施环境平台搭建 -(一)基础环境准备

微服架构基础设施环境平台搭建 -&#xff08;一&#xff09;基础环境准备 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、测试、发布的自动…

微服架构基础设施环境平台搭建 -(四)在Kubernetes集群基础上搭建Kubesphere平台

微服架构基础设施环境平台搭建 -&#xff08;四&#xff09;在Kubernetes集群基础上搭建Kubesphere平台 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动…

微服架构简介

什么是微服务&#xff1f; 专业解释&#xff1a; 微服务架构是一种架构模式&#xff0c;它提倡将单一应用程序划分成一组小的服务&#xff0c;服务之间互相协调、互相配合&#xff0c;为用户提供最终价值。每个服务运行在其独立的进程中&#xff0c;服务与服务间采用轻量级的…

微服架构基础设施环境平台搭建 -(五)Docker常用命令

微服架构基础设施环境平台搭建 -&#xff08;五&#xff09;Docker常用命令 本文主要列出了Docker常用的命令 微服架构基础设施环境平台搭建 系列文章 微服架构基础设施环境平台搭建 -&#xff08;一&#xff09;基础环境准备 微服架构基础设施环境平台搭建 -&#xff08;二&am…

微服务简介

文章目录 微服务&#xff08;micro services&#xff09;简介一 什么是微服务&#xff1f;二 微服务这个概念的由来三 微服务和单体式架构区别3.1 单体式架构缺点3.2 微服务架构的解决方案3.3 微服务缺点3.4 微服务优点3.5 单体式和微服务对比 微服务&#xff08;micro service…

微服务什么是以及微服务框架

微服务&#xff08;microservice&#xff09; 微服务是分布式架构的一种&#xff0c;分布式架构其实就是要把服务做一个拆分&#xff0c;而springcloud只是解决了拆分过程中的服务治理问题。 在单体架构中&#xff0c;我们把所有的服务都写在一起&#xff0c;随着业务的复杂代…

微服篇01——微服务的发展

01——微服务的发展 1&#xff1a;Monolith&#xff08;整体架构&#xff09; 服务所对应的代码由多个项目所组成&#xff0c;最终合并在一起形成一个WAR包&#xff0c;再部署到Web容器。 负载与扩容&#xff1a; 2&#xff1a;微服务(Microservice)架构模式 Microservic…

配置不同网段的dhcp服务器

dhcp中继设置 试题4&#xff1a;DHCP实验 背景&#xff1a; 某公司新建了局域网&#xff0c;要求网内地址采用DHCP分配&#xff0c;DHCP服务器地址为172.16.1.5/24&#xff0c;请建立172.16.1.128/25的域为局域网用户分配置IP地址&#xff0c;域中DNS地址为&#xff1a;202.97.…

Linux服务器配置(详细版)

本来就很麻烦&#xff0c;如果错了一个很有可能从新再来&#xff0c;非常麻烦所以要细心 请省略这段部分 --------------------------------------------------------------------------------------------------------------------------------------------- Linux命令 1.进…

银河麒麟服务器修改ip,银河麒麟配置DNS服务器

银河麒麟配置DNS服务器 内容精选 换一换 MindInsight为MindSpore提供了简单易用的调优调试能力。在训练过程中,可以将标量、张量、图像、计算图、模型超参、训练耗时等数据记录到文件中,通过MindInsight可视化页面进行查看及分析。MindInsight组件为MindStudio的独立组件,请…

查看服务器sftp用户信息,linux查看sftp服务器配置

linux查看sftp服务器配置 内容精选 换一换 为加强对系统数据的容灾管理,云堡垒机支持配置日志备份,提高审计数据安全性和系统可扩展性。本小节主要介绍如何在系统配置FTP/SFTP服务器参数,将日志远程备份至FTP/SFTP服务器。开启远程备份后,系统默认在每天零点备份前一天的系…