Spring Boot整合Mybatis + Swagger(Yapi)+ Redis + Sercurity (JWT)


本文的主要记录,通过Spring Boot整合MybatisSwaggerRedisSercurity实现基本开发框架的搭建,然后通过实现一个实现一个完整的登录验证和权限验证访问接口的例子的来测试框架的搭建。

一、项目说明

开发环境说明

  • 数据库:MySQL5.7
  • 开发工具:Idea2021.2
  • 数据库脚本:参考开源项目mall

框架版本说明

框架 版本 备注
Spring Boot 2.5.8 Maven依赖
Swagger 2.7.0

项目准备

  • 新建一个名叫base-dev-arch的新项目,点击Next
image-20211231142613399
  • 选择Spring Boot版本为2.5.8,点击Finish
image-20211231142653155
  • 清理一些不必要的文件,打造一个清爽的项目

文件说明

.gitignore: 用git做版本控制时,用这个文件控制那些文件或文件夹 不被提交(不用git的话可删除 没影响)。
HELP.md: 是一种文档格式 这个就是你项目的帮助文档(可删除 没影响)。
mvnwlinux上处理maven版本兼容问题的脚本(可删除 没影响)。
mvnw.cmdwindows上处理maven版本兼容问题的脚本(可删除 没影响)。
xxx.iml: 是IDEA特有的文件每个导入IDEA的项目都会生成一个项目同名的 .iml文件 用于保存你对这个项目的配置 (删了程序重新导入后还会生成 但由于配置丢失可能会造成程序异常。

image-20211231143043849


二、整合Swagger

配置Mybatis-generator

修改pom.xml

 <dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--MyBatis分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.10</version>
        </dependency>
        <!--集成druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- MyBatis 生成器 -->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.7</version>
        </dependency>
        <!--Mysql数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
    </dependencies>
		

修改application.yml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    
mybatis:
  mapper-locations:
    - classpath:mapper/*.xml
    - classpath*:com/**/mapper/*.xml

创建mybatis-generator.properties

jdbc_driverClassName=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
jdbc_username=root
jdbc_password=123456

创建generator-configuration.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <properties resource="mybatis-generator.properties"/>
    <classPathEntry location="/Users/shudesheng/IdeaProjects/mall-practice/documents/jar/mysql-connector-java-8.0.16.jar"/>
    <context id="MySqlContext" targetRuntime="MyBatis3" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>
        <property name="javaFileEncoding" value="UTF-8"/>
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <!--生成mapper.xml时覆盖原文件-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
        <!-- 为生成的Java模型创建一个toString方法 -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
        <!-- 取消生成注解,自动生成的注解没有用 -->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--配置数据库连接-->
        <jdbcConnection driverClass="${jdbc_driverClassName}"
                        connectionURL="${jdbc_url}"
                        userId="${jdbc_username}"
                        password="${jdbc_password}">
            <!--解决mysql驱动升级到8.0后不生成指定数据库代码的问题-->
            <property name="nullCatalogMeansCurrent" value="true" />
        </jdbcConnection>
        <!--指定生成model的路径-->
        <javaModelGenerator targetPackage="com.sds.basedevarch.entity" targetProject="src/main/java"/>
        <!--指定生成mapper.xml的路径-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>
        <!--指定生成mapper接口的的路径-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.sds.basedevarch.mapper"
                             targetProject="src/main/java"/>
        <!--生成全部表tableName设为%-->
        <table tableName="pms_brand">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>
    </context>
</generatorConfiguration>

注意:文件中classPathEntry的路径可以直接使用pom.xml下载的mybatis-generator.jar,找到目标jar的位置即可,使用决定路径


生成entity和mapper

这里通过mybatis-generator-maven-plugin生成代码

添加代码生成插件

    <build>
      <!-- 通过插件实现实体类和数据访问映射类的代码生成 -->
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
                <configuration>
                    <!-- 输出详细信息 -->
                    <verbose>true</verbose>
                    <!-- 覆盖生成文件 -->
                    <overwrite>true</overwrite>
                    <!-- 定义配置文件 -->
                    <configurationFile>${basedir}/src/main/resources/generator-configuration.xml</configurationFile>
                </configuration>
            </plugin>
        </plugins>
    </build>

生成代码

确认前面的配置文件都没有问题,配置好需要生成的类,更新一下maven,然后点击generate按钮生成代码。

image-20211231153856330

添加MyBatisConfig

package com.sds.basedevarch.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

/**
 * @description: Mapper扫描类
 * @author: shuds
 * @date: 2021/12/31
 **/
@Configuration
@MapperScan("com.sds.basedevarch.mapper")
public class MyBatisConfig {
}


增加UmsBrand接口

实现Controller接口

实现PmsBrand表中的添加、修改、删除及分页查询接口。

  • 引入公共结果返回对象

一些通用的结果返回类设计,详情请查看

image-20211231162300765
  • PmsBrandController
package com.sds.basedevarch.controller;

import com.sds.basedevarch.common.api.CommonPage;
import com.sds.basedevarch.common.api.CommonResult;
import com.sds.basedevarch.entity.PmsBrand;
import com.sds.basedevarch.service.PmsBrandService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @description: 品牌管理Controller
 * @author: shuds
 * @date: 2021/12/31
 **/

@Api(value = "PmsBrandController",description = "商品品牌管理")
@RestController
@RequestMapping("/brand")
public class PmsBrandController {
    @Autowired
    private PmsBrandService brandService;

    @ApiOperation("获取所有品牌信息")
    @GetMapping("listAll")
    public CommonResult<List<PmsBrand>> getBrandList() {
        return CommonResult.success(brandService.listAllBrand());
    }

    @ApiOperation("添加品牌")
    @PostMapping("/create")
    public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
        return  brandService.createBrand(pmsBrand);
    }

    @ApiOperation("更新品牌信息")
    @PostMapping("/update/{id}")
    public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrandDto, BindingResult result) {
        return brandService.updateBrand(id, pmsBrandDto);
    }

    @ApiOperation("根据主键ID删除品牌")
    @GetMapping("/delete/{id}")
    public CommonResult deleteBrand(@PathVariable("id") Long id) {
       return brandService.deleteBrand(id);
    }

    @ApiOperation("分页查询商品品牌信息")
    @GetMapping("/list")
    public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
                                                        @RequestParam(value = "pageSize", defaultValue = "3") Integer pageSize) {
        List<PmsBrand> brandList = brandService.listBrand(pageNum, pageSize);
        return CommonResult.success(CommonPage.restPage(brandList));
    }

    @ApiOperation("根据主键ID查询品牌详情")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
        return CommonResult.success(brandService.getBrand(id));
    }
}

