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

【SpringBoot--JWT的后端搭建前后分离】博观而约取,厚积而薄发。这篇文章主要讲述SpringBoot--JWT的后端搭建前后分离相关的知识,希望能为你提供帮助。
SpringBoot-零基础搭建前后端分离--后端搭建1.创建父项目verse

  1. 点击??Create New Project??
SpringBoot--JWT的后端搭建前后分离

文章图片

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next
SpringBoot--JWT的后端搭建前后分离

文章图片

  1. 输入GroupID: com.verse 、ArtiactID:verse 点击 Finish
SpringBoot--JWT的后端搭建前后分离

文章图片

  1. 创建完成后,删除??src??
SpringBoot--JWT的后端搭建前后分离

文章图片

  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的后端搭建前后分离

文章图片

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next
SpringBoot--JWT的后端搭建前后分离

文章图片

  1. 输入GroupID:?? com.verse.commons???、ArtiactID:??verse-commons?? 点击 Finish
SpringBoot--JWT的后端搭建前后分离

文章图片

  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 = https://www.songbingjia.com/android/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 =https://www.songbingjia.com/android/= 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的后端搭建前后分离

文章图片

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next
SpringBoot--JWT的后端搭建前后分离

文章图片

  1. 输入GroupID:?? com.verse.jwt???、ArtiactID:??verse-jwt?? 点击 Finish
SpringBoot--JWT的后端搭建前后分离

文章图片

  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的后端搭建前后分离

文章图片

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的后端搭建前后分离

文章图片

生成结果如下
SpringBoot--JWT的后端搭建前后分离

文章图片

将system包放在??verse-jwt??的com.verse.jwt包下,结果如下:
SpringBoot--JWT的后端搭建前后分离

文章图片

将mapper放在??verse-jwt??的??src\\main\\resources??包下,结果如下:
SpringBoot--JWT的后端搭建前后分离

文章图片

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 = "https://www.songbingjia.com/android/info")
@GetMapping(value = "https://www.songbingjia.com/info")
public SysUser info(@RequestParam("username") String username)
return sysuserService.getUserByUserName(username);


测试
启动项目后,访问http://localhost:8888/swagger-ui/地址,结果如下
SpringBoot--JWT的后端搭建前后分离

文章图片

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

    推荐阅读