当前位置:   article > 正文

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

bearer auth


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

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;

/**
 *
 * 根据<a href="https://jwt.io/introduction"></a> 进行构建标准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 <a href=“https://datatracker.ietf.org/doc/html/rfc6750”></a>
     * @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<String>: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>Title</title>
    <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="submit">登录</button>-->
                <button id="loginBtn" type="button">登录</button>
                <!--<button id="logoutBtn" type="button" ><a href="/user3/logout?access_token=">退出</a></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

在这里插入图片描述

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/277133
推荐阅读
相关标签
  

闽ICP备14008679号