最近接触JWT,顺便记录下
目录
JWT简介
JWT是JSON Web Token的简称,是目前流行的跨域的认证解决方案,作为传递信息的凭证,它是由服务器端签发的且是带签名的,服务器端通过数字签名来保证数据的完整性和有效性。
JWT组成
Jwt由三部分组成: Header(头部)、Payload(负载)、Signature(签名)
这里简单讲解下吧,其实已经很多博主都讲了的,我这边就...照搬~
头部(Header) :JWT 的头部通常由两部分信息组成:令牌的类型(即JWT)和所使用的签名算法,例如:{ "alg": "HS256", "typ": "JWT"}
载荷(Payload) :JWT的载荷包含了一些声明(Claim),用于描述用户信息、权限、过期时间等等,例如:{ "name": "zhang", "userid": "123"}
签名(Signature):JWT的签名由头部和载荷组成,并使用密钥进行加密生成
JWT使用流程
基于系统登陆 :用户使用用户名和密码进行登录,服务器验证用户信息是否正确。
1、服务器通过JWT生成token,将用户信息、权限等信息写入载荷中,并使用密钥对头部和载荷进行签名。
2、服务器将生成的token返回给客户端,客户端将其存储在本地,通常是在浏览器的cookie或本地存储中。
3、客户端在后续的请求中,将token作为请求头部或请求参数传递给服务器。
4、服务器收到请求后,验证token的签名是否正确,如果正确则解析出用户信息、权限等信息,进行后续操作。
基于对接验证 :第三方对外接口时可使用JWT生成的token作为验证凭证(有时间限制)1、请求第三方服务器,将第三方授权信息等信息写入载荷中,并使用第三方提供的密钥对头部和载荷进行签名。
2、第三方将生成的token返回给请求方,请求方将token存储至数据库或缓存,可设置自动失效或失效后自动获取token
3、请求方在后续的请求接口中,将token作为请求头部或请求参数传递给第三方。
4、第三方收到请求后,验证token的签名是否正确,如果正确则解析出授权信息进行验证,若无误再进行后续操作
JWT实战
引入Maven
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
核心代码
Jwt工具类
java
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
public class JwtUtils {
//密钥(自定义)
private static String secret = "JwtTest";
//过期时间(分钟)
private static int expireMinutes = 10;
//创建Token
public static String getToken(Map<String,String> map){
Calendar instance = Calendar.getInstance();
//获取具体失效时间
instance.add(Calendar.SECOND,60 * expireMinutes);
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
//设置过期时间
builder.withExpiresAt(instance.getTime());
return builder.sign(Algorithm.HMAC256(secret));
}
//验证Token
public static void isVerify(String token){
DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
}
}
拦截器(实现登录后验证获取信息)
java
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhangximing.springboot_jwt.util.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* JWT拦截器
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HashMap<String, Object> map = new HashMap<>();
System.out.println("JWT拦截器拦截到请求");
//获取请求头中的token
String token = request.getHeader("token");
try {
//验证令牌
JwtUtils.isVerify(token);
//放行请求
return true;
}catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg","无效签名");
}catch (TokenExpiredException e){
e.printStackTrace();
map.put("msg","token过期");
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("msg","算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","token无效");
}
map.put("state",false);
// 将map转为json,响应给前端
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器配置
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**")
//除登陆方法外其他路径都拦截
.excludePathPatterns("/user/login");
}
/**
* 如果实现了Filter跨域拦截,这个跨域无效
* 拦截器实现 跨域支持
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*") //本人测试时springboot2.7版本用的是这个
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT","OPTIONS","HEAD")
.allowedHeaders("*")
.maxAge(3600);
}
}
测试方法
java
import com.zhangximing.springboot_jwt.util.JwtUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
//测试验证
@RequestMapping("/test")
public Map<String,Object> test(){
HashMap<String, Object> map = new HashMap<>();
map.put("state",true);
map.put("msg","请求成功");
return map;
}
//用户登录,认证token
@RequestMapping("/login")
public Map<String,Object> login(@RequestBody Map<String,Object> paramMap){
HashMap<String, Object> map = new HashMap<>();
try {
//模拟登陆
Map<String, String> loginInfo = MyLogin((String) paramMap.get("userName"), (String) paramMap.get("password"));
//登陆成功后,返回token
Map<String,String> payload = new HashMap<>();
payload.put("userId",loginInfo.get("userId"));
payload.put("userName",loginInfo.get("userName"));
//生成JWT令牌
String token = JwtUtils.getToken(payload);
//token返回
map.put("state",true);
map.put("msg","认证成功");
map.put("token",token);
} catch (Exception e) {
map.put("state",false);
map.put("msg",e.getMessage());
}
return map;
}
//模拟登陆
public Map<String,String> MyLogin(String userName,String password){
if ("zhangximing".equals(userName) && "123456".equals(password)){
Map<String,String> map = new HashMap<>();
map.put("userId","1");
map.put("userName","zhangximing");
return map;
}else{
throw new RuntimeException("用户名或密码错误");
}
}
}
测试结果(模拟登陆、正常登陆、登陆失效)
JWT优缺点
引用一个博主的总结 https://blog.csdn.net/wingrant/article/details/126445880
JWT优点:
1、因为json的通用性,JWT是可以跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等语言都能使用。
2、因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
3、便于传输。JWT的构成非常简单,字节占用很小,所以它是非常便于传输的。 它不需要在服务端保存会话信息, 所以它易于应用的扩展。
JWT缺点:1、占带宽: 正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10万次的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人会在 JWT 中存储的信息会更多。
2、无法在服务端注销。用户主动注销时一般会让前端清理token,后端不理会,很难解决劫持问题。其他解决办法都是有状态的,例如通过Redis存储token副本、Redis存储token版本号、Redis存储过期时间等。
3、性能问题: JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web身份认证应用中,JWT 都会被存储到 Cookie中,这就是说你有了两个层面的签名。听着似乎很牛逼,但是没有任何优势,为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的Web 应用,这并不理想,尤其对于单线程环境。