一.本文介绍

上篇文章讲到Spring Boot整合Swagger的时候其实我就在思考关于接口安全的问题了,在这篇文章了我整合了JWT用来保证接口的安全性。我会先简单介绍一下JWT然后在上篇文章的基础上整合JWT。

二.JWT简介(参考链接

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。我理解的JWT主要的两点作用:1.用来校验权限角色等;2.用来校验传输的信息是否被篡改过。JWT有这样的功能和它的结构是有关的,它由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

  • Header
  • Payload
  • Signature

因此,一个典型的JWT看起来是这个样子的:

xxxxx.yyyyy.zzzzz

接下来,具体看一下每一部分:

Header

header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。

例如:

然后,用Base64对这个JSON编码就得到JWT的第一部分

Payload

JWT的第二部分是payload,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。

  • Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
  • Public claims : 可以随意定义。
  • Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。

下面是一个例子:

对payload进行Base64编码就得到JWT的第二部分

注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

Signature

为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。

例如:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

三.Spring Boot整合JWT

首先还是需要引入JWT的相关依赖如下

        <dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
</dependency>

写一个JWT的工具类用于创建token并校验token的合法性代码如下

package cn.test.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.util.StringUtils; import java.util.Date;
import java.util.HashMap;
import java.util.Map; public class JWTUtil { public static final String SECRET = "JKKLJOoasdlfj"; public static final Long EXPIRE = 5 * 60 * 1000L; public static String createToken(Long userId) throws Exception {
// header Map
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
// build token
// param backups {iss:Service, aud:APP}
String token = JWT.create().withHeader(map) // header
.withClaim("iss", "Service") // payload
.withClaim("aud", "APP").withClaim("user_id", null == userId ? null : userId.toString())
//.withIssuedAt() // sign time
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE)) // expire time
.sign(Algorithm.HMAC256(SECRET)); // signature
return token;
} public static Map<String, Claim> verifyToken(String token) {
DecodedJWT jwt = null;
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
jwt = verifier.verify(token);
} catch (Exception e) {
//token 校验失败, 抛出Token验证非法异常
e.printStackTrace();
}
return jwt.getClaims();
} public static Long getUserId(String token) {
Map<String, Claim> claims = verifyToken(token);
Claim userIdClaim = claims.get("user_id");
if (null == userIdClaim || StringUtils.isEmpty(userIdClaim.asString())) {
// token 校验失败, 抛出Token验证非法异常
}
return Long.valueOf(userIdClaim.asString());
}
}

其中userId作为token的主要载荷数据,大体思路是配合拦截器使用,第一次登录时把userId对应的权限存到redis中,拦截器对于每个请求都根据token里的userId对应的权限对接口进行权限控制,在这里需要注意几点:1.token我认为放到cookie里更合适防止直接被获取;2.如果允许的话最好使用https;3.token里的校验信息最好加上客户端的环境比如说ip和User Agent等;设置token的超时时间等在代码里已经有体现了。拦截器代码如下

package cn.test.interceptor;

import cn.test.util.CookieUtil;
import cn.test.util.JWTUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class VerifyTokenInterceptor implements HandlerInterceptor { private Logger logger = LoggerFactory.getLogger(VerifyTokenInterceptor.class); @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
try {
String token = CookieUtil.get(request, "token").getValue();
Long userId = JWTUtil.getUserId(token);
logger.info("****userId = {}*****",userId);
//todo 校验token是否合法,如果不合法拦截器做处理
}catch (Exception e){
//发生异常跳转到指定接口或做其他处理
response.sendRedirect(request.getContextPath()+"/login/verify");
}
return true;
}
}

在这里需要注意,正常使用拦截器需要把拦截路径配置在配置文件里但Spring Boot不需要使用这样的配置文件,我们需要一个配置类,加上@Configuration注解的类的作用实际和配置文件是一样的,我们把拦截器注册到Spring中代码如下

package com.daojia.config;

import com.daojia.interceptor.VerifyTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new VerifyTokenInterceptor())
.addPathPatterns("/**")
//登录接口不用通过拦截器否则会形成死循环,因为第一次登录没有token信息会一直跳转到登录接口
.excludePathPatterns("/login/verify");
super.addInterceptors(registry);
}
}

其中用到的CookieUtil代码如下

package cn.test.util;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map; /**
* cookie工具类
*/
public class CookieUtil { /**
* 设置
* @param response
* @param name
* @param value
* @param maxAge
*/
public static void set(HttpServletResponse response,
String name,
String value,
int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
} /**
* 获取cookie
* @param request
* @param name
* @return
*/
public static Cookie get(HttpServletRequest request,
String name) {
Map<String, Cookie> cookieMap = readCookieMap(request);
if (cookieMap.containsKey(name)) {
return cookieMap.get(name);
}else {
return null;
}
} /**
* 将cookie封装成Map
* @param request
* @return
*/
private static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
Map<String, Cookie> cookieMap = new HashMap<>();
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie: cookies) {
cookieMap.put(cookie.getName(), cookie);
}
}
return cookieMap;
}
}

模拟登录代码如下

package cn.test.controllers;

