JWT(json web tokens)是目前比较流行的跨域认证解决方案;说通俗点就是比较流行的token生成和校验的方案。碰巧公司有个app的项目的token采用了jwt方案,因此记录下后端项目集成jwt的过程,方便后续查阅。

一、jwt的简单介绍

    jwt生成的token是一种无状态的token,服务端不需要对该token进行保存;它一般由客户端保存。客户端访问请求服务时,服务端对token进行校验,然后进行各种控制。下面直接拿一个生成好的token来讲解
        
    通过上图我们可以发现jwt生成的token是非常长的字符串,并且字符串中有2个小点("."),通过这2个小点我们可以把这token分成3部分。
      • header:头部,是用来描述这个token是什么类型,采用了何种加密算法;token中header是经过base64编码的
      • payload:荷载,用来存放需要传递的数据。官方提供的几个标准字段,同时也可以自己往里面加自定义的字段和内容,用来存放一些不敏感的用户信息。可以简单的把它想像成一个Map集合;token中payload也是经过base64编码的
      • signature:签名,主要是将header和payload的base64编码后内容用点拼接在一起然后进行加密生成签名。服务端需要利用这签名来校验token是否被篡改(验签)
    所以通俗的来讲,token = base64(header) + "." + base64(payload) + "." + 签名
    网上很多博文对jwt的介绍都比较详细,因此本文就不再详细的介绍jwt相关细节,重点放在java代码该怎么写。jwt相关详细介绍可以参考如下链接:
    下面直接上代码

二、Maven依赖版本说明

    pom中部分重要jar包依赖版本如下:
    <!-- 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>
14
 
1
    <!-- SpringBoot 版本 -->
2
    <parent>
3
        <groupId>org.springframework.boot</groupId>
4
        <artifactId>spring-boot-starter-parent</artifactId>
5
        <version>2.1.4.RELEASE</version>
6
        <relativePath/> <!-- lookup parent from repository -->
7
    </parent>
8

9
        <!--jwt 依赖-->
10
        <dependency>
11
            <groupId>io.jsonwebtoken</groupId>
12
            <artifactId>jjwt</artifactId>
13
            <version>0.9.1</version>
14
        </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;
} }
83
 
1
package com.psx.gqxy.web.jwt;
2
import io.jsonwebtoken.Claims;
3
import io.jsonwebtoken.Jwts;
4
import io.jsonwebtoken.SignatureAlgorithm;
5
import lombok.extern.slf4j.Slf4j;
6
import org.apache.commons.lang3.exception.ExceptionUtils;
7
import java.util.Date;
8
import java.util.HashMap;
9
import java.util.Map;
10
import java.util.Objects;
11

12
/**
13
 * JwtUtils
14
 * @author ZENG.XIAO.YAN
15
 * @blog https://www.cnblogs.com/zeng1994/
16
 * @version 1.0
17
 */
18
@Slf4j
19
public final class JwtUtils {
20

21
    /** 存放token的请求头对应的key的名字 */
22
    private static String headerKey = "token";
23
    /** 加密的secret */
24
    private static String secret = "zxyTestSecret";
25
    /** 过期时间,单位为秒 */
26
    private static long expire = 1800L;
27

28
    static {
29
        // TODO 上面变量的值应该从配置文件中读取,方便测试这里就不从配置文件中读取
30
        // 利用配置文件中的值覆盖静态变量初始化的值
31
    }
32

33

34
    /**
35
     * 生成jwt token
36
     */
37
    public static String generateToken(Map<String, Object> userInfoMap) {
38
        if (Objects.isNull(userInfoMap)) {
39
            userInfoMap = new HashMap<>();
40
        }
41
        //  过期时间
42
        Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);
43
        return Jwts.builder()
44
                .setHeaderParam("typ", "JWT")   // 设置头部信息
45
                .setClaims(userInfoMap)               // 装入自定义的用户信息
46
                .setExpiration(expireDate)            // token过期时间
47
                .signWith(SignatureAlgorithm.HS512, secret)
48
                .compact();
49
    }
50

