SpringBoot--JWT的后端搭建前后分离

Posted springboot葵花宝典

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot--JWT的后端搭建前后分离相关的知识,希望对你有一定的参考价值。

SpringBoot-零基础搭建前后端分离--后端搭建

1.创建父项目verse

  1. 点击​​Create New Project​

​SpringBoot--JWT的后端搭建前后分离_swagger

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next

​SpringBoot--JWT的后端搭建前后分离_swagger_02

  1. 输入GroupID: com.verse 、ArtiactID:verse 点击 Finish

​SpringBoot--JWT的后端搭建前后分离_spring_03

  1. 创建完成后,删除​​src​

​SpringBoot--JWT的后端搭建前后分离_springsecurity_04

  1. 在​​pom.xml​​中添加依赖管理
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.verse</groupId>
<artifactId>verse</artifactId>
<version>1.0.0</version>
<description>前后端分离verse</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.parent>2.5.13</spring.parent>
<mybatis-plus-version>3.5.1</mybatis-plus-version>
<hutool.all.version>5.5.7</hutool.all.version>
<swagger.version>3.0.0</swagger.version>
</properties>
<modules>
</modules>


<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>$spring.parent</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>$mybatis-plus-version</version>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>$hutool.all.version</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>$swagger.version</version>
</dependency>

</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>$maven.compiler.source</source>
<target>$maven.compiler.target</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

2.创建verse-commons

概述

通用异常处理以及通用响应数据结构等内容

新建verse-commons  Module

  1. 右击 verse模块名,点击 New > Module

​SpringBoot--JWT的后端搭建前后分离_spring_05

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next

​SpringBoot--JWT的后端搭建前后分离_swagger_06

  1. 输入GroupID:​​ com.verse.commons​​​、ArtiactID:​​verse-commons​​ 点击 Finish

​SpringBoot--JWT的后端搭建前后分离_spring_07

  1. 修改​​verse-commons​​的pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>verse</artifactId>
<groupId>com.verse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.verse.commons</groupId>
<artifactId>verse-commons</artifactId>
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!--spring-web-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

添加统一返回结果

创建返回码接口IResultCode

/**
* 返回码接口
*/
public interface IResultCode

/**
* 返回码
*
* @return int
*/
int getCode();

/**
* 返回消息
*
* @return String
*/
String getMsg();

创建返回接口码的实现类ResultCode

package com.verse.commons.api;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
* 返回码实现
*/
@Getter
@AllArgsConstructor
public enum ResultCode implements IResultCode

/**
* 操作成功
*/
SUCCESS(200, "操作成功"),
/**
* 业务异常
*/
FAILURE(400, "业务异常"),

/**
* 业务异常
*/
Unauthorized(401, "用户、密码输入错误"),
/**
* 服务未找到
*/
NOT_FOUND(404, "服务未找到"),
/**
* 服务异常
*/
ERROR(500, "服务异常"),
USER_INPUT_ERROR(400,"您输入的数据格式错误或您没有权限访问资源!"),
/**
* Too Many Requests
*/
TOO_MANY_REQUESTS(429, "Too Many Requests");



/**
* 状态码
*/
final int code;
/**
* 消息内容
*/
final String msg;

创建 统一响应消息报文Result类

/**
* 统一响应消息报文
* @param <T>
*/
@Data
@Getter
public class Result<T> implements Serializable

private static final long serialVersionUID = 1L;

private int code;

private String msg;


private long time;


@JsonInclude(JsonInclude.Include.NON_NULL)
private T data;

private Result()
this.time = System.currentTimeMillis();


private Result(IResultCode resultCode)
this(resultCode, null, resultCode.getMsg());


private Result(IResultCode resultCode, String msg)
this(resultCode, null, msg);


private Result(IResultCode resultCode, T data)
this(resultCode, data, resultCode.getMsg());


private Result(IResultCode resultCode, T data, String msg)
this(resultCode.getCode(), data, msg);


