헤르메스 LIFE

Spring Security on REST API 구축 실패기...!!! 본문

Spring Framework

Spring Security on REST API 구축 실패기...!!!

헤르메스의날개 2016. 8. 24. 22:10
728x90

개발환경.

ajax + RESTful + Spring Security


시나리오.

RESTful API 환경 구축.

oAuth를 생각해 봤지만, 갑자기 눈에 들어온 것이 Spring Security... 시련의 시작...


1. 시작


그림, 소스 출처 : Spring Security on REST


수많은 검색 끝에 찾아낸 결론이였습니다.

소스도 있고 결과만 확인 하면 됐습니다. ( 뭐.. 항상 그렇지만 쉽지만은 않습니다. )


여차저차해서 전체적으로 약간의 수정으로 테스트가 이뤄졌습니다.

( restServicesEntryPoint 를 추가해 넣었습니다. )


<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:sec="http://www.springframework.org/schema/security"

    xsi:schemaLocation="

      http://www.springframework.org/schema/security

      http://www.springframework.org/schema/security/spring-security-3.2.xsd

      http://www.springframework.org/schema/beans

      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">


    <!-- Rest authentication entry point configuration -->

    <http use-expressions="true" entry-point-ref="restServicesEntryPoint">

        <intercept-url pattern="/api/**" />

        <sec:form-login username-parameter="username"

                    password-parameter="password"  

                    authentication-success-handler-ref="mySuccessHandler" 

                    authentication-failure-handler-ref="myFailureHandler" />

        <logout />

    </http>

   



    <!-- 인증 entry point 설정 - 인증 실패 시 401 Return -->

    <beans:bean id="restServicesEntryPoint" class="rest.api.security.RestAuthenticationEntryPoint" />

    

    <!-- Connect the custom authentication success handler -->

    <beans:bean id="mySuccessHandler" class="rest.api.security.RestAuthenticationSuccessHandler" />

    

    <!-- Using default failure handler     -->

    <beans:bean id="myFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" />


    <!-- Authentication manager -->

    <authentication-manager alias="authenticationManager">

        <authentication-provider>

            <user-service>

                <user name="temporary" password="temporary" authorities="ROLE_ADMIN" />

                <user name="user" password="userPass" authorities="ROLE_USER" />

            </user-service>

        </authentication-provider>

    </authentication-manager>


    <!-- Enable the annotations for defining the secure role -->

    <global-method-security secured-annotations="enabled" />


</beans:beans>



HttpClient를 이용해서 POST 방식으로 인증하고, 그 인증된 권한으로 실제요청을 하는 시나리오는 성공했습니다.



