乐趣区

关于java:Java基于JWT的token认证

一、背景引入

因为 Http 协定自身是无状态的,那么服务器是怎么辨认两次申请是不是来自同一个客户端呢,传统用户辨认是基于 seesion 和 cookie 实现的。大抵流程如下:

  1. 用户向服务器发送用户名和明码申请用户进行校验,校验通过后创立 session 绘画,并将用户相干信息保留到 session 中服务器将 sessionId 回写到用户浏览器 cookie 中用户当前的申请,都会鞋带 cookie 发送到服务器服务器失去 cookie 中的 sessionId,从 session 汇合中找到该用户的 session 回话,辨认用户

这种模式有很多毛病,对于分布式架构的反对以及扩展性不是很好。而且 session 是保留在内存中,单台服务器部署如果登陆用户过多占用服务器资源也多,做集群必须得实现 session 共享的话,集群数量又不易太多,否则服务器之间频繁同步 session 也会十分耗性能。当然也能够引入长久层,将 session 保留在数据库或者 redis 中,保留数据库的话效率不高,存 redis 效率高,然而对 redis 依赖太重,如果 redis 挂了,影响整个利用。还有一种方法就是不存服务器,而是把用户标识数据存在浏览器,浏览器每次申请都携带该数据,服务器做校验,这也是 JWT 的思维。

二、JWT 介绍

2.1 概念介绍

Json Web Token(JWT)是目前比拟风行的跨域认证解决方案,是一种基于 JSON 的开发规范,因为数据是能够通过签名加密的,比拟安全可靠,个别用于前端和服务器之间传递信息,也能够用在挪动端和后盾传递认证信息。

2.2 组成构造

JWT 就是一段字符串,格局如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.qfd0GelhE1aGr15LrnYlIZ_3UToaOM5HeMcXrmDG

因为三局部组成,之间用 ”.” 接。第一局部是头信息 Header,两头局部是载荷 Payload,最初局部是签名信息 Signature。

头信息 Header:形容 JWT 根本信息,typ 示意采纳 JWT 令牌,alg(algorithm) 示意采纳什么算法进行签名,常见算法有 HmacSHA256(HS256)、HmacSHA384(HS384)、HmacSHA512(HS512)、SHA256withECDSA(ES256)、SHA256withRSA(RS256)、SHA512withRSA(RS512)等。如果采纳 HS256 则头信息结构为:

{
 "typ": "JWT",
 "alg": "HS256
}

载荷 Payload:载荷(也能够叫载体)是具体的传输内容,包含一些规范属性,iss: 该 JWT 的签发者,exp: 过期工夫戳,iat: 签发工夫戳,jti: JWTID 等等。也能够增加其余须要传递的内容数据。构造为:

{
 "iss": "kkk",
 "iat": 1548818203,
 "exp": 1548818212,
 "sub": "test.com
}

签名 Signature:对头信息和载荷进行签名,保障传输过程中信息不被篡改,比方:将头信息和载荷别离进行 base64 加密失去字符串 a 和 b,将字符串 a 和 b 以点相连并签名失去字符串 c,将字符串 a、b、c 以点相连失去最终 token。

2.3 验证流程

应用 JWT 的验证流程为:

  1. 用户提交用户名,明码到服务器后盾后盾验证通过,服务器端生成 Token 字符串,返回到客户端客户端保留 Token,下一次申请资源时,附带上 Token 信息服务器端验证 Token 是否由服务器签发的(个别在拦截器中验证),若 Token 验证通过,则返回须要的资源

验证流程和基于 session 大体雷同,只不过不是基于 session,而是采纳拦截器在代码中试验验证,返回给客户端的也不是 sessionid,而是通过肯定算法得进去的 token 字符串。

2.4 源码剖析

Java 中有封装好的开源哭 JWT 能够间接应用,上面就剖析下要害代码验证以下内容。

Header 头信息结构剖析要害源码如下:

//token 生成办法
public static void main(String[] args) {String token= JWT.create().withAudience("audience")
 .withIssuedAt(new Date())
 .withSubject("subject")
 .withExpiresAt(new Date()).withJWTId("jtiid")
 .sign(Algorithm.HMAC256(user.getPassword()));
}
public abstract class Algorithm {
 private final String name;
 private final String description;
 //... 其余办法省略...
 public static Algorithm HMAC256(String secret) throws IllegalArgumentException {return new HMACAlgorithm("HS256", "HmacSHA256", secret);
 }
 //... 其余办法省略...
}
class HMACAlgorithm extends Algorithm {
 private final CryptoHelper crypto;
 private final byte[] secret;//... 其余办法省略...
HMACAlgorithm(String id, String algorithm, byte[] secretBytes)
throws IllegalArgumentException {this(new CryptoHelper(), id, algorithm, secretBytes);
}
//... 其余办法省略..
}
public String sign(Algorithm algorithm) throws IllegalArgumentException,
JWTCreationException {if (algorithm == null) {throw new IllegalArgumentException("The Algorithm cannot be null.");
} else {this.headerClaims.put("alg", algorithm.getName());
 this.headerClaims.put("typ", "JWT");
 String signingKeyId = algorithm.getSigningKeyId();
 if (signingKeyId != null) {this.withKeyId(signingKeyId);
}
public final class JWTCreator {
 private final Algorithm algorithm;
 private final String headerJson;
 private final String payloadJson;
 private JWTCreator(Algorithm algorithm,
 Map<String, Object> headerClaims,
 Map<String, Object> payloadClaims) throws JWTCreationException {
 this.algorithm = algorithm;
 try {ObjectMapper mapper = new ObjectMapper();
 SimpleModule module = new SimpleModule();
 module.addSerializer(ClaimsHolder.class, new PayloadSerializer());
 mapper.registerModule(module);
 mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
 this.headerJson = mapper.writeValueAsString(headerClaims);
 this.payloadJson =
 mapper.writeValueAsString(new ClaimsHolder(payloadClaims));
 } catch (JsonProcessingException var6) {
 throw new JWTCreationException(
 "Some of the Claims couldn't be converted to a valid JSON format.",
 var6);
 }
}
//... 其余办法省略...

headerClaims 是一个 Map,包含两个属性 typ 和 alg,typ 值固定 JWT,alg 传过来的签名算法这里应用的

HmacSHA256 简称 HS256。typ 和 alg 组成 Header 头信息。

Payload 载荷构造剖析要害源码如下:

public abstract class JWT {public JWT() { }
 public static DecodedJWT decode(String token) throws JWTDecodeException {return new JWTDecoder(token);
 }
 public static Verification require(Algorithm algorithm) {return JWTVerifier.init(algorithm);
 }
 public static Builder create() {return JWTCreator.init();
 }
}
public static class Builder {private final Map<String, Object> payloadClaims = new HashMap();
 private Map<String, Object> headerClaims = new HashMap();
 Builder() {}
 public JWTCreator.Builder withHeader(Map<String, Object> headerClaims) {this.headerClaims = new HashMap(headerClaims);
 return this;
 }
 public JWTCreator.Builder withKeyId(String keyId) {this.headerClaims.put("kid", keyId);
 return this;
 }
 public JWTCreator.Builder withIssuer(String issuer) {this.addClaim("iss", issuer);// 签发人
 return this;
 }
 public JWTCreator.Builder withSubject(String subject) {this.addClaim("sub", subject);// 主题
 return this;
 }
 public JWTCreator.Builder withAudience(String... audience) {this.addClaim("aud", audience);// 承受一方
 return this;
 }
 public JWTCreator.Builder withExpiresAt(Date expiresAt) {this.addClaim("exp", expiresAt);// 过期工夫
 return this;
 }
 public JWTCreator.Builder withNotBefore(Date notBefore) {this.addClaim("nbf", notBefore);// 失效工夫
 return this;
 }
 public JWTCreator.Builder withIssuedAt(Date issuedAt) {this.addClaim("iat", issuedAt);// 签发工夫
 return this;
 }
 public JWTCreator.Builder withJWTId(String jwtId) {this.addClaim("jti", jwtId);// 编号
 return this;
 }
 public JWTCreator.Builder withClaim(String name, Boolean value)
 throws IllegalArgumentException {this.assertNonNull(name);
 this.addClaim(name, value);
 return this;
 }
 public JWTCreator.Builder withClaim(String name, Integer value)
 throws IllegalArgumentException {this.assertNonNull(name);
 this.addClaim(name, value);
 return this;
 }
 //... 其余办法省略...
}

Payload 是一个 json 对象,寄存须要传递的数据,JTW 默认规定了几个属性,如果须要增加其余属性能够调用其重载办法 witchClaim() 增加。

Signature 签名局部源码如下:

private String sign() throws SignatureGenerationException {
 String header = Base64.encodeBase64URLSafeString(this.headerJson.getBytes(StandardCharsets.UTF_8));
 String payload = Base64.encodeBase64URLSafeString(this.payloadJson.getBytes(StandardCharsets.UTF_8));
 String content = String.format("%s.%s", header, payload);
 byte[] signatureBytes = this.algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
 String signature = Base64.encodeBase64URLSafeString(signatureBytes);
 return String.format("%s.%s", content, signature);
}

从这里能够看出,所谓 token 就是别离对 header 和 payload 的 json 字符串做 Base64 加密失去 a 和 b,并将后果拼接一起,在进行签名失去 c,最终把 a、b、c 三局部内容以点拼接起来造成 token,返回客户端保留,客户端当前每次申请都在 header 中退出 token,服务器采纳拦截器形式获取 header 中的 token 做校验,辨认用户。

三、示例

3.1 数据筹备

创立用户表

3.2 搭建 springboot 工程

设置工程 Group、Artifact、Version、Name 等信息

Spring Boot 的版本抉择 2.0.8,抉择导入 web 的起步器

创立工程胜利之后,将各个包创立进去,工程目录构造如下:

3.3 引入 pom 依赖

3.4 编写 application.yml 配置文件

3.5 编写 User 实体类

Result 类:用于对立返回音讯的封装

TokenUtil 类,用于生成 token

VerifyToken 注解类:加到 controller 办法上示意该办法须要验证 token。

3.6 编写 mapper 接口和 service 层

mapper 类:

UserService 接口:

UserServiceImpl 实现类:

3.7 编写拦截器和全局异样处理器

AuthInterceptor 拦截器类:用于 token 验证。

全局异样处理器 GloabllExceptionHandler:用于异样的捕捉。

3.8 编写配置类及 controller

拦截器配置类 InterceptorConfig:配置拦挡所有申请

UserController 类:

3.9 测试

测试 1:应用 postman 发送 get 申请 http://localhost:8088/user/getUser?id=1

测试 2:发送 post 申请 http://localhost:8088/user/login 明码成心输错

测试 3:发送 post 申请 http://localhost:8088/user/login 填正确的用户名明码

退出移动版