SpringBoot--JWT的后端搭建前后分离
Posted springboot葵花宝典
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot--JWT的后端搭建前后分离相关的知识,希望对你有一定的参考价值。
SpringBoot-零基础搭建前后端分离--后端搭建
1.创建父项目verse
- 点击
Create New Project
- 选择 Maven ,选择本地安装的JDK, 点击 Next
- 输入GroupID: com.verse 、ArtiactID:verse 点击 Finish
- 创建完成后,删除
src
- 在
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
- 右击 verse模块名,点击 New > Module
- 选择 Maven ,选择本地安装的JDK, 点击 Next
- 输入GroupID:
com.verse.commons
、ArtiactID:verse-commons
点击 Finish
- 修改
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
- 右击 verse模块名,点击 New > Module
- 选择 Maven ,选择本地安装的JDK, 点击 Next
- 输入GroupID:
com.verse.jwt
、ArtiactID:verse-jwt
点击 Finish
- 修改`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
文件下
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
生成结果如下
将system包放在verse-jwt
的com.verse.jwt包下,结果如下:
将mapper放在verse-jwt
的src\\main\\resources
包下,结果如下:
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/地址,结果如下
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属性配置类
- 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;
- 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);
- 在
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的工具类
- 添加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的后端搭建前后分离的主要内容,如果未能解决你的问题,请参考以下文章