public static void test01() throws Exception {

CloseableHttpClient client = HttpClients.createDefault();


HttpPost httpPost = new HttpPost(loginUrl);

Map<String, String> parameterMap = new HashMap<String, String>();

parameterMap.put("username", "temporary");

parameterMap.put("password", "temporary");

UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(getParam(parameterMap), "UTF-8");

httpPost.setEntity(postEntity);

//System.out.println("request line:" + httpPost.getRequestLine());

try {

HttpResponse httpResponse = client.execute(httpPost);


//status:HTTP/1.1 401 Unauthorized

System.out.println("Status :: " + httpResponse.getStatusLine());

printResponse(httpResponse);


System.out.println("----the same client");

HttpGet httpGet = new HttpGet(testUrl);

System.out.println("request line:" + httpGet.getRequestLine());

HttpResponse httpResponse1 = client.execute(httpGet);

printResponse(httpResponse1);



} catch (IOException e) {

e.printStackTrace();

} finally {

try {

client.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}




request line:http://localhost:8080/j_spring_security_check HTTP/1.1

status:HTTP/1.1 200 OK

headers:

Server: Apache-Coyote/1.1

Set-Cookie: JSESSIONID=AE5211F53CB844576E8171A4D86422C3; Path=/; HttpOnly

Content-Type: text/html;charset=utf-8

Transfer-Encoding: chunked

Vary: Accept-Encoding

Date: Wed, 24 Aug 2016 11:32:35 GMT

response length:0

response content: 

----the same client

request line:POST http://localhost:8080/api/employees HTTP/1.1

status:HTTP/1.1 200 OK

headers:

Server: Apache-Coyote/1.1

Cache-Control: no-cache

Content-Type: application/json;charset=UTF-8

Content-Language: ko

Transfer-Encoding: chunked

Vary: Accept-Encoding

Date: Wed, 24 Aug 2016 11:32:35 GMT

response length:1084

response content:[{"MASTER_ID":"","ZIP_CODE":"1111","BIRTHDAY":"","ID":"CUST001","ADDR_TYPE":"","ADDR2":"주소2","ADDR1":"서울시 마포구","EMAIL":"hong@test.com","CUST_BIGO":"","NAME":"홍길동","MOBILE":"010111111111","TEL":"0211111111"},{"MASTER_ID":"","ZIP_CODE":"2222","BIRTHDAY":"","ID":"CUST002","ADDR_TYPE":"","ADDR2":"주소2","ADDR1":"주소1","EMAIL":"hong@test.com","CUST_BIGO":"","NAME":"허균","MOBILE":"01022222222","TEL":"0222222222"}]



뭐... 글쓰신 분도 curl 로 테스트 하셨을 때는 잘 된다. 하시던군요..

맞습니다. 잘 됩니다...

그런데...

ajax 로 하면 안됩니다. ㅡㅡ;;


처음엔 CORS 오류가 저를 괴롭혔습니다. 그래서 여차저차해서 몇일을 고생해서 해결했습니다. ( 참조 : [CORS] javascript ajax 크로스 도메인 요청 하기 )



authRequest("http://localhost:8080/j_spring_security_check", {

"username" : "temporary",

"password" : "temporary"

    }, function(xhr, statusText) {

console.log("인증 결과 :: " + statusText);

console.log("인증 status :: " + xhr.status);


//if(xhr.status == 200) {

if(xhr.status == 0) {

ajaxRequest("http://localhost:8080/api/employees", {

               "param", "param"

        });

    }

});


function authRequest(baseurl, params, onSuccess)

{


  $.ajax({

      url : baseurl

    , async : true

    , type : "POST"

    , cache:false

    , timeout : 30000 

    , data : JSON.parse(JSON.stringify(params))

    , dataType : "json"

    , crossDomain: true

    , xhrFields: {

         withCredentials: true

      }

    , beforeSend: function (xhr) {

    xhr.setRequestHeader("Accept", "application/json");

      }

    , complete : function (xhr, statusText) {

    onSuccess(xhr, statusText)

      }

    , error: function(xhr, statusText, errorThrown) {

        console.log("xhr :: " + xhr.status);

        console.log("statusText :: " + statusText);

        console.log("errorThrown :: " + errorThrown);

      }

  });

}


function ajaxRequest(baseurl, params)

{

$.ajax({

      url : "http://localhost:8080/api/employees"

    , async : true

    , type : "POST"

    , cache:false

    , timeout : 30000 

    , data : JSON.parse(JSON.stringify(params))

    , dataType : "json"

    , xhrFields: {

            withCredentials: true

      }

    , beforeSend: function (xhr) {

        xhr.setRequestHeader("Accept", "application/json");

      }

    , success : function (data, statusText, xhr) { 

        console.log("success status :: " + xhr.status + " || statusText :: " + statusText);

      }

    , error : function (xhr, statusText, errorThrown) { 

        console.log("error status :: " + xhr.status + " || statusText :: " + xhr.responseText + " || errorThrown :: " + errorThrown);

      }

  });

}


테스트 때 걸렸던 내용..


1. withCredentials: true 를 사용한 이유...

jsessionid를 같이 사용하기 위해 사용했습니다.

한 트랜잭션 안에서 실행이 되어야 할 듯 합니다.

왜냐하면 권한을 인증하고, 그 인증된 결과를 가지고 다시 실제요청을 해야합니다. ( 달리 방법을 찾지 못했습니다. 방법이 있을까요..? )


2. CORS

Filter를 적용해서 해결했습니다. ( 공부좀 했져.. ^^;; )

XMLHttpRequest cannot load http://localhost:8080/accnt/api/employees. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:7070' is therefore not allowed access.


ajax를 이용해서 인증 + 실제요청을 실패한 이유..


1 번 이유와 2번 이유가 충돌이 납니다.

1번 이유 때문에 withCredentials: true 를 정의했는데..

CORS 때문에 Filter를 적용하고 나면 인증 쪽에서 Access Denied 가 떨어집니다.


CORS Filter를 막으면 withCredentials: true 가 정상적으로 동작을 합니다.

인증도 정상적으로 처리됩니다.

Service 영역에서 조회되서 결과를 Return 하는 결과까지 확인이 됩니다.

문제는 CORS 때문인지 xhr.status 가 0 이 됩니다. ( 여러가지 이유가 있겠죠.. )


현재로서는 해결의 방법이 없습니다. ㅠ.ㅠ


이제 생각할 수 있는 방법은..

ajax + CORS Filter + oAuth 를 이용해서 RESTful API 환경을 구축하는 겁니다.


그래도 malalanayake ( http://ko.gravatar.com/malalanayake ) 께 감사드립니다.

자신의 소스를 전부 내주셨으니 덕분에 테스트를 해 볼 수 있었습니다.



728x90