一.本文介绍

上篇文章讲到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. php Pthread 多线程 Worker

    <?php //PHP 高级编程之多线程 http://www.netkiller.cn/journal/thread.php.html#idp57489856 //worker 是一个具有持久 ...

  2. MySQL开发——【多表关系、引擎、外键、三范式】

    多表关系 一对一关系 一对多或多对一关系 多对多关系 MySQL引擎 所谓的MySQL引擎就是数据的存储方式,常用的数据库引擎有以下几种: Myisam与InnoDB引擎之间的区别(面试) ①批量插入 ...

  3. Java代码获取spring 容器的bean几种方式

    一.目的 写了一个项目,多个module,然后想在A模块中实现固定的config注入,当B模块引用A时候,能够直接填写相对应的配置信息就行了.但是遇到一个问题,B引用A时候,A的配置信息总是填充不了, ...

  4. Eclipse中 *.properties 文件编码设置

    Eclipse 中的默认编码格式为 ISO-8895-1,在此编码下中文的会显示如下的效果 解决方法 Windows --> Preference --> General Types -- ...

  5. java利用反射获取对象前后修改的内容(用于日志记录)

    import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Metho ...

  6. 9. Bookshops in London 伦敦书店

    9. Bookshops in London 伦敦书店 (1) Londoner are greater readers.They buy vast numbers of newspapers and ...

  7. MySQL中Decimal类型和Float Double等区别

    MySQL中存在float,double等非标准数据类型,也有decimal这种标准数据类型. 其区别在于,float,double等非标准类型,在DB中保存的是近似值,而Decimal则以字符串的形 ...

  8. python函数(一)

    python函数(一) 1.函数的定义: def test(): print('test is running...') return 定义一个函数,有3个部分需要注意: 函数名称.函数的命名规范与变 ...

  9. overlay fs挂载及操作测试

    overlayfs是目前使用比较广泛的层次文件系统,实现简单,性能较好,可以充分利用不同或则相同overlay文件系统的page cache,具有 上下合并 同名遮盖 写时拷贝 等特点. 一个 ove ...

  10. freeRTOSConfig.h文件对FreeRTOS进行系统配置

    FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制.每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪定制FreeRTOS内核.这个配置文件是针 ...