您现在的位置是:首页 > 正文

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

2024-02-29 15:36:52阅读 0

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

1.创建父项目verse

  1. 点击Create New Project

图片

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

图片

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

图片

  1. 创建完成后,删除src

图片

  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

图片

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

图片

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

图片

  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

图片

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

图片

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

图片

  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文件下

图片

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-jwtsrc\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属性配置类

  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();
    }
}

添加MyUserDetailsServiceMapper
import com.verse.jwt.auth.dto.MyUserDetails;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * 用户信息Mapper
 */
public interface MyUserDetailsServiceMapper {

    //根据username查询用户信息
    @Select("SELECT username,password,enabled\n" +
            "FROM sys_user u\n" +
            "WHERE u.username = #{username}")
    MyUserDetails findByUserName(@Param("username") String username);

    //根据username查询用户角色列表
    @Select("SELECT role_code\n" +
            "FROM sys_role r\n" +
            "LEFT JOIN sys_user_role ur ON r.id = ur.role_id  AND r.status = 0\n" +
            "LEFT JOIN sys_user u ON u.id = ur.user_id\n" +
            "WHERE u.username = #{username}")
    List<String> findRoleByUserName(@Param("username") String username);

    //根据用户角色查询用户菜单权限
    @Select({
            "<script>",
            "SELECT url " ,
            "FROM sys_menu m " ,
            "LEFT JOIN sys_role_menu rm ON m.id = rm.menu_id " ,
            "LEFT JOIN sys_role r ON r.id = rm.role_id ",
            "WHERE r.role_code IN ",
            "<foreach collection='roleCodes' item='roleCode' open='(' separator=',' close=')'>",
            "#{roleCode}",
            "</foreach>",
            " AND m.status = 0",
            "</script>"
    })
    List<String> findMenuByRoleCodes(@Param("roleCodes") List<String> roleCodes);
    //根据用户角色查询用户接口访问权限
    @Select(
            "SELECT url \n" +
                    "FROM sys_api a \n" +
                    "LEFT JOIN sys_role_api ra ON a.id = ra.api_id \n" +
                    "LEFT JOIN sys_role r ON r.id = ra.role_id \n" +
                    "WHERE r.role_code = #{roleCode} \n" +
                    "AND a.status = 0"
    )
    List<String> findApiByRoleCode(@Param("roleCode") String roleCode);
}


添加MyUserDetails
public class MyUserDetails implements UserDetails {

    String password; //密码
    String username;  //用户名
    boolean accountNonExpired;   //是否没过期
    boolean accountNonLocked;   //是否没被锁定
    boolean credentialsNonExpired;  //是否没过期
    boolean enabled;  //账号是否可用
    Collection<? extends GrantedAuthority> authorities;  //用户的权限集合

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }
}

添加JwtAuthService

JwtAuthService是认证服务的Service

/**
 * 认证登录服务
 */
public class JwtAuthService {

    private AuthenticationManager authenticationManager;
    private JwtTokenUtil jwtTokenUtil;

    @Resource
    private MyUserDetailsServiceMapper myUserDetailsServiceMapper;

    private JwtAuthService(){}

    public JwtAuthService(AuthenticationManager authenticationManager,
                          JwtTokenUtil jwtTokenUtil) {
        this.authenticationManager = authenticationManager;
        this.jwtTokenUtil = jwtTokenUtil;
    }

