ASP.NET 웹 API를 보호하는 방법 [닫기]

타사 개발자가 내 응용 프로그램 데이터에 액세스하는 데 사용할 ASP.NET 웹 API를 사용하여 RESTful 웹 서비스 를 만들고 싶습니다 .

OAuth 에 대해 많이 읽었 으며 표준으로 보이지만 작동 방식 (실제로는 작동합니다!)을 설명하는 문서를 사용하여 좋은 샘플을 찾는 것은 매우 어렵습니다 (특히 OAuth 초보자에게는).

실제로 빌드하고 작동하며이를 구현하는 방법을 보여주는 샘플이 있습니까?

수많은 샘플을 다운로드했습니다.

  • DotNetOAuth-초보자 관점에서 문서가 희망이 없습니다.
  • Thinktecture-빌드 할 수 없음

또한 간단한 토큰 기반 체계 (이와 같은 )를 제안하는 블로그를 보았습니다. 이것은 바퀴를 다시 발명하는 것처럼 보이지만 개념적으로 상당히 간단하다는 이점이 있습니다.

SO에는 이와 같은 많은 질문이 있지만 좋은 답변은없는 것 같습니다.

이 공간에서 모두가 무엇을하고 있습니까?



답변

최신 정보:

JWT에 관심이있는 모든 사람들을 위해 ASP.NET 웹 API에 JWT 인증을 사용하는 방법에 대한 다른 답변 에이 링크를 추가했습니다 .


웹 API를 보호하기 위해 HMAC 인증을 적용했으며 정상적으로 작동했습니다. HMAC 인증은 소비자와 서버 모두 메시지를 hmac 해시하는 것으로 알고있는 각 소비자에 대해 비밀 키를 사용하므로 HMAC256을 사용해야합니다. 대부분의 경우 소비자의 해시 비밀번호는 비밀 키로 사용됩니다.

메시지는 일반적으로 HTTP 요청의 데이터 또는 HTTP 헤더에 추가 된 사용자 정의 데이터로 작성되며 메시지에는 다음이 포함될 수 있습니다.

  1. 타임 스탬프 : 요청이 전송 된 시간 (UTC 또는 GMT)
  2. HTTP 동사 : GET, POST, PUT, DELETE
  3. 데이터 게시 및 쿼리 문자열
  4. URL

기본적으로 HMAC 인증은 다음과 같습니다.

소비자는 HTTP 요청의 템플릿 인 서명 (hmac 해시 출력)을 작성한 후 웹 서버에 HTTP 요청을 보냅니다.

User-Agent: {agent}
Host: {host}
Timestamp: {timestamp}
Authentication: {username}:{signature}

GET 요청의 예 :

GET /webapi.hmac/api/values

User-Agent: Fiddler
Host: localhost
Timestamp: Thursday, August 02, 2012 3:30:32 PM
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=

서명을 얻기 위해 해시 할 메시지 :

GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n

쿼리 문자열이있는 POST 요청의 예

POST /webapi.hmac/api/values?key2=value2

User-Agent: Fiddler
Host: localhost
Content-Type: application/x-www-form-urlencoded
Timestamp: Thursday, August 02, 2012 3:30:32 PM
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=

key1=value1&key3=value3

서명을 얻기 위해 해시 할 메시지

GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n
key1=value1&key2=value2&key3=value3

양식 데이터와 쿼리 문자열은 순서대로 정렬되어야하므로 서버의 코드는 쿼리 문자열과 양식 데이터를 가져와 올바른 메시지를 작성합니다.

HTTP 요청이 서버에 도착하면 HTTP 동사, 타임 스탬프, URI, 양식 데이터 및 쿼리 문자열과 같은 정보를 얻기 위해 요청을 구문 분석 한 다음이를 기반으로 시크릿으로 서명 (hmac 해시 사용)을 작성하는 인증 조치 필터가 구현됩니다. 서버의 키 (해시 비밀번호)

비밀 키는 요청시 사용자 이름으로 데이터베이스에서 가져옵니다.

그런 다음 서버 코드는 요청의 서명과 서명이 작성된 서명을 비교합니다. 동일하면 인증이 전달되고 그렇지 않으면 인증이 실패합니다.

서명을 작성하는 코드 :

private static string ComputeHash(string hashedPassword, string message)
{
    var key = Encoding.UTF8.GetBytes(hashedPassword.ToUpper());
    string hashString;

    using (var hmac = new HMACSHA256(key))
    {
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
        hashString = Convert.ToBase64String(hash);
    }

    return hashString;
}

그렇다면 리플레이 공격을 막는 방법은 무엇입니까?

