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. Lp距离, L1范数, 和L2范数(转载)

    范式可以理解成距离 转载自: https://blog.csdn.net/hanhuili/article/details/52079590 内容如下: 由此可见,L2其实就是欧式距离.工程上,往往不 ...

  2. python27期尚哥讲网络编程:

    python27day26网络编程----------------------------------------------------------------------------------- ...

  3. zz斯坦福Jure Leskovec图表示学习:无监督和有监督方法

    斯坦福Jure Leskovec图表示学习:无监督和有监督方法(附PPT下载) 2017 年 12 月 18 日  专知 专知内容组(编) 不要讲得太清楚 [导读]现实生活中的很多关系都是通过图的形式 ...

  4. USACO Score Inflation

    洛谷 P2722 总分 Score Inflation https://www.luogu.org/problem/P2722 JDOJ 1697: Score Inflation https://n ...

  5. 学习-velocity

    Velocity是什么?  Velocity是一个基于java的模板引擎(template engine).它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码 ...

  6. Oracle常用函数集锦

    1.wmsys.wm_concat函数 将列转为行.例子: --表里的数据如下 SQL> select * from idtable; ID NAME ---------- ---------- ...

  7. Navicat Keygen - for Windows

    如何使用这个注册机 从这里下载最新的release. 使用navicat-patcher.exe替换掉navicat.exe和libcc.dll里的Navicat激活公钥. navicat-patch ...

  8. ORA-12528: TNS:listener: all appropriate instances are blocking new connections

    Oracle问题:ORA-12528: TNS: 监听程序: 所有适用例程都无法建立新连接 问题原始描述: ORA-12528: TNS:listener: all appropriate insta ...

  9. 基于Laravel框架下使用守护进程supervisor实现定时任务(毫秒)

    本篇文章给大家带来的内容是关于基于Laravel框架下使用守护进程supervisor实现定时任务(毫秒),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 公司需要实现X分钟内每隔Y秒 ...

  10. BScroll使用

    当页面内容的高度超过视口高度的时候,会出现纵向滚动条:当页面内容的宽度超过视口宽度的时候,会出现横向滚动条.也就是当我们的视口展示不下内容的时候,会通过滚动条的方式让用户滚动屏幕看到剩余的内容. 话说 ...