    /**
     * 登录认证换取JWT令牌
     * @return JWT
     */
    public String login(String username,
                        String password,
                        Map<String,String> payloads) throws BaseException {
        try {
            UsernamePasswordAuthenticationToken upToken =
                    new UsernamePasswordAuthenticationToken(username, password);
            Authentication authentication = authenticationManager.authenticate(upToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }catch (AuthenticationException e){
            throw new BaseException(ResultCode.FAILURE.getCode()
                    ,"用户名或者密码输入错误,或者新建用户未赋予角色权限!");
        }

        return jwtTokenUtil.generateToken(username,payloads);
    }


    public String refreshToken(String oldToken){
        if(!jwtTokenUtil.isTokenExpired(oldToken)){
            return jwtTokenUtil.refreshToken(oldToken);
        }
        return null;
    }

    /**
     * 获取角色信息列表
     * @param token
     * @return
     */
    public List<String> roles(String token){
        String username = jwtTokenUtil.getUsernameFromToken(token);
        //加载用户角色列表
        List<String> roleCodes =
                myUserDetailsServiceMapper.findRoleByUserName(username);
        return roleCodes;
    }

}

添加MyRBACService

MyRBACService用户角色service

/**
 * 权限服务
 */
@Component("verseService")
public class MyRBACService {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Resource
    private VerseApiProperties verseApiProperties;

    @Resource
    private MyUserDetailsServiceMapper myUserDetailsServiceMapper;

    /**
     * 判断某用户是否具有该request资源的访问权限
     */
    public boolean hasPermission(HttpServletRequest request, Authentication authentication){

        Object principal = authentication.getPrincipal();

        if(principal instanceof UserDetails){

            UserDetails userDetails = ((UserDetails)principal);
            List<GrantedAuthority> authorityList =
                    AuthorityUtils.commaSeparatedStringToAuthorityList(request.getRequestURI());
            return userDetails.getAuthorities().contains(authorityList.get(0))
                    || verseApiProperties.getIgnoreUrl().contains(request.getRequestURI());
        }

        return false;
    }


    public MyUserDetails findByUserName(String username) {
        return myUserDetailsServiceMapper.findByUserName(username);
    }

    public List<String> findRoleByUserName(String username) {
        return myUserDetailsServiceMapper.findRoleByUserName(username);
    }

    public List<String> findApiByRoleCode(String roleCode) {
        return myUserDetailsServiceMapper.findApiByRoleCode(roleCode);
    }

}

添加MyUserDetailsService
@Component
public class MyUserDetailsService implements UserDetailsService {

    @Resource
    MyRBACService myRBACService;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {

        //加载基础用户信息
        MyUserDetails myUserDetails = myRBACService.findByUserName(username);

        //加载用户角色列表
        List<String> roleCodes = myRBACService.findRoleByUserName(username);

        List<String> authorities = new ArrayList<>();
        for(String roleCode : roleCodes){
            //通过用户角色列表加载用户的资源权限列表
            authorities.addAll(myRBACService.findApiByRoleCode(roleCode));
        }

        //角色是一个特殊的权限,ROLE_前缀
        roleCodes = roleCodes.stream()
                .map(rc -> "ROLE_" +rc)
                .collect(Collectors.toList());

        authorities.addAll(roleCodes);

        myUserDetails.setAuthorities(
                AuthorityUtils.commaSeparatedStringToAuthorityList(
                        String.join(",",authorities)
                )
        );
        return myUserDetails;
    }
}

添加JwtAuthenticationTokenFilter

在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。

/**
 * JWT令牌授权过滤器
 * 1.判断令牌的有效性
 * 2.根据令牌为该用户授权可以访问的资源
 */
@Slf4j
@Configuration
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private JwtProperties jwtProperties;
    private JwtTokenUtil jwtTokenUtil;
    private MyUserDetailsService myUserDetailsService;

    private JwtAuthenticationTokenFilter(){}

    public JwtAuthenticationTokenFilter(JwtProperties jwtProperties,
                                        JwtTokenUtil jwtTokenUtil,
                                        MyUserDetailsService myUserDetailsService) {
        this.jwtProperties = jwtProperties;
        this.jwtTokenUtil = jwtTokenUtil;
        this.myUserDetailsService = myUserDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        //获取token
        String authHeader = request.getHeader(jwtProperties.getHeader());
        if (!StrUtil.isEmpty(authHeader) ) {

            String username = jwtTokenUtil.getUsernameFromToken(authHeader);
            logger.info("checking username:"+ username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authHeader, username)) {
                    //给使用该JWT令牌的用户进行授权
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                    logger.info("authenticated user:"+username);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        filterChain.doFilter(request, response);
    }
}

添加JwtAuthController

JwtAuthController主要用户获取token,刷新token

@RestController
@Api(tags = "JwtAuthController")
@RequestMapping("/jwtauth")
public class JwtAuthController {

