JSON Web Token (JWT),它是目前最流行的跨域身份验证解决方案。现在的项目开发一般都是前端端分离,这就涉及到跨域和权鉴问题。
由三部分组成:头部(Header)、载荷(Payload)与签名(signature)
头部(Header):
头部信息由两部分组成1、令牌的类型,即JWT;2、使用的签名算法,例如HMASSHA256或RSA;
{
"alg": "HS256",
"typ": "JWT"
}
这个json中的typ属性,用来标识整个token字符串是一个JWT字符串;它的alg属性,用来说明这个JWT签发的时候所使用的签名和摘要算法,typ跟alg属性的全称其实是type algorithm,分别是类型跟算法的意思。之所以都用三个字母来表示,也是基于JWT最终字串大小的考虑,同时也是跟JWT这个名称保持一致,这样就都是三个字符了…typ跟alg是JWT中标准中规定的属性名称。
载荷(Payload):
payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的一组声明,这些声明被JWT标准称为claims,它的一个“属性值对”其实就是一个claim(要求), 每一个claim的都代表特定的含义和作用。
我们可以在claim里放一些业务信息。
签名(signature):
签名是把header和payload对应的json结构进行base64url编码之后得到的两个串用 '英文句点号' 拼接起来,然后根据header里面alg指定的签名算法生成出来的。
算法不同,签名结果不同。以alg: HS256为例来说明前面的签名如何来得到。
按照前面alg可用值的说明,HS256其实包含的是两种算法:HMAC算法和SHA256算法,前者用于生成摘要,后者用于对摘要进行数字签名。这两个算法也可以用HMACSHA256来统称
jwt数据结构图:
1.第一次发送登录请求,必然会携带用户信息uname和pwd
2.通过用户信息uname和pwd登录成功,会将用户信息通过jwt工具类生成一个加密的字符串
3.加密字符串 会以response header 响应头的形式 相应到前端
4.前端服务器会有响应拦截器拦截,截取到响应头承载的jwt串,又会放到Vuex中
5.当第二次请求,前端服务器中有一个请求拦截器,会将Vuex中的jwt串放入request header 请求当中
6.当请求通过跨域的方式到达后台服务器,后台服务器中又有一个过滤器,会截取到 request header 请求当中的jwt串
7.jwt工具类会对jwt串进行解析,解析成用户信息,最终进行校验
整体思路:
当前端访问后台登录接口login时,先根据用户名和密码判断用户表是否存在该用户,如果存在该用户,则生成jwt串,可以在jwt串里添加些业务信息(比如用登录账号,用户真实姓名等),并把jwt串返给前端
当前端拿到jwt串后放到所有请求的header中,比如token=jwt串
后端开发一个filter,拦截所有请求(除login请求外,因为login请求还没有生成jwt),并从request的header中获取jwt(即token的值),对jwt校验和获取jwt中的业务信息,在把这些业务信息放到request的header中,这样方便后端接口直接从header中获取
如果filter中jwt过期,或者校验失败,则返回给前端提示,前端返回登录页面,让用户重新登录。
1、在pom.xml引入依赖
com.auth0
java-jwt
3.8.3
2、开发jwt生成工具类,代码如下:
package com.lsl.exam.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.lsl.exam.entity.TabUser;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private static final long EXPIRE_TIME = 1000 * 60 * 60 *24;
//设置私钥
private static final String TOKEN_SECRET = "aa082c-66rt89-29sr3t-y9t7b8";
/**
* 创建携带自定义信息和声明的自定义私钥的jwt
* @param user 用户信息表
* @return jwt串
*/
public static String creatJwt(TabUser user){
//构建头部信息
Map header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//根据私钥构建密钥信息
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
//根据当前用户密码构建密钥信息
// Algorithm algorithm = Algorithm.HMAC256(user.getUserpwd());
//设置过期时间为当前时间一天后
Date nowDate = new Date();
Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
String jwt = JWT.create().withHeader(header)
.withClaim("account",user.getAccount())//业务信息:员工号
.withClaim("username",user.getUsername())//业务信息:员工姓名
.withClaim("rolename",user.getRoleName())//业务信息:角色
.withIssuer("SERVICE")//声明,签名是有谁生成 例如 服务器
.withNotBefore(new Date())//声明,定义在什么时间之前,该jwt都是不可用的
.withExpiresAt(expireDate)//声明, 签名过期的时间
.sign(algorithm);//根据algorithm生成签名
return jwt;
}
}
3、后端login接口逻辑如下:
package com.lsl.exam.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lsl.exam.entity.TabUser;
import com.lsl.exam.entity.backresult.ResultVO;
import com.lsl.exam.service.ITabRoleService;
import com.lsl.exam.service.IUserService;
import com.lsl.exam.utils.Base64Util;
import com.lsl.exam.utils.JwtUtil;
import com.lsl.exam.utils.ResultVoUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/exam")
public class UserController {
private static final Logger LOG = org.slf4j.LoggerFactory.getLogger("UserController");
@Autowired
IUserService userService;
@Autowired
ITabRoleService roleService;
@PostMapping(value = "login",produces = "application/json;charset=UTF-8")
@ResponseBody
public ResultVO login(@RequestBody Map params){
Map reuslt = new HashMap();
String account = params.get("account") == null ? "" : params.get("account").toString();
String pwd = params.get("pwd") == null ? "" : params.get("pwd").toString();
if ("".equals(account) || "".equals(pwd)){
return ResultVoUtil.error(30000,"用户名或者密码不能为空!");
}
//pwd解密
String decodePwd = Base64Util.decode(pwd);
if ("".contains(decodePwd)){
return ResultVoUtil.error(30000,"密码错误!");
}
TabUser user = userService.getOne(new QueryWrapper()
.eq("account",account)
.eq("userpwd",decodePwd));
if (null == user){
return ResultVoUtil.error(30000,"用户名或者密码错误");
}
//获取当前用户拥有的角色
String userId = user.getId();
Map roleMap = new HashMap();
roleMap.put("userId",userId);
List
4、开发filter,进行jwt校验
package com.lsl.exam.filter;
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lsl.exam.entity.backresult.ResultVO;
import com.lsl.exam.utils.ResultVoUtil;
import org.apache.tomcat.util.http.MimeHeaders;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* jwt校验过滤器
*/
@Component
@WebFilter(filterName = "jwtFilter",urlPatterns = {"/*"})
public class AuthJwtFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String url = httpServletRequest.getRequestURL().toString();
//配置不进行jwt校验的请求路径
List urlList = new ArrayList<>();
urlList.add("/exam/login");
boolean flag = false;
for (String strUrl : urlList){
if (url.contains(strUrl)){
flag = true;
}
}
try {
if (!flag){
String token = httpServletRequest.getHeader("token");
//校验token,jwt过期有jwt自行校验,如果超时了,会执行catch里代码
DecodedJWT decodeJwt = JWT.require(Algorithm.HMAC256("aa082c-66rt89-29sr3t-y9t7b8")).build().verify(token);
//获取jwt中的业务信息
String account = decodeJwt.getClaim("account").asString();
String username = decodeJwt.getClaim("username").asString();
String rolename = decodeJwt.getClaim("rolename").asString();
Map headerMap = new HashMap<>();
headerMap.put("account",account);
headerMap.put("username",username);
headerMap.put("rolename",rolename);
//把业务信息添加到request的header
addHeader(httpServletRequest,headerMap);
// Class superclass = servletRequest.getClass().getSuperclass().getSuperclass();
// Field requestField = superclass.getDeclaredField("request");
// requestField.setAccessible(true);
// RequestFacade requestFacadeInstance = (RequestFacade) requestField.get(servletRequest);
RequestFacade requestFacadeInstance = (RequestFacade)superclass3;
// Field requestField1 = requestFacadeInstance.getClass().getDeclaredField("request");
// requestField1.setAccessible(true);
// Object requestInstance = requestField1.get(requestFacadeInstance);
// Field coyoteRequestField = requestInstance.getClass().getDeclaredField("coyoteRequest");
// coyoteRequestField.setAccessible(true);
//
// Object coyoRequestInstance = requestField1.get(requestInstance);
// Field headersField = coyoRequestInstance.getClass().getDeclaredField("headers");
// headersField.setAccessible(true);
//
// MimeHeaders headers = (MimeHeaders) headersField.get(coyoRequestInstance);
// headers.removeHeader("token");
// headers.addValue("account").setString(account);
// headers.addValue("username").setString(username);
// headers.addValue("roleid").setString(roleid);
//
}
} catch (Exception e) {
//jwt校验失败,返给前端的code=1,前端要重定向到登录页面
PrintWriter writer = null;
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html; charset=utf-8");
try {
writer = servletResponse.getWriter();
ResultVO vo = ResultVoUtil.successLogout();
String msg = JSON.toJSONString(vo);
writer.println(msg);
} catch (IOException ex) {
} finally {
if (writer != null){
writer.close();
}
return;
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
/**
* 向request的header中放业务信息
* @param request
* @param headerMap
*/
private void addHeader(HttpServletRequest request, Map headerMap) {
if (headerMap==null||headerMap.isEmpty()){
return;
}
Class c=request.getClass();
//System.out.println(c.getName());
System.out.println("request实现类="+c.getName());
try{
Field requestField=c.getDeclaredField("request");
requestField.setAccessible(true);
Object o=requestField.get(request);
Field coyoteRequest=o.getClass().getDeclaredField("coyoteRequest");
coyoteRequest.setAccessible(true);
Object o2=coyoteRequest.get(o);
System.out.println("coyoteRequest实现类="+o2.getClass().getName());
Field headers=o2.getClass().getDeclaredField("headers");
headers.setAccessible(true);
MimeHeaders mimeHeaders=(MimeHeaders) headers.get(o2);
for (Map.Entry entry:headerMap.entrySet()){
mimeHeaders.removeHeader(entry.getKey());
mimeHeaders.addValue(entry.getKey()).setString(entry.getValue());
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void destroy() {
}
}