一.什么是JWT JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名 简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息 ?
?
{
id:888,
name:'wnn',
expire:10000
}
funtion 加密(object, appsecret){
xxxx
return base64( token);
}
function 解密(token ,appsecret){
xxxx
//成功返回true,失败返回false
}
二、
优点: ?? ?生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库 ?? ?存储在客户端,不占用服务端的内存资源 缺点: ?? ?token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等 ?? ?如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥
三、
JWT格式组成 头部、负载、签名 header+payload+signature ?? ?头部:主要是描述签名算法 ?? ?负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户 ?? ?签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token 四、关于jwt客户端存储 ?? ?可以存储在cookie,localstorage和sessionStorage里面
五、上代码
pom.xml
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
JWT工具类,参数是业务里面的用户登录VO-->loginuser
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import net.wnn.model.LoginUser;
import java.util.Date;
@Slf4j
public class JWTUtil {
/**
* 主题
*/
private static final String SUBJECT = "wnn";
/**
* 加密密钥
*/
private static final String SECRET = "wnn.net168";
/**
* 令牌前缀
*/
private static final String TOKNE_PREFIX = "dcloud-link";
/**
* token过期时间,7天
*/
private static final long EXPIRED = 1000 * 60 * 60 * 24 * 7;
/**
* 生成token
*
* @param loginUser
* @return
*/
public static String geneJsonWebTokne(LoginUser loginUser) {
if (loginUser == null) {
throw new NullPointerException("对象为空");
}
String token = Jwts.builder().setSubject(SUBJECT)
//配置payload
.claim("head_img", loginUser.getHeadImg())
.claim("account_no", loginUser.getAccountNo())//根据业务,在payload中加入字段
.claim("username", loginUser.getUsername())//根据业务,在payload中加入字段
.claim("mail", loginUser.getMail())//根据业务,在payload中加入字段
.claim("phone", loginUser.getPhone())//根据业务,在payload中加入字段
.claim("auth", loginUser.getAuth())//根据业务,在payload中加入字段
.setIssuedAt(new Date())//令牌发布时间
.setExpiration(new Date(CommonUtil.getCurrentTimestamp() + EXPIRED))//令牌过期时间
.signWith(SignatureAlgorithm.HS256, SECRET).compact();
token = TOKNE_PREFIX + token;
return token;
}
/**
* 解密jwt
* @param token
* @return
*/
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKNE_PREFIX, "")).getBody();
return claims;
} catch (Exception e) {
log.error("jwt 解密失败");
return null;
}
}
}
String token = JWTUtil.geneJsonWebTokne(loginUser);
将获取到的token信息返回给前端。
?后端将token加入threadlocal,在项目之间传递
开发登录拦截器 ?? ?解密JWT ?? ?传递登录用户信息 attribute传递、threadLocal传递
拦截器
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import net.wnn.enums.BizCodeEnum;
import net.wnn.model.LoginUser;
import net.wnn.util.CommonUtil;
import net.wnn.util.JWTUtil;
import net.wnn.util.JsonData;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return true;
}
String accessToken = request.getHeader("token");
if (StringUtils.isBlank(accessToken)) {
accessToken = request.getParameter("token");
}
if (StringUtils.isNotBlank(accessToken)) {
Claims claims = JWTUtil.checkJWT(accessToken);
if (claims == null) {
//未登录
CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
return false;
}
Long accountNo = Long.parseLong(claims.get("account_no").toString());
String headImg = (String) claims.get("head_img");
String username = (String) claims.get("username");
String mail = (String) claims.get("mail");
String phone = (String) claims.get("phone");
String auth = (String) claims.get("auth");
LoginUser loginUser = LoginUser.builder()
.accountNo(accountNo)
.auth(auth)
.phone(phone)
.headImg(headImg)
.mail(mail)
.username(username)
.build();
//request.setAttribute("loginUser",loginUser);
//通过threadlocal
threadLocal.set(loginUser);
return true;
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
threadLocal.remove();
}
}
/**
* 响应json数据给前端
*
* @param response
* @param obj
*/
public static void sendJsonMessage(HttpServletResponse response, Object obj) {
response.setContentType("application/json; charset=utf-8");
try (PrintWriter writer = response.getWriter()) {
writer.print(JsonUtil.obj2Json(obj));
response.flushBuffer();
} catch (IOException e) {
log.warn("响应json数据给前端异常:{}", e);
}
}
拦截器配置 ? ? 可配置项目中需要拦截的路径和不拦截路径
import lombok.extern.slf4j.Slf4j;
import net.wnn.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
//添加拦截的路径
.addPathPatterns("/api/account/*/**", "/api/traffic/*/**")
//排除不拦截
.excludePathPatterns(
"/api/account/*/register","/api/account/*/upload","/api/account/*/login",
"/api/notify/v1/captcha","/api/notify/*/send_code");
}
}
项目中任意地方获取用户登录信息 可使用:
LoginUser loginUser = LoginInterceptor.threadLocal.get();
|