热门标签 | 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;


推荐阅读
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 本文详细介绍了IBM DB2数据库在大型应用系统中的应用,强调其卓越的可扩展性和多环境支持能力。文章深入分析了DB2在数据利用性、完整性、安全性和恢复性方面的优势,并提供了优化建议以提升其在不同规模应用程序中的表现。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 本文详细分析了Hive在启动过程中遇到的权限拒绝错误,并提供了多种解决方案,包括调整文件权限、用户组设置以及环境变量配置等。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 优化Flask应用的并发处理:解决Mysql连接过多问题
    本文探讨了在Flask应用中通过优化后端架构来应对高并发请求,特别是针对Mysql 'too many connections' 错误的解决方案。我们将介绍如何利用Redis缓存、Gunicorn多进程和Celery异步任务队列来提升系统的性能和稳定性。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 深入解析 Spring Security 用户认证机制
    本文将详细介绍 Spring Security 中用户登录认证的核心流程,重点分析 AbstractAuthenticationProcessingFilter 和 AuthenticationManager 的工作原理。通过理解这些组件的实现,读者可以更好地掌握 Spring Security 的认证机制。 ... [详细]
  • 深入理解 .NET 中的中间件
    中间件是插入到应用程序请求处理管道中的组件,用于处理传入的HTTP请求和响应。它在ASP.NET Core中扮演着至关重要的角色,能够灵活地扩展和自定义应用程序的行为。 ... [详细]
  • Python + Pytest 接口自动化测试中 Token 关联登录的实现方法
    本文将深入探讨 Python 和 Pytest 在接口自动化测试中如何实现 Token 关联登录,内容详尽、逻辑清晰,旨在帮助读者掌握这一关键技能。 ... [详细]
  • 本文介绍如何配置SecureCRT以正确显示Linux终端的颜色,并解决中文显示问题。通过简单的步骤设置,可以显著提升使用体验。 ... [详细]
  • 本文详细介绍了JSP(Java Server Pages)的九大内置对象及其功能,探讨了JSP与Servlet之间的关系及差异,并提供了实际编码示例。此外,还讨论了网页开发中常见的编码转换问题以及JSP的两种页面跳转方式。 ... [详细]
  • MySQL锁机制详解
    本文深入探讨了MySQL中的锁机制,包括表级锁、行级锁以及元数据锁,通过实例详细解释了各种锁的工作原理及其应用场景。同时,文章还介绍了如何通过锁来优化数据库性能,避免常见的并发问题。 ... [详细]
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社区 版权所有