51
    /**
52
     * 校验token并解析token
53
     * @param token
54
     * @return Claims:它继承了Map,而且里面存放了生成token时放入的用户信息
55
     */
56
    public static Claims verifyAndGetClaimsByToken(String token) {
57
        try {
58
            /* 如果过期或者是被篡改,则会抛异常.
59
                注意点:只有在生成token设置了过期时间(setExpiration(expireDate))才会校验是否过期,
60
                可以参考源码io.jsonwebtoken.impl.DefaultJwtParser的299行。
61
                拓展:利用不设置过期时间就不校验token是否过期的这一特性,我们不设置Expiration;
62
                      而采用自定义的字段来存放过期时间放在Claims(可以简单的理解为map)中;
63
                      通过token获取到Claims后自己写代码校验是否过期。
64
                      通过这思路,可以去实现对过期token的手动刷新
65
            */
66
            return Jwts.parser()
67
                    .setSigningKey(secret)
68
                    .parseClaimsJws(token)
69
                    .getBody();
70
        }catch (Exception e){
71
            log.debug("verify token error:[{}] ", ExceptionUtils.getStackTrace(e));
72
            return null;
73
        }
74
    }
75

76
    public static String getHeaderKey() {
77
        return headerKey;
78
    }
79

80

81
}
82

83

(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));
} }
65
 
1
package com.psx.gqxy.web.jwt;
2
import com.alibaba.fastjson.JSON;
3
import com.psx.gqxy.common.base.CommonConstant;
4
import com.psx.gqxy.common.base.ModelResult;
5
import io.jsonwebtoken.Claims;
6
import org.apache.commons.lang3.StringUtils;
7
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
8
import javax.servlet.http.HttpServletRequest;
9
import javax.servlet.http.HttpServletResponse;
10
import java.io.IOException;
11

12
/**
13
 * jwtToken校验拦截器
14
 * @author ZENG.XIAO.YAN
15
 * @blog https://www.cnblogs.com/zeng1994/
16
 * @version 1.0
17
 */
18
public class JwtInterceptor extends HandlerInterceptorAdapter {
19

20
    public static final String USER_INFO_KEY = "user_info_key";
21

22
    @Override
23
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
24
        //  获取用户 token
25
        String token = request.getHeader(JwtUtils.getHeaderKey());
26
        if (StringUtils.isBlank(token)) {
27
            token = request.getParameter(JwtUtils.getHeaderKey());
28
        }
29
        //  token为空
30
        if(StringUtils.isBlank(token)) {
31
            this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
32
                    JwtUtils.getHeaderKey() + " can not be blank",
33
                    response);
34
            return false;
35
        }
36
        //  校验并解析token,如果token过期或者篡改,则会返回null
37
        Claims claims = JwtUtils.verifyAndGetClaimsByToken(token);
38
        if(null == claims){
39
            this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
40
                    JwtUtils.getHeaderKey() + "失效,请重新登录",
41
                    response);
42
            return false;
43
        }
44
        //  校验通过后,设置用户信息到request里,在Controller中从Request域中获取用户信息
45
        request.setAttribute(USER_INFO_KEY, claims);
46
        return true;
47
    }
48

49
    /**
50
     * 利用response直接输出错误信息
51
     * @param code
52
     * @param msg
53
     * @param response
54
     * @throws IOException
55
     */
56
    private void writerErrorMsg (String code, String msg, HttpServletResponse response) throws IOException {
57
        ModelResult<Void> result = new ModelResult<>();
58
        result.setCode(code);
59
        result.setMsg(msg);
60
        response.setContentType("application/json;charset=UTF-8");
61
        response.getWriter().write(JSON.toJSONString(result));
62
    }
63

64
}
65

四、拦截器的配置和功能测试

(1)编写一个Controller

    写一个Controller,里面包含一登录方法和一个test方法
      • 登录方法用来实现登录,登录成功后返回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";
}
} }
53
 