添加Service接口

package com.sds.basedevarch.service;

import com.sds.basedevarch.common.api.CommonResult;
import com.sds.basedevarch.entity.PmsBrand;

import java.util.List;

/**
 * @description: PmsBrandService
 * @author: shuds
 * @date: 2021/12/31
 **/
public interface PmsBrandService {
    List<PmsBrand> listAllBrand();

    CommonResult createBrand(PmsBrand brand);

    CommonResult updateBrand(Long id, PmsBrand brand);

    CommonResult deleteBrand(Long id);

    List<PmsBrand> listBrand(int pageNum, int pageSize);

    PmsBrand getBrand(Long id);
}

实现Service接口

package com.sds.basedevarch.service.impl;

import com.github.pagehelper.PageHelper;
import com.sds.basedevarch.common.api.CommonResult;
import com.sds.basedevarch.controller.PmsBrandController;
import com.sds.basedevarch.entity.PmsBrand;
import com.sds.basedevarch.entity.PmsBrandExample;
import com.sds.basedevarch.mapper.PmsBrandMapper;
import com.sds.basedevarch.service.PmsBrandService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @description: PmsBrandService实现类
 * @author: shuds
 * @date: 2021/12/31
 **/
@Service
public class PmsBrandServiceImpl implements PmsBrandService {
    @Autowired
    private PmsBrandMapper brandMapper;
    private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);

    @Override
    public List<PmsBrand> listAllBrand() {
        return brandMapper.selectByExample(new PmsBrandExample());
    }

    @Override
    public CommonResult createBrand(PmsBrand brand) {
        // 1 声明返回结果对象
        CommonResult commonResult;

        // 2 校验参数
        if(brand == null){
            LOGGER.debug("createBrand failed:{}", brand);
            commonResult = CommonResult.failed("参数为空,请重新确认参数");
        }else{
            // 3 进行相应数据库操作
            int count = brandMapper.insertSelective(brand);
            if (count == 1) {
                commonResult = CommonResult.success(brand);
                LOGGER.debug("createBrand success:{}", brand);
            }else {
                commonResult = CommonResult.failed("操作失败");
                LOGGER.debug("createBrand failed:{}", brand);
            }
        }
        return commonResult;
    }

    @Override
    public CommonResult updateBrand(Long id, PmsBrand brand) {
        // 1 声明返回结果对象
        CommonResult commonResult;

        // 2 校验参数合法性
        if(id == null || brand == null){
            LOGGER.debug("updateBrand failed:{}", brand);
            commonResult = CommonResult.failed("操作失败");
        }else {
            // 3 执行持久层操作
            brand.setId(id);
            int count = brandMapper.updateByPrimaryKeySelective(brand);
            if (count == 1) {
                commonResult = CommonResult.success(brand);
                LOGGER.debug("updateBrand success:{}", brand);
            }else {
                commonResult = CommonResult.failed("操作失败");
                LOGGER.debug("createBrand failed:{}", brand);
            }
        }
        return commonResult;
    }

    @Override
    public CommonResult deleteBrand(Long id) {
        // 1 声明返回结果对象
        CommonResult commonResult;

        // 2 校验参数合法性
        if(id == null){
            commonResult = CommonResult.failed("操作失败");
            LOGGER.debug("updateBrand failed:{}", id);
        }else {
            // 3 执行持久层操作
            int count = brandMapper.deleteByPrimaryKey(id);
            if (count == 1) {
                LOGGER.debug("deleteBrand success :id={}", id);
                commonResult = CommonResult.success(null);
            }else {
                commonResult = CommonResult.failed("操作失败");
                LOGGER.debug("createBrand failed:id={}", id);
            }
        }
        return commonResult;
    }

    @Override
    public List<PmsBrand> listBrand(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        return brandMapper.selectByExample(new PmsBrandExample());
    }

    @Override
    public PmsBrand getBrand(Long id) {
        return brandMapper.selectByPrimaryKey(id);
    }
}

