一、介绍
1.1、Session、Cookie、Token区别
session:存储再服务端,无法引用与分布式场景,并且需要占用服务端的资源
cookie:存储再客户端,适用于分布式场景,但是存在安全问题,不支持垮域访问
token:存储在localstorage中,更加灵活
1.2、如何实现登录认证
- 用户使用账号密码登录成功
- 通过JWT生成一串字符串作为Token,返回给前端
- 前端每次请求的时候都在请求头中携带上这个Token
- 后端每次都使用JWT对该Token进行校验,还原出一些用户信息,以此来判断用户是否登录
1.3、JWT组成
1.3.1、样例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjpbXSwiaWF0IjoxNjg0ODIzNzIzLCJleHAiOjE2ODQ4NTc1OTksImF1ZCI6IiIsImlzcyI6ImFxaSIsInN1YiI6IiJ9.W306xll5X2lHWL_B0AUZs7nf9e7Zn5QvgoasnviBeaQ
1.3.2、组成
JWT生成的字符串由三个部分组成
第一部分(header:JWT头,该部分只用Base64编码,未加密)
头部由2个属性组成
1、typ:令牌类型,固定设置为JWT
2、alg:加密算法,默认为HS256
第二部分(Payload:有效载荷,该部分只用Base64编码,未加密,避免存放隐私信息)
就是JWT的主体部分
1、issuer:发行者
2、IssuedAt:发布时间
3、expiration:到期时间
4、subject:主题
5、Not Before:生效时间
6、JWT ID:用于标识该 JWT
7、audience:用户
第三部分(Signature:签名,该部分是安全的,无法被解密)
该部分可以设置secret(俗称:加盐)的方式增加该部分的破解难度
1.4、优缺点
1.4.1、优点
- 是json格式,跨语言的
- 可以利用Payload存储一些非敏感的信息
- 不需要存储在服务端,可以用于分布式场景
- 一般存储在localStorage中,不存在于Cookie中,避免了一些安全性问题
- 便于实现单点登录功能
1.4.2、缺点
- 一旦生成就无法修改过期时间,需要搭配缓存来实现过期或者退出效果
- 同样Token过期无法进行续签
- 不可以在JWT中存储敏感信息
二、使用
2.1、引入POM依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
2.2、编写工具类
package com.xx.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;
import java.util.Date;/*** @author aqi* DateTime: 2020/11/9 3:27 下午* Description: JWT工具类*/
public class JwtUtils {/*** 设置Token过期时间*/public static final long EXPIRE = 1000 * 60 * 60 * 24;/*** 秘钥*/public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";/*** 生成Token字符串(Token组成:头+荷载+签名)* @param id 用户id* @param nickname 用户名称* @return Token字符串*/public static String getJwtToken(String id, String nickname){return Jwts.builder()// 设置JWT的头信息.setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")// 设置Token过期时间// 主题.setSubject("guli-user")// 签发时间.setIssuedAt(new Date())// 过期时间.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))// 设置Token主体部分.claim("id", id).claim("nickname", nickname)// 签名算法,秘钥.signWith(SignatureAlgorithm.HS256, APP_SECRET).compact();}/*** 判断token是否存在与有效* @param jwtToken token* @return 判断token是否存在与有效*/public static boolean checkToken(String jwtToken) {if(StringUtils.isEmpty(jwtToken)) {return false;}try {Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 判断token是否存在与有效* @param request request* @return 判断token是否存在与有效*/public static boolean checkToken(HttpServletRequest request) {try {String jwtToken = request.getHeader("token");if(StringUtils.isEmpty(jwtToken)) {return false;}Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 根据token获取会员id* @param request request* @return 会员id*/public static String getMemberIdByJwtToken(HttpServletRequest request) {String jwtToken = request.getHeader("token");if(StringUtils.isEmpty(jwtToken)) {return "";}Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);Claims claims = claimsJws.getBody();return (String)claims.get("id");}}
2.3、登录代码
public String login(UcenterMember member) {//获取登录手机号和密码String mobile = member.getMobile();String password = member.getPassword();//手机号和密码非空判断if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {throw new MyException(20001,"登录失败");}//判断手机号是否正确QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();wrapper.eq("mobile",mobile);UcenterMember mobileMember = baseMapper.selectOne(wrapper);//判断查询对象是否为空if(mobileMember == null) {//没有这个手机号throw new MyException(ResultCode.CODE_20001);}//判断密码if(!DigestUtils.md5DigestAsHex(password.getBytes()).equals(mobileMember.getPassword())) {throw new MyException(ResultCode.CODE_20001);}//判断用户是否禁用if(mobileMember.getIsDisabled()) {throw new MyException(ResultCode.CODE_20001);}//登录成功,生成token字符串return JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());}
2.4、使用JWT做拦截
/*** 根据token获取用户信息*/@GetMapping("/getMemberInfo")public R getMemberInfo(HttpServletRequest request) {//调用jwt工具类的方法。根据request对象获取头信息,返回用户idString memberId = JwtUtils.getMemberIdByJwtToken(request);if (memberId.isEmpty()) {return R.error().data("userInfo", "登陆失效,请重新登录");}//查询数据库根据用户id获取用户信息UcenterMember member = memberService.getById(memberId);return R.success().data("userInfo",member);}
三、续签和过期问题
3.1、思路
将生成的JWT字符串不返回给前端,而是自己生成一个UUID,将该UUID返回给前端,再使用该UUID存储