热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入浅出JWT

JWT(JSONWEBTOKEN)的组成https:jwt.ioheader(头部)承载两部分信息:声明
JWT(JSON WEB TOKEN)的组成

https://jwt.io/

header(头部)承载两部分信息:

  • 声明类型,这里是JWT;
  • 声明加密的算法,通常直接使用 HMAC SHA256

playload(载荷)就是存放有效信息的地方,这些有效信息包含三个部分:

  • 标准中注册的声明;
  • 公共的声明;
  • 私有的声明;

signature(签证信息)

  • header+.+payload进行base64编码,然后实用签证算法和密钥加密

在这里插入图片描述

三种认证流程

基于session+COOKIE的认证流程


  • 用户在浏览器中输入用户名和密码,服务器通过密码校验后生成一个session并保存到数据库
  • 服务器为用户生成一个sessionId,并将具有sesssionId的COOKIE放置在用户浏览器中,在后续的请求中都将带有这个COOKIE信息进行访问
  • 服务器获取COOKIE,通过获取COOKIE中的sessionId查找数据库判断当前请求是否有效

不足之处:

  • 服务器压力增大:通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。
  • CSRF跨站伪造请求攻击:session是基于COOKIE进行用户识别的, COOKIE如果被截获,用户就会很容易受到跨站请求伪造的攻击。
  • 扩展性不强:如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆过。

基于JWT的认证流程

JWT保存在客户端

  • 用户在浏览器中输入用户名和密码,服务器通过密码校验后生成一个token并保存到数据库
  • 前端获取到token,存储到COOKIE或者local storage中,在后续的请求中都将带有这个token信息进行访问。当用户希望访问一个受保护的路由或者资源的时候,可以把它放在 COOKIE 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT。
  • 服务器获取token值,通过查找数据库判断当前token是否有效

JWT和session的优缺点


  • 都保存了用户身份信息,都有过期时间。session翻译为会话,token翻译为令牌。session是空间换时间,token是时间换空间。

  • JWT保存在客户端,在分布式环境下不需要做额外工作。而session因为保存在服务端,分布式环境下需要实现多机数据共享

  • session一般需要结合COOKIE实现认证,所以需要浏览器支持COOKIE,因此移动端无法使用session认证方案

  • 安全性:JWT的payload使用的是base64编码的,因此在JWT中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。

  • 性能:经过编码之后JWT将非常长,COOKIE的限制大小一般是4k,COOKIE很可能放不下,所以JWT一般放在local storage里面。并且用户在系统中的每一次http请求都会把JWT携带在Header里面,HTTP请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用JWT的HTTP请求比使用session的开销大得多

  • 一次性:无状态是JWT的特点,但也导致了这个问题,JWT是一次性的。想修改里面的内容,就必须签发一个新的JWT,若想废弃,一种常用的处理手段是结合redis或者续约


什么是 Token(令牌)

Acesss Token

Acesss Token是访问资源接口(API)时所需要的资源凭证。简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign。每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里,基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库token 完全由应用管理,所以它可以避开同源策略

特点:

  • 服务端无状态化、可扩展性好
  • 支持移动端设备
  • 安全
  • 支持跨程序调用

token 的身份验证流程:

  • 客户端使用用户名跟密码请求登录
  • 服务端收到请求,去验证用户名与密码
  • 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
  • 客户端收到 token 以后,会把它存储起来,比如放在 COOKIE 里或者 localStorage 里
  • 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
  • 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据

Refresh Token

refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。

  • Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。
  • Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。

Token 和 JWT 的区别

相同:

  • 都是访问资源的令牌
  • 都可以记录用户的信息
  • 都是使服务端无状态化
  • 都是只有验证成功后,客户端才能访问服务端上受保护的资源

区别:

  • Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否效。
  • JWT: 将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。

SpringBoot整合JWT

JWT使用流程


  • 用户登录成功后,获取token
  • 单体项目创建拦截器,分布式项目创建网关
  • 配置拦截器

引入依赖

<dependency><groupId>com.auth0groupId><artifactId>java-jwtartifactId><version>3.18.1version>
dependency>

创建JWT工具类

public class JWTUtil {private static final Logger logger &#61; LoggerFactory.getLogger(JWTUtil.class);//私钥private static final String TOKEN_SECRET &#61; "123456";/*** 生成token&#xff0c;自定义过期时间 毫秒*/public static String generateToken(UserTokenDTO userTokenDTO) {try {// 私钥和加密算法Algorithm algorithm &#61; Algorithm.HMAC256(TOKEN_SECRET);// 设置头部信息Map<String, Object> header &#61; new HashMap<>(2);header.put("Type", "Jwt");header.put("alg", "HS256");return JWT.create().withHeader(header).withClaim("token", JSONObject.toJSONString(userTokenDTO))//.withExpiresAt(date).sign(algorithm);} catch (Exception e) {logger.error("generate token occur error, error is:{}", e);return null;}}/*** 检验token是否正确*/public static UserTokenDTO parseToken(String token) {Algorithm algorithm &#61; Algorithm.HMAC256(TOKEN_SECRET);JWTVerifier verifier &#61; JWT.require(algorithm).build();DecodedJWT jwt &#61; verifier.verify(token);String tokenInfo &#61; jwt.getClaim("token").asString();return JSON.parseObject(tokenInfo, UserTokenDTO.class);}
}

Redis工具类

