SpringBoot集成JWT
一、jwt的简单介绍
- header:头部,是用来描述这个token是什么类型,采用了何种加密算法;token中header是经过base64编码的
- payload:荷载,用来存放需要传递的数据。官方提供的几个标准字段,同时也可以自己往里面加自定义的字段和内容,用来存放一些不敏感的用户信息。可以简单的把它想像成一个Map集合;token中payload也是经过base64编码的
- signature:签名,主要是将header和payload的base64编码后内容用点拼接在一起然后进行加密生成签名。服务端需要利用这签名来校验token是否被篡改(验签)
二、Maven依赖版本说明
<!-- SpringBoot 版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--jwt 依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- SpringBoot 版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--jwt 依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
三、token生成和解析工具类及token认证拦截器的编写
(1)token生成和解析工具类编写
- 生成jwt标准的token;生成token时支持把不敏感的用户信息放在token里面,后续解析token后可以直接使用这些用户信息
- 解析token,校验token是否过期和篡改
package com.psx.gqxy.web.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* JwtUtils
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @version 1.0
*/
@Slf4j
public final class JwtUtils {
/** 存放token的请求头对应的key的名字 */
private static String headerKey = "token";
/** 加密的secret */
private static String secret = "zxyTestSecret";
/** 过期时间,单位为秒 */
private static long expire = 1800L;
static {
// TODO 上面变量的值应该从配置文件中读取,方便测试这里就不从配置文件中读取
// 利用配置文件中的值覆盖静态变量初始化的值
}
/**
* 生成jwt token
*/
public static String generateToken(Map<String, Object> userInfoMap) {
if (Objects.isNull(userInfoMap)) {
userInfoMap = new HashMap<>();
}
// 过期时间
Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT") // 设置头部信息
.setClaims(userInfoMap) // 装入自定义的用户信息
.setExpiration(expireDate) // token过期时间
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 校验token并解析token
* @param token
* @return Claims:它继承了Map,而且里面存放了生成token时放入的用户信息
*/
public static Claims verifyAndGetClaimsByToken(String token) {
try {
/* 如果过期或者是被篡改,则会抛异常.
注意点:只有在生成token设置了过期时间(setExpiration(expireDate))才会校验是否过期,
可以参考源码io.jsonwebtoken.impl.DefaultJwtParser的299行。
拓展:利用不设置过期时间就不校验token是否过期的这一特性,我们不设置Expiration;
而采用自定义的字段来存放过期时间放在Claims(可以简单的理解为map)中;
通过token获取到Claims后自己写代码校验是否过期。
通过这思路,可以去实现对过期token的手动刷新
*/
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
log.debug("verify token error:[{}] ", ExceptionUtils.getStackTrace(e));
return null;
}
}
public static String getHeaderKey() {
return headerKey;
}
}
package com.psx.gqxy.web.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* JwtUtils
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @version 1.0
*/
@Slf4j
public final class JwtUtils {
/** 存放token的请求头对应的key的名字 */
private static String headerKey = "token";
/** 加密的secret */
private static String secret = "zxyTestSecret";
/** 过期时间,单位为秒 */
private static long expire = 1800L;
static {
// TODO 上面变量的值应该从配置文件中读取,方便测试这里就不从配置文件中读取
// 利用配置文件中的值覆盖静态变量初始化的值
}
/**
* 生成jwt token
*/
public static String generateToken(Map<String, Object> userInfoMap) {
if (Objects.isNull(userInfoMap)) {
userInfoMap = new HashMap<>();
}
// 过期时间
Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT") // 设置头部信息
.setClaims(userInfoMap) // 装入自定义的用户信息
.setExpiration(expireDate) // token过期时间
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 校验token并解析token
* @param token
* @return Claims:它继承了Map,而且里面存放了生成token时放入的用户信息
*/
public static Claims verifyAndGetClaimsByToken(String token) {
try {
/* 如果过期或者是被篡改,则会抛异常.
注意点:只有在生成token设置了过期时间(setExpiration(expireDate))才会校验是否过期,
可以参考源码io.jsonwebtoken.impl.DefaultJwtParser的299行。
拓展:利用不设置过期时间就不校验token是否过期的这一特性,我们不设置Expiration;
而采用自定义的字段来存放过期时间放在Claims(可以简单的理解为map)中;
通过token获取到Claims后自己写代码校验是否过期。
通过这思路,可以去实现对过期token的手动刷新
*/
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
log.debug("verify token error:[{}] ", ExceptionUtils.getStackTrace(e));
return null;
}
}
public static String getHeaderKey() {
return headerKey;
}
}
(2)token身份认证拦截器的编写
- 1)拦截器拦截到请求后,拿请求头中的token,如果不存在只直接response输出token不能为空
- 2)拿到token后,进行token的解析,校验是否篡改或者过期。如果被篡改或者过期只直接response输出token已失效
- 3)如果校验都通过了,则把token中解析出的用户信息放在request请求域中,方便后续Controller方法取用户信息
package com.psx.gqxy.web.jwt;
import com.alibaba.fastjson.JSON;
import com.psx.gqxy.common.base.CommonConstant;
import com.psx.gqxy.common.base.ModelResult;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* jwtToken校验拦截器
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @version 1.0
*/
public class JwtInterceptor extends HandlerInterceptorAdapter {
public static final String USER_INFO_KEY = "user_info_key";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取用户 token
String token = request.getHeader(JwtUtils.getHeaderKey());
if (StringUtils.isBlank(token)) {
token = request.getParameter(JwtUtils.getHeaderKey());
}
// token为空
if(StringUtils.isBlank(token)) {
this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
JwtUtils.getHeaderKey() + " can not be blank",
response);
return false;
}
// 校验并解析token,如果token过期或者篡改,则会返回null
Claims claims = JwtUtils.verifyAndGetClaimsByToken(token);
if(null == claims){
this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
JwtUtils.getHeaderKey() + "失效,请重新登录",
response);
return false;
}
// 校验通过后,设置用户信息到request里,在Controller中从Request域中获取用户信息
request.setAttribute(USER_INFO_KEY, claims);
return true;
}
/**
* 利用response直接输出错误信息
* @param code
* @param msg
* @param response
* @throws IOException
*/
private void writerErrorMsg (String code, String msg, HttpServletResponse response) throws IOException {
ModelResult<Void> result = new ModelResult<>();
result.setCode(code);
result.setMsg(msg);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(result));
}
}
package com.psx.gqxy.web.jwt;
import com.alibaba.fastjson.JSON;
import com.psx.gqxy.common.base.CommonConstant;
import com.psx.gqxy.common.base.ModelResult;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* jwtToken校验拦截器
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @version 1.0
*/
public class JwtInterceptor extends HandlerInterceptorAdapter {
public static final String USER_INFO_KEY = "user_info_key";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取用户 token
String token = request.getHeader(JwtUtils.getHeaderKey());
if (StringUtils.isBlank(token)) {
token = request.getParameter(JwtUtils.getHeaderKey());
}
// token为空
if(StringUtils.isBlank(token)) {
this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
JwtUtils.getHeaderKey() + " can not be blank",
response);
return false;
}
// 校验并解析token,如果token过期或者篡改,则会返回null
Claims claims = JwtUtils.verifyAndGetClaimsByToken(token);
if(null == claims){
this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
JwtUtils.getHeaderKey() + "失效,请重新登录",
response);
return false;
}
// 校验通过后,设置用户信息到request里,在Controller中从Request域中获取用户信息
request.setAttribute(USER_INFO_KEY, claims);
return true;
}
/**
* 利用response直接输出错误信息
* @param code
* @param msg
* @param response
* @throws IOException
*/
private void writerErrorMsg (String code, String msg, HttpServletResponse response) throws IOException {
ModelResult<Void> result = new ModelResult<>();
result.setCode(code);
result.setMsg(msg);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(result));
}
}
四、拦截器的配置和功能测试
(1)编写一个Controller
- 登录方法用来实现登录,登录成功后返回token
- test方法,主要通过拦截器拦截该方法的请求,当用户带有效的token访问时才允许访问该方法
package com.psx.gqxy.web.controller;
import com.psx.gqxy.common.base.CommonConstant;
import com.psx.gqxy.common.base.ModelResult;
import com.psx.gqxy.domain.dto.UserLoginDTO;
import com.psx.gqxy.web.jwt.JwtInterceptor;
import com.psx.gqxy.web.jwt.JwtUtils;
import io.jsonwebtoken.Claims;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* TestJwtController
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @Date 2019-07-14
* @version 1.0
*/
@RestController
@RequestMapping("jwt")
public class TestJwtController {
@PostMapping("login")
public ModelResult<String> login(@RequestBody UserLoginDTO dto) {
ModelResult<String> result = new ModelResult<>();
// 这里登录就简单的模拟下
if ("root".equals(dto.getUserName()) && "123456".equals(dto.getPassword())) {
Map<String, Object> userInfoMap = new HashMap<>();
userInfoMap.put("userName", "隔壁老王");
String token = JwtUtils.generateToken(userInfoMap);
result.setData(token);
} else {
result.setCode(CommonConstant.FAIL);
result.setMsg("用户名或密码错误");
}
return result;
}
@GetMapping("test")
public String test(HttpServletRequest request) {
// 登录成功后,从request中获取用户信息
Claims claims = (Claims) request.getAttribute(JwtInterceptor.USER_INFO_KEY);
if (null != claims) {
return (String) claims.get("userName");
} else {
return "fail";
}
}
}
package com.psx.gqxy.web.controller;
import com.psx.gqxy.common.base.CommonConstant;
import com.psx.gqxy.common.base.ModelResult;
import com.psx.gqxy.domain.dto.UserLoginDTO;
import com.psx.gqxy.web.jwt.JwtInterceptor;
import com.psx.gqxy.web.jwt.JwtUtils;
import io.jsonwebtoken.Claims;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* TestJwtController
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @Date 2019-07-14
* @version 1.0
*/
@RestController
@RequestMapping("jwt")
public class TestJwtController {
@PostMapping("login")
public ModelResult<String> login(@RequestBody UserLoginDTO dto) {
ModelResult<String> result = new ModelResult<>();
// 这里登录就简单的模拟下
if ("root".equals(dto.getUserName()) && "123456".equals(dto.getPassword())) {
Map<String, Object> userInfoMap = new HashMap<>();
userInfoMap.put("userName", "隔壁老王");
String token = JwtUtils.generateToken(userInfoMap);
result.setData(token);
} else {
result.setCode(CommonConstant.FAIL);
result.setMsg("用户名或密码错误");
}
return result;
}
@GetMapping("test")
public String test(HttpServletRequest request) {
// 登录成功后,从request中获取用户信息
Claims claims = (Claims) request.getAttribute(JwtInterceptor.USER_INFO_KEY);
if (null != claims) {
return (String) claims.get("userName");
} else {
return "fail";
}
}
}
(2)拦截器的配置
/**
* web相关的定制化配置
* @author ZENG.XIAO.YAN
* @version 1.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
// WebMvcConfigurerAdapter 这个类在SpringBoot2.0已过时,官方推荐直接实现WebMvcConfigurer 这个接口
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration jwtInterceptorRegistration = registry.addInterceptor(jwtInterceptor());
// 配置拦截器的拦截规则和放行规则
jwtInterceptorRegistration.addPathPatterns("/jwt/**")
.excludePathPatterns("/jwt/login");
}
}
/**
* web相关的定制化配置
* @author ZENG.XIAO.YAN
* @version 1.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
// WebMvcConfigurerAdapter 这个类在SpringBoot2.0已过时,官方推荐直接实现WebMvcConfigurer 这个接口
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration jwtInterceptorRegistration = registry.addInterceptor(jwtInterceptor());
// 配置拦截器的拦截规则和放行规则
jwtInterceptorRegistration.addPathPatterns("/jwt/**")
.excludePathPatterns("/jwt/login");
}
}
(3)相关测试
- 不带token访问 /jwt/test接口,被拦截器拦截;返回token不能为空; 效果如下图
- 访问登录接口,进行登录;登录成功,同时返回生成的token;效果如下图
- 带上登录成功返回的token访问/jwt/test接口,拦截器放行了请求,成功请求到了test方法;效果如下图
- 当token被篡改或者已过期时,访问/jwt/test接口,拦截器拦截了该请求,返回token已失效;效果如下图
五、小结
SpringBoot集成JWT的更多相关文章
- SpringBoot集成JWT 实现接口权限认证
JWT介绍 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的, 特别适用于分布式站点 ...
- SpringBoot集成JWT实现token验证
原文:https://www.jianshu.com/p/e88d3f8151db JWT官网: https://jwt.io/ JWT(Java版)的github地址:https://github. ...
- SpringBoot集成JWT实现权限认证
目录 一.JWT认证流程 二.SpringBoot整合JWT 三.测试 上一篇文章<一分钟带你了解JWT认证!>介绍了JWT的组成和认证原理,本文将介绍下SpringBoot整合JWT实现 ...
- springboot之Jwt验证
简介 什么是JWT(Json Web Token) jwt是为了在网络应用环境间传递声明而执行的一种基于json的开放标准.该token被设计紧凑且安全的,特别适用于SSO场景. jwt的声明一般被用 ...
- SpringBoot系列 - 集成JWT实现接口权限认证
会飞的污熊 2018-01-22 16173 阅读 spring jwt springboot RESTful API认证方式 一般来讲,对于RESTful API都会有认证(Authenticati ...
- SpringBoot 集成SpringSecurity JWT
目录 1. 简介 1.1 SpringSecurity 1.2 OAuth2 1.3 JWT 2. SpringBoot 集成 SpringSecurity 2.1 导入Spring Security ...
- Springboot shiro JWT集成总结
SpringBoot Shiro JWT 1.建表 DDL.sql CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, ` ...
- spring boot 2 集成JWT实现api接口认证
JSON Web Token(JWT)是目前流行的跨域身份验证解决方案.官网:https://jwt.io/本文使用spring boot 2 集成JWT实现api接口验证. 一.JWT的数据结构 J ...
- 轻松上手SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制实战
前言 我们知道在项目开发中,后台开发权限认证是非常重要的,springboot 中常用熟悉的权限认证框架有,shiro,还有就是springboot 全家桶的 security当然他们各有各的好处,但 ...
随机推荐
- SHELL脚本--变量
环境变量 环境变量就是运行在"环境"上下文的,在这个上下文都可以引用.例如,常见的cd.ls等命令严格来说应该使用绝对路径如/bin/ls来执行,由于/bin目录加入到了PATH环 ...
- 283.移动零 关于列表list与remove原理*****(简单)
题目: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 注意,该题目要求不开辟行的数组空间,在原数据上进行操作. 示例: 输入: [0,1,0,3,12 ...
- Python内容
1.Python介绍.计算机硬件.网络.变量.数据类型:列表+元组+字典+布尔值+字符串+数字+集合.格式化输出.if判断.for循环.while循环. 2.三元运算.字符编码.文件处理:r/rb(读 ...
- zz图像、神经网络优化利器:了解Halide
动图示例实在太好 图像.神经网络优化利器:了解Halide Oldpan 2019年4月17日 0条评论 1,327次阅读 3人点赞 前言 Halide是用C++作为宿主语言的一个图像处理相 ...
- clickhouse 离线/在线 安装和java通过jdbc链接
检查 需要确保是否x86_64处理器构架.Linux并且支持SSE 4.2指令集 grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 ...
- Salesforce 版本控制 - VS Code + GitHub + Salesforce
使用VS Code开发Salesforce有个很好的地方是可以联接GitHub进行代码版本控制,点击查看使用VS Code开发SalesForce 第一步:安装GIthub Desktop Githu ...
- Salesforce 开发整理(十一) 自定义放大镜查找效果
有时候在自定义的visualforce页面上,需要实现系统标准的查找样式,当不能使用标准的style的时候,我们只能选择自定义实现,下面分享一个demo,预览效果如下: 实现代码,Visualforc ...
- 姿态角(RPY)的优化目标函数
在Pose-Graph的过程中,如果使用G2O优化函数库,那么似乎是不用自己编写代价函数(也就是优化目标函数)的,因为G2O有封装好的SE3等格式,使得Pose-Graph的过程变得简单了,即只需要设 ...
- windows 排查javaWeb程序占用CPU过高问题(可追踪到问题代码所在行)
1.情景展示 java虚拟机占用这么高的CPU,肯定不正常! 2.原因分析 第一个是tomcat,正在运行java项目: 第二个是eclipse,因为eclipse的运行依赖于java. 现在的问 ...
- java常量池-字符串常量池、class常量池和运行时常量池
原文链接:http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/ 在java的内存分配中,经常听到很多关于常量 ...