再来回顾下5.2节中请求数据时的header信息: ![](https://img.kancloud.cn/c6/ad/c6ad92a7720f4d71211ed4fd48a58d37_989x478.png) 此header中带有很多项信息,比如:Accept的值为application/json,text/plain,*/*; Accept-Encoding的值为:gzip, deflate, br。我们把这种存储方式称为:键值对存储。由于http协议传输的特性,在header中`键`与`值`的类型均为string(有人说也可以是number,其实这并不重要,重要的是明了只能传字符串过来就行)。所以若要实现使用header传递令牌,首先要为令牌在header设置一个专业的`键`。此键的名称可以随意起,但一般习惯的命名为`auth-token 认证令牌`。 确认了令牌的键为`auth-token`后,按以下流程来分别完成开发: ![](https://img.kancloud.cn/ad/53/ad53edbbafc0cfca3c1bc7682204d450_334x188.png) ## 获取header中的令牌 filter/TokenFilter.java ```java private String TOKEN_KEY = "auth-token"; ... @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 获取 header中的token String token = request.getHeader(this.TOKEN_KEY); ``` ## 有效性判断 filter/TokenFilter.java ```java // 获取 header中的token String token = request.getHeader(this.TOKEN_KEY); if (!this.validateToken(token)) { // 如果无效则分发送的token } ... } /** * 验证token的有效性 * @param token token * @return */ private boolean validateToken(String token) { // 验证token的有效性 } ``` 令牌是否有效,需要判断其是否为系统分发的。传入的令牌如果为系统分发,则有效;如系统未分发过该令牌,视为无效。因而,令牌有效性判断的前提是系统存储了分发过的令牌可以验证用户传入的令牌是否存在于系统分发的令牌中。而java中的HashSet恰恰能够解决当前问题: filter/TokenFilter.java ```java /** 存储已分发过的令牌 */ private HashSet<String> tokens = new HashSet<>(); ``` 则验证token是否有效的代码如下: filter/TokenFilter.java ```java /** * 验证token的有效性 * @param token token * @return */ private boolean validateToken(String token) { if (token == null) { return false; } return this.tokens.contains(token); } ``` ## 分发新token 发新token时主要注意要将生成的token存入历史表中,再次接收到该token时以便进行校验。 filter/TokenFilter.java ```java // 有效性判断 if (!this.validateToken(token)) { // 如果无效则分发送的token token = this.makeToken(); } ... /** * 生成token * 将生成的token存入已分发的tokens中 * @return token */ private String makeToken() { String token = UUID.randomUUID().toString()➊; this.tokens.add(token); return token; } ``` * ➊ 获取非重复的随机字符串 ## 测试 写一点测试一点绝对是个好习惯。在代码中加入一些日志语句,然后阶段性的测试一下以验证当前的代码是符合预期的。 filter/TokenFilter.java ```java // 获取 header中的token String token = request.getHeader(this.TOKEN_KEY); logger.info("获取到的token为" + token); // 有效性判断 if (!this.validateToken(token)) { // 如果无效则分发送的token token = this.makeToken(); logger.info("原token无效,发布的新的token为" + token); } ``` 重启应用后,使用http request进行测试。 ### 测试一:未传token ``` 2020-02-10 17:54:06.298 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 获取到的token为null 2020-02-10 17:54:06.298 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 原token无效,发布的新的token为e953808a-015e-4b59-b348-6978f8d7c9cb ``` 未传入token时分发新token符合预期。 ### 测试二:传入有效的token 在测试文件中如下加入header信息。键为auth-token,值为刚刚控制台打印的token。 ``` GET http://localhost:8080/Teacher auth-token: e953808a-015e-4b59-b348-6978f8d7c9cb ``` 发起请求: ``` 2020-02-10 17:57:26.775 INFO 86327 --- [nio-8080-exec-9] c.m.springbootstudy.filter.TokenFilter : 获取到的token为e953808a-015e-4b59-b348-6978f8d7c9cb ``` 使用已分发的token进行请求,获取了token并被验证为有效。 ### 测试三:传入无效的token 随便的修正一下auth-token的值,再进行测试: ``` GET http://localhost:8080/Teacher auth-token: e953808a-015e-4b59-b348-12342321 ``` 测试结果: ``` 2020-02-10 17:58:51.978 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 获取到的token为e953808a-015e-4b59-b348-12342321 2020-02-10 17:58:51.978 INFO 86327 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 原token无效,发布的新的token为34d471c9-fc54-4ad6-8163-6a52a2ad2efe ``` # 生活中的令牌 生活中的令牌随处可见: * 商铺的会员卡就是一个令牌,每张会员卡都是一个令牌。会员卡在我们与商家家进行传递,商家根据会员卡来确认我们可以享有何种权益。 * 信用卡也是一个令牌,这个令牌更加智能。信用卡的密码机制使该令牌拥有一种数据加密解密的能力;信用卡还拥有过期时间,只能在有效的时间内使用,这便是令牌的过期机制;同时当老的信用卡快过期时,发卡行还会为我们邮寄新卡,这便是令牌的过期更新机制。 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.2](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.2) | - |