JWT使用

JWT公司的主流Json Web Token 令牌 如何使用,取代session,还可以运用分布式认证

JWT简介:

JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。广义上讲JWT是一个标准的名称;狭义上JWT指的就是用来传递的那个token字符串

jwt特点:

  • JWT无需存储在服务器(不使用Session/Cookie),不占用服务器资源(也就是Stateless无状态的),也就不存在多服务器共享Session的问题
  • 使用简单,用户在登录成功拿到 Token后,一般访问需要权限的请求时,在Header附上Token即可。

**JWT公司官网: **

https://jwt.io/

jwt的token结构:

JWT的数据结构以及签发的过程
JWT由三部分构成:header(头部)、payload(载荷)和signature(签名)。
xxxxx.yyyyy.zzzzz

1、Header 头部信息:指定类型和算法

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

typ:用来标识整个token字符串是一个JWT字符串
alg:用来说明这个JWT签发的时候所使用的签名和摘要算法

常用的值以及对应的算法如下:

加密算法

一般签发JWT的时候,header对应的json结构只需要typ和alg属性就够了。JWT的header部分是把前面的json结构,经过Base64Url编码之后生成出来的

2、Payload 荷载信息:存放Claims声明信息既主体信息组成。用来存储JWT基本信息,或者是我们的信息。

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true 
}

payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的一组声明,这些声明被JWT标准称为claims。当后面对JWT进行验证的时候,这些claim都能发挥特定的作用。

sub代表这个token的所有人,存储的是所有人的ID;
name表示这个所有人的名字;
admin表示所有人是否管理员的角色。

根据JWT的标准,这些claims可以分为以下三种类型:

  • Reserved claims
    保留的claims都是可选的,JWT标准里面针对它自己规定的claim都提供了有详细的验证规则描述,每个实现库都会参照这个描述来提供JWT的验证实现。

JWT标准的claim:

属性 全称 描述 认证
iss Issuser 代表签发主体 完全匹配
sub Subject 代表的主体,它的所有人 完全匹配
aud Audience 代表JWT的接收对象 包含即可
exp Expiration time 代表这个JWT的过期时间 过期失败
nbf Not Before 生效时间 早于该时间失败
iat Issued at 签发时间,maxAge之类的验证 大于指定值失败
jti JWT ID JWT的唯一标识 完全匹配
  • Public claims
    不重要

  • Private claims
    自定义的claim不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行;标准的claim知道如何进行验证。

把这个json结构做base64url编码之后,就能生成payload部分的串:

image

3、Signature

把前两者对应的Json结构进行base64url编码之后的字符串拼接起来和密钥放一起加密后的签名,验证是否是我们服务器发起的Token,secret是我们的密钥。使用base64拼接很容易破解,所以建议在传输过程中采用ssl加密是最稳妥的。

组成方式为: header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiLlvKDlvLoiLCJuYW1lIjoiNzg5IiwiZXhwIjoxNTI4MzY0MjU5LCJpYXQiOjE1MjgzNjMwNTl9.574koY-c9SqMNNzfvAWQuKEnimWeZAcoFQ5XudNWF3o

下面例子使用HMAC SHA256算法进行签名

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

拿前面的header和payload串来测试,并把“secret”这个字符串就当成密钥来测试:

image

最后的结果B其实就是JWT需要的signature。不过对比我在介绍JWT的开始部分给出的JWT的举例:

image

在线工具生成的signature有细微区别,在于最后是否有“=”字符。这些实现库最后编码用base64url编码,在线工具都是bas64编码,导致编码结果有区别。

JWT的验证过程

WT的验证规则主要包括签名验证和payload里面各个标准claim的验证。验证成功的JWT,才能当做有效的凭证来使用。

  • 签名验证:对JWT的完整性进行验证就是签名认证。
  • 验证方法:把header做base64url解码,获取JWT签名算法,用该算法再次用同样的逻辑对header和payload做一次签名。获得的串与JWT本身的第三个部分的串比较。不同认为这个JWT被篡改过,验证失败了。
  • 生成签名:接收方生成签名须使用跟JWT发送方相同的密钥,做好密钥的安全传递或共享。

签名认证是每个实现库都会自动做的,但是payload的认证是由使用者来决定的。因为JWT里面可能不会包含任何一个标准的claim,所以它不会自动去验证这些claim。

登录认证在签发JWT时,可以用sub存储用户的id,用exp存储它本次登录之后的过期时间,然后在验证的时候仅验证exp这个claim,以实现会话的有效期管理。

JWT 令牌注销或者销毁

痛点:当用户点击了“注销”按钮,用户的令牌在客户端会从授权认证服务移除,但此令牌仍旧是有效,可以被攻击者窃取到用于API调用,直至jsonwebtoken的有效时间结束。

解决方案:利用Redis撤销JSON Web Token产生的令牌,当用户点击注销按钮时。且令牌在Redis存储的时间与令牌在jsonwebtoken中定义的有效时间相同。当有效时间到了后,令牌会自动被Redis删除。应用检查各终端上传的令牌在Redis中是否存在。

客户端发起请求

image

服务器端获取Token

image

你该如何去理解JWT帮你做些什么?

首先前端登录后端通过用户名与密码验证成功后,使用jwt生成一个具备有效期的token令牌, 这个token里面存储着 token本身的结构,加上token的过期时间,和一个自己定义的字符串类似于加密签名所用的盐,这个盐只有你自己知道,还可以包含一些自定义的用户信息放在荷载信息里。

所涉及的jar包如下:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!-- 阿里巴巴json -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.4</version>
</dependency>

生成Token

public class JwtUtil { 
    private static Logger logger = Logger.getLogger(JwtUtil.class); 
    token的key 也是名 不要写成token这样,要按照规范来
    public static final String TOKEN_HEADER = "Authorization";
    token值的前缀,这是一种规范
    public static final String TOKEN_PREFIX = "Bearer ";
    加密时候用  是对称的秘钥盐
    private static final String SECRET = "mrLang";
    获取用户的功能使用的key
    public static final String FUNCTS = "FUNCTS";
    获取用户使用的key
    public static final String USERINFO = "USER";
    token的生命周期30分
    private static final long EXPIRATION = 1800L;
    /**
     * 创建token令牌 以下为参数都是自定义信息
     * @param loginName  一般我们放用户的唯一标识登录名
     * @param functs 当前用户的功能集合, 
     * 本人的rbac权限比较个性化且很负责,一般你们放role角色就可以了
     * @param user 当前用户
     * @return 
      */
    public static String createToken(String loginName, 
      List<Object> functs, Users user) {
        Map<String, Object> map = new HashMap<>(); //当前用户拥有的功能
        map.put(FUNCTS, JsonUtil.set(functs)); //当前用户信息
        map.put(USERINFO, JsonUtil.set(user));

        设置HS256加密,并且把你的盐 放里,这里推荐使用SH256证书加密

        String token = Jwts.builder()
                .setSubject(loginName)//主题  主角是谁?    赋值登录名
                .setClaims(map)
                .setIssuedAt(new Date())//设置发布时间,也是生成时间
                .setExpiration(new Date(System.currentTimeMillis() 
                  + EXPIRATION * 1000))//设置过期时间
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();//创建完成
        return token;
}

公共获取自定义数据

public static Claims getTokenBody(String token) { 
  return Jwts.parser().setSigningKey(SECRET)
            .parseClaimsJws(token).getBody();
}

验证Token是否过期

public static boolean isExpiration(String token) {
  try { 
      return getTokenBody(token).getExpiration().before(new Date());
    } catch (Exception e) { 
      return true;
    }
}

获取自定义的信息

// 获取主角,登录名
public static String getUserName(String token) { 
  return getTokenBody(token).getSubject();
} 

// 获取token中存储的功能
public static List<Object> getUserFuncts(String token) {
    String str = getTokenBody(token).get(FUNCTS).toString();
    List<Object> list = JsonUtil.getArray(str); 
    return list;
} 

// 获取token存储的用户
public static Object getUser(String token) {
    String str = getTokenBody(token).get(USERINFO).toString(); 
    return JsonUtil.getObj(str);
} 

刷新Token

public static String refreshToken(String token) { 
  if (isExpiration(token)) {
      logger.info("token刷新失败!! 过期了!!"); return null;
  } 

  // 获取用户 权限信息
  String functs = getTokenBody(token).get(FUNCTS).toString();
  String user = getTokenBody(token).get(USERINFO).toString();
  String username = getTokenBody(token).getSubject();
  Map<String, Object> map = new HashMap<>();
  map.put(FUNCTS, JsonUtil.set(functs));
  map.put(USERINFO, JsonUtil.set(user));
  token = Jwts.builder().signWith(SignatureAlgorithm.HS256, SECRET)
     .setClaims(map).setSubject(username)
     .setIssuedAt(new Date())
     .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
     .compact(); 

return token;
}

token发送给前端

传入当前用户的功能与用户信息,登录名生成token,写入response的返回头中,前端获取后保存在前端的本地缓存中,后续前端请求要把token放在头header里。

//登录成功之后
List<Object> functs=(List<Object>) authResult.getAuthorities();
//当前功能列表
String loginName=authResult.getName();//登录名
Users obj=(Users)authResult.getPrincipal();//用户信息
String token=JwtUtil.createToken(loginName,functs,obj);

//生成token  TOKEN_HEADER= Authorization TOKEN_PREFIX=Bearer token值
response.setHeader(JwtUtil.TOKEN_HEADER,JwtUtil.TOKEN_PREFIX+token);
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK); //个人编写的视图对象
DTO dto=new DTO<>();
dto.setCode("000000");
dto.setMessage("认证通过");

PrintWriter pw=response.getWriter();
pw.write(JsonUtil.set(dto));//写入json
pw.flush();//强制刷新
pw.close();//关闭流

验证用户请求携带token

你可以自定义一个过滤器,或者使用某某框架,自定义拦截器或者aop。

String header = request.getHeader(JwtUtil.TOKEN_HEADER);
 if (null == header || !header.toLowerCase().startsWith(JwtUtil.TOKEN_PREFIX)) { 
  // 如果头部 Authorization 未设置或者不是 basic 认证头部,则当前 
  // 请求不是该过滤器关注的对象,直接放行,继续filter chain 的执行
   chain.doFilter(request, response);
   return;
} 

try {
  String token = header.replace(JwtUtil.TOKEN_PREFIX, ""); 
  // 验证token是否过期
  if (JwtUtil.isExpiration(token)) { 
      throw new javax.security.sasl.AuthenticationException("token 验证不通过");
} 

//檢查token是否能解析
Users user = (Users) JwtUtil.getUser(token); 
if (null == user) { 
    throw new javax.security.sasl.AuthenticationException("token 验证不通过");
} 

//验证成功

JWT签名算法中HS256和RS256有什么区别?

JWT签名算法:HS256 和 RS256。
签名实际上是一个加密的过程,生成一段标识(也是JWT的一部分)作为接收方验证信息是否被篡改的依据。

RS256 (采用SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共/私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。由于公钥 (与私钥相比) 不需要保护,因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据URL)。

HS256 (带有 SHA-256 的 HMAC对称算法, 双方之间仅共享一个密钥。由于使用相同的密钥生成签名和验证签名,因此必须注意确保密钥不被泄密。

在开发应用的时候启用JWT,使用RS256更加安全,你可以控制谁能使用什么类型的密钥。另外,如果你无法控制客户端,无法做到密钥的完全保密,RS256会是个更佳的选择,JWT的使用方只需要知道公钥。

建议:

为了双重保险,建议您使用RS256,最好是生成一个证书读取使用证书里面的公钥加密,私钥留着以后每次前端请求验证token的合法性就可以了
jwt提供了方法可以验证token的合法性,过期时间,还可以根据token读取用户信息,这样你把token存储前端的localstorage中,
后端的缓存都省了

这样做安全么?
你要知道 RS256非对称加密都是一套体系的,公钥私钥是一组, 都存在的后端,别人不进你服务器盗取你就是安全的,要是真能登录你服务器那就直接删你库了,同样本文用大的对称加密也是 你自定定义的秘钥 盐 本文的mrlang都是存在你自己的服务器中,别人是不知道的,

分布式中如何验证Token?

你可以把token工具类copy到不同的机器上,让每台机器自己去做验证,只要你每台机器token工具类里存储的 盐或者一对秘钥 都一致 那么验证就会得到一致通过 ,这样做的好处就是 你不必远调用远程验证服务器了,所以真的很好用,换个思维想想 你去调用验证服务器还需要花费很多时间,这对访问量特别大的项目来说,压力真的不小,所以本文的JWT Token 不管作为单机,集群,分布式 你都值得拥有

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351