private Result(int code, T data, String msg)
this.code = code;
this.data = data;
this.msg = msg;
this.time = System.currentTimeMillis();


/**
* 返回状态码
*
* @param resultCode 状态码
* @param <T> 泛型标识
* @return ApiResult
*/
public static <T> Result<T> success(IResultCode resultCode)
return new Result<>(resultCode);


public static <T> Result<T> success(String msg)
return new Result<>(ResultCode.SUCCESS, msg);


public static <T> Result<T> success(IResultCode resultCode, String msg)
return new Result<>(resultCode, msg);


public static <T> Result<T> data(T data)
return data(data, VerseConstant.DEFAULT_SUCCESS_MESSAGE);


public static <T> Result<T> data(T data, String msg)
return data(ResultCode.SUCCESS.code, data, msg);


public static <T> Result<T> data(int code, T data, String msg)
return new Result<>(code, data, data == null ? VerseConstant.DEFAULT_NULL_MESSAGE : msg);


public static <T> Result<T> fail()
return new Result<>(ResultCode.FAILURE, ResultCode.FAILURE.getMsg());


public static <T> Result<T> fail(String msg)
return new Result<>(ResultCode.FAILURE, msg);


public static <T> Result<T> fail(int code, String msg)
return new Result<>(code, null, msg);


public static <T> Result<T> fail(IResultCode resultCode)
return new Result<>(resultCode);


public static <T> Result<T> fail(IResultCode resultCode, String msg)
return new Result<>(resultCode, msg);


public static <T> Result<T> condition(boolean flag)
return flag ? success(VerseConstant.DEFAULT_SUCCESS_MESSAGE) : fail(VerseConstant.DEFAULT_FAIL_MESSAGE);

创建基础异常处理类BaseException

package com.verse.commons.exception;

import com.verse.commons.api.ResultCode;
import lombok.Data;
import org.springframework.http.HttpStatus;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

/**
* 基础异常处理类
*/
@Data
public class BaseException extends RuntimeException

private static final long serialVersionUID = 5782968730281544562L;

private int status = INTERNAL_SERVER_ERROR.value();

public BaseException(String message)
super(message);


public BaseException(HttpStatus status, String message)
super(message);
this.status = status.value();



public BaseException(int code, String message)
super(message);
this.code = code;
this.message =message;


//异常错误编码
private int code ;
//异常信息
private String message;

private BaseException()

public BaseException(ResultCode resultCode)
this.code = resultCode.getCode();
this.message = resultCode.getMsg();

创建verse基本常量

/**
* verse基本常量
*/
public class VerseConstant

/**
* 默认成功消息
*/
public static final String DEFAULT_SUCCESS_MESSAGE = "处理成功";

/**
* 默认失败消息
*/
public static final String DEFAULT_FAIL_MESSAGE = "处理失败";

/**
* 默认为空消息
*/
public static final String DEFAULT_NULL_MESSAGE = "承载数据为空";

3.创建verse-jwt

概述

项目的后端核心服务(Spring Boot web应用)

新建verse-jwt   Module

  1. 右击 verse模块名,点击 New > Module

​SpringBoot--JWT的后端搭建前后分离_springsecurity_08

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next

​SpringBoot--JWT的后端搭建前后分离_spring_09

  1. 输入GroupID:​​ com.verse.jwt​​​、ArtiactID:​​verse-jwt​​ 点击 Finish

