什么是JWT——Json Web Token
JSON Web Token (JWT) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。 该信息可以被验证和信任,因为它是经过数字签名的。 JWT 可以使用秘密(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
说到token想必大家都并不陌生,在springboot接入微信登陆,用户授权后就能获得由微信发送的token,以此来获取用户的详细信息,虽然在微信登录后同样发送了用户的一些信息。
JSON Web Tokens 的应用场景
授权:这是使用 JWT 最常见的场景。 用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。 单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够轻松跨不同域使用。
信息交换:JSON Web Tokens 是一种在各方之间安全传输信息的好方法。 因为 JWT 可以被签名——例如,使用公钥/私钥对——你可以确定发件人就是他们所说的那样。 此外,由于使用标头和有效负载计算签名,因此您还可以验证内容是否未被篡改。
基于token的认证和传统的session认证的区别
http是一个无状态协议
什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。这种无状态的的好处是快速。
Cookie和Session
- 首先,客户端会发送一个http请求到服务器端。
- 服务器端接受客户端请求后,建立一个session,并发送一个http响应到客户端,这个响应头,其中就包含Set-Cookie头部。该头部包含了sessionId。Set-Cookie格式如下,具体请看Cookie详解
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure] - 在客户端发起的第二次请求,假如服务器给了set-Cookie,浏览器会自动在请求头中添加cookie
- 服务器接收请求,分解cookie,验证信息,核对成功后返回response给客户端

Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
Token
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程如下:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *。
JSON Web Token结构
JWT由三部分组成,通过 . 来连接,这三部分分别是:
- Header
- Payload
- Signature
所以,一个JWT通常像这样 xxxxxxx.yyyyyyyy.zzzzzz
Header
这部分通常由两部分组成:token的类型——JWT,和使用的签名算法。
{
"alg": "HS256",
"typ": "JWT"
}
然后,这个 JSON 被 Base64Url 编码以形成 JWT 的第一部分
Payload
令牌的第二部分是负载,其中包含声明。 声明是关于实体(通常是用户)和附加数据的声明。 共有三种类型的声明:注册声明、公共声明和私人声明。
- Registered claims:这些是一组预定义的声明,这些声明不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明。 其中一些是:
iss(发行者)、exp(到期时间)、sub(主题)、aud(受众)等。 - Public claims:这些可以由使用 JWT 的人随意定义。 但是为了避免冲突,它们应该在 IANA JSON Web Token Registry 中定义或定义为包含抗冲突命名空间的 URI。
- Private claims:这些自定义声明是为了在同意使用它们的各方之间共享信息而创建的,这些声明既不是注册声明也不是公开声明。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对有效负载进行 Base64Url 编码以形成 JSON Web 令牌的第二部分。
*** 请注意,对于已签名的令牌,此信息虽然受到防篡改保护,但任何人都可以读取。 除非加密,否则不要将机密信息放在 JWT 的负载或标头元素中。***
Signature
要创建签名部分,您必须获取编码的标头、编码的有效载荷、秘密、标头中指定的算法,并对其进行签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
JWT的特点
(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。
(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
springboot整合JWT
-
导包
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.18.1</version> </dependency> -
在登陆成功后发放 token
// 设置密钥 String secret = ""; // 生成加密算法 Algorithm algorithm = Algorithm.HMAC256(secret.getBytes()); // 验签器 JWTVerifier verifier = JWT.require(algorithm).build();User user = (User) authentication.getPrincipal(); String access_token = JWT.create() .withSubject(user.getUsername()) // 设置到期时间 .withExpiresAt(new Date(System.currentTimeMillis() + 10 * 60 * 1000)) .withIssuer(request.getRequestURL().toString()) .withClaim("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())) .sign(algorithm); String refresh_token = JWT.create() .withSubject(username) // 设置到期时间 .withExpiresAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) .withIssuer(request.getRequestURL().toString()) .sign(algorithm); Map<String, String> tokens = new HashMap<>(); tokens.put("access_token", access_token); tokens.put("refresh_token", refresh_token); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(), tokens); -
设置过滤器,对某些需要拦截的URL进行处理,对token进行解析,有效的token才能访问。
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean<CustomFilter> customFilter(){ FilterRegistrationBean<CustomFilter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new CustomFilter()); filterRegistrationBean.addUrlPatterns("/user/*"); filterRegistrationBean.setOrder(1); return filterRegistrationBean; } }@Slf4j public class CustomFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; log.info("--------------------------------- the URL of this request is " + req.getRequestURL()); // 获取token String authorization = req.getHeader("Authorization"); // 判空 if (StrUtil.isEmpty(authorization)){ // 生成返回信息 Result result = Result.error().message("没有token"); String json = JSON.toJSONString(result); response.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8)); return; }else{ try{ String authorization = request.getHeader(HttpHeaders.AUTHORIZATION); DecodedJWT token = verifier.verify(authorization.substring("Bearer ".length())); }catch (TokenExpiredException tokenExpiredException){ log.error("token 过期"); // 生成返回信息 Result result = Result.TokenExpired(); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().write(JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8)); return; } catch (Exception e){ log.error("无效token, the exception is {}, exception class is {}",e.getMessage(),e.getClass().getName()); Result result = Result.error().message("无效token," + e.getMessage()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().write(JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8)); return; } } // token没问题,继续访问 chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void destroy() { Filter.super.destroy(); } } -
到这里就结束了。

2万+

被折叠的 条评论
为什么被折叠?