配置Swagger

添加Swagger依赖

<!--Swagger-UI API 2.7文档生产工具  begin-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>
<!--Swagger-UI API 2.7文档生产工具  end-->

添加Swagger2Config

package com.sds.basedevarch.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * Swagger2API文档的配置
 */

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //为当前包下controller生成API文档
                .apis(RequestHandlerSelectors.basePackage("com.sds.basedevarch.controller"))
                //为有@Api注解的Controller生成API文档
//                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                //为有@ApiOperation注解的方法生成API文档
//                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot基本开发框架搭建")
                .description("Created by SDS 2021/12/23")
                .version("1.0")
                .build();
    }
}

添加接口注释

  • @Api:用于修饰Controller类,生成Controller相关文档信息
  • @ApiOperation:用于修饰Controller类中的方法,生成接口方法相关文档信息
  • @ApiParam:用于修饰接口中的参数,生成接口参数相关文档信息
  • @ApiModelProperty:用于修饰实体类的属性,当实体类是请求参数或返回结果时,直接生成相关文档信息

启动测试

点击http://localhost:8080/swagger-ui.html#/

image-20211231164902094


三、整合Redis

本小节主要记录mall整合Redis的过程,以短信验证码的存储验证为例

添加redis依赖

pom.xml文件中添加redis相关依赖

<!--redis依赖配置-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

添加redis配置

