Java基于JWT的token认证

一、背景引入

由于Http协议本身是无状态的,那么服务器是怎么识别两次请求是不是来自同一个客户端呢,传统用户识别是基于seeion和cookie实现的。大致流程如下:

  1. 用户向服务器发送用户名和密码请求
  2. 用户进行校验,校验通过后创建session绘画,并将用户相关信息保存到session中
  3. 服务器将sessionId回写到用户浏览器cookie中
  4. 用户以后的请求,都会鞋带cookie发送到服务器
  5. 服务器得到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. 用户提交用户名,密码到服务器后台
  2. 后台验证通过,服务器端生成Token字符串,返回到客户端
  3. 客户端保存Token,下一次请求资源时,附带上Token信息
  4. 服务器端验证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 填正确的用户名密码

  • 25
    点赞
  • 139
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值