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"}]
1
获取到的我们放在 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/token HTTP/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/token HTTP/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/register HTTP/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/register HTTP/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" }
对象生成,功能验证一切正常
鉴权服务中心总结
对比基于Token与基于服务器的身份认证
传统:
最为传统的做法,客户端储存 cookie 一般是 Session id 服务器存储 Session
Session 是每次用户认证通过以后 ,服务器需要创建一条记录保存用户信息,通常是在内存中(也可以放在redis中),随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大
不同域名之前切换的时候,请求可能会被禁止,即跨越问题
基于token
JWT与Session的差异相同点是,他们都是存储用户信息。然而Session是在服务器端的,而JWT是在客户端的
JWT方式将用户状态分散到了客户端中,可以明显减轻请服务器的内存压力,服务端只需要用算法解析客户端的token就可以得到信息
两者优缺点的对比
解析方法:JWT使用算法直接解析得到用户信息;Session需要额外的数据映射。实现匹配
管理方法:JWT只有过期时间的限制,Session 数据保存在服务器,可控性更强
跨平台:JWT就是一段字符串,可以任意传播,Session跨平台需要有统一的解析平台,较为繁琐
时效性:JWT一旦生成 独立存在,很难做到特殊的控制;Session时效性完全由服务端的逻辑说了算
TIPS :各自都有优缺点,都是登陆和授权的解决方案