타임 스탬프에 대한 제약 조건을 추가하십시오.

servertime - X minutes|seconds  <= timestamp <= servertime + X minutes|seconds 

(서버 시간 : 요청이 서버에 들어오는 시간)

그리고 요청의 서명을 메모리에 캐시하십시오 (MemoryCache 사용, 시간 제한을 유지해야 함). 다음 요청에 이전 요청과 동일한 서명이있는 경우 거부됩니다.

데모 코드는 다음과 같습니다.
https://github.com/cuongle/Hmac.WebApi


답변

가장 간단한 솔루션부터 시작하는 것이 좋습니다. 시나리오에서 간단한 HTTP 기본 인증 + HTTPS로 충분할 수 있습니다.

그렇지 않은 경우 (예 : https를 사용할 수 없거나보다 복잡한 키 관리가 필요한 경우) 다른 사람이 제안한대로 HMAC 기반 솔루션을 살펴볼 수 있습니다. 이러한 API의 좋은 예는 Amazon S3입니다 ( http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html )

ASP.NET Web API에서 HMAC 기반 인증에 대한 블로그 게시물을 작성했습니다. 웹 API 서비스 및 웹 API 클라이언트에 대해 설명하고 코드는 비트 버킷에서 사용할 수 있습니다. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/

다음은 웹 API의 기본 인증에 대한 게시물입니다. http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/

타사에 API를 제공하려는 경우 클라이언트 라이브러리를 제공해야 할 수도 있습니다. 기본 인증은 대부분의 프로그래밍 플랫폼에서 기본적으로 지원되므로 여기서 중요한 이점이 있습니다. 반면 HMAC는 표준화되지 않았으며 맞춤형 구현이 필요합니다. 이들은 비교적 간단해야하지만 여전히 작업이 필요합니다.

추신. HTTPS + 인증서를 사용하는 옵션도 있습니다. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/


답변

DevDefined.OAuth를 사용해 보셨습니까?

2-Legged OAuth로 WebApi를 보호하는 데 사용했습니다. 또한 PHP 클라이언트로 성공적으로 테스트했습니다.

이 라이브러리를 사용하여 OAuth에 대한 지원을 추가하는 것은 매우 쉽습니다. 다음은 ASP.NET MVC 웹 API 용 공급자를 구현하는 방법입니다.

1) DevDefined.OAuth의 소스 코드를 가져옵니다. https://github.com/bittercoder/DevDefined.OAuth- 최신 버전에서는OAuthContextBuilder 확장 성을 .

2) 라이브러리를 빌드하고 웹 API 프로젝트에서 참조하십시오.

3) 다음에서 컨텍스트 작성을 지원하는 사용자 정의 컨텍스트 빌더를 작성하십시오 HttpRequestMessage.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Web;

using DevDefined.OAuth.Framework;

public class WebApiOAuthContextBuilder : OAuthContextBuilder
{
    public WebApiOAuthContextBuilder()
        : base(UriAdjuster)
    {
    }

    public IOAuthContext FromHttpRequest(HttpRequestMessage request)
    {
        var context = new OAuthContext
            {
                RawUri = this.CleanUri(request.RequestUri),
                Cookies = this.CollectCookies(request),
                Headers = ExtractHeaders(request),
                RequestMethod = request.Method.ToString(),
                QueryParameters = request.GetQueryNameValuePairs()
                    .ToNameValueCollection(),
            };

        if (request.Content != null)
        {
            var contentResult = request.Content.ReadAsByteArrayAsync();
            context.RawContent = contentResult.Result;

            try
            {
                // the following line can result in a NullReferenceException
                var contentType =
                    request.Content.Headers.ContentType.MediaType;
                context.RawContentType = contentType;

                if (contentType.ToLower()
                    .Contains("application/x-www-form-urlencoded"))
                {
                    var stringContentResult = request.Content
                        .ReadAsStringAsync();
                    context.FormEncodedParameters =
                        HttpUtility.ParseQueryString(stringContentResult.Result);
                }
            }
            catch (NullReferenceException)
            {
            }
        }

        this.ParseAuthorizationHeader(context.Headers, context);

        return context;
    }

    protected static NameValueCollection ExtractHeaders(
        HttpRequestMessage request)
    {
        var result = new NameValueCollection();

        foreach (var header in request.Headers)
        {
            var values = header.Value.ToArray();
            var value = string.Empty;

            if (values.Length > 0)
            {
                value = values[0];
            }

            result.Add(header.Key, value);
        }

        return result;
    }

