前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【SpringBoot WEB 系列】RestTemplate 之 Basic Auth 授权

【SpringBoot WEB 系列】RestTemplate 之 Basic Auth 授权

原创
作者头像
一灰灰blog
修改2020-07-07 10:24:49
4.9K0
修改2020-07-07 10:24:49
举报
文章被收录于专栏:小灰灰

【WEB 系列】RestTemplate 之 Basic Auth 授权

前面介绍的 RestTemplate 的所有使用姿势都是不需要鉴权的,然而实际情况可不一定都这么友好;Http Basic Auth 属于非常基础的一种鉴权方式了,将用户名和密码以 Base64 编码之后,携带在请求头,从而实现身份校验;

本文将主要介绍 RestTemplate 实现 Basic Auth 鉴权的几种姿势

<!-- more -->

I. 项目环境

博文测试项目完全基于【WEB 系列】RestTemplate 基础用法小结的项目环境,建议配合查看

基本环境:IDEA + maven + SpringBoot 2.2.1.RELEASE

1. 鉴权端点

代码语言:txt
复制
private String getHeaders(HttpServletRequest request) {
    Enumeration<String> headerNames = request.getHeaderNames();
    String name;

    JSONObject headers = new JSONObject();
    while (headerNames.hasMoreElements()) {
        name = headerNames.nextElement();
        headers.put(name, request.getHeader(name));
    }
    return headers.toJSONString();
}

private String getParams(HttpServletRequest request) {
    return JSONObject.toJSONString(request.getParameterMap());
}

private String getCookies(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    if (cookies == null || cookies.length == 0) {
        return "";
    }

    JSONObject ck = new JSONObject();
    for (Cookie cookie : cookies) {
        ck.put(cookie.getName(), cookie.getValue());
    }
    return ck.toJSONString();
}

private String buildResult(HttpServletRequest request) {
    return buildResult(request, null);
}

private String buildResult(HttpServletRequest request, Object obj) {
    String params = getParams(request);
    String headers = getHeaders(request);
    String cookies = getCookies(request);

    if (obj != null) {
        params += " | " + obj;
    }

    return "params: " + params + "\nheaders: " + headers + "\ncookies: " + cookies;
}

/**
 * 标准的http auth验证
 *
 * @param request
 * @param response
 * @return
 * @throws IOException
 */
@GetMapping(path = "auth")
public String auth(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String auth = request.getHeader("Authorization");
    if (StringUtils.isEmpty(auth)) {
        response.setStatus(401);
        response.setHeader("WWW-Authenticate", "Basic realm=\"input username and password\"");
        return buildResult(request) + "\n>>>no auth header";
    }

    String[] userAndPass = new String(new BASE64Decoder().decodeBuffer(auth.split(" ")[1])).split(":");
    if (userAndPass.length < 2) {
        response.setStatus(401);
        response.setHeader("WWW-Authenticate", "Basic realm=\"input username and password\"");
        return buildResult(request) + "\n>>>illegal auth: " + auth;
    }

    if ("user".equalsIgnoreCase(userAndPass[0]) && "pwd".equalsIgnoreCase(userAndPass[1])) {
        return buildResult(request) + "\n>>>auth: success!";
    }

    response.setStatus(401);
    response.setHeader("WWW-Authenticate", "Basic realm=\"input username and password\"");
    return buildResult(request) + "\n>>>illegal user or pwd!";
}

一个简单的鉴权逻辑如上,从请求头中拿到Authorization对应的 value,并解析用户名密码,如果满足则正确返回;如果不存在 or 不满足,则返回 http 状态码为 401,并携带对应的提示信息

II. Basic Auth 鉴权姿势

1. 请求头方式

最基础的一种是实现方式,完全根据 Basic Auth 的规则来,既然是校验请求头,那么我直接在请求头中加上即可

代码语言:txt
复制
RestTemplate restTemplate = new RestTemplate();

// 1. 最原始的办法,直接在请求头中处理
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic " + Base64Utils.encodeToString("user:pwd".getBytes()));

HttpEntity<String> ans = restTemplate
        .exchange("http://127.0.0.1:8080/auth?name=一灰灰&age=20", HttpMethod.GET, new HttpEntity<>(null, headers),
                String.class);
log.info("auth by direct headers: {}", ans);

输出

代码语言:txt
复制
(auth by direct headers: <200,params: {"name":["一灰灰"],"age":["20"]}
headers: {"authorization":"Basic dXNlcjpwd2Q=","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Java/1.8.0_171"}
cookies:
>>>auth: success!,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"264", Date:"Mon, 29 Jun 2020 09:46:06 GMT"]>

