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

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篇原创内容

公众号

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 外卖项目的前后端分离是指将项目的前端部分和后端部分进行分离开发,前端使用Vue框架,后端使用Spring Boot框架。 前端使用Vue框架可以提供良好的用户界面和交互体验。Vue具有组件化的特点,使得前端开发更加模块化和可维护。同时,Vue的数据绑定和响应式设计可以帮助实现快速更新页面的功能。通过Vue,用户可以方便地浏览外卖项目的菜单、下单、支付等操作,提升用户的使用体验。 后端使用Spring Boot框架可以提供强大的后台支持。Spring Boot是一种轻量级的Java框架,可以快速搭建和部署项目,减少开发的复杂度。使用Spring Boot,开发人员可以方便地实现外卖项目的后台逻辑,例如订单的处理、菜单的管理、支付的接口等。同时,Spring Boot集成了许多常用且可靠的开源库,为项目提供了高效、稳定的基础设施。 前后端分离的优势在于前端和后端可以并行开发,提高开发效率。前端和后端之间通过定义接口进行通信,降低了耦合度,灵活性更强。同时,单独部署前端和后端也可以提高项目的可维护性和可扩展性。例如,当需要添加新的功能或修改现有功能时,只需要修改相应的前端或后端代码,而不会影响到整个项目。 总之,外卖项目的前后端分离以及使用Vue和Spring Boot框架的设计选择,可以帮助实现一个高效、稳定、可扩展的外卖平台。 ### 回答2: 外卖项目采用前后端分离的架构,前端使用Vue框架,后端采用Spring Boot框架。 前端使用Vue框架可以实现用户界面的可视化设计和交互体验。Vue框架具有简单易用、灵活可扩展、高效性能等特点,适用于构建复杂的单页面应用(SPA)。通过Vue框架,可以实现用户注册、登录、浏览菜单、购物车管理、订单处理等功能的前端设计和开发。前端通过调用后端接口,获取后端处理的数据,并将数据展示在用户界面上。 后端使用Spring Boot框架可以实现业务逻辑的处理和数据存储。Spring Boot框架提供了快速构建、简化配置和集成多种功能的特性,适用于快速开发和维护可靠的应用程序。通过Spring Boot框架,可以处理用户注册、登录验证、菜单管理、订单处理等业务逻辑,并与数据库进行交互,存储与外卖项目相关的数据。后端还需要提供RESTful接口,供前端调用和交互。 前后端分离架构的好处是可以实现前端与后端的解耦,提高开发效率和维护性。前端和后端可以同时进行开发,并可采用不同的技术栈,使得团队成员能够专注于自己的领域。前后端分离还可以实现多端复用,例如可以用同一组后端接口提供给Web端和移动端调用。 总的来说,外卖项目采用前后端分离的架构,借助Vue和Spring Boot框架实现了用户界面的展示和交互以及业务逻辑的处理和数据存储,从而使得项目开发更加高效和可维护。 ### 回答3: 外卖项目采用前后端分离的架构,前端使用Vue.js框架进行开发,后端使用Spring Boot框架进行开发。 前端使用Vue.js框架的原因是因为Vue.js具有简洁、高效、灵活的特点,能够轻松构建交互式的用户界面。Vue.js还拥有一套完整的生态系统,能够方便地进行组件化开发,并提供了强大的工具来处理数据和状态的变化。 后端使用Spring Boot框架的原因是因为Spring Boot是一个简化了Spring开发的微框架,能够快速构建可独立运行的、生产级的应用。Spring Boot提供了大量的开箱即用的特性,如自动配置、快速开发等,能够极大地提高开发效率。 在外卖项目中,前端负责用户界面的展示和交互逻辑的实现。前端通过Vue.js进行组件化开发,将页面拆分为多个可复用的组件,提高开发效率和代码维护性。前端还通过Vue.js提供的路由功能,实现不同页面之间的跳转和导航。同时,前端还与后端通过HTTP协议进行通信,获取后端提供的数据和服务,并将其展示给用户。 后端负责处理前端发送的请求,并根据业务逻辑进行相应的处理,最终返回数据给前端。后端还负责与数据库交互,对数据进行增删改查操作。后端使用Spring Boot提供的RESTful风格的API,能够轻松构建出符合规范的接口。同时,后端还可以利用Spring Security进行权限管理,确保只有具备相应权限的用户才能访问特定的接口。 综上所述,外卖项目采用前后端分离的架构,前端使用Vue.js框架进行开发,后端使用Spring Boot框架进行开发,能够提高开发效率和代码的可维护性,同时还能够满足用户对于界面交互和数据操作的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值