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