SpringBoot+vue+Axios实现Token令牌的详细过程
作者:凉冰24
认识Token
对Token有了解可以跳过。
使用Token存储用户信息,认证用户。 Token是一个访问系统的令牌(字符串)。Token 是在服务端产生的。前端可以使用用户名/密码向服务端请求认证(登录),服务端认证成功,服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的身份。服务器端在处理请求之前,先验证Token,验证通过,可以访问系统,执行业务请求处理。
Token可以使用自己的算法自定义,比如给每个用户分配一个UUID ,UUID值就看做是一个Token 。
Token统一的规范是JWT( json web token ) 。用json表示token。JWT定义了一种紧凑而独立的方法,用于在各方之间安全地将信息作为JSON对象传输。
常使用的Token库是 jjwt
JWT组成
JWT这是一个很长的字符串,中间用点(.
)分隔成三个部分。JWT 内部是没有换行的,一行数据。
JWT 的三个部分依次如下。
Header(头部)
Payload(负载)
Signature(签名)
形如:
eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTE5NzgzNDQsImlhdCI6MTY5MTk3Nzc0NCwianRpIjoiNDEyNjRhNTctYTY0ZC00Y2E5LTljMzMtY2I1MGJmNDc5YjEzIiwicm9sZSI6Iue7j-eQhiIsIm5hbWUiOiLmnY7lm5siLCJ1c2VySWQiOjEwMDF9.t4IrCIs8Y1zLwehouUugeAc-8PBlndpXz5xhibtQIgo
Header
Header: 是一个json对象,存储元数据
{ "alg": "HS256", // alg:是签名的算法名称(algorithm),默认是 HMAC SHA256(写成 HS256); "typ": "JWT" //typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。 }
元数据的json对象使用base64URL编码.
Payload
Payload :负载,是一个json对象。是存放传递的数据 ,数据分为Public的和Private的。
Public是JWT中规定的一些字段,可以自己选择使用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):该JWT所面向的用户
- aud (audience):受众,接收该JWT的一方
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
Private是自己定义的字段
{ "role": "经理", "name": "张凡", "id": 2345 }
Playload默认是没有加密的, 以上数据都明文传输的,所以不要放敏感数据.此部分数据也是json对象使用base64URL编码,是一个字符串
Signature
Signature:签名。签名是对Header和Payload两部分的签名,目的是防止数据被篡改
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
签名算法:先指定一个 secret秘钥, 把base64URL的header , base64URL的payload 和 secret秘钥 使用 HMAC SHA256 生成签名字符串
JWT简单使用
首先用户使用类似像用户名,密码登录服务器,校验用户身份,服务器返回jwt token
客户端获取 jwt token , 使用cookie或者localStorage存储jwt token,但这样不能跨域, 常用的方式:
放在请求header 的 Authorization中 Authorization: Bearer
也可以放在 get 或者 post请求的参数中
客户端访问其他api接口, 传递token给服务器, 服务器认证token后,返回给请求的数据
创建JWT
@Test public void testCreateJwt(){ String key = "7d8e742c14674758b9162892eec9c59c";// 32位、全局唯一 // 创建secereKey SecretKey secretKey = Keys.hmacShaKeyFor(key.getBytes(StandardCharsets.UTF_8)); // 存储用户数据 Map<String,Object> map = new HashMap(); map.put("userId",1001); map.put("name","李四"); map.put("role","经理"); // 获取签发时间 Date curDate = new Date(); // 创建jwt String jwt = Jwts.builder().signWith(secretKey, SignatureAlgorithm.HS256) // Header .setExpiration(DateUtils.addMinutes(curDate, 10)) // 设置10分钟后过期 .setIssuedAt(curDate) // 设置签发时间 .setId(UUID.randomUUID().toString()) // Token唯一编号 .addClaims(map).compact(); // 添加有关用户或其他实体的声明信息 System.out.println("jwt = " + jwt); // eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTE5NzgzNDQsImlhdCI6MTY5MTk3Nzc0NCwianRpIjoiNDEyNjRhNTctYTY0ZC00Y2E5LTljMzMtY2I1MGJmNDc5YjEzIiwicm9sZSI6Iue7j-eQhiIsIm5hbWUiOiLmnY7lm5siLCJ1c2VySWQiOjEwMDF9.t4IrCIs8Y1zLwehouUugeAc-8PBlndpXz5xhibtQIgo }
解析JWT
@Test public void testReadJwt(){ String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTE5Nzk4MTMsImlhdCI6MTY5MTk3OTIxMywianRpIjoiYzAyYzRiMzMtODAzZS00MDk1LWI0NTctZjVmY2NkYTMyOTU2Iiwicm9sZSI6Iue7j-eQhiIsIm5hbWUiOiLmnY7lm5siLCJ1c2VySWQiOjEwMDF9.vUsmvOF-rv6XVCzuNgJZCGBohzDeROUFUf-c3iRv6NA"; String key = "7d8e742c14674758b9162892eec9c59c";// 加密用的32位key // 创建secretKey SecretKey secretKey = Keys.hmacShaKeyFor(key.getBytes(StandardCharsets.UTF_8)); // 解析Jwt 没有异常 解析成功 Jws<Claims> claims = Jwts.parserBuilder() // 1.获取Buider对象 .setSigningKey(secretKey) // 2.设置key .build() // 3.获取Parser .parseClaimsJws(jwt); // 4.解析数据 // 读数据 Claims body = claims.getBody(); Integer userId = body.get("userId",Integer.class); System.out.println("userId = " + userId); Object uId = body.get("userId"); System.out.println("uId = " + uId); Object name = body.get("name"); if(name != null){ System.out.println("name = " + name); } String jwtId = body.getId(); System.out.println("jwtId = " + jwtId); Date expiration = body.getExpiration(); System.out.println("expiration = " + expiration); }
常见异常
ClaimJwtException 获取Claim异常 ExpiredJwtException token过期异常 IncorrectClaimException token无效 MalformedJwtException 密钥验证不一致 MissingClaimException JWT无效 RequiredTypeException 必要类型异常 SignatureException 签名异常 UnsupportedJwtException 不支持JWT异常
后端
Maven依赖
<!--jwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred --> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
封装JWT工具
public class JwtUtile { // 全局唯一、乱序的32位字符串。存储在配置文件中 private String selfKey; public JwtUtile(String selfKey) { this.selfKey = selfKey; } // 创建Token public String creatJwt(Map<String,Object> data,Integer minute) throws Exception{ Date cruDate = new Date(); SecretKey secretKey = Keys.hmacShaKeyFor(selfKey.getBytes(StandardCharsets.UTF_8)); String jwt = Jwts.builder().signWith(secretKey, SignatureAlgorithm.HS256) .setExpiration(DateUtils.addMinutes(cruDate, minute)) .setIssuedAt(cruDate) .setId(UUID.randomUUID().toString().replaceAll("-", "").toUpperCase()) .addClaims(data) .compact(); return jwt; } // 读取Jwt public Claims readJWT(String jwt) throws Exception{ SecretKey secretKey = Keys.hmacShaKeyFor(selfKey.getBytes(StandardCharsets.UTF_8)); Claims body = Jwts.parserBuilder().setSigningKey(secretKey) .build().parseClaimsJws(jwt).getBody(); return body; } }
获取并响应Token
@PostMapping("/login") public RespResult userLogin(@RequestParam String phone, @RequestParam String pword, @RequestParam String scode) throws Exception{ RespResult result = RespResult.fail(); if (CommonUtil.checkPhone(phone) && (pword != null || pword.length() == 32)) { // 检查验证码是否正确 if (loginSmsService.checkSmsCode(phone, scode)) { // 查找是否存账号密码匹配用户 User user = userService.userLogin(phone, pword); if (user != null) { // 登录成功 , 生成Token Map<String ,Object> data = new HashMap<>(); data.put("uid",user.getId());// 设置荷载位uid // 设置120分钟过期,并获取Token String jwtToken = jwtUtile.creatJwt(data, 120); result = RespResult.ok(); // 响应Token result.setAccessToken(jwtToken); Map<String,Object> userInfo = new HashMap<>(); userInfo.put("uid",user.getId()); userInfo.put("phone",user.getPhone()); userInfo.put("name",user.getName()); result.setData(userInfo); }else { result.setRCode(RCode.PHONE_LOGIN_PASSWORD_INVALID); } } else { result.setRCode(RCode.SMS_CODE_INVALID); } } else { result.setRCode(RCode.REQUEST_PARAM_ERR); } return result; }
拦截器验证Token
public class TokenInterceptor implements HandlerInterceptor { private String secret = ""; public TokenInterceptor(String secret) { this.secret = secret; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1 如果是OPTIONS ,放行 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } boolean requestSend = false; try { // 请求头中的 uid String headerUid = request.getHeader("uid"); // 2 获取Token ,验证 String headToken = request.getHeader("Authorization"); if (StringUtils.isNotBlank(headToken)) { // Bearer。。 Authorization中 Authorization: Bearer <token> String jwt = headToken.substring(7); // 读jwt JwtUtile jwtUtile = new JwtUtile(secret); Claims claims = jwtUtile.readJWT(jwt); // 获取 Jwt中的uid Integer jwtUid = claims.get("uid", Integer.class); // 对比 headerUid 和 JwtUid if (headerUid.equals(String.valueOf(jwtUid))) { // token 和发起请求的用户是同一个, 请求可以被处理 requestSend = true; } } } catch (Exception e) { requestSend = false; } // token 没有通过,需要给vue错误提示 if (requestSend == false) { // 返回失败的JSON数据给前端 RespResult result = RespResult.fail(); result.setRCode(RCode.TOKEN_INVALID); // 使用HTTPServletResponse 输出JSON String respJson = JSONObject.toJSONString(result); response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); out.print(respJson); out.flush(); out.close(); } return requestSend; } }
前端
登录+存储Token
userLogin(){ this.checkPhone(); this.checkPassword(); this.checkCode(); if( this.phoneErr == '' && this.passwordErr == '' & this.codeErr==''){ //发起登录请求 let param = { phone:this.phone,pword:md5(this.password),scode:this.code } doPost('/v1/user/login',param).then(resp=>{ if( resp && resp.data.code == 1000){ //登录成功,存储数据到localStorage,存的是字符串 window.localStorage.setItem("token",resp.data.accessToken); //把 json对象转为 string window.localStorage.setItem("userinfo", JSON.stringify(resp.data.data)); //登录之后,如果name没有值,需要进入到实名认证页面 //如果name有值,进入到用户中心 if(resp.data.data.name == ''){ //实名认证 this.$router.push({ path:'/page/user/realname' }) } else { //用户中心 this.$router.push({ path:'/page/user/usercenter' }) } } }) } }
前端拦截器
import axios from 'axios' //创建拦截器 axios.interceptors.request.use(function (config) { // 在需要用户登录后的操作,在请求的url中加入token // 判断访问服务器的url地址, 需要提供身份信息,加入token let storageToken = window.localStorage.getItem("token"); let userinfo = window.localStorage.getItem("userinfo"); if (storageToken && userinfo) { // 添加需要Token验证的url if (config.url == '/v1/user/realname' || config.url == '/v1/user/usercenter' || config.url == '/v1/recharge/records' || config.url=='/v1/invest/product') { //在header中传递token 和一个userId config.headers['Authorization'] = 'Bearer ' + storageToken; config.headers['uid'] = JSON.parse(userinfo).uid; } } return config; }, function (err) { console.log("请求错误" + err); }) //创建应答拦截器,统一对错误处理, 后端返回 code > 1000 都是错误 axios.interceptors.response.use(function (resp) { if (resp && resp.data.code > 1000) { let code = resp.data.code; if (code == 3000) { //token无效,重新登录 window.location.href = '/page/user/login'; } else { layx.msg(resp.data.msg, {dialogIcon: 'warn', position: 'ct'}); } } return resp; }, function (err) { console.log("应答拦截器错误:" + err) //回到首页 window.location.href = '/'; })
其他请求正常请求就行
axios.get("/v1/user/usercenter").then(resp => { if (resp && resp.data.code == 1000) { this.userBaseInfo = resp.data.data; } });
到此这篇关于SpringBoot+vue+Axios实现Token令牌的文章就介绍到这了,更多相关SpringBoot vue实现Token令牌内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!