[Spring Security] Spring-Boot 1.2.5와(8080포트) AngularJS(9000포트)의 포트 다를 시, CORS 필터 적용하기

최근 AngularJS에 대한 공부와, Yeoman에 대한 공부를 마치고, Yeoman을 사용한 프론트 세팅과 실행, 그리고 갑작스럽지만 내게는 딱 맞다 생각하는 Spring-Boot의 약간의 공부를 진행했다. 뭐 스프링 부트는 공부할 것도 없었지만..

Gradle이란 놈이 참으로 훌륭하더라. 여기에 Front/Backend의 프로젝트와 의존성을 물려놓고, 서로 커멘드 등의 명령어를 통해 실행하니 정말 잘 모듈별로 독립적이면서도 잘 맞물린다.

이제는 기존 Spring MVC와 Velocity, MyBatis로 구성된 소스를 SpringBoot – Hibernate(JPA)그리고 AngularJS를 통해 구성하기. 가장 기본적인 것은 로그인인데, 이 참에 Spring-Security를 적용해 보기로 하고, 가장 적절한 강좌를 찾았다.(아래 링크)

https://spring.io/guides/tutorials/spring-security-and-angular-js/

그런데 문제는, 이 강좌의 경우는 리소스를 WEB-INF에 넣고 AngularJS를 Spring Boot와 함께 돌리는데, 내 경우는 Spring-Boot는 Embedded Tomcat으로 8080포트를, AngularJS는 Yeoman으로 생성했기 때문에 node.js를 통해 9000포트가 되버렸다. 물론 나도 함께 구동하고 싶은 욕심은 있지만, 이걸 함께 구동하면 모듈화의 의미가 없어지기 때문에..

결국, 그렇게 해서 저 튜토리얼을 따라가다 보니 Cross-Domain문제에 직면했다. 인터넷을 찾아보니 아래 처럼 Filter를 implement해서 access-control을 정의하란다.

@Component
public class FiammCORSFilter implements Filter {
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

그런데 이 필터가 전혀! 먹히지 않는다. 크롬 디버거로 HTTP헤더를 봐도, 적용이 안되었다.. 

그래서 혹시나 해서, 위 강좌에 나온 CsrfHeaderFilter 안에다가 저 내용을 넣어봤더니, 그제서야 먹는다..

public class CsrfHeaderFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
                .getName());
        if (csrf != null) {
            Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
            String token = csrf.getToken();
            if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
                cookie = new Cookie("XSRF-TOKEN", token);
                cookie.setPath("/");
                response.addCookie(cookie);
            }
        }

        response.setHeader("Access-Control-Allow-Origin", "http://localhost:9000");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");

        filterChain.doFilter(request, response);
    }
}

저렇게, Access-Control-Allow-Origin에 나의 경우는 http://localhost:9000을 넣어주고,

angular.module('frontendApp')
  .controller('LoginCtrl',  function($rootScope, $scope, $http, $location) {
      // Authentication
      var authenticate = function(credentials, callback) {
        var headers = credentials ? {
          authorization:"Basic " + btoa(credentials.userId + ":" + credentials.passwd)
        } : {};

        $http.get($rootScope.serverUrl+'/doLogin', {headers:headers}).success(function(data) {
          if(data.userName) {
            $rootScope.authenticated = true;
          }else{
            $rootScope.authenticated = false;
          }
        }).error(function(data) {
          $rootScope.authenticated = false;
          callback && callback();
        });
      }

      authenticate();
      $scope.credentials = {};
      $scope.login = function() {
        authenticate($scope.credentials, function() {
          if($rootScope.authenticated){
            $location.path("/");
            $scope.error = false;
          }else{
            $location.path("/login");
            $scope.error = true;
          }
        });
      }
  });

위에서처럼 $rootScope.serverUrl+'/doLogin' 를 통해 통신하게 했다. $rootScope.serverUrl은 App.js에 

angular
  .module('frontendApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch',
    'ui.sortable'
  ])
  .run(['$rootScope','$http', function($rootScope, $http){

      // Definition to Global Variables and Functions
      $rootScope.serverUrl = "http://localhost:8080"
      });
    }]);

이런식으로 마치 전역변수처럼 서버 URL을 생성해 두었다. 물론, 나중에 배포 문제 때문에 직접 URL을 쓰지 않고 $location 을 사용하여 바꿀 예정이다.