JWT
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
你做数据操作时,后端怎么知道是你这个人操作了而不是别人,一般web服务肯定不是你一个人在用吧?
倘若是前后端分离的那种形式,后端只出api,那怎么保证是你允许的人来访问你的api呢?不可能人人都响应吧?
那就要谈一谈token的认证和传统的session认证,http协议本身是一种无状态的协议,那怎么保证是我们允许的人来操作呢?
首先我们要有身份的确立吧,比如登录,登录完,我们给它分配一个用于识别身份的信息,每次请求都要求他们把身份信息给到我们,我们就能知道是谁操作了吧!
传统的Session,都知道我们是把它保存在内存中的,小用户量可以很完美的跑起来,但是用户量巨大,在线人数巨大,那对服务器来说这种是一个不小的负担,那么怎么解决呢?
Token 倘若我们不保存它们的信息,把它们的身份信息按照与它们的约定做成一个凭证,我们服务器可以校验这个凭证的真伪,那就没有这种压力了吧(因为是可以解密的所以你懂的)
好,我们就来看看jwt
这是我整合的一个例子 主要用到了 springboot + swagger + mybatisplus+jwt+mysql
<?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