import cn.test.util.CookieUtil;
import cn.test.util.JWTUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; @RestController
@RequestMapping("/login")
public class LoginController { @RequestMapping("/verify")
public String verifyLogin(HttpServletResponse response){
try {
String token = JWTUtil.createToken(12345l);
CookieUtil.set(response,"token",token,5 * 60 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
}

整个流程是:用户第一次登录或者在没登录的情况下访问其他接口会经过登录拦截并强制跳转到登录接口,用户经过登录接口会形成token信息并放入到cookie中,下次再访问时会带上cookie,如果cookie还在有效时间内就可以通过拦截器的校验。

四.总结

Spring Boot整合JWT主要作用是为了校验权限和数据是否被篡改,首先需要引入JWT的依赖,然后写JWT生成token和校验token的工具类,然后配合拦截器进行使用,这里要注意把拦截器通过@Configuration注解注册到Spring中。

Spring Boot初识(4)- Spring Boot整合JWT的更多相关文章

  1. spring transaction 初识

    spring 事务初识 1.spring事务的主要接口,首先盗图一张,展示出spring 事务的相关接口.Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibern ...

  2. Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...

  3. Spring Boot初识(2)- Spring Boot整合Mybaties

    一.本文介绍 首先读这篇文章之前如果没有接触过Spring Boot可以看一下之前的文章,并且读这篇文章还需要你至少能写基本的sql语句.我在写这篇文章之前也想过到底是选择JPA还是Mybaties作 ...

  4. spring boot:spring security整合jwt实现登录和权限验证(spring boot 2.3.3)

    一,为什么使用jwt? 1,什么是jwt? Json Web Token, 它是JSON风格的轻量级的授权和身份认证规范, 可以实现无状态.分布式的Web应用授权 2,jwt的官网: https:// ...

  5. 初识在Spring Boot中使用JPA

    前面关于Spring Boot的文章已经介绍了很多了,但是一直都没有涉及到数据库的操作问题,数据库操作当然也是我们在开发中无法回避的问题,那么今天我们就来看看Spring Boot给我们提供了哪些疯狂 ...

  6. Spring Boot 2.x 综合示例-整合thymeleaf、mybatis、shiro、logging、cache开发一个文章发布管理系统

    一.概述 经过HelloWorld示例(Spring Boot 2.x 快速入门(上)HelloWorld示例)( Spring Boot 2.x 快速入门(下)HelloWorld示例详解)两篇的学 ...

  7. 【Spring Boot&&Spring Cloud系列】Spring Boot初识

    项目代码地址:https://github.com/AndyFlower/Spring-Boot-Learn/tree/master/Spring-boot-helloworld 一.Spring B ...

  8. Spring Boot + MyBatis + Druid + Redis + Thymeleaf 整合小结

    Spring Boot + MyBatis + Druid + Redis + Thymeleaf 整合小结 这两天闲着没事想利用**Spring Boot**加上阿里的开源数据连接池**Druid* ...

  9. 解决Spring Boot(2.1.3.RELEASE)整合spring-data-elasticsearch3.1.5.RELEASE报NoNodeAvailableException[None of the configured nodes are available

    Spring Boot(2.1.3.RELEASE)整合spring-data-elasticsearch3.1.5.RELEASE报NoNodeAvailableException[None of ...

随机推荐

  1. 最近素数问题——C语言

    从键盘输入一个整数,输出距离该数最近的素数 #include<stdio.h> #include<math.h> int judge(int x) { //判断素数 if (x ...

  2. 20165213Java第二次实验

    实验二Java面向对象程序设计 实验1 实验目的与要求: 参考http://www.cnblogs.com/rocedu/p/6371315.html#SECUNITTEST 完成单元测试的学习 提交 ...

  3. 《C#从现象到本质》读书笔记(三)第3章C#类型基础(下)

    <C#从现象到本质>读书笔记第3章C#类型基础(下) 常量以关键字const修饰.C#支持静态字段(类型字段)和实例字段. 无参属性的get方法不支持参数,而有参属性的get方法支持传入一 ...

  4. 第二阶段第一次spring会议

    昨天我们讨论了改进方法,打算建立数据库. 今天我对39个组发表了建议以及总结了改进意见和改进方案. 明天我将对软件添加回收站.

  5. Centos7通过yum安装最新MySQL

    一:去官网查看最新安装包 https://dev.mysql.com/downloads/repo/yum/ 二:下载MySQL源安装包 wget http://dev.mysql.com/get/m ...

  6. java 支持 超大上G , 多附件上传

    首先 确定要上传的目录 WEB.XML 文件 Java代码   <listener> <listener-class><!-- 临时文件收集器 , 支持超大附件必须项 - ...

  7. unique(未完成)

    const unique = arr => { const sortedArr = arr.sort((a, b) => a > b); const first = sortedAr ...

  8. Vue 父组件ajax异步更新数据,子组件props获取不到

    转载 https://blog.csdn.net/d295968572/article/details/80810349 当父组件 axjos 获取数据,子组件使用 props 接收数据时,执行 mo ...

  9. document.domain实现不同域名跨域

    利用document.domain 实现跨域:前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域. 两个子域名:aaa.xxx ...

  10. shell if 条件判断

    condition='123' if [ -z condition]; then echo "condition 是空的" fi 字符串判断: = 两个字符串相等. != 两个字符 ...