JWT(JSON Web Token)是一种轻量级的、可扩展的、基于JSON的身份验证和授权机制,用于在不同的应用程序之间安全地传输信息。JWT是由三部分组成:头部、载荷和签名。头部通常包含有关JWT的元数据,如过期时间、签名算法等;载荷包含要传输的信息,例如用户ID、角色等;签名则用于验证JWT是否被篡改过。
互联网服务离不开用户认证,一般流程是这样的:
session 认证的方式应用非常普遍,但也存在一些问题,扩展性不好,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session,针对此种问题一般有两种方案: 一种解决方案是session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。 一种方案是服务器不再保存 session 数据,所有数据都保存在客户端,每次请求都发回服务器。Token认证就是这种方案的一个代表
Token是在服务端产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证,流程如下:
基于token的用户认证是一种服务端无状态的认证方式,服务端不用存放token 数据。 用解析 token的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库 token 完全由应用管理,所以它可以避开同源策略
JSON Web Token(简称JWT)是一个token的具体实现方式,是目前最流行的跨域认证解决方案。
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户。
用户于服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
JWT的由三个部分组成,依次如下: Header(头部)、Payload(负载)、Signature(签名)
三部分最终组合为完整的字符串,中间使用 . 分隔,如下: Header.Payload.Signature
Header部分是一个JSON对象,描述JWT的元数据
{
"alg" : "HS256",
"typ" : "JWT"
}
alg属性表示签名的算法 (algorithm),默认是HMAC SHA256 (写成HS256) typ属性表示这个令牌 (token)的类型 (ype),JWT 令牌统一写为JWT
最后,将上面的JSON 对象使用 Base64URL算法转成字符串
Payload 部分也是一个JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
Signature部分是对前两部分的签名,防止数据篡改
首先,需要指定一个密钥 (secret) 。这个密钥只有服务器才知道,不能泄露给用户 然后,使用 Header 里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名
客户端收到服务器返回的JWT,可以储存在 Cookie 里面,也可以储存在ocalStorage。 客户端每次与服务器通信,都要带上这个JWT,可以把它放在 Cookie 里面自动发送,但是这样不能跨域。 更好的做法是放在HTTP 请求的头信息Authorization字段里面,单独发送
首先引入Maven依赖。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
创建工具类,用户创建JWT字符串和解析JWT
@Component
public class JwtUtil {
@Value("${jwt.secretKey}")
private String secretKey;
public String createJWT(String id, String subject, long ttlMillis, Map<String, Object> map) throws Exception {
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject) // 发行者
.setIssuedAt(new Date()) // 发行时间
.signWith(SignatureAlgorithm.HS256, secretKey) // 签名类型 与 密钥
.compressWith(CompressionCodecs.DEFLATE);// 对载荷进行压缩
if (!CollectionUtils.isEmpty(map)) {
builder.setClaims(map);
}
if (ttlMillis > 0) {
builder.setExpiration(new Date(System.currentTimeMillis() + ttlMillis));
}
return builder.compact();
}
public Claims parseJWT(String jwtString) {
return Jwts.parser().setSigningKey(secretKey)
.parseClaimsJws(jwtString)
.getBody();
}
}
接着在application.yml配置文件配置jwt.secretKey
## 用户生成jwt字符串的secretKey
jwt:
secretKey: ak47
接着创建一个实体类
public class BaseResponse {
private String code;
private String msg;
public static BaseResponse success() {
return new BaseResponse("0", "成功");
}
public static BaseResponse fail() {
return new BaseResponse("1", "失败");
}
//构造器、getter、setter方法
}
public class JwtResponse extends BaseResponse {
private String jwtData;
public static JwtResponse success(String jwtData) {
BaseResponse success = BaseResponse.success();
return new JwtResponse(success.getCode(), success.getMsg(), jwtData);
}
public static JwtResponse fail(String jwtData) {
BaseResponse fail = BaseResponse.fail();
return new JwtResponse(fail.getCode(), fail.getMsg(), jwtData);
}
//构造器、getter、setter方法
}
接着是Controller控制类:
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@RequestMapping(value = "/login", method = RequestMethod.POST)
public JwtResponse login(@RequestParam(name = "userName") String userName,
@RequestParam(name = "passWord") String passWord){
String jwt = "";
try {
jwt = userService.login(userName, passWord);
return JwtResponse.success(jwt);
} catch (Exception e) {
e.printStackTrace();
return JwtResponse.fail(jwt);
}
}
}
还有Service层
@Service
public class UserServiceImpl implements UserService {
@Resource
private JwtUtil jwtUtil;
@Resource
private UserMapper userMapper;
@Override
public String login(String userName, String passWord) throws Exception {
//登录验证
User user = userMapper.findByUserNameAndPassword(userName, passWord);
if (user == null) {
return null;
}
//如果能查出,则表示账号密码正确,生成jwt返回
String uuid = UUID.randomUUID().toString().replace("-", "");
HashMap<String, Object> map = new HashMap<>();
map.put("name", user.getName());
map.put("age", user.getAge());
return jwtUtil.createJWT(uuid, "login subject", 0L, map);
}
}
还有xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.github.yehongzhi.jwtdemo.mapper.UserMapper">
<select id="findByUserNameAndPassword" resultType="io.github.yehongzhi.jwtdemo.model.User">
select * from user where user_name = #{userName} and pass_word = #{passWord}
</select>
</mapper>
后续就可以做接口验证啦
@RestController
@RequestMapping("/jwt")
public class TestController {
@Resource
private JwtUtil jwtUtil;
@RequestMapping("/test")
public Map<String, Object> test(@RequestParam("jwt") String jwt) {
//这个步骤可以使用自定义注解+AOP编程做解析jwt的逻辑,这里为了简便就直接写在controller里
Claims claims = jwtUtil.parseJWT(jwt);
String name = claims.get("name", String.class);
String age = claims.get("age", String.class);
HashMap<String, Object> map = new HashMap<>();
map.put("name", name);
map.put("age", age);
map.put("code", "0");
map.put("msg", "请求成功");
return map;
}
}