1前言
1.1. JWT 介绍
1.2. JWT 特点
2应用场景
3 JWT的结构:在紧凑的形式中,JWT包含三个由点(.)分隔的部分,它们分别是
//下面这种形式 x代表头,y代表负载,z代表签名
xxxxx.yyyyy.zzzzz
3.1分别对JWT三部分进行讲解
3.11 Header:通常由两部分组成:令牌的类型,即JWT。和常用的散列算法,如HMAC SHA256或RSA。
{
"alg": "HS256",
"typ": "JWT"
}
3.2Payload:这里放声明内容,可以说就是存放沟通讯息的地方,在定义上有3种声明(Claims):
//对于已签名的令牌,此信息尽管受到篡改保护,但任何人都可以阅读。 除非加密,否则不要将秘密信息放在JWT的有效内容或标题元素中。所以在负载中不要存放用户的密码等敏感信息。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
3.3Signature:第三部分signature用来验证发送请求者身份,由前两部分加密形成。要创建签名部分,您必须采用编码标头,编码有效载荷,秘钥,标头中指定的算法并签名。
4JWT工作原理:在身份验证中,当用户使用他们的凭证成功登录时,JSON Web Token将被返回并且必须保存在本地(通常在本地存储中,但也可以使用Cookie),而不是在传统方法中创建会话 服务器并返回一个cookie。
执行流程如下图:
5项目开发中的代码编写
5.1代码适用场景:当用户第一次登陆的时候产生token返回给前端,后续访问服务器时前端把token放到Header中,后端进行解析查看是否正确,正确放行,错误返回登陆页面。
5.2引入依赖
- 引入依赖前先多嘴两句:第一句,jwt的框架有很多,第二句,今天介绍一个现在比较流行的
- 参考网址:(https://www.jianshu.com/p/dfa089448348)
//此实验基于springboot2.1.5版本
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>7.1</version>
</dependency>
5.3登录逻辑
/**
* 登录
*
* @param dto
* @author Lys
* @date: 4:25 下午 2019/5/29
*
* @version: 1.2.6
*/
@Override
public SysUserVO login(SysUserLoginDTO dto) {
SysUserNew sysUserNew = SysUserNew.builder().phoneNumber(dto.getPhoneNumber()).build();
sysUserNew = userNewMapper.selectOne(sysUserNew);
SysUserVO sysUserVO = BeanUtils.copyProperties(sysUserNew, SysUserVO.class);
// 如果为 null,该账号不存在
if (sysUserVO == null) {
ExceptionHandler.publish("1204");
}
String password = dto.getPassword();
if (!password.equals(sysUserNew.getPassword())) {
ExceptionHandler.publish("1206");
}
/**
* 以上是对登录账号的正确性进行逻辑判断
* 以下是对账号登录正确后生成token的代码
*/
//assert sysUserVO != null;
String token = TokenUtils.createToken(sysUserVO.getPhoneNumber());
sysUserVO.setToken(token);
TokenCache tokenCache = BeanUtils.copyProperties(sysUserVO, TokenCache.class);
//ip()是判断是否是同一ip登录,根据功能需要也可以去掉
tokenCache.setIp(ip());
log.info("缓存用户信息{}", JSON.toJSONString(tokenCache));
//存入redis,一手机号为key
stringRedisTemplate.opsForValue().set(sysUserVO.getPhoneNumber(), JSON.toJSONString(tokenCache));
//把带有token的对象返回前端
return sysUserVO;
}
5.4生成token的通用代码
public class TokenUtils {
/**
* Function: Payload负载信息的一些设置,为生成token做准备;不仅可以添加示例代码中的三条,还可以添 加好多条,负载里的属性可以解析出来
*
* @return Token
* @author Zhang
**/
public static String createToken(String uid) {
Map<String, Object> map = new HashMap<>(3);
//token过期时间;表示now向后推迟1小时
LocalDateTime now= LocalDateTime.now();
//当前时间向后推迟1小时
LocalDateTime localDateTime = now.plusHours(1);
map.put("uid", uid);
map.put("startTime", now.getTime());
map.put("endTime", exp.getTime());
try {
return creatToken(map);
} catch (JOSEException e) {
log.error("生成token失败", e);
}
return null;
}
/**
* Function: 创建token
*
* @return token
* @author Zhang
**/
public static String creatToken(Map<String, Object> payloadMap) throws JOSEException {
// 先建立一个头部Header
JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256);
// 建立一个载荷Payload
Payload payload = new Payload(new JSONObject(payloadMap));
// 将头部和载荷结合在一起
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 建立一个密匙
JWSSigner jwsSigner = new MACSigner(getFinalSecret());
// 签名
jwsObject.sign(jwsSigner);
// 生成token
return jwsObject.serialize();
}
/**
* Function: 根据token解析获取mobile信息
*
* @author Zhang
**/
public static String validTokenGetMobile(String token) {
try {
// 解析token
JWSObject jwsObject = JWSObject.parse(token);
// 获取到载荷
Payload payload = jwsObject.getPayload();
// 建立一个解锁密匙
JWSVerifier jwsVerifier = new MACVerifier(getFinalSecret());
if (jwsObject.verify(jwsVerifier)) {
JSONObject jsonObject = payload.toJSONObject();
String uid = jsonObject.get(Constant.UID.getChineseMsg()).toString();
if (StringUtil.isNotEmpty(uid)) {
return uid;
}
}
} catch (ParseException e) {
logger.error("解析TOKEN出错",e);
} catch (JOSEException e) {
logger.error("解析TOKEN出错",e);
}
return null;
}
/**
* Function: 获取加密后的密钥
*
* @return secret
* @author Zhang
**/
private static String getFinalSecret() {
StringBuffer sb = new StringBuffer(MD5Util.md5(SECRET)).append(MD5Util.md5(MD5Util.md5(SECRET)));
return sb.toString();
}
}
5.5做请求拦截器
package com.xm.hardwaremanagement.interception;
import com.alibaba.fastjson.JSONObject;
import com.xm.hardwaremanagement.exception.ExceptionHandler;
import com.xm.hardwaremanagement.util.StringUtil;
import com.xm.hardwaremanagement.util.ThreadLocalUtils;
import com.xm.hardwaremanagement.util.dto.SysUserDTO;
import com.xm.hardwaremanagement.util.redis.RedisService;
import com.xm.hardwaremanagement.util.tkmybatis.BeanUtils;
import com.xm.hardwaremanagement.util.tokenUtil.ITokenVerification;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.*;
import static com.xm.hardwaremanagement.common.RedisKey.token;
/**
* 登录拦截器
*
* @author csw
* @date 2020-05-026
*/
@Component
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
@Resource
private RedisService redisService;
@Resource
private ITokenVerification iTokenVerification;
@Override
@SuppressWarnings("unchecked")
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//获取请求头中token数据
String token = request.getHeader("Authorization");
if (StringUtil.isEmpty(token)) {
//非平台端发起请求不做校验
return true;
}
//解析token数据中手机号
String phoneNumber = TokenUtils.validTokenGetMobile(token);
if (StringUtil.isEmpty(phoneNumber)) {
ExceptionHandler.publish("1012");
}
String object = redisService.get(phoneNumber);
log.info("object:{}", object);
SysUserDTO sysUserDTO = JSONObject.parseObject(object, SysUserDTO.class);
//获取ip(根据功能需求要求取舍)
String ip = getIpAddress(request);
if (!ip.equals(sysUserDTO.getIp())) {
//请求ip与登录ip不一致 直接拒绝访问
ExceptionHandler.publish("1010");
}
if (!token.equals(sysUserDTO.getToken())) {
ExceptionHandler.publish("1010");
}
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
5.6 配置拦截规则
@Configuration
public class WebAppConfig extends WebMvcConfigurationSupport {
@Autowired
LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> patterns = new ArrayList<>();
patterns.add("/userLogin/getAuthenticationCode");
patterns.add("/userLogin/register");
patterns.add("/userLogin/forgetPassword");
patterns.add("/userLogin/login");
patterns.add("/equipmentRemote/**");
patterns.add("/deviceManagement/thirdDeleteEquipment");
patterns.add("/deviceManagement/settingFaceCapacity");
patterns.add("/deviceManagement/judgeEquipmentStatus");
patterns.add("/deviceManagement/selectEquipmentCount");
patterns.add("/app-release.apk");
patterns.add("/employee/**");
patterns.add("/thirdPlatform/**");
patterns.add("/thirdParty/bindThirdParty");
patterns.add("/swagger-resources/**");
patterns.add("/swagger-ui.html/**");
// addInterceptor 使用自定义拦截器的某一个
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
registry.addInterceptor(logInterceptor).addPathPatterns(patterns)
.excludePathPatterns("/wheatSunshine/auth");
//拦截配置生效
super.addInterceptors(registry);
}
}
5.7附加一个类:获取请求ip地址
package com.xm.hardwaremanagement.base;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.function.Function;
public abstract class Base{
/**
* 得到request对象
*/
public static HttpServletRequest getRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 参考文章: http://developer.51cto.com/art/201111/305181.htm
*
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
* 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
*/
public static String getIpAddress() {
HttpServletRequest request=getRequest();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}