    protected NameValueCollection CollectCookies(
        HttpRequestMessage request)
    {
        IEnumerable<string> values;

        if (!request.Headers.TryGetValues("Set-Cookie", out values))
        {
            return new NameValueCollection();
        }

        var header = values.FirstOrDefault();

        return this.CollectCookiesFromHeaderString(header);
    }

    /// <summary>
    /// Adjust the URI to match the RFC specification (no query string!!).
    /// </summary>
    /// <param name="uri">
    /// The original URI. 
    /// </param>
    /// <returns>
    /// The adjusted URI. 
    /// </returns>
    private static Uri UriAdjuster(Uri uri)
    {
        return
            new Uri(
                string.Format(
                    "{0}://{1}{2}{3}",
                    uri.Scheme,
                    uri.Host,
                    uri.IsDefaultPort ?
                        string.Empty :
                        string.Format(":{0}", uri.Port),
                    uri.AbsolutePath));
    }
}

4)이 자습서를 사용하여 OAuth 제공자를 작성 하십시오 (http://code.google.com/p/devdefined-tools/wiki/OAuthProvider) . 마지막 단계 (보호 자원 액세스 예)에서이 코드를 AuthorizationFilterAttribute속성 에 사용할 수 있습니다 .

public override void OnAuthorization(HttpActionContext actionContext)
{
    // the only change I made is use the custom context builder from step 3:
    OAuthContext context =
        new WebApiOAuthContextBuilder().FromHttpRequest(actionContext.Request);

    try
    {
        provider.AccessProtectedResourceRequest(context);

        // do nothing here
    }
    catch (OAuthException authEx)
    {
        // the OAuthException's Report property is of the type "OAuthProblemReport", it's ToString()
        // implementation is overloaded to return a problem report string as per
        // the error reporting OAuth extension: http://wiki.oauth.net/ProblemReporting
        actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
            {
               RequestMessage = request, ReasonPhrase = authEx.Report.ToString()
            };
    }
}

나는 내 공급자를 구현 했으므로 위의 코드를 테스트하지는 않았지만 (물론 WebApiOAuthContextBuilder공급자에서 사용하는 코드는 제외 ) 제대로 작동합니다.


답변

웹 API [Authorize]는 보안을 제공하기 위해 속성 을 도입했습니다 . 전역으로 설정할 수 있습니다 (global.asx)

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

또는 컨트롤러 당 :

[Authorize]
public class ValuesController : ApiController{
...

물론 인증 유형이 다를 수 있으며 자체 인증을 수행하고자 할 수 있습니다.이 경우 인증 속성에서 상속을 받고 요구 사항에 맞게 확장 할 수 있습니다.

public class DemoAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (Authorize(actionContext))
        {
            return;
        }
        HandleUnauthorizedRequest(actionContext);
    }

    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
        throw new HttpResponseException(challengeMessage);
    }

    private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        try
        {
            var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault();
            return someCode == "myCode";
        }
        catch (Exception)
        {
            return false;
        }
    }
}

그리고 컨트롤러에서 :

[DemoAuthorize]
public class ValuesController : ApiController{

다음은 WebApi 인증을위한 다른 사용자 정의 구현에 대한 링크입니다.

http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/


답변

서버에서 서버 방식으로 API를 보호하려는 경우 (2 개의 인증을 위해 웹 사이트로 리디렉션하지 않음) OAuth2 Client Credentials Grant 프로토콜을 볼 수 있습니다.

https://dev.twitter.com/docs/auth/application-only-auth

WebAPI에 이러한 종류의 지원을 쉽게 추가 할 수있는 라이브러리를 개발했습니다. NuGet 패키지로 설치할 수 있습니다.

https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0

라이브러리는 .NET Framework 4.5를 대상으로합니다.

패키지를 프로젝트에 추가하면 프로젝트 루트에 추가 정보 파일이 생성됩니다. 이 readme 파일을보고이 패키지를 구성 / 사용하는 방법을 볼 수 있습니다.

건배!


답변

@ Cuong Le의 답변을 계속하면 재생 공격을 방지하는 나의 접근 방식은

// 공유 개인 키 (또는 사용자 비밀번호)를 사용하여 클라이언트 측에서 Unix Time을 암호화

// 요청 헤더의 일부로 서버에 보냅니다 (WEB API)

// 공유 개인 키 (또는 사용자 암호)를 사용하여 Unix Time at Server (WEB API)의 암호를 해독합니다

// 클라이언트의 유닉스 시간과 서버의 유닉스 시간 사이의 시간 차이를 x 초보다 크지 않아야합니다.

// User ID / Hash Password가 정확하고 해독 된 UnixTime이 서버 시간의 x 초 내에 있으면 유효한 요청입니다.


답변