JWT
一、 JWT 实现无状态 Web 服务
1、什么是有状态
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。
例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。
缺点是什么?
- 服务端保存大量数据,增加服务端压力
- 服务端保存用户状态,无法进行水平扩展
- 客户端请求依赖服务端,多次请求必须访问同一台服务器
2、什么是无状态
服务器不需要记录客户端的状态信息,即:
- 服务端不保存任何客户端请求者信息
- 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份
带来的好处是什么呢?
- 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
- 服务端的集群和状态对客户端透明
- 服务端可以任意的迁移和伸缩
- 减小服务端存储压力
3、如何实现无状态
无状态登录的流程:
- 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
- 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
- 以后每次请求,客户端都携带认证的token
- 服务的对token进行解密,判断是否有效。
流程图:
客户端请求登录,登录之后颁发凭证
整个登录过程中,最关键的点是什么?
token的安全性
token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。
采用何种方式加密才是安全可靠的呢?
我们将采用:JWT + RSA非对称加密
4、JWT简介
JWT全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io
JWT包含三部分数据:
-
Header:头部,通常头部有两部分信息:
- 声明类型,这里是JWT 自描述信息
我们会对头部进行base64编码,得到第一部分数据 base64编码和解码的
-
Payload:载荷,就是有效数据,一般包含下面信息:
- 用户身份信息(注意,这里因为采用base64编码,可解码 是可逆的,因此不要存放敏感信息)
- 注册声明:如token的签发时间,过期时间,签发人等 这部分内容 好比身份证的信息
这部分也会采用base64编码,得到第二部分数据
-
Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法(不可逆的)生成一个签名。用于验证整个数据完整和可靠性。
生成的数据格式:
可以看到分为3段,每段就是上面的一部分数据
5、JWT交互流程
流程图:
步骤翻译:
- 1、用户登录
- 2、服务的认证,通过后根据secret生成token
- 3、将生成的token返回给浏览器
- 4、用户每次请求携带token
- 5、服务端利用公钥解读jwt签名,判断签名有效后,从Payload中获取用户信息
- 6、处理请求,返回响应结果
因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,完全符合了Rest的无状态规范。
6、非对称加密
加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密),加密技术的要点是加密算法,加密算法可以分为三类:
- 对称加密,如AES
- 基本原理:将明文分成N个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。
- 优势:算法公开、计算量小、加密速度快、加密效率高
- 缺陷:双方都使用同样密钥,安全性得不到保证
- 非对称加密,如RSA
- 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
- 私钥加密,持有私钥或公钥才可以解密
- 公钥加密,持有私钥才可解密
- 优点:安全,难以破解
- 缺点:算法比较耗时
- 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
- 不可逆加密,如MD5,SHA
- 基本原理:加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。
RSA算法历史:
1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA
目前流行的还有oauth2
二、nimbus-jose-jwt 库
nimbus-jose-jwt、jose4j、java-jwt 和 jjwt 是几个 Java 中常见的操作 JWT 的库。就使用细节而言,nimbus-jos-jwt(和jose4j)要好于 java-jwt 和 jjwt 。
nimbus-jose-jwt 官网:https://connect2id.com/products/nimbus-jose-jwt
所需坐标
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.11.1</version>
</dependency>
1、 JWT 和 JWS
这里我们需要了解下 JWT、JWS、JWE 三者之间的关系:
- JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用 JWT 在两个组织之间传递安全可靠的信息。
- JWS(JSON Web Signature)和 JWE(JSON Web Encryption)是 JWT 规范的两种不同实现,我们平时最常使用的实现就是 JWS 。
简单来说,JWT 和 JWS、JWE 类似于接口与实现类。由于,我们使用的是 JWS ,所以,后续内容中,就直接列举 JWS 相关类,不再细分 JWS 和 JWE 了,numbus-jose-jwt 中的 JWE 相关类和接口我们也不会使用到
2、加密算法
- 对称加密』指的是使用相同的秘钥来进行加密和解密,如果你的秘钥不想暴露给解密方,考虑使用非对称加密。在加密方和解密方是同一个人(或利益关系紧密)的情况下可以使用它。
- 『非对称加密』指的是使用公钥和私钥来进行加密解密操作。对于加密操作,公钥负责加密,私钥负责解密,对于签名操作,私钥负责签名,公钥负责验证。非对称加密在 JWT 中的使用显然属于签名操作。在加密方和解密方是不同人(或不同利益方)的情况下可以使用它。
nimbus-jose-jwt 支持的算法都在它的 JWSAlgorithm 和 JWEAlgorithm 类中有定义。
例如:JWSAlgorithm algorithm = JWSAlgorithm.HS256
3、核心 API
a、加密过程
-
在 nimbus-jose-jwt 中,使用 Header 类代表 JWT 的头部,不过,Header 类是一个抽象类,我们使用的是它的子类 JWSHeader 。
创建头部对象:
@Test public void createToken(){ //创建头部对象 JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法 .type(JOSEObjectType.JWT) // 静态常量 .build(); System.out.println(jwsHeader); }
你可以通过
.toBase64URL()
方法求得头部信息的 Base64 形式(这也是 JWT 中的实际头部信息): -
使用 Payload 类的代表 JWT 的荷载部分
创建荷载部对象:
@Test public void createToken(){ //创建头部对象 JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法 .type(JOSEObjectType.JWT) // 静态常量 .build(); System.out.println(jwsHeader); //创建载荷 Payload payload = new Payload("hello world"); System.out.println(payload); }
你可以通过
.toBase64URL()
方法求得荷载部信息的 Base64 形式(这也是 JWT 中的实际荷载部信息): -
签名部分
签名部分没有专门的类表示,只有通用类 Base64URL ,而且签名部分并非你自己创建出来的,而是靠
头部 + 荷载部 + 加密算法
算出来的。在 nimbus-jose-jwt 中,签名算法由 JWSAlgorithm 表示。注意:在创建 JWSHeader 对象时就需要指定签名算法,因为在标准中,头部需要保存签名算法名字。
用头部和荷载部分,再加上指定的签名算法和密钥来生成签名部分的过程,在 nimbus-jose-jwt 中被称为『签名(sign)』。nimbus-jose-jwt 专门提供了一个签名器 JWSSigner ,用来参与到签名过程中。密钥就是在创建签名器的时候指定的:
JWSSigner jwsSigner = new MACSigner("密钥"); //MACSigner()中要指定一个密钥
最终,整个 JWT 由一个 JWSObject 对象表示:
JWSObject jwsObject = new JWSObject(jwsHeader, payload); // 进行签名(根据前两部分生成第三部分) jwsObject.sign(jwsSigner);
在 nimbus-jose-jwt 中 JWSObject 是有状态的:未签名、已签名和签名中。很显然,在执行完
.sign()
方法之后,JWSObject 对象就变成了已签名状态。当然,我们最终『要』的是 JWT 字符串,而不是对象,这里接着对代表 JWT 的 JWSObject 对象调用
.serialize()
方法即可:String token = jwsObject.serialize();
完整示例:
@Test public void createToken() throws JOSEException { //创建头部对象 JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法 .type(JOSEObjectType.JWT) // 静态常量 .build(); //创建载荷 Payload payload = new Payload("hello world"); //创建签名器 JWSSigner jwsSigner = new MACSigner("woniu");//woniu为密钥 //创建签名 JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷 jwsObject.sign(jwsSigner);//再+签名部分 //生成token字符串 String token = jwsObject.serialize(); System.out.println(token); }
如果出现:com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits异常,是因为密钥的长度不够增加密钥长度即可
//创建签名器 JWSSigner jwsSigner = new MACSigner("woniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniu"); 生成的token如下: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.aGVsbG8gd29ybGQ.p1E7jMNXs4zzCHDDbFzQXOko6s9NtT7Sqt15-T-7KVY
b、获得token中的内容
/**
* 取出token中的内容
*/
@Test
public void getToken() throws IOException {
//获得token头部内容
String jwsHeader="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9";
//反编码
BASE64Decoder decoder=new BASE64Decoder();
byte[] txtByte= jwsHeader.getBytes(StandardCharsets.UTF_8);
System.out.println(new String(decoder.decodeBuffer(jwsHeader),"UTF-8"));
//获得token载荷中的内容
String payload="aGVsbG8gd29ybGQ";
System.out.println(new String(decoder.decodeBuffer(payload),"UTF-8"));
}
b、 解密
反向的解密和验证过程核心 API 就 2 个:JWSObject 的静态方法 parse 方法和验证其 JWSVerifier 对象。
JWSObject.parse()
方法是上面的 serialize 方法的反向操作,它可以通过一个 JWT 串生成 JWSObject 。有了 JWObject 之后,你就可以获得 header 和 payload 部分了。
如果你想直接验证 JWSObject 对象的合法性,你需要创建一个 JWSVerifier 对象。
//创建验证器
JWSVerifier jwsVerifier = new MACVerifier("密钥");//密钥要和加密时的相同
然后直接调用 jwsObject 对象的 verify 方法:
if (!jwsObject.verify(jwsVerifier)) {
throw new RuntimeException("token 签名不合法!");
}
案例二:验证token是否合法
/**
* 验证token
*/
@Test
public void validationToken() throws Exception {
//获得token
String token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.aGVsbG8gd29ybGQ.p1E7jMNXs4zzCHDDbFzQXOko6s9NtT7Sqt15-T-7KVYA";
//创建验证器
JWSVerifier jwsVerifier = new MACVerifier("woniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniu");
//获得签名对象
JWSObject jwsObject=JWSObject.parse(token);
//使用jws较验器,验证用户传递token是否合法
if (!jwsObject.verify(jwsVerifier)) {
throw new RuntimeException("token 签名不合法!");
}
}
4、创建jwt工具类
/**
* jwt工具类
*/
@Component
public class JwtUtils {
//使用uuid生成密钥
private final String secret= UUID.randomUUID().toString();
//用户数据的key
private final String usernameKey="usernameKey";
/**
* 生成token
* @param username 用户名
* @return
*/
public String createJwtToken(String username) throws Exception {
//创建头部对象
JWSHeader jwsHeader =
new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法
.type(JOSEObjectType.JWT) // 静态常量
.build();
//创建载荷
Map<String,Object> map=new HashMap<String,Object>();
map.put(usernameKey, username);
Payload payload= new Payload(map);
//创建签名器
JWSSigner jwsSigner = new MACSigner(secret);//密钥
//创建签名
JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
jwsObject.sign(jwsSigner);//再+签名部分
//生成token字符串
return jwsObject.serialize();
}
/**
* 验证jwt token是否合法
* @param jwtStr
* @return
*/
@SneakyThrows
public boolean verify(String jwtStr) {
JWSObject jwsObject=JWSObject.parse(jwtStr);
JWSVerifier jwsVerifier=new MACVerifier(secret);
return jwsObject.verify(jwsVerifier);
}
/**
* 从token中解析出用户名
* @param jwtStr
* @return
*/
@SneakyThrows
public String getUserNameFormJwt(String jwtStr){
JWSObject jwsObject=JWSObject.parse(jwtStr);
Map<String,Object> map=jwsObject.getPayload().toJSONObject();
return (String) map.get(usernameKey);
}
}
三、根据帐号密码登录
(不整合security 情况下用拦截器实现,整合时交给security管理)
1、登录步骤
-
输入帐号密码到数据库中查询
-
查询成功根据帐号生成token保存到redis中,同时发送token给浏览器
-
service代码
@Service @Transactional public class UserService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private JwtUtils jwtUtils; /** * 登录业务 * @param account 帐号 * @param password 密码 * @return */ @SneakyThrows public String getLogin(String account, String password){ String token=null; if(account.equals("tom") && password.equals("123")){ //登录成功,生成token保存到redis中 token=jwtUtils.createJwtToken(account); //保存token到redis中,有效期一分钟 ValueOperations<String,String> operations=redisTemplate.opsForValue(); operations.set("login:token:"+account, token, 60, TimeUnit.SECONDS); } return token; } }
-
controller代码
@RestController public class LoginController { @Autowired private UserService userService; /** * 登录 * @param account * @param password * @return */ @GetMapping("login") public ResponseResult<Object> login(String account,String password){ String token=userService.getLogin(account, password); if(token!=null){ return new ResponseResult<Object>(2000,"OK",token); } return new ResponseResult<Object>(2001,"帐号或密码错误"); } }
-
2、非登录步骤
-
发送请求将token一起发给服务器
-
服务端获得token对tken进行合法性验证
-
验证成功放行到controller,验证失败则提示
-
controller代码
@RestController public class UserController { /** * 查询用户列表 * @return */ public ResponseResult<Void> userList(){ return new ResponseResult<Void>(2000,"用户列表成功"); } }
-
拦截器代码
/** * token验证拦截器 */ @Component public class TokenHandler implements HandlerInterceptor { @Autowired private StringRedisTemplate redisMapper; @Autowired private JwtUtils jwtUtils; //json工具 private ObjectMapper objectMapper=new ObjectMapper(); /** * 在请求到达conttroller前判断权限 * @param request * @param response * @param object * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception { //获得请求头中的token String token=request.getHeader("x-jwt-token"); //请求头中没有token if(token==null){ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6001,"请求中没有token")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } //验证请求头中的token是否合法 if(jwtUtils.verify(token)==false){ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6002,"token不合法")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } //token合法则取出token中保存的帐号 String account=jwtUtils.getUserNameFormJwt(token); //根据帐号验证redis中保存的token是否存在 if(redisMapper.hasKey("login:token:"+account)==false){ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6003,"未登录或token已过期")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
-
springmvc配置类
@Configuration public class WebConfigurer implements WebMvcConfigurer { @Autowired private TokenHandler tokenHandler; /** * 注册自定义拦截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenHandler) .addPathPatterns("/**") //拦截哪些url /**表示拦截所有 .excludePathPatterns("/","/login"); //放行哪些请求 所有在登录前需要直接显示的就放行 } }
-
四、JWT手机登录
1、登录流程
-
输入手机号,根据手机号生成验证码保存到redis中并将验证码发给用户
- service
@Service @Transactional public class PhoneService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private JwtUtils jwtUtils; //生成验证码 public String createdCode(String phone) { // 随机产生4位激活码 String[] codes = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; String code = ""; Random random = new Random(); for (int i = 0; i < 4; i++) { String str = codes[random.nextInt(codes.length)]; code += str; } return code; } }
-
controller
@RestController public class PhoneController { @Autowired private PhoneService phoneService; /** * 根据手机号生成验证码 * @param phone * @return */ @GetMapping("phone/code") public ResponseResult<Object> getCode(String phone){ String code=phoneService.createdCode(phone); if(!code.equals("")){ //保存验证码到redis中,有效时间2分钟 //setIfAbsent:如果redis中存在某个键则添加失败 redisTemplate.opsForValue().setIfAbsent("login:code:"+phone, code, 2, TimeUnit.MINUTES); return new ResponseResult<Object>(2000,"OK",code); } else { return new ResponseResult<Object>(5000,"验证码发送失败"); } } }
-
发送手机号和验证码到服务器,服务端对验证码进行验证
-
验证成功,根据手机号到数据库中查询用户信息
-
没有查到信息表示手机号没注册
-
查到信息将用户帐号保存到redis中
- service
@Service @Transactional public class PhoneService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private JwtUtils jwtUtils; //生成验证码 public String createdCode(String phone) { // 随机产生4位激活码 String[] codes = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; String code = ""; Random random = new Random(); for (int i = 0; i < 4; i++) { String str = codes[random.nextInt(codes.length)]; code += str; } return code; } /** * 生成token * * @param account 帐号 * @return */ @SneakyThrows public String createdToken(String account) { //登录成功,生成token保存到redis中 String token = jwtUtils.createJwtToken(account); //保存token到redis中,有效期一分钟 ValueOperations<String, String> operations = redisTemplate.opsForValue(); operations.set("login:token:" + account, token, 60, TimeUnit.SECONDS); return token; } }
-
controller
@RestController public class PhoneController { @Autowired private PhoneService phoneService; @Autowired private StringRedisTemplate redisTemplate; /** * 根据手机号生成验证码,放入redis 设置过期时间 * @param phone * @return */ @GetMapping("phone/code") public ResponseResult<Object> getCode(String phone){ String code=phoneService.createdCode(phone); if(!code.equals("")){ //保存验证码到redis中,有效时间2分钟 //setIfAbsent:如果redis中存在某个键则添加失败 redisTemplate.opsForValue().setIfAbsent("login:code:"+phone, code, 2, TimeUnit.MINUTES); return new ResponseResult<Object>(2000,"OK",code); } else { return new ResponseResult<Object>(5000,"验证码发送失败"); } } /** * 该手机用发送的验证吗登录 * @param phone * @return */ @GetMapping("phone/login") public ResponseResult<Object> phoneLogin(String phone,String code){ //根据手机号取出redis中的验证码 String redisCode=redisTemplate.opsForValue().get("login:code:"+phone); if(redisCode==null){ return new ResponseResult<Object>(1009,"验证码已过期"); } if(!redisCode.toUpperCase().equals(code)){ return new ResponseResult<Object>(1010,"验证码输入错误"); } //模仿根据手机号去查询数据库,去查用户名信息,判断是否注册 List<User> list=new ArrayList<User>(); list.add(new User("tom", "123", "123123")); list.add(new User("jack", "123", "456456")); //模拟查询结果 User user=null; for(User u:list){ if(u.getPhone().equals(phone)){ user=u; break; } } if(user==null){ return new ResponseResult<Object>(2010,"手机号未注册"); } String token= phoneService.createdToken(user.getAccount()); return new ResponseResult<Object>(2000,"OK",token); } }
五、token续期
在实际的开发中,token不可能一直有效,比如30分钟内一次都没有进行操作,则认证过期,需要重新登录,如果一直在进行请求访问则token一直有效,直到上一次访问距离下一次访问的时间超过了30分钟,则认证过期。
基于上面的需求:如果用户一直在进行请求,则认证一直有效,如何做到这一点。需要对token进行续期操作。对token进行续期有很多种方案,常见的方案是:使用redis控制过期时间,使用双Token。我们采用redis方案
-
修改token拦截器
/** * token验证拦截器 */ @Component public class JwtTokenHandler implements HandlerInterceptor { @Autowired private StringRedisTemplate redisMapper; @Autowired private JwtUtils jwtUtils; //json工具 private ObjectMapper objectMapper=new ObjectMapper(); /** * 在请求到达conttroller前判断权限 * @param request * @param response * @param object * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception { //获得请求头中的token String token=request.getHeader("x-jwt-token"); //请求头中没有token if(token==null){ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6001,"未登录,请先登录")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } //验证请求头中的token是否合法 if(jwtUtils.verify(token)==false){ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6001,"未登录,请先登录")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } //token合法则取出token中保存的帐号 String account=jwtUtils.getUserNameFormJwt(token); //根据帐号验证redis中保存的token是否存在 if(redisMapper.hasKey("login:token:"+account)==false){ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6003,"token已过期")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } //对token进行续期 if(redisMapper.hasKey("login:token:"+account)){ String jwtToken=redisMapper.opsForValue().get("login:token:"+account); //判断是不是同一个token if(token.equals(jwtToken)){ //给login:token 重新设置时间 //redisMapper.getExpire("login:token:"+account); redisMapper.opsForValue().set("login:token:"+account, jwtToken,1, TimeUnit.MINUTES); return true; }else{ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6003,"未登录或token已过期")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
}else{ String json=objectMapper.writeValueAsString(new ResponseResult<Void>(6003,"未登录或token已过期")); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json); return false; } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); }
}