JWT认证原理
JWT简介: JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于JSON对象在各方之间安全的传输信息。该信息可以被验证和信任,因为它是数字签名的。
JWT的结构: JWT由三个部分组成,各部分之间用小数点连接。这三部分分别是Header(请求头)、Payload(有效载荷)、Signature(签名),一个典型的JWT看起来是这个样子的:xxx.yyy.zzz
1. header请求头: 由Token的类型和算法名称组成,如下所示。然后用Base64对这个JSON编码就得到JWT的第一部分。
{
"alg": "HS256",
"typ": "JWT"
}
2. Payload载荷: 载荷就是存放有效信息的地方。如:用户注册信息中可公开的数据,如下所示。对Payload进行Base64编码就得到JWT的第二部分。
注意: 不要在JWT的payload或header中放置敏感信息,除非它们是加密的。
{
"username": "BigHu",
"age": "25"
}
3. Signature签名: 为了得到签名部分,必须有编码过的header、编码过的payload、一个密钥,签名算法是header中指定的那个,然后对它们签名即可。签名是用于验证消息在传递过程中是否被更改。
生成的Token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.1eyJleHAiOjE2MDUwMTY2MzcsInVzZXJJZCI6MTIzLCJ1c2VybmFtZSI6ImJpZy5odSJ9.QuCF1F2_J-oX4UMKzgehRZxRgimPIXktTMfVgO6MO4Y
JWT与OAuth的区别?
- OAuth2是一种授权框架,JWT是一种认证协议。
- 无论使用哪种方式切记用https来保证数据的安全性。
- OAuth2用在使用第三方账号登录的情况下(如QQ、微信…),而JWT是用在前后端分离,需要见到那的对后台API进行保护使用。
JWT认证流程
- 前端通过Web表单将自己的用户名和密码发送到后端接口。
- 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一共形同xxx.yyy.zzz的字符串。
- 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回结果保存在localStoreage或sessionStoreage上,退出登陆时前端删除保存的JWT即可。
- 前端每次请求时将JWT放入HTTP Header的Authorization位,(解决XSS和XSRF问题)HEADER
- 后端检查是否存在,如存在验证JWT的有效性。例如检查签名是否正确;检查Token是否过期;检查Token接收方是否是自己(可选)。
- 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。
JWT实现
引入jar包
<!-- java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
用户实体类
@Data
@Accessors(chain = true)
public class User {
private String id;
private String username;
private String password;
}
JWT工具类
package com.jiezai.jwt.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* JWT工具类
*
* @author Big.Hu
* @date 2020/11/11 14:30
*/
public class JWTUtils {
private static final String SIGN = "!@#$%--Big.Hu--456789";
/**
* 生成token:header.payload.sign
*
* @param map 登陆信息,如:{"username":"张三"}
* @return token
*/
public static String getToken(Map<String, String> map) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 7); // 过期时间:7天
JWTCreator.Builder builder = JWT.create();
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
Map<String, Object> header = new HashMap<>();
header.put("typ", "JWT");
header.put("alg", "HMAC256");
String token = builder.withHeader(header)
.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256(SIGN));
return token;
}
/**
* 验证token的合法性并返回用户名
*
* @param token
* @return 用户名
*/
public static String verify(String token) {
JWTVerifier build = JWT.require(Algorithm.HMAC256(SIGN)).build();
DecodedJWT decodedJWT = build.verify(token);
return decodedJWT.getClaim("username").asString();
}
}
自定义Token拦截器
package com.jiezai.jwt.interceptors;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jiezai.jwt.utils.JWTUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* 登陆拦截器
* @author Big.Hu
* @date 2020/11/11 13:32
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中获取Token
String token = request.getHeader("token");
HashMap<String, Object> map = new HashMap<>();
try {
// 认证token,如果没报错证明token有效,直接放行
String username = JWTUtils.verify(token);
System.out.println("用户 " + username + " 认证成功!");
return true;
} catch (AlgorithmMismatchException e) {
map.put("msg", "签名算法不对");
} catch (SignatureVerificationException e) {
map.put("msg", "签名不对");
} catch (TokenExpiredException e) {
map.put("msg", "令牌过期");
} catch (Exception e) {
map.put("msg", "token无效");
e.printStackTrace();
}
map.put("status", false);
// 转换成json格式并响应
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
配置拦截器
package com.jiezai.jwt.config;
import com.jiezai.jwt.interceptors.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器配置类
* @author Big.Hu
* @date 2020/11/11 15:18
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/user/login"); // 排除登陆请求
}
}
controller类
package com.jiezai.jwt.controller;
import com.jiezai.jwt.entity.User;
import com.jiezai.jwt.service.UserService;
import com.jiezai.jwt.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author Big.Hu
* @date 2020/11/11 15:30
*/
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("login")
public Map<String, Object> login(User user) {
HashMap<String, Object> map = new HashMap<>();
try {
// 去数据库验证账号密码,UserService这部分代码省略
User userDB = userService.login(user);
HashMap<String, String> userMap = new HashMap<>();
userMap.put("userId", userDB.getId());
userMap.put("username", userDB.getUsername());
// 生成Token并返回
String token = JWTUtils.getToken(userMap);
map.put("status", true);
map.put("token", token);
map.put("msg", "认证成功");
} catch (Exception e) {
map.put("status", false);
map.put("msg", "认证失败");
e.printStackTrace();
}
return map;
}
@PostMapping("test")
public Map<String, Object> test() {
HashMap<String, Object> map = new HashMap<>();
map.put("status", true);
map.put("msg", "认证成功");
return map;
}
}
以上文章有什么不对的地方,望大佬们不吝赐教!