一、什么是JWT
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公用/专用密钥对对JWT进行签名。
通俗一点JWT就是通过Json形式作为web应用中的令牌,用于在各方之间安全的将信息作为Json对象传输,在传输过程中可进行数据加密和签名。
二、传统Session
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
显露的问题:
- 服务端开销大Session 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,(会在cookie中保存一个sessionId,用来从服务器端获取session)而随着认证用户的增多,服务端的开销会明显增大。
- 扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
- CSRF攻击: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
三、JWT认证:
JWT认证流程
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skooudcH-1599993604848)(https://i.loli.net/2018/08/25/5b8132249d221.png)]
JWT令牌的结构
JWT的格式为:Header.Payload.Signature,即JWT包含三部分。分别为header,payload和signature,因此,JWT通常如下显示
-
头部(Header):头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。header存放的内容说明编码对象是一个JWT。例如:
{ "alg": "HS256", "typ": "JWT" }
头部进行base64加密(该加密是可以对称解密的),构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
-
载荷(payload):payload主要包括claim,claim是一些实体(通常指用户)的状态和额外的元数据,有三种类型。Reserved、Public和Private。例如
{ "sub": "1234567890", "name": "John Doe", "admin": true }
Reserved calim是JWT预先定义的claim。例如:在JWT中推荐使用。JWT 规定了7个官方字段,供选用,如下
iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 数字类型,说明在该事件之前,jwt不能被接收和处理 iat: jwt的签发时间 jti: 标明jwt的唯一ID,主要用来作为一次性token,从而回避重放攻击。
-
签名(signature):签名需要使用编码后的header和payload及一个密匙,使用header中指定的签名算法进行签名。用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份。签名的流程如下:
- 将header和claim分别使用Base64进行编码。生成字符串header和payload。
- 将header和payload以header.payload的格式组合在一起,形成一个字符串。
- 使用上面定义好的加密算法和一个存放在服务器上用于验证的密匙来对这个字符串进行签名,形成一个新的字符串,这个字符串就是signature。
注意:secret是保存在服务器端的,jwt的签名发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。
JWT的特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
四、使用springboot整合token测试
4.1、在pom中导入JWT的依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
</dependencies>
4.2、编写JWT工具类
public class JwtUtils {
//服务端secret密匙(不能泄露)
private static final String KEY = "!##FR$%%^GTHYJ&*FF45";
/**
* 签名生成Token
* @param map
* @return
*/
public static String sign(Map<String,Object> map){
//头部信息的map集合
Map<String,Object> headerMap = new HashMap<>();
headerMap.put("alg", "HS256");
headerMap.put("typ", "JWT");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR,2);
JWTCreator.Builder builder = JWT.create();
//添加头部
builder.withHeader(headerMap);
//添加载荷
builder.withClaim("map",map);
//设置过期时间为两个小时
builder.withExpiresAt(calendar.getTime());
//生成token
String token = builder.sign(Algorithm.HMAC256(KEY));
return token;
}
/**
* 验证Token并返回信息
* @param token
* @return
*/
public static DecodedJWT verify(String token){
DecodedJWT tokenInfo = JWT.require(Algorithm.HMAC256(KEY)).build().verify(token);
return tokenInfo;
}
4.3、拦截器配置
public class JwtInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求中的令牌
String token = request.getHeader("Authorization");
Map<String,Object> map = new HashMap<>();
try {
//验证token
JwtUtils.verify(token);
return true;
}catch (Exception e){
map.put("msg","无效的签名,或者token过期,或者token无效,或者token算法不一致~~");
e.printStackTrace();
}
map.put("code",0);
String resultJson = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(resultJson);
return false;
}
}
@Configuration
public class JwtConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration interceptorRegistration = registry.addInterceptor(new JwtInterceptor());
interceptorRegistration.addPathPatterns("/user/**");
interceptorRegistration.excludePathPatterns("/user/login");
}
}
4.4、Controller
@Controller
@RequestMapping("/user")
public class JwtController {
@Autowired
private UserService userService;
@PostMapping("/login")
@ResponseBody
public Map<String,Object> login(User user, HttpServletResponse response){
Map<String, Object> resultMap = new HashMap<>();
User loginUer = userService.login(user);
if (null != loginUer) {
resultMap.put("code", 1);
resultMap.put("msg", "登录成功,已经生成token!");
//tokenMap存放用户信息
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("username", loginUer.getUsername());
tokenMap.put("id", loginUer.getId());
tokenMap.put("address", loginUer.getAddress());
//生成token令牌
String token = JwtUtils.sign(tokenMap);
System.out.println(token);
//将令牌写到Header中返回给客户端
response.setHeader("Authorization",token);
}
return resultMap;
}
@GetMapping("/speak")
@ResponseBody
public Map<String,Object> speak(){
System.out.println("一giao我里giao!");
Map<String,Object> map = new HashMap<>();
map.put("code",1);
map.put("msg","已验证token");
return map;
}
}
dao层和service层就不贴了,就一个登录查询
五、测试
用使用admin和12346登录,返回结果登录成功!
查看请求头,已经将token给我们返回
在访问speak时通过拦截器进行拦截,并进行token验证,此时我们不在请求头中添加token,无法访问到speak。
当我们在请求头中加上token
请求成功!
以上就是对JWT的简单入门学习,如有错误还请指出
参考链接:https://blog.csdn.net/achenyuan/article/details/80829401
参考链接:www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html