一.本文介绍

上篇文章讲到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. js多维数组转一维数组

    1,使用map方法 var arr = [1,[2,[[3,4],5],6]]; function unid(arr){ var arr1 = (arr + '').split(',');//将数组转 ...

  2. 1.编译cartographer ROS

    1.系统要求 cartographer ROS与Cartographer要求一样,即 64-bit, modern CPU (e.g. 3rd generation i7) 16 GB RAM Ubu ...

  3. MFC停靠窗口实现(CDockablePane)

    工作中编写MFC界面程序时用到了停靠窗口,为了避免之后用到时再去查询,这里记录下. 步骤 1.定义一个继承自CDockablePane的类 Class CDockableTest : public C ...

  4. Codeforces Round #546 (Div. 2) E 推公式 + 线段树

    https://codeforces.com/contest/1136/problem/E 题意 给你一个有n个数字的a数组,一个有n-1个数字的k数组,两种操作: 1.将a[i]+x,假如a[i]+ ...

  5. sql pivot(行转列) 和unpivot(列转行)的用法

    1.PIVOT用法(行转列) select * from Table_Score as a pivot (sum(score) for a.name in ([语文],[数学],[外语],[文综],[ ...

  6. Java生成静态HTML文件

    private static final String FILEPATH = "/opt/nginx/html/banner/"; private static final Str ...

  7. GitHub上好的Java项目

    1. java-design-patterns(Star:36k)Github地址:https://github.com/iluwatar/java-design-patterns 介绍:设计模式是形 ...

  8. 第 1 篇 Scrum 冲刺博客

    各个成员在 Alpha 阶段认领的任务 姓名 Alpha 阶段认领的任务 徐婉萍 创建服务器.域名,环境搭建查询界面及页面的设计,查询方法的编写 谭燕 支出.收入添加界面及设计,收入.支出的方法编写, ...

  9. 学习在dos下使用gcc来编译

    这两年里,断断续续的学习和使用c,平时都是在CodeBlocks里写代码,编译程序,点一下按钮就行了.对整个编译过程是一点儿都不了解.相比当年学习java,真的是选择了两个不同的路,当年学习java的 ...

  10. 剑指offer编程题Java实现——面试题9斐波那契数列

    题目:写一个函数,输入n,求斐波那契数列的第n项. package Solution; /** * 剑指offer面试题9:斐波那契数列 * 题目:写一个函数,输入n,求斐波那契数列的第n项. * 0 ...