    @Resource
    private JwtProperties jwtProperties;

    @Resource
    private JwtAuthService jwtAuthService;

    @Resource
    private MyUserDetailsServiceMapper myUserDetailsServiceMapper;


    /**
     * 使用用户名密码换JWT令牌
     */
    @ApiOperation(value = JWTConstants.CONTROLLER_AUTHENTICATION)
    @RequestMapping(value = JWTConstants.CONTROLLER_AUTHENTICATION)
    public Result login(@RequestBody Map<String,String> map){

        String username  = map.get(jwtProperties.getUserParamName());
        String password = map.get(jwtProperties.getPwdParamName());

        if(StrUtil.isEmpty(username)
                || StrUtil.isEmpty(password)){
            return Result.fail(
                    ResultCode.Unauthorized,
                    "用户名或者密码不能为空");
        }
        try {
            return Result.data(
                    jwtAuthService.login(username, password,null));
        }catch (BaseException e){
            return Result.fail(ResultCode.FAILURE,e.getMessage());
        }
    }

    /**
     * 刷新JWT令牌
     */
    @ApiOperation(value = JWTConstants.CONTROLLER_REFRESH)
    @RequestMapping(value = JWTConstants.CONTROLLER_REFRESH)
    public  Result refresh(@RequestHeader("${verse.jwt.header}") String token){
        return Result.data(jwtAuthService.refreshToken(token));
    }


    /**
     * 获取用户角色列表接口
     */
    @ApiOperation(value = JWTConstants.CONTROLLER_ROLES)
    @RequestMapping(value = JWTConstants.CONTROLLER_ROLES)
    public  Result roles(
            @RequestHeader("${verse.jwt.header}") String token){
        return Result.data(jwtAuthService.roles(token));
    }

}


修改Swagger的配置

通过修改配置实现调用接口自带Authorization头,这样就可以访问需要登录的接口了。

package com.verse.jwt.config;

import io.swagger.annotations.Api;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
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 {

    @Value("${verse.jwt.header}")
    private  String header ;


    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //.apis(RequestHandlerSelectors.basePackage("com.verse.jwt.system.controller"))
                //为有@Api注解的Controller生成API文档
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .paths(PathSelectors.any())
                .build()
                //添加登录认证
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts())
                ;

    }


    private List<SecurityScheme> securitySchemes() {
        //设置请求头信息
        List<SecurityScheme> result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("JWTVerseHeaderName", "JWTVerseHeaderName", "header");
        result.add(apiKey);
        return result;
    }

    private List<SecurityContext> securityContexts() {
        //设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/sys-role/.*"));
        return result;
    }

    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex(pathRegex))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("JWTVerseHeaderName", authorizationScopes));
        return result;
    }



    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);
                }
            }
        };
    }
}


认证与授权流程演示

运行项目,访问API

Swagger api地址:http://localhost:8888/swagger-ui/

图片

免密登录访问接口

图片

未登录前访问接口

图片

获取token

图片

  • 点击Authorize按钮,在弹框中输入登录接口中获取到的token信息

图片

  • 登录后访问获取权限列表接口,发现已经可以正常访问

图片

如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!

springboot葵花宝典

主要分享JAVA技术,主要包含SpringBoot、SpingCloud、Docker、中间件等技术,以及Github开源项目

93篇原创内容

公众号

原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!

