• 认证学习4 - Bearer认证(Token认证)讲解、代码实现、演示



    认证大全(想学习其他认证,请到此链接查看)

    Bearer认证(JWT-Token)- 用户信息存客户端中

    讲解(Bearer认证)

    参考文章: https://blog.csdn.net/qq_35642036/article/details/82788588

    官网(JWT): https://jwt.io/introduction

    官方文章(Bearer认证定义细节 == 必须看 == 英语菜逼得自行谷歌翻译): https://datatracker.ietf.org/doc/html/rfc6750

    注意: 登录成功的响应报文中有个基于用户信息生成一个token字段,下一次新请求时每次在请求头都会携带token值过去给服务端,服务端在校验token值是否合法,合法则说明用户有权限访问链接,不合法直接响应报文返回报错或者401

    在这里插入图片描述

    在这里插入图片描述

    能控制请求头的情况
    不能控制请求头的情况下
    不能控制请求头的情况下
    三种方式进行发送token给服务器进行认证
    1. 请求头:Authorization: Bearer token字符串
    2. Get请求:访问地址?access_token=token字符串
    3. Post请求且是application/x-www-form-urlencoded == 说明了其实就是表单 post请求commit的发送格式== 设置表单参数access_token=token字符串
    选填
    demo1
    demo2
    响应码400
    响应码401
    响应码402
    认证失败响应头格式:WWW-Authenticate: Bearer 认证其他信息
    realm:需要什么角色权限的token说明描述
    scope:需要什么角色范围的token才能访问到当前资源
    scope=openid profile email
    scope=urn:example:channel=HBO&urn:example:rating=G,PG-13
    error:认证失败的状态码
    invalid_request:请求缺失必要的参数,如没传token信息过来
    invalid_token:token不合法,认证失效
    insufficient_scope:当前token合法,但是没权限访问当前URL
    error_description:认证失败原因,用人话翻译一遍error的具体状态描述
    浏览器不得缓存结果
    浏览器不得缓存结果
    授权成功获取Token的响应请求头设置
    Cache-Control:no-store
    Pragma:no-cache

    官方:Bearer认证失败响应案例(400、401、402)
    在这里插入图片描述

    官方:授权成功(登录成功)后获取token的响应案例
    在这里插入图片描述

    实现(Bearer认证)
    代码(Bearer认证)

    文件结构
    在这里插入图片描述


    application.yml

    server:
      port: 8080
    
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        map-underscore-to-camel-case: true
        cache-enabled: true
      mapper-locations: classpath:mapper/*Mapper.xml
      global-config:
        db-config:
          id-type: assign_uuid
          logic-delete-value: 1
          logic-not-delete-value: 0
          logic-delete-field: is_del
          where-strategy: not_empty 
          update-strategy: not_empty
          insertStrategy: not_empty
    spring:
      datasource:
        driver-class-name: com.p6spy.engine.spy.P6SpyDriver
        username: root
        password: root
        url: jdbc:p6spy:mysql://localhost:3306/lrc_blog?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    
    
    
      freemarker:
        suffix: .html
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29


    MyTokenUtil.java

    package work.linruchang.qq.mybaitsplusjoin.common.util;
    
    import cn.hutool.core.codec.Base64;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.lang.Console;
    import cn.hutool.core.lang.Dict;
    import cn.hutool.core.util.ObjectUtil;
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.crypto.digest.HMac;
    import cn.hutool.crypto.digest.HmacAlgorithm;
    import cn.hutool.json.JSONUtil;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Date;
    import java.util.List;
    import java.util.Optional;
    
    /**
     *
     * 根据 进行构建标准Token
     * @author LinRuChang
     * @version 1.0
     * @date 2022/08/06
     * @since 1.8
     **/
    public class MyTokenUtil {
    
        private static Dict HEADER = Dict.create();
    
        static {
            //签名算法可以是HS256 或者 RSA 我这里写死成HS256算法
            HEADER.set("alg", "HS256")
                    .set("typ", "JWT");
        }
    
        private static Dict DEFAULT_PAYLOAD = Dict.create();
    
        static {
            DEFAULT_PAYLOAD.set("iss", "发行系统:mybaits-plus-join项目")
                    .set("exp", null)  //过期时间  毫秒
                    .set("cre", null) //创建时间 毫秒
                    .set("sub ", "可访问资源:系统任何资源都可访问")
                    .set("aud", "授权给谁:超级管理员");
        }
    
    
        /**
         * 签名密钥
         */
        private static final String secret = "sdfsdfds";
    
    
        /**
         * 生成token
         *
         * @param payload 负载
         * @return
         */
        public static String createToken(Dict payload) {
            Dict allPayload = new Dict(DEFAULT_PAYLOAD);
            if (CollUtil.isNotEmpty(payload)) {
                allPayload.putAll(payload);
            }
            if(ObjectUtil.isEmpty(allPayload.get("cre"))) { //创建时间
                allPayload.put("cre", new Date().getTime());
            }
    
    
            String HEADERJson = JSONUtil.toJsonStr(HEADER);
            String HEADERJson_Base64Url = Base64.encodeUrlSafe(HEADERJson);
    
            String allPayloadJson = JSONUtil.toJsonStr(allPayload);
            String allPayload_Base64Url = Base64.encodeUrlSafe(allPayloadJson);
    
            HMac mac = new HMac(HmacAlgorithm.HmacSHA256, secret.getBytes());
            String signSouceContent = StrUtil.format("{}.{}", HEADERJson_Base64Url, allPayload_Base64Url);
            String signature = mac.digestBase64(signSouceContent, true);
    
            return StrUtil.format("{}.{}.{}", HEADERJson_Base64Url, allPayload_Base64Url, signature);
        }
    
    
    
        /**
         * 校验token
         *  签名匹配且未过期
         * @param token
         * @return
         */
        public static boolean verify(String token) {
            List<String> splitContent = StrUtil.split(token, ".");
    
            boolean verifyFlag = false;
            if (CollUtil.size(splitContent) == 3) {
                String HEADERJson_Base64Url = splitContent.get(0);
                String allPayload_Base64Url = splitContent.get(1);
                String signature = splitContent.get(2);
    
                //系统根据前面头信息以及负载生成签名
                HMac mac = new HMac(HmacAlgorithm.HmacSHA256, secret.getBytes());
                String systemSignSourceContent = StrUtil.format("{}.{}", HEADERJson_Base64Url, allPayload_Base64Url);
                String systemSignature = mac.digestBase64(systemSignSourceContent, true);
    
                //比对签名
                if (StrUtil.equals(systemSignature, signature)) {
                    String allPayloadJson = Base64.decodeStr(allPayload_Base64Url);
                    Dict dict = JSONUtil.toBean(allPayloadJson, Dict.class);
                    String exp = dict.getStr("exp");
    
                    //比对token有效期
                    if (StrUtil.isBlank(exp) || (exp != null && new Date().getTime() < dict.getLong("exp"))) {
                        verifyFlag = true;
                    }
                }
            }
            return verifyFlag;
        }
    
        /**
         * 获取负载信息
         * @param token
         * @return
         */
        public static Dict parse(String token) {
            if (verify(token)) {
                String allPayloadJson = Base64.decodeStr(StrUtil.split(token, ".").get(1));
                return JSONUtil.toBean(allPayloadJson, Dict.class);
            }
            return null;
        }
    
    
        /**
         * 根据Bearer认证标准从用户请求中获取token 
         * @return
         */
        public static String getCurrentRequestToken()  {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String authorization = request.getHeader("Authorization");
            String token = null;
    
            //请求头
            if(StrUtil.isNotBlank(authorization)) {
                List<String> authorizationInfos = StrUtil.splitTrim(authorization, StrUtil.SPACE);
                if(CollUtil.size(authorizationInfos) == 2 && StrUtil.equals(authorizationInfos.get(0), "Bearer")) {
                    token = authorizationInfos.get(1);
                }
            } else if(StrUtil.equalsIgnoreCase(request.getMethod(),"GET") || (StrUtil.equalsIgnoreCase(request.getMethod(),"POST") && StrUtil.containsIgnoreCase(request.getHeader("Content-Type"),"application/x-www-form-urlencoded"))) {
                token = request.getParameter("access_token");
            }
    
            return Optional.ofNullable(token)
                    .orElse(null);
        }
    
    
        public static void main(String[] args) {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiLlj5HooYzns7vnu5_vvJpteWJhaXRzLXBsdXMtam9pbumhueebriIsInN1YiAiOiLlj6_orr_pl67otYTmupDvvJrns7vnu5_ku7vkvZXotYTmupDpg73lj6_orr_pl64iLCJhdWQiOiLmjojmnYPnu5nosIHvvJrotoXnuqfnrqHnkIblkZgiLCJ1c2VyTmFtZSI6ImxyYyJ9._Au456JLDQ4yIlkBYo8xiHTklrn1b2AMp46KHuKrfIU";
            boolean verify = verify(token);
            Dict parseInfo = parse(token);
            Console.log(verify);
            Console.log(parseInfo);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167


    BearerAuthInterceptor.java

    
    /**
     * 作用:Bearer认证-其实就是token认证
     *
     * @author LinRuChang
     * @version 1.0
     * @date 2022/08/04
     * @since 1.8
     **/
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class BearerAuthInterceptor implements HandlerInterceptor {
    
        /**
         * 认证错误信息
         */
        @AllArgsConstructor
        @Getter
        enum AuthErrorEnum {
    
            INVALID_REQUEST(400, "invalid_request", "请求不符合Bearer认证标准"),
            INVALID_TOKEN(401, "invalid_token", "认证失败"),
            INSUFFICIENT_SCOPE(402, "insufficient_scope", "token权限不足");
    
    
            Integer errorCode;
            String error;
            String errorDescription;
        }
    
    
        /**
         * key:token
         * List:token可访问的链接
         */
        public final static ConcurrentHashMap<String, List<String>> allSystemTokensMap = new ConcurrentHashMap();
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            boolean authStatusFlag = false;
            AuthErrorEnum authErrorEnum = AuthErrorEnum.INVALID_TOKEN;
    
            String token = MyTokenUtil.getCurrentRequestToken(); //当前的请求头token
            String uri = request.getRequestURI(); //当前的uri链接
    
            if (StrUtil.isNotBlank(token)) {
                boolean verifyToken = MyTokenUtil.verify(token);
                if (CollUtil.contains(allSystemTokensMap.keySet(), token) && verifyToken) {
                    List<String> pathPatternList = allSystemTokensMap.get(token);
                    if (CollUtil.isNotEmpty(pathPatternList)) {
                        AntPathMatcher antPathMatcher = new AntPathMatcher();
                        authStatusFlag = pathPatternList.stream().anyMatch(pathPattern -> antPathMatcher.match(pathPattern, uri));
                        if (!authStatusFlag) { //此token权限不足以访问此请求内容
                            authErrorEnum = AuthErrorEnum.INSUFFICIENT_SCOPE;
                        }
                    } else {
                        authStatusFlag = true;
                    }
                } else { //token非法或过期,从系统allSystemTokens中排除
                    allSystemTokensMap.remove(token);
                }
            } else { //token缺失-没传或者没按规范进行装填Token
                authErrorEnum = AuthErrorEnum.INVALID_REQUEST;
            }
    
    
            //认证失败
            if (!authStatusFlag) {
                response.setStatus(authErrorEnum.getErrorCode());
                response.setHeader("WWW-Authenticate", StrUtil.format("Bearer realm=\"admin token\";charset=UTF-8;error=\"{}\";error_description=\"{}\"", authErrorEnum.getError(), URLUtil.encode(authErrorEnum.getErrorDescription())));
                response.setHeader("Content-Type", "application/json;charset=UTF-8");
                response.getWriter().write(JSONUtil.toJsonStr(Dict.create()
                        .set("msg", authErrorEnum.getErrorDescription())
                        .set("code", authErrorEnum.getErrorCode())));
            }
    
            return authStatusFlag;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80


    MyConfig.java

    @Configuration
    public class MyConfig implements WebMvcConfigurer {
        @Autowired
        BearerAuthInterceptor bearerAuthInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(bearerAuthInterceptor)
                    .addPathPatterns("/**")
                    .excludePathPatterns("/js/**","/user*/**","/user*/**");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13


    ArticleCategoryController.java

    @RestController
    @RequestMapping("article-category")
    public class ArticleCategoryController {
    
        @Autowired
        ArticleCategoryService articleCategoryService;
    
        /**
         * 根据ID进行查询
         * @param id
         * @return
         */
        @GetMapping("/one/{id}")
        public CommonHttpResult<ArticleCategory> findById(@PathVariable("id") String id) {
            return CommonHttpResult.success(articleCategoryService.getById(id));
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19


    ArticleCommentController.java

    @RestController
    @RequestMapping("article-comment")
    public class ArticleCommentController {
    
        @Autowired
        ArticleCommentService articleCommentService;
    
    
        /**
         * 根据ID进行查询
         * @param id
         * @return
         */
        @GetMapping("/one/{id}")
        public CommonHttpResult<ArticleComment> findById(@PathVariable("id") String id) {
            return CommonHttpResult.success(articleCommentService.getById(id));
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20


    UserController3.java

    @Controller
    @RequestMapping("user3")
    public class UserController3 {
    
        /**
         * 登录页面
         *
         * @param httpServletRequest
         * @param modelAndView
         * @return
         */
        @GetMapping("login")
        public ModelAndView loginPage(HttpServletRequest httpServletRequest, ModelAndView modelAndView) {
            modelAndView.setViewName("login3");
            return modelAndView;
        }
    
        /**
         * 登录
         *
         * @param httpServletRequest
         * @param httpServletResponse
         * @param modelAndView
         */
        @PostMapping("login")
        @SneakyThrows
        @ResponseBody
        public Dict login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, ModelAndView modelAndView) {
            String userName = httpServletRequest.getParameter("userName");
            String password = httpServletRequest.getParameter("password");
    
            if (StrUtil.equals(userName, "admin") && StrUtil.equals(password, "admin123")) {
                Dict tokenPayload = Dict.create()
                        .set("num", UUID.fastUUID().toString(true))
                        .set("userName", userName)
                        .set("credentials", SecureUtil.md5(StrUtil.format("{}:{}", userName, password)));
                String accessToken = MyTokenUtil.createToken(tokenPayload);
                BearerAuthInterceptor.allSystemTokensMap.put(accessToken, Arrays.asList("/article-category/**"));
                return Dict.create().
                        set("code","success").
                        set("msg","登录成功==每次请求其他接口时请将access_token放置到请求头上传给服务器").
                        set("access_token",accessToken).
                        set("expires_in", -1).
                        set("token_type","Bearer");
    
            }
    
            return Dict.create().
                    set("code","failure").
                    set("msg","登录失败");
        }
    
        /**
         * 退出登录 == 命令浏览器删除cookie
         *
         * @return
         */
        @GetMapping("logout")
        @ResponseBody
        public Dict logout() {
            String currentRequestToken = MyTokenUtil.getCurrentRequestToken();
            BearerAuthInterceptor.allSystemTokensMap.remove(currentRequestToken);
            return Dict.create()
                    .set("code", "success")
                    .set("msg", StrUtil.isNotBlank(currentRequestToken) ? StrUtil.format("token已失效:{}", currentRequestToken) : null);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69


    login3.html

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
        <script src="/js/jquery.min.js">script>
        <style>
            .testRequest {
                display: block;
                margin-bottom: 15px;
            }
        style>
    head>
    <body>
    <table border="1">
    
        <form action="/user3/login" method="post">
            <tr>
                <td>账号:td>
                <td><input id="userName" name="userName" type="text">td>
            tr>
            <tr>
                <td>密码:td>
                <td><input id="password" name="password"  type="text">td>
            tr>
            <tr>
                <td colspan="2" style="text-align: center">
                    
                    <button id="loginBtn" type="button">登录button>
                    
                    <button id="logoutBtn" type="button" >退出button>
                td>
            tr>
    
    
        form>
    
    
    table>
    <div><span style="font-weight: bolder">当前用户Token:span><span id="currentUser">span>div>
    
    
    <div style="margin-top: 20px;">
        请填入access_token:<input id="accessToken" type="text" value="" placeholder="请填入access_token">
    div>
    <a target="_blank" id="testRequest1" tar class="testRequest" href="/article-category/one/c08d391e02bc11eb9416b42e99ea3e62?access_token=">测试请求(token有权限):<span id="testRequest1Href">/article-category/one/c08d391e02bc11eb9416b42e99ea3e62?access_token2=span>a>
    <a target="_blank" id="testRequest2" class="testRequest" href="/article-comment/one/1346e1060e95de36d8d8a7bbc8925dfb?access_token=">测试请求(token无访问权限):<span id="testRequest2Href">/article-category/one/c08d391e02bc11eb9416b42e99ea3e62?access_token2=span>a>
    body>
    <script>
    
        $(function () {
    
    
            var logoutLink = '/user3/logout?access_token=';
            $("input[id='accessToken']").bind('input propertychange', function() {
                var access_token = $(this).val();
                logoutLink = logoutLink.substring(0,logoutLink.indexOf('=')) + '=' + access_token;
                $(".testRequest").each(function (index) {
                    href = $(this).attr("href")
                    console.log(href)
                    var hrefPrefix = href.substring(0,href.indexOf('='))
                    var newHref = hrefPrefix + '=' + access_token;
    
                    $(this).attr("href", newHref);
                    $('#testRequest' + (index+1) + 'Href').text(newHref)
                })
            });
    
            $("#currentUser").text(localStorage.getItem("access_token"))
    
            /**
             * 登录
             */
            $("#loginBtn").click(function () {
                var userName = $("#userName").val();
                var password = $("#password").val();
    
                $.post("/user3/login",{
                    userName: userName,
                    password : password
                }, function (data) {
                    console.log(data)
                    if(data.access_token) {
                        localStorage.setItem("access_token",data.access_token);
                        $("#currentUser").text(localStorage.getItem("access_token"))
                    }
                    alert(data.msg + ": " + (data.access_token?data.access_token:''));
                },'json')
            })
    
            /**
             * 退出
             */
            $("#logoutBtn").click(function () {
                var inputToken = $("input[id='accessToken']").val()
                if(!$("input[id='accessToken']").val()) {
                    alert("请输入需要退出的Token")
                    return;
                }
                if(localStorage.getItem("access_token") == inputToken) {
                    localStorage.removeItem("access_token")
                    $("#currentUser").text('')
                }
                console.log('退出:' + logoutLink)
                $.get(logoutLink,{}, function (data) {
                    alert(data.msg)
                },'json')
            })
    
        })
    
    script>
    
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    演示(Bearer认证)
    浏览器

    在这里插入图片描述

    postman

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    【第五周】函数和代码复用
    Mysql数据库 4.SQL语言 DQL数据查询语言 查询
    torchvision中的标准ResNet50网络结构
    Lc第307场周赛 6166. 最大回文数字(贪心/分类讨论)
    利用Optional解决空指针异常
    项目管理流程文件,招标支撑文件,项目研发,验收等系列支撑文件
    轻舟程序创建的centos7.x磁盘合并到根的操作方法
    基于SpringBoot+Vue+uniapp的考试系统的详细设计和实现(源码+lw+部署文档+讲解等)
    ES6 模块化
    RFSoC应用笔记 - RF数据转换器 -21- API使用指南之配置ADC相关工作状态
  • 原文地址:https://blog.csdn.net/weixin_39651356/article/details/126396244