application.xml文件中配置Reids信息

  • spring节点下添加Reids配置信息
redis:
  host: localhost # Redis服务器地址
  database: 0  # Redis数据库索引(默认为0)
  port: 6379  # Redis服务器连接端口
  password:   # Redis服务器连接密码(默认为空)
  jedis:
    pool:
      max-active: 8  # 连接池最大连接数(使用负值表示没有限制)
      max-wait: -1ms  # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-idle: 8  # 连接池中最大空闲连接
      min-idle: 0  # 连接池中最小空闲连接
  timeout: 3000ms  # 连接超时时间(毫秒)
  • 在根结点下添加自定义redis key
# 自定义redis key
redis:
  key:
    prefix:
      authCode: "portal:authCode:"
    expire:
      authCode: 60 # 验证码超期时间

定义RedisService

封装redis一些常用的操作

  • RedisService
package com.sds.basedevarch.service;

/**
 * redis操作service
 * 对象和数组都以Json形式进行存储
 */
public interface RedisService {

    /**
     * 存储数据
     * @param key
     * @param value
     */
    void set(String key, String value);

    /**
     * 获取数据
     * @param key
     * @return
     */
    String get(String key);

    /**
     * 设置超时时间
     * @param key
     * @param expireTime
     * @return
     */
    boolean expire(String key, long expireTime);

    /**
     * 删除数据
     * @param key
     */
    void remove(String key);

    /**
     * 自增长
     * @param key
     * @param delta 自增长步长
     * @return
     */
    Long increment(String key, long delta);
}
  • RedisServiceImpl
package com.sds.basedevarch.service.impl;

import com.sds.basedevarch.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RedisServiceImpl implements RedisService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key,value);
    }

    @Override
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    @Override
    public boolean expire(String key, long expireTime) {
        return stringRedisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
    }

    @Override
    public void remove(String key) {
        stringRedisTemplate.delete(key);
    }

    @Override
    public Long increment(String key, long delta) {
        return stringRedisTemplate.opsForValue().increment(key,delta);
    }
}

增加UmsMember接口

会员用户的获取验证码和校验验证码操作

  • UmsMemberController
package com.sds.basedevarch.controller;

import com.sds.basedevarch.common.api.CommonResult;
import com.sds.basedevarch.service.UmsMemberService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/sso")
@Api(tags = "UmsMemberController", description = "会员登录注册管理")
public class UmsMemberController {
    @Autowired
    private UmsMemberService memberService;

    @ApiOperation("获取验证码")
    @GetMapping("/getAuthCode")
    public CommonResult getAuthCode(@RequestParam String telephone){
        return memberService.generateAuthCode(telephone);
    }

    @ApiOperation("判断验证码是否正确")
    @PostMapping("/verifyAuthCode")
    public CommonResult updatePassword(@RequestParam String telephone, @RequestParam String authCode) {
        return memberService.verifyAuthCode(telephone, authCode);
    }
}
  • UmsMemberService
package com.sds.basedevarch.service;


import com.sds.basedevarch.common.api.CommonResult;

public interface UmsMemberService {
    /**
     * 生成验证码
     * @param telephone
     * @return
     */
    CommonResult generateAuthCode(String telephone);

    /**
     * 判断手机号和验证码是否匹配
     * @param telephone
     * @param authCode
     * @return
     */
    CommonResult verifyAuthCode(String telephone, String authCode);
}
  • UmsMemberServiceImpl
package com.sds.basedevarch.service.impl;

import com.sds.basedevarch.common.api.CommonResult;
import com.sds.basedevarch.service.RedisService;
import com.sds.basedevarch.service.UmsMemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Random;

@Service
public class UmsMemberServiceImpl implements UmsMemberService {
    @Autowired
    private RedisService redisService;
    @Value("${redis.key.prefix.authCode}")
    private String REDIS_KEY_PREFIX_AUTH_CODE;
    @Value("${redis.key.expire.authCode}")
    private Long AUTH_CODE_EXPIRE_SECONDS;