网站文章

  • 一文带你读懂 BigDecimal 源码

    一文带你读懂 BigDecimal 源码

    点击上方「蓝字」关注我们本章带来的是BigDecimal类的源码解读。BigDecimal类是 Java 在 java.math 包中提供的API类,用来对超过16位有效位的数进行精确的...

    2024-02-29 15:36:23
  • Tomcat的部署与优化(服务部署、虚拟主机配置及相关配置文件参数优化)

    Tomcat的部署与优化(服务部署、虚拟主机配置及相关配置文件参数优化)

    1 Tomcat服务器简介1.1 Java Servlet1.2 JSP 全称Java Server Pages1.3 Tomcat 三大核心组件1.4 Tomcat 功能组件结构1.4.1 Cont...

    2024-02-29 15:36:15
  • bootloader跳转APP注意事项 F4

    bootloader跳转APP注意事项 F4

    如果bootloader程序使用操作系统,在设置栈指针时需要注意当前使用的指针时MSP还是PSP,有PSP切换到MSP可以通过触发SVC异常,就相当于进行一次上下文切换,只不过切换到的是APP程序而不...

    2024-02-29 15:36:08
  • libphonenumber:Google的公共电话号码解析库

    原文参考 https://blog.csdn.net/lib739449500/article/details/80976183其他使用方法可以参考:https://www.baeldung.com/java-libphonenumber官方文档:https://github.com/google/libphonenumber

    2024-02-29 15:35:42
  • 解析互联网广告术语 CPM、CPC、CPA、CPS、CPL、CPR 是什么意思

    解析互联网广告术语 CPM、CPC、CPA、CPS、CPL、CPR 是什么意思

    之前就很想在泪雪博客分享这些互联网中最基础的广告术语,却一直没有被提上日程,导致子凡之前以前都追求高质量的干货内容,从而有缺少一些积淀性的内容,也正是因为昨天谈及到了OCPC 目标转化出价的一个话题,所以就想着把这些给补上。 其实说真的,子凡英语很差,但是对于一些专业性的术语还是非常感兴趣,而且在写文章的时候也更喜欢用简单的中文名称来记录,而有些时候使用英文简写就显得非常方面,中文又...

    2024-02-29 15:35:36
  • Android 炫酷自定义 View - 剑气加载

    Android 炫酷自定义 View - 剑气加载

    这个效果仔细看,就是有三个类似月牙形状的元素进行循环转动,我们只需要拆解出一个月牙来做效果即可,最后再将三个月牙组合起来就可以达到最终效果了。我们上面做了固定角度的 X, Y 轴的旋转,这个时候我们只...

    2024-02-29 15:35:29
  • 分布式事务(3)TCC方案

    分布式事务(3)TCC方案

    分布式事务(3)TCC

    2024-02-29 15:35:00
  • [Portal参考手册]Portlet核心API

    Portlet 类 Portlet 类是一个Portlet 的代码表示,它从PortletAdapter 继承而来。Portlet instance (portlet实例) Portlet类实例是一个Portlet 类的实例,由PortletConfig 中提供的一系列参数参数化的结果,每一个Portlet类实例中都包括一个PortletConfi...

    2024-02-29 15:34:54
  • 中兴服务器bios启动顺序设置,主板四大品牌BIOS设置开机第一启动项图文教程

    中兴服务器bios启动顺序设置,主板四大品牌BIOS设置开机第一启动项图文教程

    这篇文章主要介绍了主板四大品牌BIOS设置开机第一启动项图文教程,本文讲解了AMI BIOS、Award BIOS、Phoenix Bios、Insyde Bios等品牌BIOS设置开机第一启动项的方...

    2024-02-29 15:34:41
  • 查找指定字符_Excel常用查找函数

    查找指定字符_Excel常用查找函数

    对于数据分析师来讲Excel的重要性不言而喻。Excel因为其简化的界面和强大的功能至今都是各个企业进行数据分析高频的使用工具。为了实现分析者的目的,Excel中拥有强大的函数功能,可以通过函数的组合...

    2024-02-29 15:34:13