`
zrj_software
  • 浏览: 201507 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

WebApi接口安全认证——HTTP之摘要认证

阅读更多

       摘要访问认证是一种协议规定的Web服务器用来同网页浏览器进行认证信息协商的方法。它在密码发出前,先对其应用哈希函数,这相对于HTTP基本认证发送明文而言,更安全。从技术上讲,摘要认证是使用随机数来阻止进行密码分析的MD5加密哈希函数应用。它使用HTTP协议。

 

一、摘要认证基本流程:

 

1.客户端请求 (无认证)

GET /dir/index.html HTTP/1.0
Host: localhost

 

2.服务器响应

服务端返回401未验证的状态,并且返回WWW-Authenticate信息,包含了验证方式Digest,realm,qop,nonce,opaque的值。其中:

Digest:认证方式;

realm:领域,领域参数是强制的,在所有的盘问中都必须有,它的目的是鉴别SIP消息中的机密,在SIP实际应用中,它通常设置为SIP代理服务器所负责的域名;

qop:保护的质量,这个参数规定服务器支持哪种保护方案,客户端可以从列表中选择一个。值 “auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。需要看更详细的描述,请参阅RFC2617;

nonce:为一串随机值,在下面的请求中会一直使用到,当过了存活期后服务端将刷新生成一个新的nonce值;

opaque:一个不透明的(不让外人知道其意义)数据字符串,在盘问中发送给用户。

 

HTTP/1.0 401 Unauthorized
Server: HTTPd/0.9
Date: Sun, 10 Apr 2005 20:26:47 GMT
WWW-Authenticate: Digest realm="testrealm@host.com",
                        qop="auth,auth-int",
                        nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                        opaque="5ccc069c403ebaf9f0171e9517f40e41"

 

3.客户端请求  (用户名 "Mufasa", 密码 "Circle Of Life")

客户端接受到请求返回后,进行HASH运算,返回Authorization参数

其中:realm,nonce,qop由服务器产生;

uri:客户端想要访问的URI;

nc:“现时”计数器,这是一个16进制的数值,即客户端发送出请求的数量(包括当前这个请求),这些请求都使用了当前请求中这个“现时”值。例如,对一个给定的“现时”值,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的,是让服务器保持这个计数器的一个副本,以便检测重复的请求。如果这个相同的值看到了两次,则这个请求是重复的;

cnonce:这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护;

response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令。

response计算过程:
HA1=MD5(A1)=MD5(username:realm:password)
如果 qop 值为“auth”或未指定,那么 HA2 为
HA2=MD5(A2)=MD5(method:digestURI)
如果 qop 值为“auth-int”,那么 HA2 为
HA2=MD5(A2)=MD5(method:digestURI:MD5(entityBody))
如果 qop 值为“auth”或“auth-int”,那么如下计算 response:
response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)
如果 qop 未指定,那么如下计算 response:
response=MD5(HA1:nonce:HA2)

 

 请求头:

GET /dir/index.html HTTP/1.0
Host: localhost
Authorization: Digest username="Mufasa",
                     realm="testrealm@host.com",
                     nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                     uri="/dir/index.html",
                     qop=auth,
                     nc=00000001,
                     cnonce="0a4f113b",
                     response="6629fae49393a05397450978507c4ef1",
                     opaque="5ccc069c403ebaf9f0171e9517f40e41"

 

4.服务器响应

当服务器接收到摘要响应,也要重新计算响应中各参数的值,并利用客户端提供的参数值,和服务器上存储的口令,进行比对。如果计算结果与收到的客户响应值是相同的,则客户已证明它知道口令,因而客户的身份验证通过。

 

HTTP/1.0 200 OK

 

二、服务端验证

编写一个自定义消息处理器,需要通过System.Net.Http.DelegatingHandler进行派生,并重写SendAsync方法。

 

public class AuthenticationHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            HttpRequestHeaders headers = request.Headers;
            if (headers.Authorization != null)
            {
                Header header = new Header(request.Headers.Authorization.Parameter, request.Method.Method);

                if (Nonce.IsValid(header.Nonce, header.NounceCounter))
                {
                    // Just assuming password is same as username for the purpose of illustration
                    string password = header.UserName;

                    string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).ToMD5Hash();

                    string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash();

                    string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
                                        ha1, header.Nonce, header.NounceCounter,header.Cnonce, "auth", ha2).ToMD5Hash();

                    if (String.CompareOrdinal(header.Response, computedResponse) == 0)
                    {
                        // digest computed matches the value sent by client in the response field.
                        // Looks like an authentic client! Create a principal.
                        var claims = new List<Claim>
                        {
                                        new Claim(ClaimTypes.Name, header.UserName),
                                        new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)
                        };

                        ClaimsPrincipal principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });

                        Thread.CurrentPrincipal = principal;

                        if (HttpContext.Current != null)
                            HttpContext.Current.User = principal;
                    }
                }
            }

            HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));
            }

            return response;
        }
        catch (Exception)
        {
            var response = request.CreateResponse(HttpStatusCode.Unauthorized);
            response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));

            return response;
        }
    }
}
 

 Header类

 

public class Header
{
    public Header() { }

    public Header(string header, string method)
    {
        string keyValuePairs = header.Replace("\"", String.Empty);

        foreach (string keyValuePair in keyValuePairs.Split(','))
        {
            int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);
            string key = keyValuePair.Substring(0, index);
            string value = keyValuePair.Substring(index + 1);

            switch (key)
            {
                case "username": this.UserName = value; break;
                case "realm": this.Realm = value; break;
                case "nonce": this.Nonce = value; break;
                case "uri": this.Uri = value; break;
                case "nc": this.NounceCounter = value; break;
                case "cnonce": this.Cnonce = value; break;
                case "response": this.Response = value; break;
                case "method": this.Method = value; break;
            }
        }

        if (String.IsNullOrEmpty(this.Method))
            this.Method = method;
    }

    public string Cnonce { get; private set; }
    public string Nonce { get; private set; }
    public string Realm { get; private set; }
    public string UserName { get; private set; }
    public string Uri { get; private set; }
    public string Response { get; private set; }
    public string Method { get; private set; }
    public string NounceCounter { get; private set; }

    // This property is used by the handler to generate a
    // nonce and get it ready to be packaged in the
    // WWW-Authenticate header, as part of 401 response
    public static Header UnauthorizedResponseHeader
    {
        get
        {
            return new Header()
            {
                Realm = "MyRealm",
                Nonce = WebApiDemo.Nonce.Generate()
            };
        }
    }

    public override string ToString()
    {
        StringBuilder header = new StringBuilder();
        header.AppendFormat("realm=\"{0}\"", Realm);
        header.AppendFormat(",nonce=\"{0}\"", Nonce);
        header.AppendFormat(",qop=\"{0}\"", "auth");
        return header.ToString();
    }
}
 nonce类

 

public class Nonce
{
    private static ConcurrentDictionary<string, Tuple<int, DateTime>>
    nonces = new ConcurrentDictionary<string, Tuple<int, DateTime>>();

    public static string Generate()
    {
        byte[] bytes = new byte[16];

        using (var rngProvider = new RNGCryptoServiceProvider())
        {
            rngProvider.GetBytes(bytes);
        }

        string nonce = bytes.ToMD5Hash();

        nonces.TryAdd(nonce, new Tuple<int, DateTime>(0, DateTime.Now.AddMinutes(10)));

        return nonce;
    }

    public static bool IsValid(string nonce, string nonceCount)
    {
        Tuple<int, DateTime> cachedNonce = null;
        //nonces.TryGetValue(nonce, out cachedNonce);
        nonces.TryRemove(nonce, out cachedNonce);//每个nonce只允许使用一次

        if (cachedNonce != null) // nonce is found
        {
            // nonce count is greater than the one in record
            if (Int32.Parse(nonceCount) > cachedNonce.Item1)
            {
                // nonce has not expired yet
                if (cachedNonce.Item2 > DateTime.Now)
                {
                    // update the dictionary to reflect the nonce count just received in this request
                    //nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount), cachedNonce.Item2);

                    // Every thing looks ok - server nonce is fresh and nonce count seems to be 
                    // incremented. Does not look like replay.
                    return true;
                }
                   
            }
        }

        return false;
    }
}
 需要使用摘要验证可在代码里添加Attribute [Authorize],如:
[Authorize]
public class ProductsController : ApiController
 最后Global.asax里需注册下
GlobalConfiguration.Configuration.MessageHandlers.Add(
new AuthenticationHandler());
 
三、客户端的调用
这里主要说明使用WebClient调用
public static string Request(string sUrl, string sMethod, string sEntity, string sContentType,
    out string sMessage)
{
    try
    {
        sMessage = "";
        using (System.Net.WebClient client = new System.Net.WebClient())
        {
            client.Credentials = CreateAuthenticateValue(sUrl);
            client.Headers = CreateHeader(sContentType);

            Uri url = new Uri(sUrl);
            byte[] bytes = Encoding.UTF8.GetBytes(sEntity);
            byte[] buffer;
            switch (sMethod.ToUpper())
            {
                case "GET":
                    buffer = client.DownloadData(url);
                    break;
                case "POST":
                    buffer = client.UploadData(url, "POST", bytes);
                    break;
                default:
                    buffer = client.UploadData(url, "POST", bytes);
                    break;
            }

            return Encoding.UTF8.GetString(buffer);
        }
    }
    catch (WebException ex)
    {
        sMessage = ex.Message;
        var rsp = ex.Response as HttpWebResponse;
        var httpStatusCode = rsp.StatusCode;
        var authenticate = rsp.Headers.Get("WWW-Authenticate");

        return "";
    }
    catch (Exception ex)
    {
        sMessage = ex.Message;
        return "";
    }
}
 关键代码,在这里添加用户认证,使用NetworkCredential
private static CredentialCache CreateAuthenticateValue(string sUrl)
{
    CredentialCache credentialCache = new CredentialCache();
    credentialCache.Add(new Uri(sUrl), "Digest", new NetworkCredential("Lime", "Lime"));

    return credentialCache;
}
 至此整个认证就ok了。
分享到:
评论
1 楼 zhudoney 2015-05-06  
用了这种验证的WEBAPI怎么调用啊? 我按你的方法写了,调用返回401了,然后怎么做呢? 能不能帮忙指导一下,我QQ406238340

相关推荐

    C#进阶系列 WebApi身份认证解决方案推荐:Basic基础认证

    【C#进阶系列:WebApi身份认证解决方案推荐——Basic基础认证】 在Web开发中,尤其是在涉及API服务时,确保接口的安全性至关重要。C#的WebApi提供了多种身份验证方式,其中Basic认证是一种简单但实用的策略。本文将...

    HTTP Programming Recipes for C# Bots

    这些API提供了简单且功能强大的接口来发送HTTP请求并处理响应数据。例如,`HttpClient`支持异步操作,这对于提高程序效率至关重要。 #### 四、C# Bot编程核心知识点 ##### 4.1 HTTP通信 - **不安全HTTP**:使用...

    SpringSecurity 3.0.1.RELEASE.CHM

    22.2. 把X.509认证添加到你的web系统中 22.3. 为tomcat配置SSL 23. 替换验证身份 23.1. 概述 23.2. 配置 A. 安全数据库表结构 A.1. User表 A.1.1. 组权限 A.2. 持久登陆(Remember-Me)表 A.3. ACL表 A....

    javaAPI 各个包的内容

    - **概述**:`java.security`包提供了安全性相关的类和接口,包括加密解密、认证授权等,占比约为40%。 - **重点掌握**: - `KeyPairGenerator` 类:用于生成密钥对。 - `Cipher` 类:用于加密和解密数据。 - `...

    数据库系统大作业-基于Python的科研管理系统

    数据库系统大作业——基于Python的科研管理系统是一种利用Python编程语言构建的应用程序,旨在高效管理和组织科研数据。Python因其简洁明了的语法、丰富的库支持以及跨平台性,成为了开发此类系统的理想选择。在这个...

    JavaScriptMD5实现

    2. **API接口**:提供简单易用的API供开发者调用,如`md5(string)`或`md5(buffer)`,接受字符串或二进制数据作为输入。 3. **环境适配**:通过条件编译或模块系统(如CommonJS或AMD),确保代码能在Node.js和浏览器...

    requests-2.21.0.tar.gz

    3. 支持基本认证(HTTP Basic Auth)和摘要认证(HTTP Digest Auth)。 4. 使用预读取和流处理支持大文件上传和下载,不会一次性加载整个响应体,减少内存占用。 5. 提供易于使用的 SSL/TLS 验证机制。 6. 支持...

    计算机科学与技术_基于Android的体检中心信息管理系统设计与实现.docx

    - **后端**:基于SpringBoot框架构建,提供RESTful API接口。 - **数据库**:使用MySQL存储体检数据及相关信息。 2. **核心功能模块**: - **体检预约**:用户可以根据自身需求选择合适的体检套餐并预约时间。 ...

    基于ssm+mysql的大美新疆在线论坛交流系统源码数据库.doc

    通过RESTful API的形式提供服务接口,实现前后端分离的设计模式。 - **数据持久层**:使用MyBatis或JPA等ORM(对象关系映射)工具与MySQL数据库交互,实现对用户信息、帖子内容、评论数据等的有效管理。 - **安全性...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    综上所述,“认我测”在线认证检测系统,率先填补了认证检测领域移动端的空缺,提供了Web浏览器+移动端的双端访问模式,给用户提供了多种访问途径,真正实现了用户和检测机构的随时随地在线下单检测。 关键词:...

    Dasmotos-Arts-and-Crafts:密码学实践

    8. **Web Crypto API**:现代浏览器提供了Web Crypto API,这是一个允许开发者在浏览器中进行加密操作的标准化接口,该项目可能利用了这个API。 通过对"Dasmotos-Arts-and-Crafts"项目的深入探究,我们可以学习到...

    JAVA上百实例源码以及开源项目源代码

    Java局域网通信——飞鸽传书源代码 28个目标文件 内容索引:JAVA源码,媒体网络,飞鸽传书 Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java...

    MAC systmeOverview(苹果系概述中文版)

    - **Aqua**:介绍Mac OS X的图形界面——Aqua。 - **Finder**:探讨了Mac OS X的核心文件管理器。 - **应用程序支持**:讲解了如何在Mac OS X上开发和运行应用程序。 - **多用户**:描述了多用户环境下Mac OS X...

Global site tag (gtag.js) - Google Analytics