    @Override
    public CommonResult generateAuthCode(String telephone) {
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            sb.append(random.nextInt(10));
        }

        // 验证码绑定手机号并存储到redis
        redisService.set(REDIS_KEY_PREFIX_AUTH_CODE + telephone, sb.toString());
        redisService.expire(REDIS_KEY_PREFIX_AUTH_CODE + telephone, AUTH_CODE_EXPIRE_SECONDS);
        return CommonResult.success(sb.toString(), "获取验证码成功");
    }

    // 对输入的验证码进行校验
    @Override
    public CommonResult verifyAuthCode(String telephone, String authCode) {
        if (StringUtils.isEmpty(authCode)){
            return CommonResult.failed("请输入验证码");
        }

        String realAuthCode = redisService.get(REDIS_KEY_PREFIX_AUTH_CODE + telephone);
        boolean result = authCode.equals(realAuthCode);
        if (result){
            return CommonResult.success(null, "验证码校验成功");
        }else
            return CommonResult.failed("验证码校验不正确");
    }
}

启动测试

点击http://localhost:8080/swagger-ui.html#/,查看swagger界面

image-20220101132541491


四、整合Spring Sercurity + JWT

项目使用框架

SpringSecurity

SpringSecurity是一个强大的可高度定制的认证和授权框架,对于Spring应用来说它是一套Web安全标准。SpringSecurity注重于为Java应用提供认证和授权功能,像所有的Spring项目一样,它对自定义需求具有强大的扩展性。


JWT

JWT是JSON WEB TOKEN的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。

JWT的组成
  • JWT token的格式:header.payload.signature

  • header中用于存放签名的生成算法

    {"alg": "HS512"}Copy to clipboardErrorCopied
    
  • payload中用于存放用户名、token的生成时间和过期时间

    {"sub":"admin","created":1489079981393,"exp":1489684781}Copy to clipboardErrorCopied
    
  • signature为以header和payload生成的签名,一旦header和payload被篡改,验证将失败

    //secret为加密算法的密钥
    String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)Copy to clipboardErrorCopied
    
JWT实例

这是一个JWT的字符串

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE1NTY3NzkxMjUzMDksImV4cCI6MTU1NzM4MzkyNX0.d-iki0193X0bBOETf2UN3r3PotNIEAV7mzIxxeI5IxFyzzkOZxS0PGfF_SK6wxCv2K8S0cZjMkv6b5bCqc0VBwCopy to clipboardErrorCopied

可以在该网站上获得解析结果:https://jwt.io/ img

JWT实现认证和授权的原理
  • 用户调用登录接口,登录成功后获取到JWT的token;
  • 之后用户每次调用接口都在http的header中添加一个叫Authorization的头,值为JWT的token;
  • 后台程序通过对Authorization头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。

Hutool

Hutool是一个丰富的Java开源工具包,它帮助我们简化每一行代码,减少每一个方法,mall项目采用了此工具包。


项目使用表

  • ums_admin:后台用户表
  • ums_role:后台用户角色表
  • ums_permission:后台用户权限表
  • ums_admin_role_relation:后台用户和角色关系表,用户与角色是多对多关系
  • ums_role_permission_relation:后台用户角色和权限关系表,角色与权限是多对多关系
  • ums_admin_permission_relation:后台用户和权限关系表(除角色中定义的权限以外的加减权限),加权限是指用户比角色多出的权限,减权限是指用户比角色少的权限

整合Spring Sercurity + JWT

添加项目依赖pom.xml

<!--SpringSecurity依赖配置-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Hutool Java工具包-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.5.7</version>
</dependency>
<!--JWT(Json Web Token)登录支持-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
<!--SpringSecurity 和 JWT end-->

添加项目配置application.yml

# 自定义jwt key
jwt:
  tokenHeader: Authorization #JWT存储的请求头
  secret: mySecret #JWT加解密使用的密钥
  expiration: 604800 #JWT的超期限时间(60*60*24)
  tokenHead: Bearer  #JWT负载中拿到开头

