JWT
JSON Web Token (JWT)是一個開放标準(RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為JSON對象在各方之間安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。
你做資料操作時,後端怎麼知道是你這個人操作了而不是别人,一般web服務肯定不是你一個人在用吧?
倘若是前後端分離的那種形式,後端隻出api,那怎麼保證是你允許的人來通路你的api呢?不可能人人都響應吧?
那就要談一談token的認證和傳統的session認證,http協定本身是一種無狀态的協定,那怎麼保證是我們允許的人來操作呢?
首先我們要有身份的确立吧,比如登入,登入完,我們給它配置設定一個用于識别身份的資訊,每次請求都要求他們把身份資訊給到我們,我們就能知道是誰操作了吧!
傳統的Session,都知道我們是把它儲存在記憶體中的,小使用者量可以很完美的跑起來,但是使用者量巨大,線上人數巨大,那對伺服器來說這種是一個不小的負擔,那麼怎麼解決呢?
Token 倘若我們不儲存它們的資訊,把它們的身份資訊按照與它們的約定做成一個憑證,我們伺服器可以校驗這個憑證的真僞,那就沒有這種壓力了吧(因為是可以解密的是以你懂的)
好,我們就來看看jwt
這是我整合的一個例子 主要用到了 springboot + swagger + mybatisplus+jwt+mysql
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL6dmaNJTSU9EMFpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLxMTNxEzM0YTM0AjMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zkb</groupId>
<artifactId>spring-boot-mybatisplus</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-mybatisplus</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.22</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.22</version>
</dependency>
<!-- swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server:
port: 8081
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://localhost:3306/test-demo?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password: baishou888
# mysql8.0 驅動
driver-class-name: com.mysql.cj.jdbc.Driver
debug: false
#Druid#
name: test
type: com.alibaba.druid.pool.DruidDataSource
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: full
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/**/*Mapper.xml
global-config:
# 邏輯删除配置
db-config:
# 删除前
logic-not-delete-value: 1
# 删除後
logic-delete-value: 0
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zkb.mapper.UserMapper">
<!-- 通用查詢映射結果 -->
<resultMap id="BaseResultMap" type="com.zkb.model.User">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
</resultMap>
</mapper>
package com.zkb;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.zkb.mapper")
public class SpringBootMybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMybatisplusApplication.class, args);
}
}
package com.zkb.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ZkbToken {
boolean required() default true;
}
這邊我自定義了一個注解,用來給需要token驗證的接口标記一下
package com.zkb.model;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author zkb
* @since 2020-11-23
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_user")
@ApiModel(value = "使用者資訊", description = "使用者資訊")
public class User extends Model<User> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty(value = "主鍵")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@ApiModelProperty(value = "使用者名")
private String username;
@ApiModelProperty(value = "密碼")
private String password;
}
package com.zkb.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* <h3>spring-boot-mybatisplus</h3>
* <p></p>
*
* @author : zkb
* @date : 2020-12-04 15:31
**/
@Data
@ApiModel(value = "使用者資訊輔助", description = "使用者資訊輔助")
public class UserFo {
@ApiModelProperty(value = "使用者名")
private String username;
@ApiModelProperty(value = "密碼")
private String password;
}
package com.zkb.mapper;
import com.zkb.model.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author zkb
* @since 2020-11-23
*/
public interface UserMapper extends BaseMapper<User> {
}
package com.zkb.service.impl;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.zkb.model.User;
import com.zkb.service.TokenService;
import org.springframework.stereotype.Service;
/**
* <h3>spring-boot-mybatisplus</h3>
* <p></p>
*
* @author : zkb
* @date : 2020-12-04 15:23
**/
@Service("TokenService")
public class TokenServiceImpl implements TokenService {
@Override
public String getToken(User user) {
String token="";
token= JWT.create().withAudience(user.getId().toString())// 将 user id 儲存到 token 裡面
.sign(Algorithm.HMAC256(user.getPassword()));// 以 password 作為 token 的密鑰
return token;
}
}
package com.zkb.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zkb.model.User;
import com.zkb.mapper.UserMapper;
import com.zkb.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* <p>
* 服務實作類
* </p>
*
* @author zkb
* @since 2020-11-23
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private UserMapper mapper;
@Override
public User findByUsername(User user) {
QueryWrapper<User> query = new QueryWrapper<User>();
query.eq("username", user.getUsername());
return mapper.selectOne(query);
}
}
package com.zkb.service;
import com.zkb.model.User;
/**
* <h3>spring-boot-mybatisplus</h3>
* <p></p>
*
* @author : zkb
* @date : 2020-12-04 15:22
**/
public interface TokenService {
String getToken(User user);
}
package com.zkb.service;
import com.zkb.model.User;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服務類
* </p>
*
* @author zkb
* @since 2020-11-23
*/
public interface UserService extends IService<User> {
User findByUsername(User user);
}
package com.zkb.conf;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.zkb.annotation.ZkbToken;
import com.zkb.exc.AuthenticationException;
import com.zkb.model.User;
import com.zkb.service.UserService;
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;
import java.lang.reflect.Method;
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("token");// 從 http 請求頭中取出 token
// 如果不是映射到方法直接通過
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
//檢查有沒有需要使用者權限的注解
if (method.isAnnotationPresent(ZkbToken.class)) {
ZkbToken ZkbToken = method.getAnnotation(ZkbToken.class);
if (ZkbToken.required()) {
// 執行認證
if (token == null) {
throw new AuthenticationException("無token,請重新登入");
}
// 擷取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new AuthenticationException("401");
}
User user = userService.getById(userId);
if (user == null) {
throw new AuthenticationException("使用者不存在,請重新登入");
}
// 驗證 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new AuthenticationException("401");
}
return true;
}
}
return true;
}
@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 {
}
}
package com.zkb.conf;
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;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**"); // 攔截所有請求
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
package com.zkb.conf;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置分頁插件
*
*/
@Configuration
public class MybatisPlusConfig {
/**
* 分頁插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
package com.zkb.conf;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
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.service.ApiKey;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableSwagger2
public class SwaggerApp {
@Bean
public Docket createRestApi1() {
return new Docket(DocumentationType.SWAGGER_2).enable(true).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.apis(RequestHandlerSelectors.basePackage("com.zkb.controller"))
.paths(PathSelectors.any()).build().securitySchemes(apiKeyList()).groupName("接口中心");
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("API")
.contact(new Contact("XXXXX", "http://XXXXXX.XXXX/", ""))
.version("1.0")
.description("API 描述")
.build();
}
private List<ApiKey> apiKeyList() {
return Arrays.asList(new ApiKey("登入token", "token", In.HEADER.name()),
new ApiKey("裝置類型(android,ios,pc)---必填", "deviceType", In.HEADER.name()));
}
}
package com.zkb.controller;
import com.alibaba.fastjson.JSONObject;
import com.zkb.annotation.ZkbToken;
import com.zkb.model.User;
import com.zkb.model.UserFo;
import com.zkb.service.TokenService;
import com.zkb.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
@Api(value = "使用者api",tags = "使用者api")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private TokenService tokenService;
@PostMapping("/login")
@ApiOperation(value="使用者登入",notes = "使用者登入")
public Object login( UserFo userFo){
User user = new User();
BeanUtils.copyProperties(userFo,user);
JSONObject jsonObject=new JSONObject();
User userForBase=userService.findByUsername(user);
if(userForBase==null){
jsonObject.put("message","登入失敗,使用者不存在");
return jsonObject;
}else {
if (!userForBase.getPassword().equals(user.getPassword())){
jsonObject.put("message","登入失敗,密碼錯誤");
return jsonObject;
}else {
String token = tokenService.getToken(userForBase);
jsonObject.put("token", token);
jsonObject.put("user", userForBase);
return jsonObject;
}
}
}
@ZkbToken
@GetMapping("/getUserById")
@ApiOperation(value="根據id查詢使用者",notes = "根據id查詢使用者")
public User getUserById(@RequestParam("id") Long id){
return userService.getById(id);
}
}
package com.zkb.exc;
public class AuthenticationException extends Exception {
/*無參構造函數*/
public AuthenticationException(){
super();
}
//用詳細資訊指定一個異常
public AuthenticationException(String message){
super(message);
}
//用指定的詳細資訊和原因構造一個新的異常
public AuthenticationException(String message, Throwable cause){
super(message,cause);
}
//用指定原因構造一個新的異常
public AuthenticationException(Throwable cause) {
super(cause);
}
}
這裡我自己定義了一個異常,準備用來校驗token的時候,不正确就抛出來給攔截住
package com.zkb.exc;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* <h3>OrderSystem</h3>
* <p></p>
*
* @author : zkb
* @date : 2020-11-12 11:23
**/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthenticationException.class) //自定義身份驗證異常
@ResponseBody
public JSONObject AuthenticationException(AuthenticationException e){
JSONObject jsonObject=new JSONObject();
jsonObject.put("Token無效","");
jsonObject.put("msg",e.getMessage());
return jsonObject;
}
}
攔截住自己定義的異常,就知道是身份校驗出現異常了,就傳回給前端
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zkb.mapper.UserMapper">
<!-- 通用查詢映射結果 -->
<resultMap id="BaseResultMap" type="com.zkb.model.User">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
</resultMap>
</mapper>
run起來測試一下
好我取回token了
我如果設定一個錯誤的token去請求(或者不設定token都可以,自己試試)
如果我們設定正确的token
這就你登入生成的token令牌了,完事了
demo自取 https://download.csdn.net/download/qq_14926283/13452767