日拱一卒系列(聊一聊jwt)

1.引子

我们知道,http是一种无状态协议,即对于服务端应用来说,两次http请求之间相互独立,你不知道我,我不知道你。

那么问题来了,比如说一个电商网站,购物的时候,需要浏览商品,将商品添加到购物车,需要下单结算。这就提出了要求:我们需要知道是谁选择了商品a,是谁将商品a添加到了购物车,购物车又是谁的......等一系列问题,都指向了“谁”,即将多次不同http请求关联起来。

如何关联呢?

你应该已经反应过来了,登录认证啊!用户访问购物网站,首先要做的是提供用户名称、密码登录到购物网站,然后在网站导航的地方,通常会提示:欢迎xxx!对吧,这样以来,购物网站自然就知道了上面我们提到的一系列“谁”,到底是谁

这是从一个普通用户的角度来描述的,我们是程序员,需要从技术的角度来进行描述。你说这也不难!不就是

  • 当用户访问登录接口的时候,根据用户提供的用户名称、密码查询数据库

  • 如果用户存在,购物网站应用服务器,比如说tomcat,生成一个session,并且存储到购物网站应用服务器内存

  • 将session的唯一标识sessionId,返回给客户端(浏览器),浏览器将sessionId存储到cookie

  • 浏览器再登录成功后,每次发起http请求,比如选择商品、加入购物车、下订单、结算都带上sessionId,依据sessionId告诉服务器,一系列的谁,就是谁

  • 购物网站应用服务端,根据sessionId,自然就知道了是谁在选择商品,谁在将商品加入购物车,谁在下订单.......

通过session、cookie将多次不同的http请求关联了起来,解决了一系列谁是谁的问题,完美!不能再完美了!

这也就是我们熟悉的传统session会话的解决方案,但是这种方案,其实是有瑕疵的,我们来看一下

  • 会话session存储在服务端,需要消耗应用服务器内存,如果网站生意比较好,动不动就有几千万、上亿用户,是不是单存储会话session就需要消耗不少内存?

  • 网站太受欢迎,高并发、大流量,一台应用服务器扛不住,部署集群方案吧,比如说增加网站应用服务器a、b、c。会话session都是应用服务器内部生成的,用户在服务节点a登录了,下次请求如果打到服务节点b,节点b如何知道用户已经登录过?

  • 会话sessionId存储在浏览器cookie中的对吧,CSRF(跨站请求伪造)怎么办?

  • 移动互联网时代,大家都用智能手机,玩着各种app,没有浏览器不支持cookie怎么办?

  • 分布式、微服务时代,不同应用之间相互调用,服务a,不认识服务b的session啊,怎么办?

综合上述总结一下,传统会话session方案最大的不足在于消耗应用服务器内存、难以支撑应用弹性扩容缩容

这才有了业界当前流行的token方案,接下来让我们一起来看token方案,在这里关于token方案,我将结合jwt给你分享。

2.案例

2.1.token登录认证方案

token登录认证方案,实现思路上事实上与传统session方案差不多,毕竟token与sessionId一样,都是字符串嘛,我们从普通用户操作流程进行分析

  • 用户打开购物网站,提供用户名称、密码进行登录请求

  • 网站服务器,接收到用户登录请求,根据用户名称、密码查询数据库,检查用户是否存在

  • 如果用户存在,根据某种规则,生成一个token(一个字符串)

  • 将token响应给浏览器,用户在之后的选择商品、加入购物车、下订单等请求中,都带上token

  • 网站服务器,校验处理token即知道谁是谁了

你看,这就是token方案,对比传统session方案,它不再需要

  • token不需要在服务器存储,即不消耗服务器内存资源

  • 服务继续无状态,支持按需随意扩容缩容

2.2.什么是jwt

jwt的全称是(json web token)。 是为了在网络应用环境间传递声明的一种基于json的开放标准,通过jwt实现的token被设计为紧凑且安全,适用于分布式站点单点登录(sso)场景。

jwt实现的token主要由三部分组成,以.符号进行分割

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2NjYiLCJpYXQiOjE2MTg2NDYyNjUsImV4cCI6MTYxOTI1MTA2NX0.uVeXiEhqfhpWAnkiX8glIBE4nOG6o2zaQfRBOC-EiuY
  • 头header:标记token令牌类型,加密算法

  • 载荷payload:token数据,放置一些用户非敏感信息,比如说用户id,用户名称(切记:jwt是可以解密,密码、手机号码等用户敏感信息,千万不要放在其中)

  • 签名sign:用于验证token是否有效,放置篡改