添加JwtTokenUtil

package com.sds.basedevarch.common.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: JWT生成工具类
 * @author: shuds
 * @date: 2021/12/24
 **/
@Component
public class JwtTokenUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根据负责生成JWT的token
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从token中获取JWT中的负载
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            LOGGER.info("JWT格式验证失败:{}",token);
        }
        return claims;
    }

    /**
     * 生成token的过期时间
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 从token中获取登录用户名
     */
    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 userDetails 从数据库中查询出来的用户信息
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 判断token是否已经失效
     */
    private boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

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

    /**
     * 根据用户信息生成token
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * 判断token是否可以被刷新
     */
    public boolean canRefresh(String token) {
        return !isTokenExpired(token);
    }

    /**
     * 刷新token
     */
    public String refreshToken(String token) {
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }
}

添加SecurityConfig

package com.sds.basedevarch.config;

import com.sds.basedevarch.component.JwtAuthenticationTokenFilter;
import com.sds.basedevarch.component.RestAuthenticationEntryPoint;
import com.sds.basedevarch.component.RestfulAccessDeniedHandler;
import com.sds.basedevarch.dto.AdminUserDetails;
import com.sds.basedevarch.entity.UmsAdmin;
import com.sds.basedevarch.entity.UmsPermission;
import com.sds.basedevarch.service.UmsAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.List;

/**
 * @description: Spring Sercurity的配置类
 * @author: shuds
 * @date: 2021/12/25
 **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UmsAdminService adminService;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
                .disable()
                .sessionManagement()// 基于token,所以不需要session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/swagger-resources/**",
                        "/v2/api-docs/**",
                        "/swagger-ui/"
                )
                .permitAll()
                .antMatchers("/admin/login", "/admin/register")// 对登录注册要允许匿名访问
                .permitAll()
                .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
                .permitAll()
//                .antMatchers("/**")//测试时全部运行访问
//                .permitAll()
                .anyRequest()// 除上面外的所有请求全部需要鉴权认证
                .authenticated();
        // 禁用缓存
        httpSecurity.headers().cacheControl();
        // 添加JWT filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

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

    @Bean
    public UserDetailsService userDetailsService() {
        //获取登录用户信息
        return username -> {
            UmsAdmin admin = adminService.getAdminByUsername(username);
            if (admin != null) {
                List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId());
                return new AdminUserDetails(admin,permissionList);
            }
            throw new UsernameNotFoundException("用户名或密码错误");
        };
    }

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

添加RestfulAccessDeniedHandler

package com.sds.basedevarch.component;

import cn.hutool.json.JSONUtil;
import com.sds.basedevarch.common.api.CommonResult;
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;

/**
 * @description: 当访问没有权限时,自定义返回结果
 * @author: shuds
 * @date: 2021/12/25
 **/
@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(CommonResult.forbidden(e.getMessage())));
        response.getWriter().flush();
    }
}

添加RestAuthenticationEntryPoint

package com.sds.basedevarch.component;

import cn.hutool.json.JSONUtil;
import com.sds.basedevarch.common.api.CommonResult;
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;

/**
 * @description: 当未登录或者Token失效访问接口时,自定义返回结果
 * @author: shuds
 * @date: 2021/12/25
 **/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(e.getMessage())));
        response.getWriter().flush();
    }
}

添加AdminUserDetails

package com.sds.basedevarch.dto;

import com.sds.basedevarch.entity.UmsAdmin;
import com.sds.basedevarch.entity.UmsPermission;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @description: Spring Sercurity需要的用户详情
 * @author: shuds
 * @date: 2021/12/25
 **/
public class AdminUserDetails implements UserDetails {
    private UmsAdmin umsAdmin;
    private List<UmsPermission> permissionList;

    public AdminUserDetails(UmsAdmin umsAdmin, List<UmsPermission> permissionList) {
        this.umsAdmin = umsAdmin;
        this.permissionList = permissionList;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 返回用户权限
        return permissionList.stream()
                .filter(permission -> permission.getValue() != null)
                .map(permission -> new SimpleGrantedAuthority(permission.getValue()))
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return umsAdmin.getPassword();
    }

