Server/----- ASP.NET WebAPI

WebApi 쿠키인증을 위한 환경 설정 ( CORS )

happykidsdad 2017. 6. 28. 20:05


Trouble


쿠키인증 방식일 때 브라우저가 인증Cookie를 포함해서 전송하지 않는다. 따라서 login 후 모든 요청에 대해서 인증에러가 발생한다.

처음에는 파이어폭스에서, 그 다음은 크롬에서, 현재는 IE까지 발생.



개발환경


서버 : ASP.NET WebAPI, System.Web.Security.Membership을 이용한 인증 ( Cookie 기반인증 )

클라이언트: angularJS를 이용한 Single Page Application



방법


ASP.NET WebAPI에 ASP.NET 2.0 시절의 예전 방식의 인증을 적용하여 WebAPI에 로그인을 하면, aspxauth 명으로 인증쿠키을 돌려 준다. 이후의 비동기 XMLHttpRequest(Ajax 방식) 들 모두 인증여부를 체크하여야 보안을 해주어야 한다.


비동기 XMLHttpRequest는 보안상 위험이 크기 때문에 최근 브라우져들은 보안을 위해 쿠키를 주고 받거나 same-origin security policy를 적용하고 있다. 


same-origin security policy는 프로토콜, 호스트, 포트로 구성되는 origin 정보(서버 주소)가 같을 때만 XMLHtpRequest를 허용한다는 것이다.이는 WebService나 Firebase나 MongoLab 등을 이용해 Web서버와 WebAPI서버를 따로 구성해야 하는 상황에서 곤란한 제약조건이다.

즉 Web서버에 있는 자바스크립트가 WebAPI서버에 접근할 수 없는 것이다.


이러한 상황을 위한 해결법은 2가지로 JSONP를 이용하거나 CORS(Cross-origin resource sharing) 방법이 있는데, JSONP는 GET 요청만 사용할 수 있고, 보안에 취약하며 오류처리가 복잡하기 때문에 CORS 방식을 사용한다.


CORS는 W3C 명세로 브라우저와 서버가 잘 협력해서 적절한 요청과 응답 헤더를 보냄으로서 sam-origin security policy를 만족할 수 있게 해준다. 첫번째로 브라우저는 GET, HEAD 메서드 외의 요청인 경우, 탐색용(preflight) OPTIONS 메서드 요청을 보낸다. 서버는 이 요청을 받을 수 있도록 셋팅이 되어 있어야 하며, 허용하는 Origin, Method, Header를 브라우저에게 다시 알려 줘야 한다. 브라우저는 이 허가정보를 받고 허가된 요청만 할 수 있다.


따라서 서버는 GET, POST, HEAD 메서드 이외의 메서드에 대해서 모두 OPTIONS 요청을 받을 수 있는 API를 추가로 구성해야 한다.


서버가 보내주는 허가정보는 아래와 같다. 주의할 점은 Access-control-Allow-Headers: * 식으로는 에러가 발생한다. Content-type을 명시적으로 작성해 주어야 한다.

Access-Control-Allow-Credentials: true

Access-Control-Allow-Headers: Accept, Origin, X-Requested-With, Content-Type
Access-Control-Allow-Methods: POST, GET, PUT, DELETE
Access-Control-Allow-Origin: http://localhost:9000


또한 최신 브라우저는 Cookie를 기본적으로는 동기(synchronous) XMLHttpRequest일 경우에만 자동으로 요청시 포함해 준다. 

따라서  Cookie 인증 방식일 경우 Login 후 비동기 XMLHttpRequest를 보내면 인증오류가 나게 된다.

비동기 XMLHttpRequest에 cookie를 포함시켜 요청하려면 요청시 withCredentials를 True로 설정해서 브라우져에게 알려줘야 한다. 주의할 점을 이 경우는 동기 XMLHttpRequest시 cookie를 포함시키지 않는다는 것이다.( 직접 해보진 않았음 ) 따라서 동기와 비동기 XMLHttpRequest를 혼합해서 프로그램을 만들지 말아야 한다.


결론


# Client에서 자바스크립트로 XMLHttpRequest 시 withCredentials를 True로 설정 ( 초기에 config로 설정 시 모든 요청에 적용 )

app.config(['$httpProvider',function($httpProvider){        
  $httpProvider.defaults.withCredentials = true;
}]);


# 서버에서 응답 HTTP HEADER에 허가 정보 추가. ( Web.config 파일에 설정시 전체 적용, 보다 다양한 설정법은 아래 참고링크 참조 )


  <system.webServer>    
    <!-- CORS를 위한 설정 -->
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Credentials" value="true"/>
        <add name="Access-Control-Allow-Origin" value="http://localhost:9000"/>
        <add name="Access-Control-Allow-Headers" value="Accept, Origin, X-Requested-With, Content-Type"/>
        <add name="Access-Control-Allow-Methods" value="POST, GET, PUT, DELETE"/>       
      </customHeaders>
    </httpProtocol>   
    <!-- CORS -->
  </system.webServer>



# 서버 WebAPI에 GET, HEAD 메서드 외의 응답Controller에 OPTIONS 메서드를 위한 Controller에 Action 추가. 


        public HttpResponseMessage Options()
        {
            var rsp = new HttpResponseMessage();
            rsp.StatusCode = HttpStatusCode.OK;
            return rsp;
        }
           

# 서버 WebAPI에서 OPTIONS 메서드를 위한 Action을 모든 Action에 대해서 생성해 주지 않고 아래 함수를 Global.asax파일에 지정하여 한 번에 처리하는 방법도 있다. 하지만 보안적인 부분을 염두해 둬야 할 듯.


protected void Application_BeginRequest(object sender, EventArgs e)

{

    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods",
                     "GET, POST, PUT, DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers",
                     "Content-Type, Accept");
        HttpContext.Current.Response.End();
     }
} 




참고


CORS 에 대해서( https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS )


ASP.NET Web API 2 에서 CORS 허용하기

https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api

/ 한글 http://www.egocube.pe.kr/Translation/Content/asp-net-web-api/201402100001#enable-cors )


ASP.NET Web API 1 에서 CORS OPTIONS 요청 지원하기 

http://www.jefclaes.be/2012/09/supporting-options-verb-in-aspnet-web.html )