摘要认证,使用HttpClient实现HTTP digest authentication

2023-05-16

文章目录

  • 前言
  • 一、四个过程
  • 二、过程细节
  • 三、HttpClient 代码示例

前言

今天工作需要做了摘要认证(digest authentication),下面就工作中遇到的问题及过程做一个总结。

一、四个过程

F1:POST URL
F2: 401 Unauthorized
F3: 根据F2 返回的认证信息,带userName、password进行验证
F4: 返回 状态 200

二、过程细节

  1. 第一次客户端请求
    GET/POST
  2. 服务器产生一个随机数nonce,服务器将这个随机数放在WWW-Authenticate响应头,与服务器支持的认证算法列表,认证的域realm一起发送给客户端,如下例子:

HTTP /1.1 401 Unauthorized
WWW-Authenticate:Digest
realm= ”test realm”
qop=auth,auth-int”
nonce=”66C4EF58DA7CB956BD04233FBB64E0A4”
opaque=“5ccc069c403ebaf9f0171e9517f40e41”

•	realm的值是一个简单的字符串
•	qop是认证的(校验)方式
•	nonce是随机数, 可以用GUID
•	opaque是个随机字符串,它只是透传而已,即客户端还会原样返回过来。
•	algorithm 是个字符串,用来指示用来产生分类及校验和的算法对。如果该域没指定,则认为是“MD5“算法。

  1. 客户端发现是401响应,表示需要进行认证,则弹出让用户输入用户名和密码的认证窗口,客户端选择一个算法,计算出密码和其他数据的摘要(response),将摘要放到Authorization的请求头中发送给服务器,如果客户端要对服务器也进行认证,这个时候,可以发送客户端随机数cnonce。如下例子:
 GET/cgi-bin/checkout?a=b HTTP/1.1
 Authorization: Digest
 username="Mufasa", 
 realm="realm", 
 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c0",  uri="/xxxx/System/Register",  
 qop=auth,  nc=00000001,  cnonce="0a4f113b",
 response="6629fae49393a05397450978507c4ef1",  
 opaque="5ccc069c403ebaf9f0171e9517f40e41"

  1. 服务接受摘要,选择算法,获取数据库用户名密码,重新计算新的摘要跟客户端传输的摘要进行比较,验证是否匹配。
    200 OK

三、HttpClient 代码示例

/**
 * 摘要认证 两次请求
 *
 * @param url
 * @return 返回结果
 */
