개별 계정을 사용하는 Web API 및 ASP.NET Web API 2.2에서 로컬 로그인 보호
작성자: Mike Wasson
이 항목에서는 OAuth2를 사용하여 멤버 자격 데이터베이스에 대해 인증하는 웹 API를 보호하는 방법을 보여줍니다.
자습서에서 사용되는 소프트웨어 버전
Visual Studio 2013에서 Web API 프로젝트 템플릿은 인증을 위한 세 가지 옵션을 제공합니다.
- 개별 계정. 앱은 멤버 자격 데이터베이스를 사용합니다.
- 조직 계정. 사용자는 Azure Active Directory, Office 365 또는 온-프레미스 Active Directory 자격 증명으로 로그인합니다.
- Windows 인증. 이 옵션은 인트라넷 애플리케이션을 위한 것이며 Windows 인증 IIS 모듈을 사용합니다.
개별 계정은 사용자가 로그인하는 두 가지 방법을 제공합니다.
- 로컬 로그인. 사용자가 사이트에 등록하고 사용자 이름과 암호를 입력합니다. 앱은 멤버 자격 데이터베이스에 암호 해시를 저장합니다. 사용자가 로그인하면 ASP.NET ID 시스템에서 암호를 확인합니다.
- 소셜 로그인. 사용자는 Facebook, Microsoft 또는 Google과 같은 외부 서비스로 로그인합니다. 앱은 여전히 멤버 자격 데이터베이스에 사용자에 대한 항목을 만들지만 자격 증명은 저장하지 않습니다. 사용자는 외부 서비스에 로그인하여 인증합니다.
이 문서에서는 로컬 로그인 시나리오를 살펴봅합니다. 로컬 및 소셜 로그인의 경우 Web API는 OAuth2를 사용하여 요청을 인증합니다. 그러나 자격 증명 흐름은 로컬 및 소셜 로그인에 대해 다릅니다.
이 문서에서는 사용자가 로그인하고 인증된 AJAX 호출을 웹 API에 보낼 수 있는 간단한 앱을 보여 줍니다. 여기서 샘플 코드를 다운로드할 수 있습니다. 추가 정보에서는 Visual Studio에서 샘플을 처음부터 만드는 방법을 설명합니다.
샘플 앱은 데이터 바인딩에 Knockout.js 사용하고 jQuery를 사용하여 AJAX 요청을 보냅니다. AJAX 호출에 중점을 두므로 이 문서의 Knockout.js 알 필요가 없습니다.
그 과정에서 다음을 설명하겠습니다.
- 앱이 클라이언트 쪽에서 수행하는 작업입니다.
- 서버에서 발생하는 작업
- 중간에 있는 HTTP 트래픽입니다.
먼저 일부 OAuth2 용어를 정의해야 합니다.
- 리소스. 보호할 수 있는 일부 데이터 조각입니다.
- 리소스 서버. 리소스를 호스트하는 서버입니다.
- 리소스 소유자입니다. 리소스에 액세스할 수 있는 권한을 부여할 수 있는 엔터티입니다. (일반적으로 사용자)
- 클라이언트: 리소스에 대한 액세스를 원하는 앱입니다. 이 문서에서 클라이언트는 웹 브라우저입니다.
- 액세스 토큰입니다. 리소스에 대한 액세스 권한을 부여하는 토큰입니다.
- 전달자 토큰 누구나 토큰을 사용할 수 있는 속성이 있는 특정 유형의 액세스 토큰입니다. 즉, 클라이언트는 전달자 토큰을 사용하기 위해 암호화 키 또는 기타 비밀이 필요하지 않습니다. 이러한 이유로 전달자 토큰은 HTTPS를 통해서만 사용해야 하며 비교적 짧은 만료 시간이 있어야 합니다.
- 권한 부여 서버. 액세스 토큰을 제공하는 서버입니다.
애플리케이션은 권한 부여 서버와 리소스 서버 모두의 역할을 할 수 있습니다. Web API 프로젝트 템플릿은 이 패턴을 따릅니다.
로컬 로그인 자격 증명 흐름
로컬 로그인의 경우 Web API는 OAuth2에 정의된 리소스 소유자 암호 흐름을 사용합니다.
- 사용자가 클라이언트에 이름과 암호를 입력합니다.
- 클라이언트는 이러한 자격 증명을 권한 부여 서버로 보냅니다.
- 권한 부여 서버는 자격 증명을 인증하고 액세스 토큰을 반환합니다.
- 보호된 리소스에 액세스하기 위해 클라이언트는 HTTP 요청의 권한 부여 헤더에 액세스 토큰을 포함합니다.
Web API 프로젝트 템플릿에서 개별 계정을 선택하면 프로젝트에는 사용자 자격 증명의 유효성을 검사하고 토큰을 발급하는 권한 부여 서버가 포함됩니다. 다음 다이어그램에서는 Web API 구성 요소 측면에서 동일한 자격 증명 흐름을 보여 줍니다.
이 시나리오에서 Web API 컨트롤러는 리소스 서버 역할을 합니다. 인증 필터는 액세스 토큰의 유효성을 검사하고 [Authorize] 특성은 리소스를 보호하는 데 사용됩니다. 컨트롤러 또는 작업에 [Authorize] 특성이 있는 경우 해당 컨트롤러 또는 작업에 대한 모든 요청을 인증해야 합니다. 그렇지 않으면 권한 부여가 거부되고 Web API는 401(권한 없음) 오류를 반환합니다.
권한 부여 서버와 인증 필터는 모두 OAuth2의 세부 정보를 처리하는 OWIN 미들웨어 구성 요소를 호출합니다. 이 자습서의 뒷부분에서 디자인을 자세히 설명하겠습니다.
권한 없는 요청 보내기
시작하려면 앱을 실행하고 API 호출 단추를 클릭합니다. 요청이 완료되면 결과 상자에 오류 메시지가 표시됩니다. 이는 요청에 액세스 토큰이 포함되어 있지 않으므로 요청이 권한이 없기 때문입니다.
호출 API 단추는 AJAX 요청을 ~/api/values로 보내며, 이 요청은 Web API 컨트롤러 작업을 호출합니다. AJAX 요청을 보내는 JavaScript 코드의 섹션은 다음과 같습니다. 샘플 앱에서 모든 JavaScript 앱 코드는 Scripts\app.js 파일에 있습니다.
// If we already have a bearer token, set the Authorization header.
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: 'GET',
url: 'api/values/1',
headers: headers
}).done(function (data) {
self.result(data);
}).fail(showError);
사용자가 로그인할 때까지 전달자 토큰이 없으므로 요청에 권한 부여 헤더가 없습니다. 이렇게 하면 요청이 401 오류를 반환합니다.
HTTP 요청은 다음과 같습니다. (사용한 항목 HTTP 트래픽을 캡처하는 Fiddler 입니다.)
GET https://localhost:44305/api/values HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Accept-Language: en-US,en;q=0.5
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
HTTP 응답:
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
WWW-Authenticate: Bearer
Date: Tue, 30 Sep 2014 21:54:43 GMT
Content-Length: 61
{"Message":"Authorization has been denied for this request."}
응답에는 챌린지가 Bearer로 설정된 Www-Authenticate 헤더가 포함됩니다. 이는 서버가 전달자 토큰을 예상한다는 것을 나타냅니다.
사용자 등록
앱의 등록 섹션에서 전자 메일 및 암호를 입력하고 등록 단추를 클릭합니다.
이 샘플에 유효한 전자 메일 주소를 사용할 필요는 없지만 실제 앱은 주소를 확인합니다. (참조) 로그인, 전자 메일 확인 및 암호 재설정을 사용하여 보안 ASP.NET MVC 5 웹앱을 만듭니다.) 암호의 경우 대문자, 소문자, 숫자 및 알파 숫자가 아닌 문자와 함께 "Password1!"과 같은 문자를 사용합니다. 앱을 단순하게 유지하기 위해 클라이언트 쪽 유효성 검사를 제외했으므로 암호 형식에 문제가 있는 경우 400(잘못된 요청) 오류가 발생합니다.
등록 단추는 ~/api/Account/Register/에 POST 요청을 보냅니다. 요청 본문은 이름과 암호를 보유하는 JSON 개체입니다. 요청을 보내는 JavaScript 코드는 다음과 같습니다.
var data = {
Email: self.registerEmail(),
Password: self.registerPassword(),
ConfirmPassword: self.registerPassword2()
};
$.ajax({
type: 'POST',
url: '/api/Account/Register',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data)
}).done(function (data) {
self.result("Done!");
}).fail(showError);
HTTP 요청은 암호 $CREDENTIAL_PLACEHOLDER$
키-값 쌍의 자리 표시자입니다.
POST https://localhost:44305/api/Account/Register HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 84
{"Email":"alice@example.com",$CREDENTIAL_PLACEHOLDER1$,$CREDENTIAL_PLACEHOLDER2$"}
HTTP 응답:
HTTP/1.1 200 OK
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 00:57:58 GMT
Content-Length: 0
이 요청은 클래스에서 처리됩니다 AccountController
. 내부적으로 ASP.NET AccountController
ID를 사용하여 멤버 자격 데이터베이스를 관리합니다.
Visual Studio에서 로컬로 앱을 실행하는 경우 사용자 계정은 AspNetUsers 테이블의 LocalDB에 저장됩니다. Visual Studio에서 테이블을 보려면 보기 메뉴를 클릭하고 서버 탐색기를 선택한 다음 데이터 연결을 확장합니다.
액세스 토큰 가져오기
지금까지 OAuth를 수행하지 않았지만 액세스 토큰을 요청할 때 OAuth 권한 부여 서버가 작동하는 것을 볼 수 있습니다. 샘플 앱의 로그인 영역에서 전자 메일 및 암호를 입력하고 로그인을 클릭합니다.
로그인 단추는 토큰 엔드포인트에 요청을 보냅니다. 요청 본문에는 다음 양식 URL로 인코딩된 데이터가 포함됩니다.
- grant_type: "암호"
- 사용자 이름: <사용자의 전자 메일>
- 암호: <암호>
AJAX 요청을 보내는 JavaScript 코드는 다음과 같습니다.
var loginData = {
grant_type: 'password',
username: self.loginEmail(),
password: self.loginPassword()
};
$.ajax({
type: 'POST',
url: '/Token',
data: loginData
}).done(function (data) {
self.user(data.userName);
// Cache the access token in session storage.
sessionStorage.setItem(tokenKey, data.access_token);
}).fail(showError);
요청이 성공하면 권한 부여 서버는 응답 본문에 액세스 토큰을 반환합니다. API에 요청을 보낼 때 나중에 사용할 수 있도록 토큰을 세션 스토리지에 저장합니다. 일부 형태의 인증(예: 쿠키 기반 인증)과 달리 브라우저는 후속 요청에 액세스 토큰을 자동으로 포함하지 않습니다. 애플리케이션은 명시적으로 수행해야 합니다. 이는 CSRF 취약성을 제한하기 때문에 좋은 일입니다.
HTTP 요청:
POST https://localhost:44305/Token HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 68
grant_type=password&username=alice%40example.com&password=Password1!
요청에 사용자의 자격 증명이 포함되어 있음을 확인할 수 있습니다. 전송 계층 보안을 제공하려면 HTTPS를 사용해야 합니다.
HTTP 응답:
HTTP/1.1 200 OK
Content-Length: 669
Content-Type: application/json;charset=UTF-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:22:36 GMT
{
"access_token":"imSXTs2OqSrGWzsFQhIXziFCO3rF...",
"token_type":"bearer",
"expires_in":1209599,
"userName":"alice@example.com",
".issued":"Wed, 01 Oct 2014 01:22:33 GMT",
".expires":"Wed, 15 Oct 2014 01:22:33 GMT"
}
가독성을 위해 JSON을 들여쓰고 액세스 토큰을 잘렸습니다.
및 token_type
expires_in
속성은 access_token
OAuth2 사양에 의해 정의됩니다. 다른 속성(userName
및.issued
.expires
)은 정보 제공 용도로만 사용됩니다. /Providers/ApplicationOAuthProvider.cs 파일에서 TokenEndpoint
메서드에서 이러한 추가 속성을 추가하는 코드를 찾을 수 있습니다.
인증된 요청 보내기
이제 전달자 토큰이 있으므로 API에 대해 인증된 요청을 수행할 수 있습니다. 이 작업은 요청에서 권한 부여 헤더를 설정하여 수행됩니다. API 호출 단추를 다시 클릭하여 확인합니다.
HTTP 요청:
GET https://localhost:44305/api/values/1 HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Authorization: Bearer imSXTs2OqSrGWzsFQhIXziFCO3rF...
X-Requested-With: XMLHttpRequest
HTTP 응답:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:41:29 GMT
Content-Length: 27
"Hello, alice@example.com."
로그아웃
브라우저는 자격 증명 또는 액세스 토큰을 캐시하지 않으므로 로그아웃은 단순히 세션 스토리지에서 제거하여 토큰을 "잊어버리는" 문제입니다.
self.logout = function () {
sessionStorage.removeItem(tokenKey)
}
개별 계정 프로젝트 템플릿 이해
ASP.NET 웹 애플리케이션 프로젝트 템플릿에서 개별 계정을 선택하면 프로젝트에는 다음이 포함됩니다.
- OAuth2 권한 부여 서버입니다.
- 사용자 계정을 관리하기 위한 Web API 엔드포인트
- 사용자 계정을 저장하기 위한 EF 모델입니다.
이러한 기능을 구현하는 기본 애플리케이션 클래스는 다음과 같습니다.
AccountController
. 사용자 계정을 관리하기 위한 Web API 엔드포인트를 제공합니다. 이Register
자습서에서 사용한 작업은 이 작업뿐입니다. 클래스의 다른 메서드는 암호 재설정, 소셜 로그인 및 기타 기능을 지원합니다.ApplicationUser
/Models/IdentityModels.cs 정의됩니다. 이 클래스는 멤버 자격 데이터베이스의 사용자 계정에 대한 EF 모델입니다.ApplicationUserManager
/App_Start/IdentityConfig.cs 정의된 이 클래스는 UserManager에서 파생되며 새 사용자 만들기, 암호 확인 등과 같은 사용자 계정에 대한 작업을 수행하고 데이터베이스에 대한 변경 내용을 자동으로 유지합니다.ApplicationOAuthProvider
. 이 개체는 OWIN 미들웨어에 연결하고 미들웨어에서 발생하는 이벤트를 처리합니다. OAuthAuthorizationServerProvider에서 파생됩니다.
권한 부여 서버 구성
StartupAuth.cs 다음 코드는 OAuth2 권한 부여 서버를 구성합니다.
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// Note: Remove the following line before you deploy to production:
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
속성 TokenEndpointPath
은 권한 부여 서버 엔드포인트에 대한 URL 경로입니다. 앱이 전달자 토큰을 가져오는 데 사용하는 URL입니다.
이 속성은 Provider
OWIN 미들웨어에 연결하는 공급자를 지정하고 미들웨어에서 발생한 이벤트를 처리합니다.
앱에서 토큰을 가져오려는 경우의 기본 흐름은 다음과 같습니다.
- 액세스 토큰을 가져오기 위해 앱은 ~/Token에 요청을 보냅니다.
- OAuth 미들웨어는 공급자를 호출
GrantResourceOwnerCredentials
합니다. - 공급자는 자격 증명의
ApplicationUserManager
유효성을 검사하고 클레임 ID를 만들기 위해 호출합니다. - 성공하면 공급자는 토큰을 생성하는 데 사용되는 인증 티켓을 만듭니다.
OAuth 미들웨어는 사용자 계정에 대해 아무것도 알지 못합니다. 공급자는 미들웨어와 ASP.NET ID 간에 통신합니다. 권한 부여 서버 구현에 대한 자세한 내용은 OWIN OAuth 2.0 권한 부여 서버를 참조 하세요.
전달자 토큰을 사용하도록 Web API 구성
이 WebApiConfig.Register
메서드에서 다음 코드는 Web API 파이프라인에 대한 인증을 설정합니다.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
HostAuthenticationFilter 클래스를 사용하면 전달자 토큰을 사용하여 인증할 수 있습니다.
SuppressDefaultHostAuthentication 메서드는 요청이 IIS 또는 OWIN 미들웨어에 의해 Web API 파이프라인에 도달하기 전에 발생하는 모든 인증을 무시하도록 Web API에 지시합니다. 이러한 방식으로 Web API가 전달자 토큰만으로 인증하도록 제한할 수 있습니다.
참고 항목
특히 앱의 MVC 부분에서는 자격 증명을 쿠키에 저장하는 양식 인증을 사용할 수 있습니다. 쿠키 기반 인증을 사용하려면 CSRF 공격을 방지하기 위해 위조 방지 토큰을 사용해야 합니다. 웹 API가 위조 방지 토큰을 클라이언트에 보내는 편리한 방법이 없기 때문에 웹 API에 문제가 있습니다. (이 문제에 대한 자세한 배경은 다음을 참조하세요.Web API에서 CSRF 공격 방지.) SuppressDefaultHostAuthentication을 호출하면 Web API가 쿠키에 저장된 자격 증명의 CSRF 공격에 취약하지 않습니다.
클라이언트가 보호된 리소스를 요청하는 경우 Web API 파이프라인에서 발생하는 작업은 다음과 같습니다.
- HostAuthentication 필터는 OAuth 미들웨어를 호출하여 토큰의 유효성을 검사합니다.
- 미들웨어는 토큰을 클레임 ID로 변환합니다.
- 이 시점에서 요청은 인증되지만 권한이 부여되지 않습니다.
- 권한 부여 필터는 클레임 ID를 검사합니다. 클레임이 해당 리소스에 대해 사용자에게 권한을 부여하는 경우 요청은 권한이 부여됩니다. 기본적으로 [Authorize] 특성은 인증된 모든 요청에 권한을 부여합니다. 그러나 역할 또는 다른 클레임에 의해 권한을 부여할 수 있습니다. 자세한 내용은 Web API의 인증 및 권한 부여를 참조하세요.
- 이전 단계가 성공하면 컨트롤러는 보호된 리소스를 반환합니다. 그렇지 않으면 클라이언트는 401(권한 없음) 오류를 받습니다.
추가 리소스
- ASP.NET ID
- VS2013 RC용 SPA 템플릿의 보안 기능 이해 MSDN 블로그 게시물: Hongye Sun
- Web API 개별 계정 템플릿 해부– 2부: 로컬 계정. 도미닉 바이어에 의해 블로그 게시물.
- OWIN을 사용하여 인증 및 Web API를 호스트합니다. 브록 앨런에 대한
SuppressDefaultHostAuthentication
HostAuthenticationFilter
좋은 설명. - VS 2013 템플릿의 ASP.NET ID에서 프로필 정보 사용자 지정 Pranav Rastogi의 MSDN 블로그 게시물.
- ASP.NET Identity의 UserManager 클래스에 대한 요청 수명 관리당. 수업에 대한 좋은 설명
UserManager
과 함께 수하스 조시에 의해 MSDN 블로그 게시물.