2. 拦截器方式

上面的方式不太通用,借助前面的请求头设置姿势,如果有通用的需求,借助拦截器是一个好的选择

代码语言:txt
复制
// 2. 借助拦截器的方式来实现塞统一的请求头
ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, execution) -> {
    httpRequest.getHeaders().set("Authorization", "Basic " + Base64Utils.encodeToString("user:pwd".getBytes()));
    return execution.execute(httpRequest, bytes);
};
restTemplate.getInterceptors().add(interceptor);
ans = restTemplate.getForEntity("http://127.0.0.1:8080/auth?name=一灰灰&age=20", String.class);
log.info("auth by interceptor: {}", ans);

输出

代码语言:txt
复制
(auth by interceptor: <200,params: {"name":["一灰灰"],"age":["20"]}
headers: {"authorization":"Basic dXNlcjpwd2Q=","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Java/1.8.0_171"}
cookies:
>>>auth: success!,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"264", Date:"Mon, 29 Jun 2020 09:46:06 GMT"]>

3. 标准验证拦截器

上面的拦截器主要还是我们自己来设置请求头,实际上 Spring 已经提供了标准的BasicAuthenticationInterceptor来实现我们的需求

代码语言:txt
复制
// 3. 实际上RestTemplate提供了标准的验证拦截器
restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor("user", "pwd"));
ans = restTemplate.getForEntity("http://127.0.0.1:8080/auth?name=一灰灰&age=20", String.class);
log.info("auth by interceptor: {}", ans);

输出

代码语言:txt
复制
(auth by interceptor: <200,params: {"name":["一灰灰"],"age":["20"]}
headers: {"authorization":"Basic dXNlcjpwd2Q=","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Java/1.8.0_171"}
cookies:
>>>auth: success!,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"264", Date:"Mon, 29 Jun 2020 09:46:06 GMT"]>

4. RestTemplateBuilder 方式创建 RestTemplate

RestTemplate 除了使用 new 来构造之外,还可以借助RestTemplateBuilder来创建,有时候可能更加方便简洁

代码语言:txt
复制
// 4. 创建 RestTemplate时指定
restTemplate = new RestTemplateBuilder().basicAuthentication("user", "pwd").build();
ans = restTemplate.getForEntity("http://127.0.0.1:8080/auth?name=一灰灰&age=20", String.class);
log.info("auth by RestTemplateBuilder: {}", ans);

输出

代码语言:txt
复制
(auth by RestTemplateBuilder: <200,params: {"name":["一灰灰"],"age":["20"]}
headers: {"authorization":"Basic dXNlcjpwd2Q=","content-length":"0","host":"127.0.0.1:8080","connection":"Keep-Alive","accept-encoding":"gzip","accept":"text/plain, application/json, application/*+json, */*","user-agent":"okhttp/3.14.4"}
cookies:
>>>auth: success!,[Content-Length:"309", Content-Type:"text/plain;charset=UTF-8", Date:"Mon, 29 Jun 2020 09:46:06 GMT"]>

5. 反面 case

上面介绍的几种都是正常可以工作的,接下来给出一个不能工作的 case

对于 Basic Auth,有一种常见的方式是将用户名和密码,放在 url 里面,如

那么我们直接用 RestTemplate 这么操作呢?

代码语言:txt
复制
try {
    // 直接在url中,添加用户名+密码,但是没有额外处理时,并不会生效
    restTemplate = new RestTemplate();
    ans = restTemplate.getForEntity("http://user:pwd@127.0.0.1:8080/auth?name=一灰灰&age=20", String.class);
    log.info("auth by url mode: {}", ans);
} catch (Exception e) {
    log.info("auth exception: {}", e.getMessage());
}

输出

代码语言:txt
复制
(auth exception: 401 Unauthorized

注意直接在 url 里面添加用户名密码的方式是不行的,需要额外处理

II. 其他

0. 项目&系列博文

博文

源码

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • I. 项目环境
    • 1. 鉴权端点
    • II. Basic Auth 鉴权姿势
      • 1. 请求头方式
        • 2. 拦截器方式
          • 3. 标准验证拦截器
            • 4. RestTemplateBuilder 方式创建 RestTemplate
              • 5. 反面 case
              • II. 其他
                • 0. 项目&系列博文
                  • 1. 一灰灰 Blog
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档