public static Boolean doPostDigest(String url, String username, String password) {
    log.info("Post请求url:[{}]", url);
    CloseableHttpClient httpClient = null;
    CloseableHttpResponse response = null;
    HttpPost httpPost = null;
    String strResponse = null;
    Boolean flag = false;
    try {
        httpClient = HttpClients.createDefault();
        httpPost = new HttpPost(url);
        // 构造请求头
        httpPost.setHeader("Content-type", "application/json; charset=utf-8");
        httpPost.addHeader("Cache-Control", "no-cache"); //设置缓存
        httpPost.setHeader("Connection", "Close");

        RequestConfig.Builder builder = RequestConfig.custom();
        builder.setSocketTimeout(3000); //设置请求时间
        builder.setConnectTimeout(5000); //设置超时时间
        builder.setRedirectsEnabled(false);//设置是否跳转链接(反向代理)
        // 设置 连接 属性
        httpPost.setConfig(builder.build());
        // 执行请求
        response = httpClient.execute(httpPost);
        HttpEntity responseEntity = response.getEntity();
        // 检验返回码
        int statusCode = response.getStatusLine().getStatusCode();
        log.info("第一次发送摘要认证 Post请求 返回码:{}", statusCode);
        if (401 == statusCode) {
            strResponse = EntityUtils.toString(responseEntity, "utf-8");
            log.info("Post请求401返回结果:{}", strResponse);

            // 组织参数,发起第二次请求
            Header[] headers = response.getHeaders("WWW-Authenticate");
            HeaderElement[] elements = headers[0].getElements();
            String realm = null;
            String qop = null;
            String nonce = null;
            String opaque = null;
            String method = "POST";
            String uri = "/VIID/System/Register";
            for (HeaderElement element : elements) {
                if (element.getName().equals("Digest realm")) {
                    realm = element.getValue();
                } else if (element.getName().equals("qop")) {
                    qop = element.getValue();
                } else if (element.getName().equals("nonce")) {
                    nonce = element.getValue();
                } else if (element.getName().equals("opaque")) {
                    opaque = element.getValue();
                }
            }
            // 以上为 获取第一次请求后返回的 数据
            String nc = "00000001";
            String cnonce = "uniview";
            // 后期变成可配置
            String a1 = username + ":" + realm + ":" + password;
            String a2 = method + ":" + uri;
            String response1 = null;
            // 获取 Digest 这个字符串
            String backString = response.getFirstHeader("WWW-Authenticate").getValue();
            try {
                response1 = md5DigestAsHex((md5DigestAsHex(a1.getBytes("UTF-8")) + ":" + nonce + ":" + nc
                        + ":" + "uniview" + ":" + qop + ":" + md5DigestAsHex(a2.getBytes("UTF-8"))).getBytes("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error("MD5异常:{}", e.getLocalizedMessage());
            }
            httpPost.addHeader("Authorization", backString + ",username=\"" + username + "\"" + ",realm=\"" + realm + "\""
                    + ",nonce=\"" + nonce + "\"" + ",uri=\"" + uri + "\"" + ",qop=\"" + qop + "\"" + ",nc=\"" + nc + "\""
                    + ",cnonce=\"" + cnonce + "\"" + ",response=\"" + response1 + "\"" + ",opaque=\"" + opaque);

            // 发送第二次请求
            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            int statusCode1 = response.getStatusLine().getStatusCode();
            log.info("第二次发送摘要认证 Post请求 返回码:{}");
            if (HttpStatus.SC_OK == statusCode1) {
                strResponse = EntityUtils.toString(entity, StandardCharsets.UTF_8);
                flag = true;
                return flag;
            } else {
                strResponse = EntityUtils.toString(entity, StandardCharsets.UTF_8);
                log.error("第二次鉴权认证请求非 200 返回结果:{}", strResponse);
                return flag;
            }
        } else {
            strResponse = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
            log.error("第一次鉴权认证请求非401 返回结果:{}", strResponse);
        }
    } catch (Exception e) {
        log.error("摘要认证 发送请求失败", e.getLocalizedMessage());
    } finally {
        if (null != httpPost) {
            httpPost.releaseConnection();
        }
        if (null != response) {
            try {
                response.close();
            } catch (IOException e) {
                log.error("httpResponse流关闭异常:", e);
            }
        }
        if (null != httpClient) {
            try {
                httpClient.close();
            } catch (IOException e) {
                log.error("httpClient 流关闭异常:", e);
            }
        }
    }
    return flag;
}

    public static String encode(String password) {

        try {
            //获取MD5对象
            MessageDigest instance = MessageDigest.getInstance("MD5");
            //对字符串进行加密,返回字节数组
            byte[] digest = instance.digest(password.getBytes());

            StringBuffer sb = new StringBuffer();
            for (byte b : digest) {
                //获取字节低八位有效值
                int i = b & 0xff;
                //将整数转换为16进制
                String hexString = Integer.toHexString(i);
                //将长度为1时,补零
                if (hexString.length() < 2) {
                    hexString = "0" + hexString;
                }
                //MD5永远是32位
                sb.append(hexString);
            }

            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            //没有该算法时抛出此异常
            e.printStackTrace();

        }
        return "";
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

摘要认证,使用HttpClient实现HTTP digest authentication 的相关文章

  • 使用 Passport-facebook 的 Facebook OAuth 安全性

    我目前正在使用客户端 React 组件让用户通过我的应用程序中的 OAuth 登录 Facebook 在服务器端 我使用 npm 包护照 Facebook 令牌 https github com drudge passport facebo
  • 如何在 http POST 请求中发送 HTML 代码?

    我正在从 Google Web Toolkit 应用程序向 php 脚本发送 HTTP POST 请求 内容类型现在是 application x www form urlencoded 所以我可以将变量发送到 php 并使用 POST 在
  • 这是一个合理的用户注册流程吗?

    我正在制定内部申请的注册流程 我的初步设计如下 我的主要问题是是否真的有必要包括registration confirmation code 它是保护应用程序免受现实威胁还是只是增加不必要的复杂性 对此我不确定 用户输入电子邮件地址 由于这
  • 如何结束用户会话并确保用户已注销?

    我是 aspx 的新手 现在的问题是 因为我正在做一个支持网络的项目 所以我从用户那里登录了 我拖放登录模板 然后使用 Session Authentication username Tostring 存储当前登录用户的信息等 现在我什至使
  • 如果未登录则重定向 html 页面

    我的网页上有简单的登录表单 它使用 javascript 来登录用户并且工作正常 问题是 用户直接在地址栏中输入登陆页面 URL 他无需登录即可直接访问该页面 如果他没有登录 我想将他重定向到登录页面 以下是loding和目标页面的链接 l
  • Node.js 管道化 HTTP 客户端代理?

    Node js 中内置的 HTTP 客户端似乎不支持管道请求 https stackoverflow com a 5776649 362536 然而 我突然想到 也许可以创建一个Agent https nodejs org api http
  • Vagrant 的端口转发不起作用[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我在最后遇到了一个小问题入门指南vagrant http vagrantup com docs getting started ports html
  • 如何在我的 gstreamer 中启用 httpsrc 插件?

    我尝试使用以下命令播放通过 http 检索的 mp3 文件或流 gst launch httpsrc location http domain com music mp3 mad osssink 但出现以下错误 ERREUR le pipe
  • 检查 Javascript 中的 URL 是否损坏

    这个问题之前已经发布在 Stack 上 但没有一个具体到我想要理解的内容 检查 URL 是否正确的最简单方法是发送 http Head 请求 但是如何使用它来指定 URL 呢 我在之前的帖子中发现了这一点 function UrlExist
  • 如何在 suave webpart 中设置 Json 响应

    我从 Suave 和 F 开始 我正在尝试在我的 web 部件中传递一个 json 序列化对象以在我的响应中获取它 在 php 中我有这个 player1Key hdegftzj25 gameKey aegfhzkfszl
  • 对登录 Instagram 的 PhantomJS 代码进行故障排除

    我编写了这个 PhantomJS 脚本来自动登录 Instagram 它可以成功填写表单字段并按提交按钮 但它总是被重定向回登录屏幕并显示以下消息 你的用户名或密码不正确 我 100 确信凭据是正确的 并且我用多个 Instagram 帐户
  • 使用loopj AsyncHttpClient获取SocketTimeoutExceptions...是否可以设置超时值?

    使用loopj时异步Http客户端 http loopj com android async http 库 我在发出请求时不断收到 java net SocketTimeoutException 见下文 我可以设置一些超时值吗 Note 我
  • HTTP 请求未经客户端身份验证方案“Ntlm”的授权 从服务器收到的身份验证标头为“NTLM”

    我知道有很多与此类似的问题 但我找不到针对这一特定问题的问题 首先有几点 I have 无控制通过我们的 Sharepoint 服务器 我无法调整任何 IIS 设置 我相信我们的IIS服务器版本是IIS 7 0 我们的 Sharepoint
  • gradle - 从 url 下载并解压文件

    从 url 下载和解压文件的正确 gradle 方法是什么 http 如果可能的话 我想防止每次运行任务时重新下载 在ant get可以通过以下方式实现skipexisting true 我当前的解决方案是 task foo ant get
  • C# HttpWebRequest 服务器未返回完整响应

    我正在向返回带有数据的 HTML 的服务器发出 HTTP 请求 但有时它会 停在中间 没有任何明确的解释 例如响应结束 Content length 14336 chars p p br ul ul example com var auth
  • 如何在多个Postgresql数据库之间共享表

    我的 Web 应用程序有多个部署 每个部署都是一个具有唯一 URL 的唯一站点 每个部署都有不同的数据 UI 等 但有非常相似的 Postgresql 数据库结构 带有 PostGIS 这些数据库都位于同一数据库服务器上 我希望来自 1 个
  • 将对象数组作为请求中的 url 参数传递

    我需要将一个对象数组 每个对象有 2 个字段 作为 http 请求的 url 中的参数 我该怎么做以及这个链接应该是什么样子 您可以使用您的结构创建一个 xml 即一个对象数组 每个对象都有两个字段 然后将其转换为字符串 如下所示 举个例子
  • 在 Laravel 中创建用户表

    我在 laravel 的用户表方面遇到了一些麻烦 我很久以前就已经删除了那些默认表 现在我尝试使用 Auth 但无法注册 因为数据库中没有表 但我也无法创建表php artisan migrate 因为我已经删除了那些迁移表 所以我想再次创
  • 在工厂和控制器之间共享 http.get 数据

    我成功创建了一个获取 php 文件输出 JSON 的工厂 我的问题是如何从控制器内访问它 myApp angular module myApp myApp factory mainData http gt http get gethome
  • 从 Restful 身份验证迁移到 Devise

    许多 Rails 2 3 应用程序正在使用 Restful Authentication 但该插件似乎与 Rails 3 存在一些问题 在升级到 Rails 3 时 我一直在使用 Devise 有什么办法可以顺利地从 Restful Aut

随机推荐