前言 JWT(JSON Web Token)是一种轻量级的、可扩展的、基于JSON的身份验证和授权机制,用于在不同的应用程序之间安全地传输信息。JWT是由三部分组成:头部、载荷和签名。头部通常包含有关JWT的元数据,如过期时间、签名算法等;载荷包含要传输的信息,例如用户ID、角色等;签名则用于验证JWT是否被篡改过。
Session认证 互联网服务离不开用户认证,一般流程是这样的:
session 认证的方式应用非常普遍,但也存在一些问题,扩展性不好,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session,针对此种问题一般有两种方案: 一种解决方案是session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。 一种方案是服务器不再保存 session 数据,所有数据都保存在客户端,每次请求都发回服务器。Token认证就是这种方案的一个代表
Token认证 Token是在服务端产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证,流程如下:
客户端使用用户名跟密码请求登录,服务端收到请求,去验证用户名与密码
验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者localStorage 里
客户端每次向服务端请求资源的时候需要带着服务端签发的 token
服务端收到请求,然后去验证客户端请求里面带着的 token,如果验证成功就向客户端返回请求的数据
Token认证的特点 基于token的用户认证是一种服务端无状态的认证方式,服务端不用存放token 数据。 用解析 token的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库 token 完全由应用管理,所以它可以避开同源策略
JWT JSON Web Token(简称JWT)是一个token的具体实现方式,是目前最流行的跨域认证解决方案。
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户。
用户于服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
JWT的由三个部分组成,依次如下:Header(头部)、Payload(负载)、Signature(签名)
三部分最终组合为完整的字符串,中间使用 . 分隔,如下:Header.Payload.Signature
Header部分是一个JSON对象,描述JWT的元数据
1 2 3 4 5 6 7 { "alg" : "HS256" , "typ" : "JWT" }
alg属性表示签名的算法 (algorithm),默认是HMAC SHA256 (写成HS256) typ属性表示这个令牌 (token)的类型 (ype),JWT 令牌统一写为JWT
最后,将上面的JSON 对象使用 Base64URL算法转成字符串
Payload Payload 部分也是一个JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
iss(issuer): 签发人
exp (expiration time): 过期时间
sub (subject):主题
aud(audience):受众
nbf(Not Before): 生效时间
iat(Issued At): 签发时间
jti (WT ID): 编号
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在个部分。
这个JSON 对象也要使用 Base64URL算法转成字符串
Signature Signature部分是对前两部分的签名,防止数据篡改
首先,需要指定一个密钥 (secret) 。这个密钥只有服务器才知道,不能泄露给用户 然后,使用 Header 里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名
JWT的特点 客户端收到服务器返回的JWT,可以储存在 Cookie 里面,也可以储存在ocalStorage。 客户端每次与服务器通信,都要带上这个JWT,可以把它放在 Cookie 里面自动发送,但是这样不能跨域。 更好的做法是放在HTTP 请求的头信息Authorization字段里面,单独发送
JWT的使用 首先引入Maven依赖。
1 2 3 4 5 <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > <version > 0.9.1</version > </dependency >
创建工具类,用户创建JWT字符串和解析JWT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @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
接着创建一个实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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" , "失败" ); } } 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); } }
接着是Controller控制类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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 ; } 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
1 2 3 4 5 6 7 8 9 <?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 >
后续就可以做接口验证啦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RestController @RequestMapping("/jwt") public class TestController { @Resource private JwtUtil jwtUtil; @RequestMapping("/test") public Map<String, Object> test (@RequestParam("jwt") String jwt) { 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; } }
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1m621mn5zh4ac