1
package com.psx.gqxy.web.controller;
2
import com.psx.gqxy.common.base.CommonConstant;
3
import com.psx.gqxy.common.base.ModelResult;
4
import com.psx.gqxy.domain.dto.UserLoginDTO;
5
import com.psx.gqxy.web.jwt.JwtInterceptor;
6
import com.psx.gqxy.web.jwt.JwtUtils;
7
import io.jsonwebtoken.Claims;
8
import org.springframework.web.bind.annotation.*;
9
import javax.servlet.http.HttpServletRequest;
10
import java.util.HashMap;
11
import java.util.Map;
12

13
/**
14
 * TestJwtController
15
 * @author ZENG.XIAO.YAN
16
 * @blog https://www.cnblogs.com/zeng1994/
17
 * @Date 2019-07-14
18
 * @version 1.0
19
 */
20

21
@RestController
22
@RequestMapping("jwt")
23
public class TestJwtController {
24

25
    @PostMapping("login")
26
    public ModelResult<String> login(@RequestBody UserLoginDTO dto) {
27
        ModelResult<String> result = new ModelResult<>();
28
        // 这里登录就简单的模拟下
29
        if ("root".equals(dto.getUserName()) && "123456".equals(dto.getPassword())) {
30
            Map<String, Object> userInfoMap = new HashMap<>();
31
            userInfoMap.put("userName", "隔壁老王");
32
            String token = JwtUtils.generateToken(userInfoMap);
33
            result.setData(token);
34
        } else {
35
            result.setCode(CommonConstant.FAIL);
36
            result.setMsg("用户名或密码错误");
37
        }
38
        return result;
39
    }
40

41
    @GetMapping("test")
42
    public String test(HttpServletRequest request) {
43
        // 登录成功后,从request中获取用户信息
44
        Claims claims = (Claims) request.getAttribute(JwtInterceptor.USER_INFO_KEY);
45
        if (null != claims) {
46
            return (String) claims.get("userName");
47
        } else {
48
            return "fail";
49
        }
50
    }
51

52
}
53

(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");
}
}
x
 
1
/**
2
 * web相关的定制化配置
3
 * @author ZENG.XIAO.YAN
4
 * @version 1.0
5
 */
6
@Configuration
7
public class WebConfig implements WebMvcConfigurer {
8
    // WebMvcConfigurerAdapter 这个类在SpringBoot2.0已过时,官方推荐直接实现WebMvcConfigurer 这个接口
9

10
    @Bean
11
    public JwtInterceptor jwtInterceptor() {
12
        return new JwtInterceptor();
13
    }
14

15
    @Override
16
    public void addInterceptors(InterceptorRegistry registry) {
17
        InterceptorRegistration jwtInterceptorRegistration = registry.addInterceptor(jwtInterceptor());
18
        // 配置拦截器的拦截规则和放行规则
19
        jwtInterceptorRegistration.addPathPatterns("/jwt/**")
20
                .excludePathPatterns("/jwt/login");
21
    }
22
}    

(3)相关测试

    • 不带token访问 /jwt/test接口,被拦截器拦截;返回token不能为空; 效果如下图
                

    • 访问登录接口,进行登录;登录成功,同时返回生成的token;效果如下图
                

    • 带上登录成功返回的token访问/jwt/test接口,拦截器放行了请求,成功请求到了test方法;效果如下图
                

    • 当token被篡改或者已过期时,访问/jwt/test接口,拦截器拦截了该请求,返回token已失效;效果如下图
                

    进行完上述测试后,说明jwt的集成已经大功告成了。

五、小结

    jwt集成不麻烦,但是也有很多不完善的地方,后续再想办法把它完善。
    在我的JwtUtils的verifyAndGetClaimsByToken方法里提到了相关扩展的思路,可以通过该思路来实现token的刷新及其他的骚操作。
    当然,jwt也有很多缺点,这里就不在赘述了。


