目录
1. 概述
2. JWT的请求流程
3. Session认证与JWT认证的区别
4 JWT优缺点
4.1 优点
4.2 缺点
5. 快速入门
5.1 创建工程
5.2 导入依赖
5.3 添加配置文件
5.4 添加Swagger2配置类
5.5 添加JWT工具类
5.6 添加entity、service、controller类
5.7 添加拦截器类
5.8 添加拦截器配置文件
5.9 创建启动类
5.10 测试
5.10.1 通过swaggler测试
5.10.2 通过Postman测试
6. Demo下载地址
1. 概述
近年来,随着前后端分离、微服务等架构的兴起,传统的cookie+session身份验证模式已经逐渐被基于Token的身份验证模式取代。
备注:将token或者一个唯一标识UUID=UUID.randomUUID().toString()存进Cookie中(别存在Http的header中了),设置路径为整个项目根路径/*; 往往以这个唯一标识为key,用户信息为value缓存在服务器中,实现单点登录。
JWT (Json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。它定义了一种紧凑的,自包含的方式,用于通信双方之间以JSON对象的形式安全传递信息。JWT使用HMAC算法或者是RSA的公私秘钥的数字签名技术,所以这些信息是可被验证和信任的。
JWT官网: https://jwt.io/
JWT(Java版)的github地址: https://github.com/jwtk/jjwt
在使用 JWT 前,需要先了解它的组成结构。它是由以下三段信息构成的:
-
Header 头部(包含签名和/或加密算法的类型)
-
Payload 载荷 (存放有效信息)
-
Signature 签名/签证
将这三段信息文本用‘.’连接一起就构成完整的JWT字符串(比如:xxxxx.yyyyy.zzzzz),也是就我们需要的Token。
2. JWT的请求流程
JWT的请求流程也特别简单,首先使用账号登录获取Token,然后后面的各种请求,都带上这个Token即可。具体流程如下:
-
客户端发起登录请求,传入账号密码;
-
服务端使用私钥创建一个Token;
-
服务器返回Token给客户端;
-
客户端向服务端发送请求,在请求头添加中该Token;
-
服务器验证该Token;
-
返回结果。
3. Session认证与JWT认证的区别
基于session和基于jwt的方式的主要区别就是用户的状态保存的位置。
session是保存在服务端的,下一次再取从session当中取数据。
jwt认证:用户输入用户名与密码,校验(从数据库当中查看有没有对应的数据),如果有对应的数据,会把用户取出来,把取出的用户数据,转成JWT,以token令牌的形式传给前端,前端拿到数据之后,会给存储到cookie,以后面每一次请求都要携带token,服务器就会获取token之后,再进行jw解析,读取用户数据,如果没有数据,就代表没有登录,而jwt是保存在客户端的。
4 JWT优缺点
4.1 优点
- jwt基于json,非常方便解析
- 可以在令牌中自定义丰富的内容,易扩展
- 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高
- 资源服务使用JWT可不依赖认证服务即可完成授权
4.2 缺点
- JWT令牌较长,占存储空间比较大
5. 快速入门
5.1 创建工程
设置Maven
设置自动导入包 Auto Import
设置启动注解 Annotation Processors
5.2 导入依赖
<!--引入springboot依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<!--引入spring-boot启动器依赖, 添加启动器后web工程常用的依赖会自动帮你引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--Swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
<!--打包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
5.3 添加配置文件
resources\application.yml 当中 添加配置信息
server:
port: 8081
5.4 添加Swagger2配置类
package com.miaxis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.miaxis"))// 指定扫描包下面的注解
.paths(PathSelectors.any())
.build();
}
// 创建api的基本信息
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("集成Swagger2构建RESTful APIs")
.description("集成Swagger2构建RESTful APIs")
.termsOfServiceUrl("https://www.miaxis.com")
.contact("Mickey")
.version("1.0.0")
.build();
}
}
5.5 添加JWT工具类
package com.miaxis.util;
import com.miaxis.user.entity.User;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.logging.log4j.util.Strings;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* <p> @Title TokenUtil
* <p> @Description Token 工具类
*/
public class TokenUtil {
public static final String JWT_SECRET_KEY = "testjwt";
/**
* 创建Token
*
* @param user 用户实体
* @return Token
*/
public static String createToken(User user) {
// 登录成功后生成JWT
// JWT的header部分,该map可以是空的,因为有默认值{"alg":HS256,"typ":"JWT"}
Map<String, Object> map = new HashMap<>();
// 30分钟过期时间
Calendar instance = Calendar.getInstance();
instance.add(Calendar.MINUTE,30);
return JWT.create()
// 添加头部
.withHeader(map)
// 添加payload
.withClaim("userId",user.getId())
.withClaim("username",user.getUsername())
.withClaim("email",user.getEmail())
// 设置过期时间
.withExpiresAt(instance.getTime())
// 设置签名 密钥
.sign(Algorithm.HMAC256(JWT_SECRET_KEY));
}
/**
* 检查Token是否有效
*
* @param token Token
* @return 是否有效
*/
public static boolean isValid(String token) {
if (Strings.isNotBlank(token)) {
try {
//创建验证对象,这里使用的加密算法和密钥必须与生成TOKEN时的相同否则无法验证
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(JWT_SECRET_KEY)).build();
//验证JWT
DecodedJWT decodedJwt = jwtVerifier.verify(token);
return new Date().before(decodedJwt.getExpiresAt());
} catch (Exception e) {
return false;
}
} else {
return false;
}
}
/**
* 检查Token所有Claims
*
* @param token Token
* @return Claims
*/
public static Map<String, Object> getClaims(String token) {
if (isValid(token)) {
//创建验证对象,这里使用的加密算法和密钥必须与生成TOKEN时的相同否则无法验证
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(JWT_SECRET_KEY)).build();
//验证JWT
DecodedJWT decodedJwt = jwtVerifier.verify(token);
//获取JWT中的数据,注意数据类型一定要与添加进去的数据类型一致,否则取不到数据
Map<String, Object> map = new HashMap<>();
map.put("userId", decodedJwt.getClaim("userId").asInt());
map.put("username", decodedJwt.getClaim("username").asString());
map.put("email", decodedJwt.getClaim("email").asString());
map.put("expire", decodedJwt.getExpiresAt());
return map;
} else {
throw new RuntimeException("Token验证失败,请重新登录");
}
}
}
5.6 添加entity、service、controller类
- entity\User
package com.miaxis.user.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* <p> @Title User
* <p> @Description 用户实体
*/
@Data
@AllArgsConstructor
public class User {
/**
* 主键
*/
public Integer id;
/**
* 用户名
*/
public String username;
/**
* 密码
*/
public String password;
/**
* 邮箱
*/
public String email;
}
- service\UserService
package com.miaxis.user.service;
import com.miaxis.user.entity.User;
import org.springframework.stereotype.Service;
/**
* <p> @Title UserService
* <p> @Description 用户 业务层
*/
@Service
public class UserService {
public User findUser() {
return new User(1, "ACGkaka", "123456", "123@123.com");
}
}
- controller\UserController
controller带token的接口配置Swagger带token
package com.miaxis.user.controller;
import com.miaxis.user.entity.User;
import com.miaxis.user.service.UserService;
import com.miaxis.util.TokenUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* <p> @Title DemoController
* <p> @Description 登录页面
*/
@RestController
@RequestMapping("index")
public class UserController {
@Resource
private UserService userService;
/**
* 登录
*
* @return Token
*/
@ResponseBody
@PostMapping("/login")
public Map<String, Object> login() {
System.out.println("============login");
User user = userService.findUser();
String token = TokenUtil.createToken(user);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "登录成功");
result.put("data", token);
return result;
}
/**
* 获取Token信息
*
* @return 验证结果
*/
@ResponseBody
@GetMapping("/getInfo")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "header", name = "accessToken", required = true),
})
public Map<String, Object> getInfo(HttpServletRequest request) {
Map<String, Object> result = new HashMap<>();
String token = request.getHeader("accessToken");
Map<String, Object> data = TokenUtil.getClaims(token);
result.put("code", 200);
result.put("message", "验证成功");
result.put("data", data);
return result;
}
}
5.7 添加拦截器类
package com.miaxis.interceptor;
import com.miaxis.user.service.UserService;
import com.miaxis.util.TokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p> @Title TokenInterceptor
* <p> @Description Token 验证拦截器
*/
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("accessToken");
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
// 执行认证
if (token == null) {
throw new RuntimeException("accessToken不存在,请重新登录");
}
// 验证 token
if (TokenUtil.isValid(token)) {
return true;
} else {
throw new RuntimeException("Token验证失败,请重新登录");
}
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
5.8 添加拦截器配置文件
-
拦截器配置排除拦截Swagger
package com.miaxis.config;
import com.miaxis.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* <p> @Title InterceptorConfig
* <p> @Description 拦截器配置
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//定义排除swagger访问的路径配置
String[] swaggerExcludes=new String[]{"/swagger-ui.html","/swagger-resources/**","/webjars/**"};
// 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index/login")
.excludePathPatterns(swaggerExcludes);
WebMvcConfigurer.super.addInterceptors(registry);
}
@Bean
public TokenInterceptor authenticationInterceptor() {
return new TokenInterceptor();
}
}
5.9 创建启动类
package com.miaxis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.10 测试
5.10.1 通过swaggler测试
http://localhost:8081/swagger-ui.html
5.10.2 通过Postman测试
http://localhost:8081/index/login
http://localhost:8081/index/getInfo
accessToken
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDcwMzc3NDUsInVzZXJJZCI6MSwiZW1haWwiOiIxMjNAMTIzLmNvbSIsInVzZXJuYW1lIjoiQUNHa2FrYSJ9.TT4CZgUfmWCNhbQa6UTWhSl3Tcf48uAVsAyqNQoR6KI
6. Demo下载地址
编译器版本:IntelliJ IDEA 2020.3.2 x64
JDK版本:java 1.8.0_111
下载地址:https://download.csdn.net/download/mickey2007/89110981