​SpringBoot--JWT的后端搭建前后分离_swagger_10

  1. 修改`verse-jwt的pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>verse</artifactId>
<groupId>com.verse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.verse.jwt</groupId>
<artifactId>verse-jwt</artifactId>

<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.verse.commons</groupId>
<artifactId>verse-commons</artifactId>
<version>1.0.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mybatis-plus代码生成-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<!--velocity模板-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
</dependencies>

</project>

创建verse-jwt的入口类VerseJwtApplication

@MapperScan("com.verse.jwt.*.mapper")
@SpringBootApplication
public class VerseJwtApplication
public static void main(String[] args)
SpringApplication.run(VerseJwtApplication.class, args);

4.在verse-jwt中实现代码生成

参考​​mybatis-plus​​代码生成:https://baomidou.com/pages/779a6e/

在verse-jwt中的pom.xml添加依赖

<!--mybatis-plus代码生成-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>

创建MybatisPlusGenerator类

  • DATA_SOURCE_CONFIG:数据链接配置
  • generator:根据模板生成代码
  • getTables方法:数据库表,all表示库中所有表
  • 将模板添加到​​src\\main\\resources\\templates​​文件下

​SpringBoot--JWT的后端搭建前后分离_spring_11

public class MybatisPlusGenerator 

private static final DataSourceConfig.Builder DATA_SOURCE_CONFIG = new DataSourceConfig
.Builder("jdbc:mysql://localhost:3306/verse?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai", "root", "root");

public static void generator()
FastAutoGenerator.create(DATA_SOURCE_CONFIG)
// 全局配置
.globalConfig(builder ->
builder.author("springboot葵花宝典") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D:\\\\software\\\\file\\\\verse"); // 指定输出目录
)
// 包配置
.packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?"))
.pathInfo(Collections.singletonMap(OutputFile.xml, "D:\\\\software\\\\file\\\\verse\\\\mapper"))

)
// 策略配置
.strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
.controllerBuilder().enableRestStyle().enableHyphenStyle()
.entityBuilder().enableLombok()
// .addTableFills(
// new Column("create_by", FieldFill.INSERT),
// new Column("create_time", FieldFill.INSERT),
// new Column("update_by", FieldFill.INSERT_UPDATE),
// new Column("update_time", FieldFill.INSERT_UPDATE)
// )
.build())
/*
模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
.templateEngine(new BeetlTemplateEngine())
.templateEngine(new FreemarkerTemplateEngine())
*/
.execute();


// 处理 all 情况
protected static List<String> getTables(String tables)
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));

创建代码生成入口类GeneratorMain

public class GeneratorMain 
public static void main(String[] args)
MybatisPlusGenerator.generator();

运行GeneratorMain

​SpringBoot--JWT的后端搭建前后分离_springsecurity_12

生成结果如下

​SpringBoot--JWT的后端搭建前后分离_springboot_13

将system包放在​​verse-jwt​​的com.verse.jwt包下,结果如下:

​SpringBoot--JWT的后端搭建前后分离_springboot_14

将mapper放在​​verse-jwt​​的​​src\\main\\resources​​包下,结果如下:

​SpringBoot--JWT的后端搭建前后分离_swagger_15

5.整合Swagger-ui实现在线API文档

添加项目依赖

在​​verse-jwt​​项目的pom.xml中新增Swagger-UI相关依赖

<!--springfox swagger官方Starter-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>

添加Swagger-UI的配置

在​​verse-jwt​​​项目中添加如下类​​Swagger2Config​​:

package com.verse.jwt.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
public class Swagger2Config

@Bean
public Docket createRestApi()
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.verse.jwt.system.controller"))
.paths(PathSelectors.any())
.build()
;



private ApiInfo apiInfo()
return new ApiInfoBuilder()
.title("SwaggerUI演示")
.description("verse")
.contact(new Contact("springboot葵花宝典", null, null))
.version("1.0")
.build();

@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor()
return new BeanPostProcessor()

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider)
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));

return bean;


private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings)
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);


@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean)
try
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
catch (IllegalArgumentException | IllegalAccessException e)
throw new IllegalStateException(e);


;

修改配置

修改​​application.yml​​​文件,MVC默认的路径匹配策略为​​PATH_PATTERN_PARSER​​​,需要修改为​​ANT_PATH_MATCHER​​;

spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
url: jdbc:mysql://localhost:3306/verse?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver


mybatis-plus:
config-location:
mapper-locations:
- classpath:mapper/*.xml
- classpath*:com/**/mapper/*.xml
configuration:
map-underscore-to-camel-case: true

springfox:
documentation:
enabled: true
server:
port: 8888

添加一个测试接口

  • 在ISysUserService接口中添加方法
import com.verse.jwt.system.entity.SysUser;
import com.baomidou.mybatisplus.extension.service.IService;

/**
* <p>
* 用户信息表 服务类
* </p>
*
* @author springboot葵花宝典
* @since 2022-04-27
*/
public interface ISysUserService extends IService<SysUser>

SysUser getUserByUserName(String userName);
  • 在SysUserServiceImpl中实现
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.verse.jwt.system.entity.SysUser;
import com.verse.jwt.system.mapper.SysUserMapper;
import com.verse.jwt.system.service.ISysUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;

/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @author springboot葵花宝典
* @since 2022-04-27
*/
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService

@Resource
private SysUserMapper sysUserMapper;
/**
* 根据登录用户名查询用户信息
* @param userName 用户信息
* @return
*/
@Override
public SysUser getUserByUserName(String userName)
Assert.isTrue(StrUtil.isNotEmpty(userName),
"查询参数用户名不存在");

SysUser sysUser = sysUserMapper.selectOne(
new QueryWrapper<SysUser>().eq("username",userName));
if(sysUser != null)
sysUser.setPassword(""); //清空密码信息

return sysUser;

  • 在SysUserController中实现接口
import com.verse.jwt.system.entity.SysUser;
import com.verse.jwt.system.service.ISysUserService;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
* <p>
* 用户信息表 前端控制器
* </p>
*
* @author springboot葵花宝典
* @since 2022-04-27
*/
@RestController
@Api(tags = "SysUserController", description =" 用户信息表")
@RequestMapping("/sys-user")
public class SysUserController
@Resource
private ISysUserService sysuserService;
/**
* 根据登录用户名查询用户信息
* @param username 用户名称
* @return
*/
@ApiOperation(value = "info")
@GetMapping(value = "/info")
public SysUser info(@RequestParam("username") String username)
return sysuserService.getUserByUserName(username);

测试

启动项目后,访问http://localhost:8888/swagger-ui/地址,结果如下

​SpringBoot--JWT的后端搭建前后分离_springboot_16

6.整合SpringSecurity和JWT实现认证和授权

项目使用表说明

  • sys_user是用户信息表,用于存储用户的基本信息,如:用户名、密码
  • sys_role是角色信息表,用于存储系统内所有的角色
  • sys_menu是系统的菜单信息表,用于存储系统内所有的菜单。用id与父id的字段关系维护一个菜单树形结构。
  • sys_user_role是用户角色多对多关系表,一条userid与roleid的关系记录表示该用户具有该角色,该角色包含该用户。
  • sys_role_menu是角色菜单(权限)关系表,一条roleid与menuid的关系记录表示该角色由某菜单权限,该菜单权限可以被某角色访问。
  • sys_api,用于存储可以被访问的资源服务接口
  • sys_role_api,一条roleid与apiid的关系记录表示该角色具有某个api接口的访问权限。

添加项目依赖

  • 在​​verse-jtw​​​的pom.xml中添加​​security​​项目依赖
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--JWT(Json Web Token)登录支持-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>

添加Jwt属性配置类

  1. JwtProperties属性类
/**
* jwt配置的属性
*/
@Data
@Component
@ConfigurationProperties("verse.jwt")
public class JwtProperties
//是否开启JWT,即注入相关的类对象
private Boolean enabled;
//JWT密钥
private String secret;
//JWT有效时间
private Long expiration;
//前端向后端传递JWT时使用HTTP的header名称
private String header;
//用户获取JWT令牌发送的用户名参数名称
private String userParamName = "username";
//用户获取JWT令牌发送的密码参数名称
private String pwdParamName = "password";
//允许哪些域对本服务的跨域请求
private List<String> corsAllowedOrigins;
//允许哪些HTTP方法跨域
private List<String> corsAllowedMethods;
//是否关闭csrf跨站防御功能
private Boolean csrfDisabled = true;
//是否使用默认的JWTAuthController
private Boolean useDefaultController = true;
  1. VerseApiProperties属性类

VerseApiProperties权限全面开放的接口,不需要JWT令牌就可以访问,或者开发过程临时开放的URI

/**
* 权限全面开放的接口,不需要JWT令牌就可以访问,或者开发过程临时开放的URI
*/
@Data
@Component
@ConfigurationProperties(prefix = "verse.uaa")
public class VerseApiProperties
/**
* 监控中心和swagger需要访问的url
*/
public static final String[] ENDPOINTS =
"/jwtauth/**",
"/swagger-ui/swagger-resources/**",
"/swagger-resources/**",
"/webjars/**",
"/swagger-ui/**",
"/v2/api-docs",
"/v3/api-docs",
;
/**
* 忽略URL,List列表形式
*/
private List<String> ignoreUrl = new ArrayList<>();

/**
* 首次加载合并ENDPOINTS
*/
@PostConstruct
public void initIgnoreUrl()
Collections.addAll(ignoreUrl, ENDPOINTS);

  1. 在​​application.yml​​添加注解
verse:
jwt:
enabled: true
secret: verse
expiration: 3600000
header: JWTHeaderName
userParamName: username
pwdParamName: password
corsAllowedOrigins:
- http://localhost:8080
- http://127.0.0.1:8080
corsAllowedMethods:
- GET
- POST
useDefaultController: true
uaa:
ignoreUrl: #权限全面开放的接口,不需要JWT令牌就可以访问,或者开发过程临时开放的URI
- /sys-user/info #根据用户名获取用户信息

添加JWT token的工具类

  1. 添加JWT token的工具类用于生成和解析JWT token的工具类

相关方法说明:

  • String generateToken(String username,Map<String,String> payloads) :用于根据登录用户信息生成token
  • String getUsernameFromToken(String token):从token中获取登录用户的信息
  • Boolean validateToken(String token, String usernameParam):判断token是否还有效
/**
* JwtToken生成工具类
*/
@Slf4j
@Component
public class JwtTokenUtil

@Autowired
private JwtProperties jwtProperties;

private static final String CLAIM_KEY_CREATED = "created";

/**
* 根据用户信息生成JWT的token令牌
*
* @param username 用户
* @param payloads 令牌中携带的附加信息
* @return 令token牌
*/
public String generateToken(String username,
Map<String,String> payloads)
int payloadSizes = payloads == null? 0 : payloads.size();

Map<String, Object> claims = new HashMap<>(payloadSizes + 2);
claims.put("sub", username);
claims.put("created", new Date());

if(payloadSizes > 0)
for(Map.Entry<String,String> entry:payloads.entrySet())
claims.put(entry.getKey(),entry.getValue());



return generateToken(claims);


/**
* 从token中获取JWT中的负载
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token)
Claims claims;
try
claims = Jwts.parser()
.setSigningKey(jwtProperties.getSecret())
.parseClaimsJws(token)
.getBody();
catch (Exception e)
claims = null;

return claims;


/**
* 生成token的过期时间
*/
private Date generateExpirationDate()
return new Date(System.currentTimeMillis() + + jwtProperties.getExpiration());


/**
* 从token令牌中获取登录用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
String username;
try
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
catch (Exception e)
username = null;

return username;


/**
* 验证token是否还有效
*
* @param token 客户端传入的token令牌
* @param usernameParam 用户名的唯一标识
* @return 是否有效
*/
public Boolean validateToken(String token, String usernameParam)
//根据toekn获取用户名
String username = getUsernameFromToken(token);
return (username.equals(usernameParam) && !isTokenExpired(token));


/**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token)
try
//根据token获取获取过期时间
Date expiration = getExpiredDateFromToken( token);
return expiration.before(new Date());
catch (Exception e)
return false;



/**
* 从token中获取过期时间
*/
private Date getExpiredDateFromToken(String token)
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();


/**
* 根据负责生成JWT的token
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims)
//生成token的过期时间
Date expirationDate = generateExpirationDate();
return Jwts.builder().setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret())
.compact();


/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token)
String refreshedToken;
try
//获取负载信息
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
refreshedToken = generateToken(claims);
catch (Exception e)
refreshedToken = null;

return refreshedToken;


/**
* 从token令牌中获取登录用户名
*
* @return 用户名
*/
public String getUsernameFromToken()
String username;
try
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
String jwtToken = request.getHeader(jwtProperties.getHeader());

Claims claims = getClaimsFromToken(jwtToken);
username = claims.getSubject();
catch (Exception e)
username = null;

return username;

添加SpringSecurity的配置类

/**
* Spring Security 配置
* 可以配置多个WebSecurityConfigurerAdapter
* 但是多个Adaptor有执行顺序,默认值是100
* 这里设置为1会优先执行
*/
@Configuration
@Order(1)
public class JwtSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter

@Resource
private JwtProperties jwtProperties;

@Resource
private VerseApiProperties apiProperties;

@Resource
private MyUserDetailsService myUserDetailsService;

@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Resource
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Resource
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

@Override
public void configure(HttpSecurity http) throws Exception
if(jwtProperties.getCsrfDisabled())
http = http.csrf().disable()

;

http.cors()
.and()
.sessionManagement()// 基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
;

//通过配置实现的不需要JWT令牌就可以访问的接口
for(String uri : apiProperties.getIgnoreUrl())
http.authorizeRequests().antMatchers(uri).permitAll();

//RBAC权限控制级别的接口权限校验
http.authorizeRequests().anyRequest()
.authenticated()
//.access("@rabcService.hasPermission(request,authentication)")
;
//添加自定义未授权和未登录结果返回
http.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint);


@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(myUserDetailsService)
.passwordEncoder(passwordEncoder());


@Override
public void configure(WebSecurity web)
//将项目中静态资源路径开放出来
web.ignoring().antMatchers(apiProperties.ENDPOINTS);


@Bean
public PasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();


/**
* 跨站资源共享配置
*/
@Bean
CorsConfigurationSource corsConfigurationSource()

CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(jwtProperties.getCorsAllowedOrigins());
configuration.setAllowedMethods(jwtProperties.getCorsAllowedMethods());
configuration.applyPermitDefaultValues();

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;


@Override
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();


@Bean
public JwtAuthService jwtAuthService(JwtTokenUtil jwtTokenUtil) throws Exception
return new JwtAuthService(
this.authenticationManagerBean(),jwtTokenUtil);

相关依赖及方法说明

  • corsConfigurationSource: 跨域设置
  • configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;
  • configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
  • RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果;
  • RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果;
  • UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;
  • UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现;
  • PasswordEncoder:SpringSecurity定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder;
  • JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录
  • JwtAuthService:认证服务Service

添加RestfulAccessDeniedHandler

import cn.hutool.json.JSONUtil;
import com.verse.commons.api.Result;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 当访问接口没有权限时,自定义的返回结果
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println(JSONUtil.parse(Result.fail(e.getMessage())));
response.getWriter().flush();

添加RestAuthenticationEntryPoint

import cn.hutool.json.JSONUtil;
import com.verse.commons.api.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 当未登录或者token失效访问接口时,自定义的返回结果
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println(JSONUtil.parse(Result.fail(authException.getMessage())));
response.getWriter().flush();

添加MyU

以上是关于SpringBoot--JWT的后端搭建前后分离的主要内容,如果未能解决你的问题,请参考以下文章

JWT前后端分离demo

手摸手,带你搭建前后端分离商城系统03 整合Spring Security token 实现方案,完成主业务登录

前后端分离是什么?

Vue前后端分离项目初体验

如何在开发时部署和运行前后端分离的JavaWe

Vue.js---实现前后端分离架构中前端页面搭建