2.3.jwt实现案例

2.3.1.导入依赖

<!--jjwt依赖-->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-api</artifactId>
	<version>0.10.7</version>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-impl</artifactId>
	<version>0.10.7</version>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-jackson</artifactId>
	<version>0.10.7</version>
	<scope>runtime</scope>
</dependency>

2.3.2.jwt工具类

/**
 * token工具
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/4/17 14:31
 */
@Slf4j
@Data
public class TokenJwtUtil {

    /**
     * 秘钥,默认:aaabbbcccdddeeefffggghhhiiijjjkkklllmmm
     */
    private String secret = "aaabbbcccdddeeefffggghhhiiijjjkkklllmmm";
    /**
     * 有效时间,默认一周,单位秒
     */
    private Long expirationTime = 604800L;

    /**
     * 生成token
     * @param claims  token 数据
     * @return
     */
    public String generateToken(Map<String, Object> claims){
        // 生成时间,过期时间
        Date createdTime = new Date();
        Date expirationTime = new Date(System.currentTimeMillis() + this.expirationTime * 1000);

        // 秘钥
        byte[] keyBytes = this.secret.getBytes();
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        // 生成token
       return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdTime)
                .setExpiration(expirationTime)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();

    }

    /**
     * 校验token是否有效
     * @param token
     * @return 有效返回true,无效返回false
     */
    public Boolean validateToken(String token){
        Date expirationTime = getExpirationDateFromToken(token);
        return !expirationTime.before(new Date());
    }

    /**
     * 获取token过期时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimsFromToken(token).getExpiration();
    }

    /**
     * 解析获取token数据
     * @param token
     * @return
     */
    public Claims getClaimsFromToken(String token) {

        try{
            return  parser()
                    .setSigningKey(this.secret.getBytes())
                    .parseClaimsJws(token)
                    .getBody();
        }catch (ExpiredJwtException
                | UnsupportedJwtException
                | MalformedJwtException
                | IllegalArgumentException e){
            log.error("解析token发生异常", e);
            throw new IllegalArgumentException("Token invalided.");
        }

    }

}

2.3.3.测试使用

public static void main(String[] args) {
        TokenJwtUtil tokenJwtUtil = new TokenJwtUtil();

        // 1.生成token
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId","666");
        String token = tokenJwtUtil.generateToken(claims);
        log.info("准备token数据:{}", claims);
        log.info("生成token={}",token);

        // 2.解析token头
        String[] split = token.split("\\.");
        byte[] headBytes = Base64.decodeBase64(split[0]);
        log.info("解析token头:{}", new String(headBytes));

        // 3.解析token数据
        byte[] bodyBytes = Base64.decodeBase64(split[1]);
        log.info("解析token数据:{}", new String(bodyBytes));

        // 4.解析token签名
        byte[] signatureBytes = Base64.decodeBase64(split[2]);
        log.info("解析token签名:{}", new String(signatureBytes));

        // 5.解析token过期时间
        Date expirationDate = tokenJwtUtil.getExpirationDateFromToken(token);
        log.info("解析token过期时间:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(expirationDate));

        // 6.校验token是否有效
        Boolean isValidate = tokenJwtUtil.validateToken(token);
        log.info("校验token是否有效:{}",isValidate);
}

[com.anan.edu.common.util.Test] - 准备token数据:{userId=666}
[com.anan.edu.common.util.Test] - 生成token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2NjYiLCJpYXQiOjE2MTg2NDYyNjUsImV4cCI6MTYxOTI1MTA2NX0.uVeXiEhqfhpWAnkiX8glIBE4nOG6o2zaQfRBOC-EiuY
[com.anan.edu.common.util.Test] - 解析token头:{"alg":"HS256"}
[com.anan.edu.common.util.Test] - 解析token数据:{"userId":"666","iat":1618646265,"exp":1619251065}
[com.anan.edu.common.util.Test] - 解析token签名:�W��Hj~Vy"_�% 8�ảl�A�A8/���
[com.anan.edu.common.util.Test] - 解析token过期时间:2021-04-24 15:57:45
[com.anan.edu.common.util.Test] - 校验token是否有效:true

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章