JSON Web Token
2022/6/25 23:34:39
本文主要是介绍JSON Web Token,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
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?
跨域认证问题
互联网服务用户认证的一般流程如下所示:
- 用户向服务器发送用户名和密码
- 服务器验证通过后,在当前session保存相关数据,如用户角色,登录时间等
- 服务器向用户返回一个 session_id,写入用户的 Cookie
- 用户随后的每一个请求都会通过 Cookie 将 session_id 传回服务器
- 服务器收到 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官网首页的一个示例:
JWT认证流程
- 用户使用账号密码登录,调用服务器登录接口
- 服务器登录程序生成token,并返回给用户
- 用户后续请求携带token
- 服务器收到用户请求后,验证token的合法性、有效性,验证通过后处理请求
- 返回请求结果给用户
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
这篇关于JSON Web Token的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-21Vue3教程:新手入门到实践应用
- 2024-12-21VueRouter4教程:从入门到实践
- 2024-12-20Vue3项目实战:从入门到上手
- 2024-12-20Vue3项目实战:新手入门教程
- 2024-12-20VueRouter4项目实战:新手入门教程
- 2024-12-20如何实现JDBC和jsp的关系?-icode9专业技术文章分享
- 2024-12-20Vue项目中实现TagsView标签栏导航的简单教程
- 2024-12-20Vue3入门教程:从零开始搭建你的第一个Vue3项目
- 2024-12-20从零开始学习vueRouter4:基础教程
- 2024-12-20Vuex4课程:新手入门到上手实战全攻略