public final class RedisServiceImpl implements RedisService {/*** 过期时长*/private final Long DURATION &#61; 1 * 24 * 60 * 60 * 1000L;&#64;Resourceprivate RedisTemplate redisTemplate;private ValueOperations<String, String> valueOperations;&#64;PostConstructpublic void init() {RedisSerializer redisSerializer &#61; new StringRedisSerializer();redisTemplate.setKeySerializer(redisSerializer);redisTemplate.setValueSerializer(redisSerializer);redisTemplate.setHashKeySerializer(redisSerializer);redisTemplate.setHashValueSerializer(redisSerializer);valueOperations &#61; redisTemplate.opsForValue();}&#64;Overridepublic void set(String key, String value) {valueOperations.set(key, value, DURATION, TimeUnit.MILLISECONDS);log.info("key&#61;{}, value is: {} into redis cache", key, value);}&#64;Overridepublic String get(String key) {String redisValue &#61; valueOperations.get(key);log.info("get from redis, value is: {}", redisValue);return redisValue;}&#64;Overridepublic boolean delete(String key) {boolean result &#61; redisTemplate.delete(key);log.info("delete from redis, key is: {}", key);return result;}&#64;Overridepublic Long getExpireTime(String key) {return valueOperations.getOperations().getExpire(key);}
}

登录方法

public String login(LoginUserVO loginUserVO) {//1.判断用户名密码是否正确UserPO userPO &#61; userMapper.getByUsername(loginUserVO.getUsername());if (userPO &#61;&#61; null) {throw new UserException(ErrorCodeEnum.TNP1001001);}if (!loginUserVO.getPassword().equals(userPO.getPassword())) {throw new UserException(ErrorCodeEnum.TNP1001002);}//2.用户名密码正确生成tokenUserTokenDTO userTokenDTO &#61; new UserTokenDTO();PropertiesUtil.copyProperties(userTokenDTO, loginUserVO);userTokenDTO.setId(userPO.getId());userTokenDTO.setGmtCreate(System.currentTimeMillis());String token &#61; JWTUtil.generateToken(userTokenDTO);//3.存入token至redisredisService.set(userPO.getId(), token);return token;
}

登出方法

public boolean loginOut(String id) {boolean result &#61; redisService.delete(id);if (!redisService.delete(id)) {throw new UserException(ErrorCodeEnum.TNP1001003);}return result;
}

修改密码方法

public String updatePassword(UpdatePasswordUserVO updatePasswordUserVO) {// 1.修改密码UserPO userPO &#61; UserPO.builder().password(updatePasswordUserVO.getPassword()).id(updatePasswordUserVO.getId()).build();UserPO user &#61; userMapper.getById(updatePasswordUserVO.getId());if (user &#61;&#61; null) {throw new UserException(ErrorCodeEnum.TNP1001001);}if (userMapper.updatePassword(userPO) !&#61; 1) {throw new UserException(ErrorCodeEnum.TNP1001005);}// 2.生成新的tokenUserTokenDTO userTokenDTO &#61; UserTokenDTO.builder().id(updatePasswordUserVO.getId()).username(user.getUsername()).gmtCreate(System.currentTimeMillis()).build();String token &#61; JWTUtil.generateToken(userTokenDTO);// 3.更新tokenredisService.set(user.getId(), token);return token;
}

拦截器验证

public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {String authToken &#61; request.getHeader("Authorization");String token &#61; authToken.substring("Bearer".length() &#43; 1).trim();UserTokenDTO userTokenDTO &#61; JWTUtil.parseToken(token);//1.判断请求是否有效if (redisService.get(userTokenDTO.getId()) &#61;&#61; null || !redisService.get(userTokenDTO.getId()).equals(token)) {return false;}//2.判断是否需要续期if (redisService.getExpireTime(userTokenDTO.getId()) < 1 * 60 * 30) {redisService.set(userTokenDTO.getId(), token);log.error("update token info, id is:{}, user info is:{}", userTokenDTO.getId(), token);}return true;
}

配置拦截器

&#64;Configuration
public class InterceptorConfig implements WebMvcConfigurer {&#64;Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authenticateInterceptor()).excludePathPatterns("/logout/**") //开发的路径.excludePathPatterns("/login/**") //开发的路径.addPathPatterns("/**");}&#64;Beanpublic AuthenticateInterceptor authenticateInterceptor() {return new AuthenticateInterceptor();}
}

不使用redis的JWT

可以参考&#xff1a;https://juejin.cn/post/6962142423879270437

单点登录

将token或者一个唯一标识UUID&#61;UUID.randomUUID().toString()存进COOKIE中&#xff08;别存在Http的header中了&#xff09;&#xff0c;设置路径为整个项目根路径/*&#xff1b; 往往以这个唯一标识为key&#xff0c;用户信息为value缓存在服务器中&#xff01;&#xff01;&#xff01;


推荐阅读
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 本文详细介绍了IBM DB2数据库在大型应用系统中的应用,强调其卓越的可扩展性和多环境支持能力。文章深入分析了DB2在数据利用性、完整性、安全性和恢复性方面的优势,并提供了优化建议以提升其在不同规模应用程序中的表现。 ... [详细]
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 作为一名 Ember.js 新手,了解如何在路由和模型中正确加载 JSON 数据是至关重要的。本文将探讨两者之间的差异,并提供实用的建议。 ... [详细]
  • 2018年3月31日,CSDN、火星财经联合中关村区块链产业联盟等机构举办的2018区块链技术及应用峰会(BTA)核心分会场圆满举行。多位业内顶尖专家深入探讨了区块链的核心技术原理及其在实际业务中的应用。 ... [详细]
  • Hadoop发行版本选择指南:技术解析与应用实践
    本文详细介绍了Hadoop的不同发行版本及其特点,帮助读者根据实际需求选择最合适的Hadoop版本。内容涵盖Apache Hadoop、Cloudera CDH等主流版本的特性及应用场景。 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • 本文探讨了在通过 API 端点调用时,使用猫鼬(Mongoose)的 findOne 方法总是返回 null 的问题,并提供了详细的解决方案和建议。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
author-avatar
囌格菈帥厎
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有