    @Override
    public String getUsername() {
        return umsAdmin.getUsername();
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return umsAdmin.getStatus().equals(1);
    }
}

添加JwtAuthenticationTokenFilter

package com.sds.basedevarch.component;

import com.sds.basedevarch.common.utils.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

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

/**
 * @description:JWT登录过滤器
 * @author: shuds
 * @date: 2021/12/25
 **/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter{
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            LOGGER.info("checking username:{}", username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    LOGGER.info("authenticated user:{}", username);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

登录注册功能实现

添加UmsAdminController

package com.sds.basedevarch.controller;

import com.sds.basedevarch.common.api.CommonResult;
import com.sds.basedevarch.dto.UmsAdminLoginParam;
import com.sds.basedevarch.entity.UmsAdmin;
import com.sds.basedevarch.entity.UmsPermission;
import com.sds.basedevarch.service.UmsAdminService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;

/**
 * 后台用户管理
 *
 * @description: 实现了后台用户登录、注册及获取权限的接口
 * @author: shuds
 * @date: 2021/12/25
 **/
@RestController
@RequestMapping("/admin")
@Api(tags = "UmsAdminController", description = "后台用户管理")
public class UmsAdminController {
    @Autowired
    private UmsAdminService adminService;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @ApiOperation("用户注册")
    @PostMapping("/register")
    public CommonResult<UmsAdmin> register(@RequestBody UmsAdmin umsAdminParam, BindingResult result) {
        UmsAdmin umsAdmin = adminService.register(umsAdminParam);
        if (umsAdmin == null) {
            CommonResult.failed();
        }
        return CommonResult.success(umsAdmin);
    }

    @ApiOperation("登录后返回Token")
    @PostMapping("/login")
    public CommonResult login(@RequestBody UmsAdminLoginParam umsAdminLoginParam, BindingResult result) {
        String token = adminService.login(umsAdminLoginParam.getUsername(), umsAdminLoginParam.getPassword());
        if (token == null) {
            return CommonResult.validateFailed("用户名或密码错误");
        }
        HashMap<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);
        return CommonResult.success(tokenMap);
    }

    @ApiOperation("获取用户所有权限 (包括+-权限)")
    @GetMapping("/permission/{adminId}")
    public CommonResult<List<UmsPermission>> getPermissionList(@PathVariable Long adminId) {
        List<UmsPermission> permissionList = adminService.getPermissionList(adminId);
        return CommonResult.success(permissionList);
    }
}

添加UmsAdminService

package com.sds.basedevarch.service;

import com.sds.basedevarch.entity.UmsAdmin;
import com.sds.basedevarch.entity.UmsPermission;

import java.util.List;

/**
 * @description: 后台管理员Service
 * @author: shuds
 * @date: 2021/12/25
 **/
public interface UmsAdminService {
    /**
     * 根据用户名获取后台管理员
     * @param username
     * @return
     */
    UmsAdmin getAdminByUsername(String username);

    /**
     * 注册功能
     * @param umsAdminParam
     * @return
     */
    UmsAdmin register(UmsAdmin umsAdminParam);

    /**
     * 登录功能
     * @param username 用户名
     * @param password 密码
     * @return 生成的JWT Token
     */
    String login(String username, String password);

    /**
     * 获取用户所有权限(包括角色权限和+-权限)
     * @param adminId 管理员ID
     * @return
     */
    List<UmsPermission> getPermissionList(Long adminId);
}

添加UmsAdminServiceImpl

package com.sds.basedevarch.service.impl;

import com.sds.basedevarch.common.utils.JwtTokenUtil;
import com.sds.basedevarch.entity.UmsAdmin;
import com.sds.basedevarch.entity.UmsAdminExample;
import com.sds.basedevarch.entity.UmsPermission;
import com.sds.basedevarch.mapper.UmsAdminMapper;
import com.sds.basedevarch.mapper.UmsAdminRoleRelationMapper;
import com.sds.basedevarch.service.UmsAdminService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

/**
 * @description: UmsAdminService接口实现类
 * @author: shuds
 * @date: 2021/12/25
 **/
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
    private static final Logger LOGGER = LoggerFactory.getLogger(UmsAdminServiceImpl.class);
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private UmsAdminMapper adminMapper;
    @Autowired
    private UmsAdminRoleRelationMapper adminRoleRelationMapper;

    @Override
    public UmsAdmin getAdminByUsername(String username) {
        UmsAdminExample example = new UmsAdminExample();
        example.createCriteria().andUsernameEqualTo(username);
        List<UmsAdmin> adminList = adminMapper.selectByExample(example);
        if (adminList != null && adminList.size() > 0) {
            return adminList.get(0);
        }
        return null;
    }

    @Override
    public UmsAdmin register(UmsAdmin umsAdminParam) {
        UmsAdmin umsAdmin = new UmsAdmin();
        BeanUtils.copyProperties(umsAdminParam, umsAdmin);
        umsAdmin.setCreateTime(new Date());
        umsAdmin.setStatus(1);
        //查询是否有相同用户名的用户
        UmsAdminExample example = new UmsAdminExample();
        example.createCriteria().andUsernameEqualTo(umsAdmin.getUsername());
        List<UmsAdmin> umsAdminList = adminMapper.selectByExample(example);
        if (umsAdminList.size() > 0) {
            return null;
        }
        //将密码进行加密操作
        String encodePassword = passwordEncoder.encode(umsAdmin.getPassword());
        umsAdmin.setPassword(encodePassword);
        adminMapper.insert(umsAdmin);
        return umsAdmin;
    }

    @Override
    public String login(String username, String password) {
        String token = null;
        try {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (!passwordEncoder.matches(password, userDetails.getPassword())) {
                throw new BadCredentialsException("密码不正确");
            }
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            token = jwtTokenUtil.generateToken(userDetails);
        } catch (AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        return token;
    }

    @Override
    public List<UmsPermission> getPermissionList(Long adminId) {
        return adminRoleRelationMapper.getPermissionList(adminId);
    }
}

修改Swagger配置

package com.sds.basedevarch.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

/**
 * Swagger2API文档的配置
 */

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //为当前包下controller生成API文档
                .apis(RequestHandlerSelectors.basePackage("com.sds.basedevarch.controller"))
                //为有@Api注解的Controller生成API文档
//                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                //为有@ApiOperation注解的方法生成API文档
//                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                //添加登录认证
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot基本开发框架搭建")
                .description("Created by SDS 2021/12/23")
                .version("1.0")
                .build();
    }

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

    private List<SecurityContext> securityContexts() {
        //设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/brand/.*"));
        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("Authorization", authorizationScopes));
        return result;
    }
}

给PmsBrandController中方法添加权限

  • 给查询接口添加pms:brand:read权限
  • 给修改接口添加pms:brand:update权限
  • 给删除接口添加pms:brand:delete权限
  • 给添加接口添加pms:brand:create权限

例子:

@PreAuthorize("hasAuthority('pms:brand:read')")
public CommonResult<List<PmsBrand>> getBrandList() {
    return CommonResult.success(brandService.listAllBrand());
}

认证与授权功能演示

Swagger api地址:http://localhost:8080/swagger-ui.html

运行项目,访问Swagger

image-20220101143443633


未登录前访问接口

image-20220101143522279


登录后访问接口

可以自己注册账户,也可以使用测试账户登录,(test 123456)

  • 登录账户
image-20220101144054837
  • 保存Token

image-20220101144200525


  • 访登录后访问
image-20220101143620738

访问需要权限的接口

image-20220101143649039


改用其他权限账号登录


五、总结

小结

  • 这是一个最基本的后端开发框架
  • 在这个框架的基础上学习扎实,在延伸对于其他框架的应用和学习
  • 这个框架在角色权限上面还存在一定的问题,还需要精进

思维导图

image-20220101143829223


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM