ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JSON Web Token

2022-06-25 21:35:57  阅读:129  来源: 互联网

标签:Web claims String JWT Token JSON token org import


JWT

JWT是 JSON Web Token 的缩写,它是基于开源标准(RFC 7519)定义的一种可以安全传输的 JSON对象。

  • JWT之所以叫JSON Web Token,因为其 header 和 payload 在编码之前都是JSON格式
  • JWT规定以JSON格式传递信息,header 和 payload 通常使用 base64 编码成字符串
  • JWT是自包含的,Token本身携带了验证信息,不需要借助其他工具就能知道一个Token是否有效。但当需要高级功能如token刷新、黑名单等,还是需要借助缓存和数据库等

为什么要使用JWT?

跨域认证问题

互联网服务用户认证的一般流程如下所示:

  1. 用户向服务器发送用户名和密码
  2. 服务器验证通过后,在当前session保存相关数据,如用户角色,登录时间等
  3. 服务器向用户返回一个 session_id,写入用户的 Cookie
  4. 用户随后的每一个请求都会通过 Cookie 将 session_id 传回服务器
  5. 服务器收到 session_id 后,找到前期保存的数据,以此验证用户的身份

这种方式的问题在于,扩展性不好。如果服务器是一个集群或者是跨域的服务导向架构,就要求 session 数据共享,使每台服务器都能够读取 session 。

示例:A网站和B网站都是某公司下的服务,要求用户在登录其中一个网站后,再访问另外一个网站实现自动登录。

  • session数据持久化:将session数据写入持久层,各服务器收到请求后都向持久层拿数据。
  • JWT:服务器不保存数据,所有数据都保存在客户端,每次请求都发回服务器。

使用session数据持久化会造成工程量增大,且会因为持久层失效导致单点登录失败。

JWT的结构

Header(头部)

存放签名的生成算法和Token类型。

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

Payload(载荷)

存放携带的用户数据。

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Payload中的字段:

字段 全称 作用
iss Issuer 代表token的颁发者
sub Subject 代表token的主题
aud Audience 代表token的接收目标
exp Expiration Time 代表token的过期时间,时间戳格式
nbf Not Before 代表token在这个时间之前不能被处理,纠正服务器的时间偏差
iat Issued At 代表token的颁发时间
jti JWT ID 代表token的id

除了以上标准定义的字段外,用户可以自由添加需要的信息,如用户ID,用户名等。通常添加的是经常使用但安全性要求不高的信息。

Signature(签名)

以 header 和 payload 生成的签名,一旦 header 或 payload 被篡改,验证将失败。

// secret: 密钥, 只有服务器知道
String signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

使用Header中指定的签名算法按上述代码产生签名,然后把Header、Payload、Signature三部分拼车一个字符串,各部分之间使用 . 分隔,最后返回给用户。

下图为JSON Web Token官网首页的一个示例:

JSON Web Token

JWT认证流程

  1. 用户使用账号密码登录,调用服务器登录接口
  2. 服务器登录程序生成token,并返回给用户
  3. 用户后续请求携带token
  4. 服务器收到用户请求后,验证token的合法性、有效性,验证通过后处理请求
  5. 返回请求结果给用户

JWT使用方式

客户端收到服务器返回的JWT,可以存储在Cookie中,也可以存储在localStorage。此后客户端每次与服务器通信都要携带这个JWT,可以放在Cookie中自动发送,但不能跨域,所以更好的做法是放在HTTP请求头的 Authorization 字段中。

JWT特点

  • JWT最大的缺点是由于服务器不保存session状态,因此无法在使用过程中废除某个token或更改token权限,即token一旦签发,在到期之间会一直有效,除非服务器部署额外的逻辑
  • JWT本身包含认证信息,一旦泄漏,任何人都可以获得该令牌的所有权限。所以JWT的有效期应该设置的比较短,以防盗用
  • JWT应该使用HTTPS协议传输,为了减少盗用

Springboot集成JWT

1 添加pom依赖

<!--  SpringSecurity 依赖配置  -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--  JWT(Json Web Token)登录支持  -->
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.0</version>
</dependency>

2 配置application.yml

# 自定义JWT
jwt:
  field: Authorization         # 请求头字段
  secret: 123456               # 密钥
  expiration: 604800           # 过期时间(60*60*24s)
  tokenHead: Bearer            # token开头

3 添加JWT工具类

package com.nudt.demo_02.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: Lzy
 * @Time: 2022/4/18
 * @Description: JwtToken生成的工具类
 */
@Component
public class JwtTokenUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 生成 JWT Token
     * 构成: Header.Payload.Signature
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)    //设置payLoad
                .setExpiration(generateExpirationDate())    //设置过期时间
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 生成 token 过期时间
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);  // 一天后过期
    }

    /**
     * PayLoad
     * 从 token 中获取JWT中的payload
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }
        catch (Exception e) {
            LOGGER.info("JWT 格式验证失败 : {}", token);
        }

        return claims;
    }

    private Date getExpireDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();    //map.get("exp", Date.Class)
    }

    /**
     *
     * @param token
     * @return True —— 已过期; False —— 未过期
     */
    private boolean isTokenExpired(String token) {
        Date expireDate = getExpireDateFromToken(token);
        return expireDate.before(new Date());
    }

    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();    //从JwtMap中获得主题
        }
        catch (Exception e) {
            username = null;
        }

        return username;
    }

    /**
     * 验证token是否有效
     * @param token 客户端传入的token
     * @param userDetails 数据库查询的用户信息
     * @return
     */
    public boolean isTokenValidate(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 根据用户信息生成 token
     * @param userDetails
     * @return
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    public boolean canRefresh(String token) {
        return !isTokenExpired(token);
    }

    public String refresh(String token) {
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

}

4 JWT登录授权

package com.nudt.demo_02.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: Lzy
 * @Time: 2022/4/21
 * @Description: JWT登录授权过滤器
 */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.field}")
    private String field;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.field);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            String authToken = authHeader.substring(this.tokenHead.length());
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            LOGGER.info("Checking username: {}", username);

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.isTokenValidate(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    LOGGER.info("Authenticated user: {}", username);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }

        filterChain.doFilter(request, response);
    }
}

参考文章

[1] JSON Web Token 入门教程

[2] mall学习教程

[3] 深入浅出之JWT

标签:Web,claims,String,JWT,Token,JSON,token,org,import
来源: https://www.cnblogs.com/ylyzty/p/16412424.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有