SpringBoot集成JWT的更多相关文章

  1. SpringBoot集成JWT 实现接口权限认证

    JWT介绍 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的, 特别适用于分布式站点 ...

  2. SpringBoot集成JWT实现token验证

    原文:https://www.jianshu.com/p/e88d3f8151db JWT官网: https://jwt.io/ JWT(Java版)的github地址:https://github. ...

  3. SpringBoot集成JWT实现权限认证

    目录 一.JWT认证流程 二.SpringBoot整合JWT 三.测试 上一篇文章<一分钟带你了解JWT认证!>介绍了JWT的组成和认证原理,本文将介绍下SpringBoot整合JWT实现 ...

  4. springboot之Jwt验证

    简介 什么是JWT(Json Web Token) jwt是为了在网络应用环境间传递声明而执行的一种基于json的开放标准.该token被设计紧凑且安全的,特别适用于SSO场景. jwt的声明一般被用 ...

  5. SpringBoot系列 - 集成JWT实现接口权限认证

    会飞的污熊 2018-01-22 16173 阅读 spring jwt springboot RESTful API认证方式 一般来讲,对于RESTful API都会有认证(Authenticati ...

  6. SpringBoot 集成SpringSecurity JWT

    目录 1. 简介 1.1 SpringSecurity 1.2 OAuth2 1.3 JWT 2. SpringBoot 集成 SpringSecurity 2.1 导入Spring Security ...

  7. Springboot shiro JWT集成总结

    SpringBoot Shiro JWT 1.建表 DDL.sql CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, ` ...

  8. spring boot 2 集成JWT实现api接口认证

    JSON Web Token(JWT)是目前流行的跨域身份验证解决方案.官网:https://jwt.io/本文使用spring boot 2 集成JWT实现api接口验证. 一.JWT的数据结构 J ...

  9. 轻松上手SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制实战

    前言 我们知道在项目开发中,后台开发权限认证是非常重要的,springboot 中常用熟悉的权限认证框架有,shiro,还有就是springboot 全家桶的 security当然他们各有各的好处,但 ...

随机推荐

  1. jmeter请求参数中文乱码及无法读取CSV文件解决办法

    解决办法:参考http://blog.csdn.net/u012167045/article/details/70868306 版本:2.6 我是修改请求http请中的编码为Content encod ...

  2. HDU1213:How Many Tables(并查集入门)

    -----------刷点水题练习java------------- 题意:给定N点,M边的无向图,问有多少个连通块. 思路:可以搜索;  可以并查集.这里用并查集练习java的数组使用,ans=N, ...

  3. 7.Vue的计算属性

    1.什么是计算属性 computed:计算属性的重点突出在 属性 两个字上(属性是名词),首先它是个 属性 其次这个属性有 计算的能力(计算是动词),这里的 计算 就是个函数:简单点说,它就是一个能够 ...

  4. [LeetCode] 763. Partition Labels 分割标签

    A string S of lowercase letters is given. We want to partition this string into as many parts as pos ...

  5. oracle--ORA-38760

    01,ORA-38760: This database instance failed to turn on flashback 02,问题处理思路 第一步:查看日志文件 查看这次启动的时候alter ...

  6. K8s 集群安装(一)

    01,集群环境 三个节点   master node1 node2 IP 192.168.0.81 192.168.0.82 192.168.0.83 环境 centos 7 centos 7 cen ...

  7. Spring 常犯的十大错误,这坑你踩过吗?

    阅读本文大概需要 9 分钟. 1.错误一:太过关注底层 我们正在解决这个常见错误,是因为 “非我所创” 综合症在软件开发领域很是常见.症状包括经常重写一些常见的代码,很多开发人员都有这种症状. 虽然理 ...

  8. 为某金融企业开发团队分享DevOps Server流水线使用经验

    http://www.cnblogs.com/danzhang/  DevOps MVP 张洪君

  9. centos 7 安装python3 & pip3

    1.安装python3 https://www.cnblogs.com/Trees/p/7497482.html 2.解决:python ModuleNotFoundError: No module ...

  10. SDK-基于Windows环境搭建

    SDK安装配置 前言:相信很多小伙伴还不会搭SDK,近日一位前同事询问我SDK怎么搭建了?不妨看看吧,小编是基于appium. 1.下载SDK:http://tools.android-studio. ...