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


397

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

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

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

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

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

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

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

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

답변:


292

최신 정보:

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


2
@ 제임스 : 타임 스탬프 만 충분하지 않은 것 같습니다. 짧은 시간 동안 요청을 시뮬레이션하고 서버로 보낼 수 있습니다. 방금 내 게시물을 편집했습니다. 둘 다 사용하는 것이 가장 좋을 것입니다.
cuongle

1
이것이 제대로 작동하고 있습니까? 메시지로 타임 스탬프를 해시하고 해당 메시지를 캐싱합니다. 이것은 각 요청마다 다른 서명을 의미하므로 캐시 된 서명을 쓸모 없게 만듭니다.
Filip Stas

1
@FilipStas : 나는 당신의 요점을 알지 못하는 것 같습니다. 여기서 캐시를 사용하는 이유는 릴레이 공격을 막기위한 것입니다. 더 이상
cuongle

1
@ChrisO : [이 페이지] ( jokecamp.wordpress.com/2012/10/21/… )를 참조 할 수 있습니다 . 이 소스를 곧 업데이트하겠습니다
cuongle

1
이 솔루션은 작품을 제안,하지만 당신은 남자 - 인 - 더 - 미들 막을 수 없습니다 공격, HTTPS를 구현해야한다는 위해
리팩토링

34

가장 간단한 솔루션부터 시작하는 것이 좋습니다. 시나리오에서 간단한 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/


23

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공급자에서 사용하는 코드는 제외 ) 제대로 작동합니다.


감사합니다.이 내용을 살펴 보겠습니다. 지금은 자체 HMAC 기반 솔루션을 출시했습니다.
크레이그 시어러

1
@CraigShearer-안녕하세요, 당신은 당신이 자신의 것을 굴렸다 고 말합니다. 공유하고 싶지 않으면 몇 가지 질문이 있습니다. 나는 상대적으로 작은 MVC 웹 API를 가지고있는 비슷한 위치에 있습니다. API 컨트롤러는 양식 인증을받는 다른 컨트롤러 / 작업과 함께 있습니다. OAuth를 구현하는 데 이미 사용할 수있는 멤버 자격 공급자가 있고 소수의 작업 만 확보하면 과도하게 보일 수 있습니다. 암호화 된 토큰을 반환하는 인증 작업을 원합니다. 그런 다음 호출에서 토큰을 사용 했습니까? 기존 인증 솔루션을 구현하기 전에 모든 정보를 환영합니다. 감사!
sambomartin

@Maksymilian Majer-제공자를 더 자세히 구현 한 방법을 공유 할 수 있습니까? 고객에게 응답을 보내는 데 문제가 있습니다.
jlrolin

21

웹 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/


@Dalorzo 예제를 주셔서 감사하지만 몇 가지 문제가 있습니다. 첨부 된 링크를 보았지만 그 지침을 따르는 것은 효과가 없습니다. 또한 필요한 정보가 누락되었습니다. 먼저 새 프로젝트를 만들 때 인증을 위해 개별 사용자 계정을 선택하는 것이 맞습니까? 또는 인증없이 그대로 두십시오. 또한 언급 된 302 오류가 발생하지 않지만 401 오류가 발생합니다. 마지막으로, 내 정보에서 컨트롤러로 필요한 정보를 어떻게 전달합니까? 내 아약스 호출은 어떻게 생겼습니까? Btw, MVC 뷰에 폼 인증을 사용하고 있습니다. 그게 문제입니까?
Amanda

환상적으로 일하고 있습니다. 우리 자신의 액세스 토큰을 배우고 작업을 시작하는 것이 좋습니다.
CodeName47

하나의 작은 주석- AuthorizeAttribute다른 네임 스페이스에 동일한 이름을 가진 두 개의 다른 클래스가 있으므로주의 하십시오. 1. MVC 컨트롤러의 경우 System.Web.Mvc.AuthorizeAttribute-> 2. WebApi의 경우 System.Web.Http.AuthorizeAttribute->.
Vitaliy Markitanov

5

서버에서 서버 방식으로 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 파일을보고이 패키지를 구성 / 사용하는 방법을 볼 수 있습니다.

건배!


5
이 프레임 워크에 대한 소스 코드를 오픈 소스로 공유 / 제공하고 있습니까?
barrypicker

JFR : 첫 번째 링크가 끊어지고 NuGet 패키지는 업데이트되지 않았다
압둘 코이 욤 장관

3

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

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

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

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.