반응형

스프링 시큐리티와 앵귤러JS Spring Security and AngularJS


Part I : 단일 페이지 보안 어플리케이션 (A Secure Single Page Application)

Part II: 로그인페이지 (The Login Page)

Part III: 리소스 서버 (The Resource Server)

Part IV: API 게이트웨이 (The API Gateway)

Part V: OAuth2와 싱글사인온 (Single Sign On with OAuth2)

Part VI: 다중 UI 어플리케이션과 게이트웨이 (Multiple UI Applications and a Gateway)

Part VII: 모듈화한 AngularJS 어플리케이션  (Modular AngularJS Application)

Part VIII: Angular 어플리케이션 테스트 (Testing an Angular Application)


--------------------------------------------------------------------------------


OAuth2를 활용한 싱글사인온 Single Sign On with OAuth2

이 섹션에서 우리는 어떻게 스프링 시큐티리와 앵귤러JS로 단일 페이지 어플리케이션을 만드는지 계속 얘기해볼 것이다. 이제 우리는 API게이트웨이가 백엔드 리소스에 OAuth2 토큰 인증과 싱글사인온을 지원하도록 스프링 클라우드와 함께 스프링 시큐리티 OAuth를 어떻게 사용하는지 보여줄것이다. 이 글을 시리즈의 5번째 섹션으로 당신이 어플리케이션의 기본구성을 이해하거나 처음부터 빌드해보려면 첫번째 섹션부터 읽도록 하자 또는 그냥 Github의 소스코드로 바로 가도 된다. 이전 섹션에서 우리는 UI서버에 내장된 API 게이트웨이를 구현하기 위해 스프링 클라우드를, 백엔드 리소스의 인증을 위해 스프링 세션을 사용하여 배포가능한 작은 어플리케이션을 만들었다. 이번 섹션에서 우리의 UI서버를 인증서버로 잠재적으로 많은 싱글사인온 어플리케이션의 하나로 만들 것이다. 이것은 엔터프라이즈나 쇼셜 스타트업에서나 요즘 수많은 어플리케이션에서 흔한 패턴이다. 우리는 OAuth2 서버를 인증자로서 사용할 것이다. 또한 백엔드 서버를 위한 토큰 인증을 위해 사용할 것이다. 스프링 클라우드는 자동으로 우리의 백엔드에서 억세스 토큰access token을 중계하여 우리로 하여금 UI와 리소스 서버 양쪽의 구현을 더욱 간단하게 만들어준다.

기억하기: 만일 당신이 이 섹션의 샘플을 동작시키려면 브라우저의 쿠키와 HTTP Basic credential에 대한 캐시값을 지워주어야 한다. 크롬에선 새 incognito 창을 쓰는게 최선의 방법이다.

OAuth2 인증서버 만들기 Creating an OAuth2 Authorization Server

인증과 토근관리를 처리하는 새 서버를 만드는 첫 걸음은, Spring Boot Initializr로 시작하는 첫번째 섹션의 절차를 따르는 것이다. 유닉스계열 시스템에서는 다음과 같이 curl을 사용할 수 있다: 

$ curl https://start.spring.io/starter.tgz -d style=web \
-d style=security -d name=authserver | tar -xzvf -

그 후에 당신이 선호하는 IDE에서 프로젝트를 import하면 된다. 또는 커맨드라인에서 "mvn" 명령어를 써서 파일로 동작해도 된다.

OAuth2 의존성 추가하기 Adding the OAuth2 Dependencies

Spring OAuth 의존성을 추가해줘야한다 POM에 다음과 같이 추가하자:

pom.xml
<dependency>
  <groupId>org.springframework.security.oauth</groupId>
  <artifactId>spring-security-oauth2</artifactId>
  <version>2.0.5.RELEASE</version>
</dependency>

인증서버의 구현은 매우 쉽다. 최소한의 구현은 다음과 같다:

AuthserverApplication.java

@SpringBootApplication
public class AuthserverApplication extends WebMvcConfigurerAdapter {

  public static void main(String[] args) {
    SpringApplication.run(AuthserverApplication.class, args);
  }

  @Configuration
  @EnableAuthorizationServer
  protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints.authenticationManager(authenticationManager);
    }

@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.inMemory()
          .withClient("acme")
          .secret("acmesecret")
          .authorizedGrantTypes("authorization_code", "refresh_token",
              "password").scopes("openid");
    }

}

다음의 2가지 일만 해주면 된다. (@EnableAuthorizationServer를 추가한 후):

    • 클라이언트 "acme"를 secret과 "authorization_code"를 포함하는 인증타입을 가지도록 등록한다. 

    • 스프링 부트 자동설정으로 부터  기본 AuthenticationManager를 주입하고 이것을 OAuth2 endpoint와 연동한다.

이제 테스트를 위해 패스워드를 명시하고 9999포트에서 돌려보자:

application.properties
server.port=9999
security.user.password=password
server.contextPath=/uaa

또한 기본값("/")을 사용하지않게 하기 위해 context path를 명시한다. 이것은 이렇게 하지않으면 로컬호스트의 다른 서버로부터 얻은 쿠키를 잘못된 서버로 보낼 수 있기 때문이다. 다음과 같이 서버가 동작하는 것을 확인할 수 있다:

$ mvn spring-boot:run

또는 당신의 IDE에서 main() 메소드에서 시작하자.

인증서버 테스트하기 Testing the Authorization Server

스프링 기본 시큐리티 설정을 사용하는 우리 서버는 HTTP Basic 인증에 의해 보호되는 첫번째 섹션의 서버와 같다. authorization code token grant 를 초기화 하기위해, 당신은 인증endpoint를 거쳐야한다. 이를테면 http://localhost:9999/uaa/oauth/authorize? response_type=code&client_id=acme&redirect_uri=http://example.com. 일단 인증을 받았으면 인증코드가 첨부되어 example.com으로 리다이렉트될 것이다. 예를 들면 http://example.com/?code=jYWioI.

 샘플 어플리케이션에서 등록을 위한 리다이렉트없이 "acme"클라이언트를 만든 목적은, 우리가 example.com의 리다이렉트를 얻을 수 있게 하기위함이다. 제품화수준의 어플리케이션에셔는 항상 리다이렉트를 등록해줘야한다 (HTTPS를 사용해서)

토큰 endpoint의 "acme"클라이언트 credential을 사용하는 억세스토큰을 위해 코드를 맞교환한다:

$ curl acme:acmesecret@localhost:9999/uaa/oauth/token  \
-d grant_type=authorization_code -d client_id=acme     \
-d redirect_uri=http://example.com -d code=jYWioI
{"access_token":"2219199c-966e-4466-8b7e-12bb9038c9bb","token_type":"bearer","refresh_token":"d193caf4-5643-4988-9a4a-1c03c9d657aa","expires_in":43199,"scope":"openid"}

억세스 토큰은 서버의 메모리상주 토큰 저장소에 의해 만들어진 UUID ("2219199c…")이다. 우리는 또한 현재의 토큰의 유효기간이 끝날때 새로운 억세스토큰을 받는데 사용하는 리프레시 토큰을 받는다.

 우리가 "acme" 클라이언트에 "password" 승인을 허용하였으므로, curl과 인증코드 대신 user credential을 사용하여 토큰 endpoint로부터 직접 토큰을 얻을 수 있다. 이는 테스트용으로는 유용하지만 브라우저 기반의 클라이언트에서 적합하지않다.

위의 링크를 따라가보면, 스프링 OAuth가 제공하는 whitelabel UI를 볼 수 있다. 우리가 이것을 사용하려면 self-contained서버를 위해 곧 두번째 섹션에서 했던 것을 다시 보강해보자 

리소스 서버 바꾸기 Changing the Resource Server

네번째 섹션에서 우리의 리소스 서버는 인증을 위해 스프링 세션을 사용했다. 우리는 이것을 스프링 OAuth으로 교체할 것이다. 또한 스프링 세션과 레디스 의존성을 제거할 필요가 있다. 다음과 같이 바꿔보자:

pom.xml
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session</artifactId>
  <version>1.0.0.RC1</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

위의 의존성을 아래로 바꾸자:

pom.xml
<dependency>
  <groupId>org.springframework.security.oauth</groupId>
  <artifactId>spring-security-oauth2</artifactId>
</dependency>

그 후 메인 어플리케이션 클래서에서 Filter 세션을 지우자, 이것을 스프링 클라우드 시큐리티의  @EnableOAuth2Resource 어노테이션으로 바꾼다:

ResourceApplication.groovy
@SpringBootApplication
@RestController
@EnableOAuth2Resource
class ResourceApplication {

  @RequestMapping('/')
  def home() {
    [id: UUID.randomUUID().toString(), content: 'Hello World']
  }

  static void main(String[] args) {
    SpringApplication.run ResourceApplication, args
  }
}

이 수정으로 어플리케이션은 HTTP Basic 대신 억세스 토큰을 검수하도록 준비가 되었다. 그러나 우리는 이 프로세스를 실제로 끝마치려면 설정을 수정해줘야한다. "application.properties"에 작은 외부 설정을 추가하여 리소스 서버가 사용자를 인증하고 토큰을 디코드할 수 있게 할 것이다:

application.properties

...
spring.oauth2.resource.userInfoUri: http://localhost:9999/uaa/user

이것은 서버가 "/user" endpoint에 접근하기위해 토큰을 사용하며, 인증 정보를 얻어내는데 사용할 것이라고 알려주는 것이다. (페이스북 API의  "/me" endpoint 와 유사하다). 이는 스프링 OAuth2의 ResourceServerTokenServices 인터페이스에 의해 표출됨으로서 리소스 서버가 토큰을 디코드하는 방법을 효율적으로 제공해준다.

어플리케이션을 시작하고 클라이언트의 커맨드라인에서 홈페이지를 쳐보자:

$ curl -v localhost:9000
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:9000
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
...
< WWW-Authenticate: Bearer realm="null", error="unauthorized", error_description="An Authentication object was not found in the SecurityContext"
< Content-Type: application/json;charset=UTF-8
{"error":"unauthorized","error_description":"An Authentication object was not found in the SecurityContext"}

bearer 토큰을 가르키는 "WWW-Authenticate" 헤더를 가지는 401 을 볼 수 있을 것이다.

userInfoUri는 리소스서버가 토큰을 디코드하는 방법을 연결할 뿐 아니라 사실, 최소 공통분모의 한 종류이다 (그리고 스펙의 일부가 아니다.) 그러나 종종 (페이스북, 클라우드 파운더리, Github과 같은) OAuth2 프로바이더에서 이용가능하다. 그리고 또 다른 선택을 사용할 수 있는데 예를 들면, (JWT와 같은 예로사용자는 토큰의 유저인증 자체를 인코드할 수 있다. 또는 공유된 백엔드 저정소를 사용할 수도 있다. 클라우드 파운더리에는 /token_info endpoint가 있어 사용자 정보 endpoint보다 더 자세한 정보를 제공해준다. 그러나 더 구체적인 인증을 요구한다. 또다른 (자연스러운) 옵션으로 trade-off와 다른 종유의 혜택을 제공할 수 있지만 이들에 대한 자세한 논의는 이 섹션의 범주에서 벗어난다.

사용자 endpoint 구현하기 Implementing the User Endpoint

인증서버에 다음의 endpoint를 간단히 추가해보자:

AuthserverApplication.java
@SpringBootApplication
@RestController
@EnableResourceServer
public class AuthserverApplication {

  @RequestMapping("/user")
  public Principal user(Principal user) {
    return user;
  }

  ...

}

두번째 섹션에서 했던데로 똑같이 @RequestMapping 를 추가했다. 또한 "/oauth/* endpoint를 제외한 인증서버의 모든 곳에 기본 보안을 적용하는 스프링 OAuth의 @EnableResourceServer 어노테이션도 추가했다.

이 endpoint를 가지고 우리는 greeting 리소스를 테스트할 수 있다. 그들 둘다 이제 인증서버에 생성한 bearer 토큰을 허용하기 때문이다:

$ TOKEN=2219199c-966e-4466-8b7e-12bb9038c9bb
$ curl -H "Authorization: Bearer $TOKEN" localhost:9000
{"id":"03af8be3-2fc3-4d75-acf7-c484d9cf32b1","content":"Hello World"}
$ curl -H "Authorization: Bearer $TOKEN" localhost:9999/uaa/user
{"details":...,"principal":{"username":"user",...},"name":"user"}

(당신이 스스로 동작을 확인하기 위해 당신의 인증서버로 부터 획득한 억세스 토큰의 값으로 대체하자)

UI 서버 The UI Server

이 어플리케이션의 마지막 부분으로, 우리는 인증서버로 위임하고 인증 파트를 추출하는 UI서버를 완성시켜야한다. 이것을 위해 리소스 서버로부터 우리는 먼저 스프링 세션과  레디스 의존성을 제거하고 그들을 스프링 OAuth로 대체해줘야한다.

이것이 끝나면, 우리는 세션 필터와 "/user" endpoint 또한 지워준다. (@EnableOAuth2Sso어노테이션을 사용하여) 인증서버로 리다이렉스하기 위해 어플리케이션을 설정하자:

UiApplication.java

@SpringBootApplication
@EnableZuulProxy
@EnableOAuth2Sso
public class UiApplication {

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

  @Configuration
  @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
  protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {

네번째 섹션을 기억하자 @EnableZuulProxy의 혜택을 본 UI서버는 이제 API 게이트웨이로서 동작한다. 우리는 YAML에 라우트 매핑route mapping을 선언할 수 있다. 이제 "/user" endpoint는 인증서버로 프록시되어진다:

application.yml
zuul:
  routes:
    resource:
      path: /resource/**
      url: http://localhost:9000
    user:
      path: /user/**
      url: http://localhost:9999/uaa/user

마침내 우리는WebSecurityConfigurerAdapterOAuth2SsoConfigurerAdapter로 바꿔줘야 한다. 이제부터 @EnableOAuth2Sso에 의해 설정된 SSO filter chain이 기본값으로 바뀔것이다:

SecurityConfiguration.java

@Configuration
protected static class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {

  @Override
  public void match(RequestMatchers matchers) {
    matchers.anyRequest();
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/index.html", "/home.html", "/")
        .permitAll().anyRequest().authenticated().and().csrf()
        .csrfTokenRepository(csrfTokenRepository()).and()
        .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
  }

  ... // the csrf*() methods are the same as the old WebSecurityConfigurerAdapter
}

기본 클래스 이름과 상관없이 주요한 변경사항은 자기들 스스로의 메소드로 가게 하는 matcher이다. 이제 formLogin() 은 더이상 필요없다.

또한 @EnableOAuth2Sso 어노테이션을 위한 몇개의 의무적인 외부설정 프로퍼티가 있어 올바른 인증서버에 연결하고 인증할 수 있다. 이것을 application.yml에 넣어주자:

application.yml

spring:
  oauth2:
    sso:
      home:
        secure: false
        path: /,/**/*.html
    client:
      accessTokenUri: http://localhost:9999/uaa/oauth/token
      userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
      clientId: acme
      clientSecret: acmesecret
    resource:
      userInfoUri: http://localhost:9999/uaa/user

OAuth2 클라이언트 ("acme")와 인증서버 위치를 넣어주자. 또한 (리소스서버에 있는 것과 같은) userInfoUri가 있어 사용자가 UI 앱 자체에서 인증받을 수 있다. "home"관련 것들은 우리의 단일 페이지 어플리케이션에서 정적 리소스에 익명의 접근을 허용한다.

클라이언트에선 In the Client

UI어플리케이션의 프론트 엔드에 약간의 수정을 해주어 우리가 여전히 인증서버로 리다이트되도록 만들어줘야한다. 먼저 앵귤러 라우트로부터 "로그인" 링크를 바꿔줘야하는 네비게이션 바의 "index.html":

index.html
<div ng-controller="navigation" class="container">
  <ul class="nav nav-pills" role="tablist">
    ...
    <li><a href="#/login">login</a></li>
    ...
  </ul>
</div>

을 그냥 평범한 HTML링크로 바꿔주자:

index.html
<div ng-controller="navigation" class="container">
  <ul class="nav nav-pills" role="tablist">
    ...
    <li><a href="login">login</a></li>
    ...
  </ul>
</div>

"/login" endpoint는 스프링 시큐리티에 의해 처리된다. 만일 사용자가 인증받지 못하면 인증서버로 리다이렉트 될 것이다.

우리는 또한 "네비게이션" 컨트롤러의 login()함수와 앵귤러 설정에서 "/login" 라우트를 지울 것이다.

hello.js

angular.module('hello', [ 'ngRoute' ]).config(function($routeProvider) {

  $routeProvider.when('/', {
    templateUrl : 'home.html',
    controller : 'home'
  }).otherwise('/');

}). // ...
.controller('navigation',

function($rootScope, $scope, $http, $location, $route) {

  $http.get('user').success(function(data) {
    if (data.name) {
      $rootScope.authenticated = true;
    } else {
      $rootScope.authenticated = false;
    }
  }).error(function() {
    $rootScope.authenticated = false;
  });

  $scope.credentials = {};

  $scope.logout = function() {
    $http.post('logout', {}).success(function() {
      $rootScope.authenticated = false;
      $location.path("/");
    }).error(function(data) {
      $rootScope.authenticated = false;
    });
  }

});

어떻게 작동하는가? How Does it Work?

이제 모든 서버를 다같이 동작시키자. 브라우저를 열고 http://localhost:8080에 방문하여 "로그인" 링크를 클릭하면 UI로부터 인증한 같은 토큰을 사용해서 OAuth2리소스 서버로부터 불러오는 greeting을 가지는 UI의 홈페이지로 리다이렉트 되기전에 ,인증(HTTP Basic 팝업)과 토큰 승인(Whitelable HTML)을 위해 인증서버로 리다이트 될것이다.

브라우저와 백엔드간의 연동은 브라우저에서 확인할 수 있다. 만일 당신이 개발자툴을 쓴다면 (보통 크롬에서 F12로 열수 있고 파이어폭스에서는 플러그인이 필요할 것이다) 여기 요약본을 보자:

VerbPathStatusResponse

GET

/

200

index.html

GET

/css/angular-bootstrap.css

200

Twitter bootstrap CSS

GET

/js/angular-bootstrap.js

200

Bootstrap and Angular JS

GET

/js/hello.js

200

Application logic

GET

/home.html

200

HTML partial for home page

GET

/user

302

Redirect to login page

GET

/login

302

Redirect to auth server

GET

(uaa)/oauth/authorize

401

(ignored)

GET

/resource

302

Redirect to login page

GET

/login

302

Redirect to auth server

GET

(uaa)/oauth/authorize

401

(ignored)

GET

/login

302

Redirect to auth server

GET

(uaa)/oauth/authorize

200

HTTP Basic auth happens here

POST

(uaa)/oauth/authorize

302

User approves grant, redirect to /login

GET

/login

302

Redirect to home page

GET

/user

200

(Proxied) JSON authenticated user

GET

/home.html

200

HTML partial for home page

GET

/resource

200

(Proxied) JSON greeting

(uaa) 접두사가 붙은 요청들은 인증서버로 간다. "ignored"로 마크된 응답들은 우리는 그들이 던져주는 데이터를 따로 처리하지 않지 때문에  XHR호출로 앵귤러가 받게되는 응답이다.  "/user"리소스의 경우 우리는 인증된 사용자를 요구한다. 첫번째 호출에 인증이 없기때문에 응답은 드랍된다.

UI의 "/trace" endpoint (마지막까지 스크롤을 내려라)에서 당신은 remote:true 설정으로  "/user"와 "/resource"의 프록시된 백엔드 요청을 볼 수 있을 것이다. 네번째 섹션에서 언급한대로 쿠키대신 bearer토큰이 인증에 사용된다. 스프링 클라우드 시큐리티는 우리를 위해 이것을 알아서 해준다: @EnableOAuth2Sso 와@EnableZuulProxy의 설정을 인식함으로서, (기본값으로) 우리가 토큰을 프록시된 백엔드에 중계하기 원한다고 이해한다.

 이전 섹션에서, 인증이 섞이는것을 막기위해 "/trace"를 다른 브라우저로 사용하라고 언급했다.

로그아웃 경험 The Logout Experience

로그아웃 링크를 클릭하면 홈페이지가 바뀌는 것을 볼 수 있다 (greeting이 더이상 보이지않는다). 따라서 사용자는 더이상 UI서버에 인증되어있지않다. 로그인을 다시 클릭하면 인증서버에 승인절차를 다시 거칠 필요가 없다. (왜냐하면 당신은 그곳에 로그아웃하지않았으므로). 어떤것이 올바른 사용자 경험인지 의견이 나뉠테지만, 이는 악명높은 속임수 문제가 있다.(싱글사인아웃 Single Sign Out:Science Direct article and Shibboleth docs). 이상적인 사용자 경험은 기술적으로 취약하지 않아야한다. 또한 사용자가 원하는게 무엇인가 심사숙고해야한다. "나는 '로그아웃'하길 원한다"는 말은 충분히 쉽게 들리지만 명확한 응답은 "무엇을 로그아웃할것인가?"이다. 이 싱글사인온서버에 의해 제어되는 모든 시스템에서 로그아웃하길 원하는지 아니면 그냥 "로그아웃"링크를 클릭하는 것을 원하는가? 여기는 이 주제를 포괄적으로 논의할 곳은 아니다. 그러나 주목해야할 가치가 있다. 만일 이 논제에 관심이 있다면 몇몇의 군침이 도는 아이디어를 Open ID Connect 사양에서 토론을 할 수 있다. 

결 론 Conclusion

이제 스프링 시큐리티와 앵귤러JS 스택을 거치는 겉핥기식 여행의 끝무렵에 다다랐다. 우리는 이제 UI/API 게이트웨이, 리소스 서버, 인증서버/토큰승인자 의 명확한 책임을 가지는 각각 3개의 분리된 컴포넌트로 구성된 멋진 아키텍쳐를 가지게 되었다. 이들은 모든 레이어에서 비지니스 로직이 아닌 코드의 양을 최소화하였고 비지니스 로직을 더욱 향상시키고 확장할 수 있도록 알아보기 쉽다. 다음 단계에서 우리는 우리의 인증서버의 UI를 깔끔하게 정리할 것이다. 아마 자바스크립트 클라이언트상의 테스트를 포함하는 약간의 테스트를 추가할 것이다. 또 하나의 흥미로은 작업은 이 표준공정(boilerplate)의 코드를 모두 추출하여 앵귤러 쪽의 네비게이션 컨트롤러를 위해 스프링 시큐리티와 스프링 세션 자동설정과 몇개의 webjars리소스를 포함하는 라이브러리 (예를 들면 "spring-security-angular")안에 넣는 것이다. 앵귤러JS나 스프링 시큐리티의 내부 동작에 대해 배우길 원하는 누구나 이 시리즈의 섹션에 아마도 실망하게 될것이다. 그러나 당신이 어떻게 모두다 함께 잘 동작하는지, 최소한의 설정으로 먼 길을 떠날 수 있는지 알기원한다면 당신은 희망적으로 좋은 경험을 얻게 될것이다. Spring Cloud 는 이제 막 릴리즈 되었고 그들을 써야할 때 스냇샷으로 가져와야한다. 그러나 Release candidate이 이용가능하고 GA가 곧 나올것이다.  Github 이나 gitter.im에 피드백을 보내거나 확인할 수 있다.

이 시리즈의 다음섹션은 (인증과정에서) 접근 결정에 관한 것이다. 같은 프록시내에서 다중의 UI어플리케이션을 사용할 것이다.

부록: 인증서버를 위한 부트스트랩 UI와 JWT 토큰 Addendum: Bootstrap UI and JWT Tokens for the Authorization Server

Github의 소스코드에서 두번째 섹션에서 우리가 만들었던 로그인페이지와 유사한 방식으로 구현한 보기좋은 로그인페이지와 유저 승인 페이지를 가지는  이 어플리케이션의 또다른 버전을 찾아볼 수 있다. 또한 JWT 인코딩된 토큰을 사용한다. 그래서 "/user" endpoint를 사용하는 대신, 리소스 서버는 간단한 인증을 수행하기 위해 토큰 자체에서 충분한 정보를 뽑아올 수 있다. 브라우저 클라이언트는 UI서버를 통해 프록시되어진 이것을 사용자가 인증되었는지 결정하는데 여전히 사용한다 (실제 어플리케이션에서 리소스서버를 호출하는 숫자에 비교하면 자주 호출될 필요가 없다.)


반응형

반응형

스프링 시큐리티와 앵귤러JS Spring Security and AngularJS


Part I : 단일 페이지 보안 어플리케이션 (A Secure Single Page Application)

Part II: 로그인페이지 (The Login Page)

Part III: 리소스 서버 (The Resource Server)

Part IV: API 게이트웨이 (The API Gateway)

Part V: OAuth2와 싱글사인온 (Single Sign On with OAuth2)

Part VI: 다중 UI 어플리케이션과 게이트웨이 (Multiple UI Applications and a Gateway)

Part VII: 모듈화한 AngularJS 어플리케이션  (Modular AngularJS Application)

Part VIII: Angular 어플리케이션 테스트 (Testing an Angular Application)


--------------------------------------------------------------------------------


API 게이트웨이 The API Gateway

이 섹션에서 우리는 어떻게 스프링 시큐티리와 앵귤러JS로 단일 페이지 어플리케이션을 만드는지 계속 얘기해볼 것이다. 이제 스프링 클라우드를 사용하여 백엔드 리소스에 접근하고 인증을 제어하기 위한 API 게이트웨이를 만드는 법을 보여줄 것이다. 이 글을 시리즈의 4번째 섹션으로 당신이 어플리케이션의 기본구성을 이해하거나 처음부터 빌드해보려면 첫번째 섹션부터 읽도록 하자 또는 그냥 Github의 소스코드로 바로 가도 된다. 이전 세션에서 스프링 세션을 사용해 백엔드 리소스를 인증하기 위한 간단히 배포한 어플리케이션을 만들어보았다. 이번 섹션에서 우리는 UI서버를 백엔드 리소스 서버로 reverse proxy로 만들것이다. 지난 구현물의 문제(커스텀 토큰 인증에 의해 언급된 기술적 복잡성)를 해결하고 브라우저 클라이언트의 접근 제어를 위한 새로운 옵션을 제공할 것이다.

기억하기: 만일 당신이 이섹션의 샘플을 동작시키려면 브라우저의 쿠키와 HTTP Basic credential에 대한 캐시값을 지워주어야 한다. 크롬에선 새 incognito 창을 쓰는게 최선의 방법이다.

API 게이트웨이 만들기 Creating an API Gateway

API게이트웨이는 (이 섹션의 예제들과 같은) 브라우저에 기반의 프론트앤드 클라이언트들을 위한 단일 진입로entry이다. 클라이언트는 서버의 URL을 알아야만 하고 백엔드는 - 주요한 장점으로- 수정없이 재정의 될것이다. 중앙집중화와 제어의 또 다른 이점은 한계치 설정 인증, 감사(audit) 그리고 로깅(logging)이다. 그리고 간단한 reverse proxy를 구현하는 것은 스프링 클라우드를 쓰면 정말 간단하다.

당신이 코드를 함께 따라가려 한다면, 지난 섹션의 마지막에 했던 어플리케이션 구현은 조금 복잡하다는 것을 알고 것이다. 따라서 이 여행을 다시 시작하기에 적합한 장소는 아니다. 그러나 우리가 좀더 쉽게 시작할 수 있는 중간점이 있는데 백엔드 리소스가 아직 스프링 시큐리티에 의해 보호되지 않았던 곳이다. 이 부분의 소스코드는 Github에 별도의 프로젝트로 넣어두었으므로 우리는 여기서부터 출발할 것이다. 이 프로젝트는 UI 서버와 리소스 서버를 가지고 있고 이 둘이 서로 각각 소통하고 있다. 리소스 서버는 아직 스프링 시큐리티가 없는 상태이므로 우리는 먼저 시스템을 동작하게 한뒤 그 후 이 레이어를 추가하자.

단 한줄의 선언적 Reverse Proxy Declarative Reverse Proxy in One Line

이 프로젝트를 API 게이트웨이로 바꾸려면, UI서버를 약간 손봐줘야한다. 스프링 설정이 있는 그곳에 @EnableZuulProxy 어노테이션을 추가해줘야한다. 예를 들면 메인 어플리케이션안에 다음과 같이:

UiApplication.java

@SpringBootApplication
@RestController
@EnableZuulProxy
public class UiApplication {
  ...
}

외부 설정 파일안에 우리는 UI서버의 로컬 리소스를 외부설정("application.yml")의 원격 리소스와 맵핑 해줘야한다. 

application.yml

security:
  ...
zuul:
  routes:
    resource:
      path: /resource/**
      url: http://localhost:9000

이것의 의미는 서버의 패턴 /resource/** 의 경로를 외부서버인 localhost:9000의 같은 경로와 맵핑하라는 것이다. 간단하지만 효율적이다. (맞다. YAML로 6줄이다. 항상 이것이 필요한 것은 아니다)

이것을 동작시키려고 우리가 해야하는 전부는 클래스패스에 올바른 의존성들을 넣어주는 것이다. 이 목적을위해 우리의 메이븐 POM에 몇줄을 추가했다:

pom.xml

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-parent</artifactId>
      <version>1.0.0.BUILD-SNAPSHOT</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
  </dependency>
  ...
</dependencies>

"spring-cloud-starter-zuul"의 사용에 덧붙이자면, 이것은 스프링 부트가 가지고 있는 하나의 starter POM이다. 하지만 의존성을 관리하면 우리는 Zuul proxy가 필요하다. 또한 <dependencyManagement>를 사용하고 있는데 왜냐하면 우리는 모든 버젼의 의존성이 올바르게 의존될 수 있기를 원하기 때문이다.

클라이언트에서 Proxy 소비하기 Consuming the Proxy in the Client

위의 수정후에도 우리의 어플리케이션은 여전히 동작한다. 하지만 클라이언트를 수정할때까지 아직 새로운 프락시를 실제로 사용할 수 없다. 운좋게도 이것는 "home" 컨트롤러의 구현물을 다음과 같이 아주 경미하게 수정해주면 된다:

hello.js
angular.module('hello', [ 'ngRoute' ])
...
.controller('home', function($scope, $http) {
	$http.get('http://localhost:9000/').success(function(data) {
		$scope.greeting = data;
	})
});

로컬 리소스로는:

hello.js
angular.module('hello', [ 'ngRoute' ])
...
.controller('home', function($scope, $http) {
	$http.get('resource/').success(function(data) {
		$scope.greeting = data;
	})
});

이제 서버를 돌리면, 모든게 잘 작동한다. 요청은 UI (API게이트웨이)를 거쳐 리소스 서버로 프락시되어진다.

더욱 간소화 Further Simplifications

훨씬 더 나은점: 우리는 더이상 리소스 서버에 CORS 필터가 필요없다. 어쨋든 우리는 매우 신속하게 이것들을 내버렸고 우리가 무언가를 손에 의해 의존해야한다면 그것은 빨간불이다 (특히 보안과 관련해서). 운좋게도 이제 불필요하기때문에 우리는 이것을 버릴수 있고 밤에 편히 발뻗고 잘 수 있게 되었다.

리소스 서버 보안적용하기 Securing the Resource Server

아마 중간정도에 우리가 보안이 없는 리소스 서버로 시작한다고 얘기한 것을 기억해보자.

여담: 만일 당신의 네트워크 아키텍쳐가 어플리케이션 아키텍쳐를 미러(mirror)한다면, 소프트웨어에 보안의 결여는 문제조차 되지않을 수 있다. (당신은 리소스 서버를 물리적으로 접근불가능하게 만들수 있겠지만 UI 서버는 그렇지 않다). 리소스 서버를 로컬호스트에서만 접근가능하도록 만드는 간단한 실예로서, 리소스 서버의 application.properties에 다음을 추가해보자:

application.properties
server.address: 127.0.0.1

우와, 엄청 쉽네! 당신의 데이터 센터에서만 볼 수 있는 네트워크주소를 가지고 이렇게 해보라. 그리면 당신은 모든 사용자 데스크탑과 리소스서버에서 작동하는 시큐리티 솔루션을 가질 수 있다.

우리는 (수많은 이유와 같이) 소프트웨어 레벨의 시큐리티가 필요하다고 결정했다고 가정했다. 이것은 문제가 될수 없다. 왜냐하면 우리가 해야하는 모든 것은 스프링 시큐리티를 (리소스 서버 POM에) 의존성을 추가해주는것 뿐이기때문이다.

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

이거면 보안적용된 리소스 서버를 갖추기에 충분하다. 그러나 아직 동작하는 어플리케이션을 가질 수 없다. 두 서버 사이에 인증상태를 공유할 수 없다는 세번째 섹션에서 하지 않았던 같은 이유때문이다.

인증 상태 공유하기 Sharing Authentication State

우리는 인증(과 CSRF) 상태를 공유하기 위해 우리가 지난 섹션에서 스프링 세션을 가지고 한것과 같은 메카니즘을 사용할 수 있다. 그전에 양쪽 서버에 의존성을 추가해주자:

pom.xml
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session</artifactId>
  <version>1.0.0.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

그러나 이 시점에서의 설정은 훨씬 더 쉽다. 우리가 양쪽에 동일한 Filter선언을 추가해 두었기 때문이다. 먼저 (@EnableRedisHttpSession를 추가했던) UI서버: 

UiApplication.java

@SpringBootApplication
@RestController
@EnableZuulProxy
@EnableRedisHttpSession
public class UiApplication {

  ...

}

그 다음 리소스 서버. 다음의 3가지 작은 수정이 있다. 하나는ResourceApplication에 @EnableRedisHttpSession 추가하기:

ResourceApplication.groovy
@SpringBootApplication
@RestController
@EnableRedisHttpSession
class ResourceApplication {
  ...
}

또 하나는 리소스 서버의 HTTP Basic을 명시적으로 disable하기 (브라우저가 인증 다이얼로그가 팝업되는 것을 막기위해):

ResourceApplication.groovy
@SpringBootApplication
@RestController
@EnableRedisHttpSession
class ResourceApplication extends WebSecurityConfigurerAdapter {

  ...

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic().disable()
    http.authorizeRequests().anyRequest().authenticated()
  }

}

여담: 인증 다이얼로그를 막는 하나의 대안은 HTTP Basic을 유지하되 401 에러를 "기본"이외의 다른 액션을 취하게 변경하는 것이다. 당신은  HttpSecurity설정 콜백내의AuthenticationEntryPoint의 다 한 줄 구현으로 이렇게 할 수 있다.

그리고 마지막으로 application.properties에 none-stateless세션 생성정책을 명시화 하는 것이다:

application.properties

security.sessions: NEVER

레디스가 백그라운드에서 동작하고 있는 한 (이렇게 시작하려면 fig.yml을 사용하자) 시스템은 동작할 것이다. http://localhost:8080 의 UI에서 홈페이지를 불러서 로그인하면 백엔드로 부터의 메세지가 홈페이지에 랜더된 것을 볼 수 있을 것이다.

어떻게 동작하는가? How Does it Work?

보이지않는 곳에서 어떻게 동작되는가? 먼저 UI서버(API 게이트웨이)의 HTTP요청을 둘러보자:

VerbPathStatusResponse

GET

/

200

index.html

GET

/css/angular-bootstrap.css

200

Twitter bootstrap CSS

GET

/js/angular-bootstrap.js

200

Bootstrap and Angular JS

GET

/js/hello.js

200

Application logic

GET

/user

302

Redirect to login page

GET

/login

200

Whitelabel login page (ignored)

GET

/resource

302

Redirect to login page

GET

/login

200

Whitelabel login page (ignored)

GET

/login.html

200

Angular login form partial

POST

/login

302

Redirect to home page (ignored)

GET

/user

200

JSON authenticated user

GET

/resource

200

(Proxied) JSON greeting

쿠키이름이 사소하게 다른 것("JSESSIONID"대신 "SESSION")만 제외하면 두번째 섹션의 마지막에 언급된 순서와 동일하다. 이는 우리가 스프링 세션을 사용하고 있기 때문이다. 하지만 아키텍쳐는 다르다. 마지막 "/resource"로의 요청은 특별한데 이는 리소스 서버로의 프록시된 것이기 때문이다.

이제 UI서버의 "/trace" endpoint에서 보여지는 것처럼 실제 reverse proxy를 볼 수 있다. (스프링 클라우드의 의존성으로 우리가 추가했던 스프링 부트 Actuator). http://localhost:8080/trace 를 새 브라우저에서 열고 맨 밑으로 스크롤해보자 (아직 브라우저에 JSON 플러그인이 없다면 하나 받아두면 보기에도 가독성에도 좋다).  (브라우저 팝업에서) HTTTP Basic 인증을 해야할 것이지만 로그인폼에서 했던 같은 Credential이면 된다. 거의 끝무렵에 다음과 같은 요청의 묶음을 볼 수 있을 것이다:

인증이 겹치지 않게 하려면 다른 브라우저를 사용하자 (이를 테면 당신이 크롬으로 UI를 테스트해왔다면, 파이어폭스를 써라) - 동작하는 어플이 멈추진 않을테지만, 같은 브라우저에서 인증이 섞이면 trace의 가독성이 매우 떨어질 것이다. 

/trace

{
  "timestamp": 1420558194546,
  "info": {
    "method": "GET",
    "path": "/",
    "query": ""
    "remote": true,
    "proxy": "resource",
    "headers": {
      "request": {
        "accept": "application/json, text/plain, */*",
        "x-xsrf-token": "542c7005-309c-4f50-8a1d-d6c74afe8260",
        "cookie": "SESSION=c18846b5-f805-4679-9820-cd13bd83be67; XSRF-TOKEN=542c7005-309c-4f50-8a1d-d6c74afe8260",
        "x-forwarded-prefix": "/resource",
        "x-forwarded-host": "localhost:8080"
      },
      "response": {
        "Content-Type": "application/json;charset=UTF-8",
        "status": "200"
      }
    },
  }
},
{
  "timestamp": 1420558200232,
  "info": {
    "method": "GET",
    "path": "/resource/",
    "headers": {
      "request": {
        "host": "localhost:8080",
        "accept": "application/json, text/plain, */*",
        "x-xsrf-token": "542c7005-309c-4f50-8a1d-d6c74afe8260",
        "cookie": "SESSION=c18846b5-f805-4679-9820-cd13bd83be67; XSRF-TOKEN=542c7005-309c-4f50-8a1d-d6c74afe8260"
      },
      "response": {
        "Content-Type": "application/json;charset=UTF-8",
        "status": "200"
      }
    }
  }
},

두번째 엔트리는 클라이언트로부터 "/resource"의 게이트웨이까지의 요청이다. (브라우저에 추가된) 쿠키와 (두번째 섹션에서 언급된 앵귤러에 의해 추가된) CSRF헤더를 볼 수 있을 것이다. 첫번째 엔트리는 리소스 서버로의 호출이 트랙킹된다는 의미로remote: true 값을 가진다. "/" uri 경로를 가면 쿠키와 CSRF헤더가 역시 보내진 것을 볼 수 있다. 스프링 세션없이 이들 헤더는 리소스 서버에서의 의미가 없지만 우리는 이제 이들 헤더들을 인증과 CSRF토큰 데이터를 가지는 하나의 세션으로 재구성하도록 설정함으로서 요청이 받아들여진다.

결 론 Conclusion

우리는 이섹션에서 많은 것을 커버했다. 그러나 우리의 두서버의 기본 껍데기의 코드량을 최소화한 멋진 방법을 찾았다. 이 두 서버 모두 멋지게 보안이 적용되었고 사용자 경험을 절충하지 않아도 된다. 이것이 API 게이트웨이 패턴을 사용한 이유지만 사실 우리는 수박겉핥기수준으로 써본거다 ( 넷플리스는 수많은 부분에서에서 사용하고 있다). 스프링 클라우드를 읽고 게이트웨이의 수많은 기능들을 얼마나쉽게 추가할 수 있는지 찾아보자. 이 시리즈의  다음 섹션에서는 어플리케이션 아키텍쳐를 조금 확장하여 각각 별도의 서버에서 인증 책임을 뽑아낼 것이다 (싱글 사인온 패턴 the Single Sign On pattern).



반응형

반응형

스프링 시큐리티와 앵귤러JS Spring Security and AngularJS


Part I : 단일 페이지 보안 어플리케이션 (A Secure Single Page Application)

Part II: 로그인페이지 (The Login Page)

Part III: 리소스 서버 (The Resource Server)

Part IV: API 게이트웨이 (The API Gateway)

Part V: OAuth2와 싱글사인온 (Single Sign On with OAuth2)

Part VI: 다중 UI 어플리케이션과 게이트웨이 (Multiple UI Applications and a Gateway)

Part VII: 모듈화한 AngularJS 어플리케이션  (Modular AngularJS Application)

Part VIII: Angular 어플리케이션 테스트 (Testing an Angular Application)


--------------------------------------------------------------------------------

리소스 서버 The Resource Server

이 섹션에서 우리는 어떻게 스프링 시큐티리와 앵귤러JS로 단일 페이지 어플리케이션을 만드는지 계속 얘기해볼 것이다. 우리의 어플리케이션에서 동적 컨텐트로서 사용하고 있는 "greeting" 리소스를 별도의 서버 (먼저 보호하지않아도 되는 리소스, 그다음 토큰에 의해 보호되는 리소스)로 빼내는 걸로 시작하자. 이 글은 시리즈의 3번째 섹션이다. 당신이 어플리케이션의 기본구성을 이해하거나 처음부터 빌드해보려고 하면 첫번째 섹션부터 읽도록 하자 또는 보호하지않아도 되는 리소스토큰에 의해 보호되는 리소스 두 파트로 나누어진 Github의 소스코드를 직접 받아도 된다. 


기억하기: 샘플 어플리케이션으로 이 섹션 동안 돌릴 거면, 쿠키와 HTTP Basic credentials의 브라우저 캐시를 꼭 지워야한다. 크롬에서는 새 익명창incognito window로 여는게 최선의 방법이다.

분리된 리소스 서버 A Separate Resource Server

클라이언트쪽 수정 Client Side Changes

클라이언트 쪽은 리소스를 서로 다른 백엔드로 이동하기위해 해줘야 할게 거의 없다. 여기 "home" 컨트롤러의 마지막 섹션을 보자:

hello.js

angular.module('hello', [ 'ngRoute' ])
...
.controller('home', function($scope, $http) {
	$http.get('/resource/').success(function(data) {
		$scope.greeting = data;
	})
})
...

우리가 해줘야하는 건 URL을 바꿔주는 게 전부다. 예를 들면, 로컬호스트의 새 리소스를 돌리려면 아래와 같이 하면 된다:

hello.js
angular.module('hello', [ 'ngRoute' ])
...
.controller('home', function($scope, $http) {
	$http.get('http://localhost:9000/').success(function(data) {
		$scope.greeting = data;
	})
})
...

서버쪽 수정 Server Side Changes

 UI 서버의 수정은 아주 경미하다. 그냥 (기존에 "/resource" 였던) greeting 리소스를 위한 @RequestMapping를 지워주면 된다. 그 다음 우리가 Spring Boot Initializr를 사용한 첫번째 섹션에서 했던 대로 새로운 리소스 서버를 만들어줘야한다. 예를 들면 유닉스 계열의 시스템에선 다음과 같이 curl을 사용해보자: [주: 그냥 sts씁시다]

$ mkdir resource && cd resource
$ curl https://start.spring.io/starter.tgz -d style=web \
-d name=resource -d language=groovy | tar -xzvf -

그후 프로젝트를 당신이 선호하는 IDE에 import 하자 (기본설정은 보통 메이븐 자바 프로젝트다), 또는 커맨드라인에서 "mvn"을 써도 된다. 우리는 여기서 그루비를 사용할테지만 자바가 편하다면 자바를 써도 된다, 어쨋든 코드가 많지 않을 것이다.

예전 UI에 구현해둔 코드를 복사해서 main application class에 @RequestMapping 를 추가하자:

ResourceApplication.groovy
@SpringBootApplication
@RestController
class ResourceApplication {

  @RequestMapping('/')
  def home() {
    [id: UUID.randomUUID().toString(), content: 'Hello World']
  }comm

  static void main(String[] args) {
    SpringApplication.run ResourceApplication, args
  }

}

일단 이걸로 다음의 커맨드라인을 타입함으로서 어플리케이션을 브라우저에서 불러올 수 있다. 

$ mvn spring-boot:run --server.port=9000

브라우저에서 http://localhost:9000 를 열고 geeting을 가진 JSON을 확인해보자. ("src/main/resources"에 위치한) application.properties에서 포트넘버를 바꿀 수 있다.

application.properties
server.port: 9000

브라우저를 열고  8080 포트에서 작동중인) UI로 부터 리소스를 불러보면 제대로 동작하지않는 것을 볼 수 있다. 브라우저가 XHR요청을 허용하지 않기 때문이다.

CORS 협상 CORS Negotiation

브라우저는 Cross Origin Resource Sharing 프로토콜에 따라 접근이 허용되는지를 알기위해 우리의 리소스 서버에 협상을 한다. 이는 앵귤러JS의 소관이 아니다. 쿠키와 같이 브라우저에서 그냥 모든 자바스크립트와 같이 작동할 것이다.  이 두 서버는 자기들이 공통의 기원origin을 가지고 있다고 선언하지 않았기 때문에 브라우저는 요청을 보내는 것을 거부하고 UI가 깨지게 된다.

이것을 고치려면 우리는 "pre-flight" 옵션 요청과 호출자의 허용된 행동을 리스트하기위한 몇몇의 헤더들을 포함하는 CORS 프로토콜을 지원해야한다. 스프링 4.2는 잘 정제된 CORS 지원을 한다고 했지만 릴리즈 전까지는 (주, 이미 릴리즈됨Filter.를 써서 모든 요청에 같은 CORS응답을 보내는 어플리케이션의 목적에 맞춰주는 작업을 해야한다. 리소스 서버 어플리케이션의 같은 디렉토리에 클래스를 하나 만들고 @Component 어노테이션을 한다 (스프링 어플리케이션 컨텍스트가 스캔할 수 있도록). 예를 들면:

CorsFilter.groovy

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class CorsFilter implements Filter {

  void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    HttpServletResponse response = (HttpServletResponse) res
    HttpServletRequest request = (HttpServletRequest) req
    response.setHeader("Access-Control-Allow-Origin", "*")
    response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE")
    response.setHeader("Access-Control-Allow-Headers", "x-requested-with")
    response.setHeader("Access-Control-Max-Age", "3600")
    if (request.getMethod()!='OPTIONS') {
      chain.doFilter(req, res)
    } else {
    }
  }

  void init(FilterConfig filterConfig) {}

  void destroy() {}

}

Filter는 주요 스프링 시큐리티 필터보다 전에 적용되어야 하므로 @Order를 정의했다. 이 리소스 서버의 수정후, 재시작하면 UI로 greeting을 받아올 수 있다.

 태평스럽게  Access-Control-Allow-Origin=*를 사용하는건 빠르고 지저분하지만 잘 동작한다. 이 방식은 안전하지 않으므로 추천되는 방법은 아니다.

리소스 서버 보호하기 Securing the Resource Server

훌륭하게 새 아키텍처의 어플리케이션이 잘 동작하게 만들었다. 문제는 리소스서버가 보호되고 있지않다는 점 뿐이다.

스프링 시큐리티 추가하기 Adding Spring Security

UI서버와 같이 필터를 사용해서 리소스 서버에 시큐리티를 추가하는 법을 보자. 이것은 아마 더 기본의 방식대로 일것이고 대부분의 PaaS환경에서 최고의 선택이다. (보통 어플리케이션을 위해 private network를 만들지 않기때문에). 첫걸음은 정말 쉽다: 그냥 스프링 시큐리티를 메이븐 POM의 클래스패스에 추가해주면 된다:

pom.xml

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  ...
</dependencies>

리소스 서버를 재시작해보자. 유후! 이제 보안이 적용되었다:

$ curl -v localhost:9000
< HTTP/1.1 302 Found
< Location: http://localhost:9000/login
...

로그인 페이지로 리다이렉트되었다. curl은 우리의 앵귤러 클라이언트처럼 헤더를 보내지 않기 때문이다. 더 유사한 헤더를 보내도록 이 명령어를 수정해보자:

$ curl -v -H "Accept: application/json" \
    -H "X-Requested-With: XMLHttpRequest" localhost:9000
< HTTP/1.1 401 Unauthorized
...

우리는 단지 모든 요청에 credential을 보내도록 클라이언트에 알려주기만 하면 된다.

토큰 인증 Token Authentication

인터넷과 사람들의 스프링 백엔드 프로젝트는 커스텀 토큰 기반 인증 솔루션들로 어지럽혀져있다. 스프링 시큐리티는 사용자 스스로 시작할 수 있는 그 근간의 Filter 구현체를 제공한다.( 예로, AbstractPreAuthenticatedProcessingFilter 와TokenService를 보자). 그렇지만 스프링 시큐리티는 쓰라고 정해놓은 구현체가 없다. 이 이유중 하나는 아마 더 손쉬운 방법이 있기때문일 것이다.

이 시리지의 두번째 섹션을 기억해보자. 스프링 시큐리티는 기본값으로 인증데이터를 저장하기위해 HttpSession 을 사용한다. 비록 이것은 세션과 직접적으로 연관이 없지만: 사용자가 뒷단의 저장장소를 변경하는 중에 사용할 수 있는 추상 레이어(SecurityContextRepository)가 있다. UI에 의해 검증된 인증을 저장하기 위해 우리의 리소스 서버에 이 repository를 설정해두면, 우리는 두 서버간에 인증을 공유하는데 쓸 수 있다. UI서버는 이미 (HttpSession를 통해) 그러한 저장을 하고 있으므로 이 저장된 것을 배포하고 리소스 서버에 따라 열면 우리는 대부분의 해결책을 갖추게된다. 

스프링 세션 Spring Session

스프링 세션으로 이 부분을 손쉽게 해결할 수 있다. 우리는 그저 공유데이터 저장소가 필요할 뿐이다. (레디스는 바로 사용가능도록 지원되고 있다). Filter를 설정하기위해 서버에 몇줄의 설정만 해주면 될뿐이다.

UI 어플리케이션에선 POM에 몇개의 의존성만 추가해주면 된다:

pom.xml
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session</artifactId>
  <version>1.0.0.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

그 후 메인 어플리케이션에 @EnableRedisHttpSession 를 추가하자:

UiApplication.java
@SpringBootApplication
@RestController
@EnableRedisHttpSession
public class UiApplication {

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

  ...

}

@EnableRedisHttpSession 어노테이션은 스프링 세션에서 온 것으로 스프링 부트의 레디스 연결을 지원한다. (URL과 credential은 환경변수 또는 설정 파일을 통해 설정할 수 있다)

위의 한줄의 코드로 레디스 서버는 UI어플리케이션을 실행하는 로컬호스트에서 동작하여 유효한 사용자 credential과 세션 데이터(인증과 CSRF토큰)를 가지고 로그인정보가 레디스에 저장될 것이다.


로컬에서 동작하는 레디스 서버가 없다면, Docker(윈도우와 맥에서는 VM이 필요하다)로 쉽게 돌릴 수 있다. Github의 소스코드에서 docker-compose.yml 파일이 있어 docker-compose up명령어로 커맨드라인에서 손쉽게 동작시킬 수 있다. 

UI로부터 커스텀 토큰 보내기 Sending a Custom Token from the UI

이제 하나만 빼고 완성되었는데 저장소에서 데이터에의 키값을 위한 전송 메카니즘이다. 키값은 HttpSession ID 이므로, 우리가 UI클라이언트에서 그 키값을 유지하고 있다 리소스 서버에 커스텀 헤더로서 이것을 보낼 수 있다. 따라서 "home" 컨트롤러는 greeting 리소스를 위해 HTTP 요청의 일부로서 이 헤더를 보낼 수 있도록 수정되어야 한다. 예를 들면:

hello.js

angular.module('hello', [ 'ngRoute' ])
...
.controller('home', function($scope, $http) {
  $http.get('token').success(function(token) {
    $http({
      url : 'http://localhost:9000',
      method : 'GET',
      headers : {
        'X-Auth-Token' : token.token
      }
    }).success(function(data) {
      $scope.greeting = data;
    });
  })
});

(더 우아한 해결책은 아마 필요할 때 토큰을 가져오는 것일 것이다. 앵귤러의 interceptor 를 사용하여 리소스 서버로의 모든 요청에 헤더를 추가 할 수 있다. 인터셉터의 정의는 한 장소에서 모든것을 하고 비지니스 로직을 마구 채워가는 대신 추상화하는 것이다.

"http://localhost:9000[http://localhost:9000]"에 직접적으로 가는 대신, 우리는 하나의 호출의 성공적인 콜백안에 UI서버의 "/token"이라는 새로운 커스텀 종단을 부르도록 씌우는 것(wrap)이다. 이것의 구현은 이렇게 사소하다:

UiApplication.java
@SpringBootApplication
@RestController
@EnableRedisHttpSession
public class UiApplication {

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

  ...

  @RequestMapping("/token")
  @ResponseBody
  public Map<String,String> token(HttpSession session) {
    return Collections.singletonMap("token", session.getId());
  }

}

이렇게 UI어플리케이션은 준비가 되었고 모든 백엔드로의 호출에 "X-Auth-Token"이 헤더에 들어간 세션ID를 포함할게 될 것이다.

리소스 서버의 인증 Authentication in the Resource Server

커스텀 헤더를 받아들이려면 리소스 서버에 아주 작은 수정만 해주면 된다. CORS 필터에 원격 클라이언트로부터 허용되는 헤더를 정해줘야한다

예를 들면:

CorsFilter.groovy

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {

  void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    ...
    response.setHeader("Access-Control-Allow-Headers", "x-auth-token, x-requested-with")
    ...
  }

  ...
}

이제 남은 작업은 리소스 서버에서 커스텀 토큰을 가져와 사용자를 인증하는데 이를 사용하는 것이다. 이 작업은 매우 직관적인데, 우리가 해줄 건 단지 어디에 세션 repository가 있는가, 그리고 들어오는 요청의 어디에서서 토큰(세션ID)를 찾을 수 있는가를 스프링 시큐리티에 알려주는 것 뿐이다. 먼저 우리는 스프링 세션과 레디스 의존성을 추가해줘야하고 그 후 Filter를 설정할 수 있다:

ResourceApplication.groovy
@SpringBootApplication
@RestController
@EnableRedisHttpSession
class ResourceApplication {

  ...

  @Bean
  HeaderHttpSessionStrategy sessionStrategy() {
    new HeaderHttpSessionStrategy();
  }

}

이 생성된 필터는 UI서버의 하나를 미러링해서 세션저장소로서 레디스를 설정한다. 유일한 차이점은 기본값("JSESSIONID"라 부르는 쿠키) 대신 이 헤더안에 위치한 커스텀 HttpSessionStrategy를 사용하는 것이다. 우리는 또한 인증되지 않은 사용자에게 보여지는 팝업창을 막아줘야한다. - 어플리케이션은 보호되고 있기때문에 기본값으로 WWW-Authenticate: Basic 를 가진 401를 보내 브라우저가 사용자명과 패스워드를 타입할 수 있는 팝업창으로 응답한다. 여러 가지 방법이 있지만 우리는 이미 앵귤러가 "X-Requested-With" 헤더를 보내도록 만들어뒀기때문에 스프링 시큐리티는 기본적으로 이를 처리해준다.

리소스 서버를 새로운 인증스키마에서 작동하게 만드는 마지막 수정이 남았다. 스프링 부트의 기본 시큐리티는 stateless이고 우리는 이를 세션안에서 인증을 저장하기를 원하므로 application.yml (또는 application.properties)에서 다음과 같이 명시해줘야한다:

application.yml

security:
  sessions: NEVER

이것은 스프링 시큐리티에게 "세션을 절대 생성하지 말되 거기에 이미 있다면 그것을 사용하라"는 뜻이다 (UI에서의 인증때문에 이미 거기에 있을 것이다.)

리소스 서버를 재시작하고 새 브라우저 창에서 UI를 열어보자

왜 쿠키와는 작동하지 않나요? Why Doesn’t it All Work With Cookies?

우리는 커스텀 헤더를 사용하고 헤더를 넣기위해 클라이언트의 코드를 만들었다. 이것은 끔찍하게 복잡하진 않으나 가능한한 쿠키와 세션을 사용하라는 두번째 섹션의 내용과 모순되는 것처럼 보인다. 불필요한 복잡성을 추가하려고 이걸 언급한게 아니다 우리가 지금 가지고 있는 구현체는 이미 지금까지 봐온 최고의 복잡성을 가졌다는 걸 확인하려는거다: 우리 솔루션의 기술적인 부분은 (명확히 아주 작고 사소한) 비지니스 로직보다 훨씬 더 가치가 있다. 이는 명백히 정당한 비판이다. (그리고 우리가 이 시리즈의 다음 섹션에서 언급할 것이다.) 그러나 왜 모든 것에 쿠키와 세션을 사용하는 것만큼 간단하지 않은지 잠시 둘러보자.

적어도 우리는 여전히 세션을 사용한다. 스프링 시큐리티와 서블릿 컨테이너가 우리의 파트에 어떠한 노력없이 어떻게 이것을 하는지 알고 있기 때문이다. 하지만, 우리가 인증토큰을 전송하기위해 쿠키를 사용해야하는 것 아닌가? 멋진 생각이지만 이것이 제대로 동작하지않는 이유가 있다. 그리고 브라우저도 이렇게 쓰는 것을 허용하지 않는다. 당신이 자바스크립트 라이브러리를 써서 브라우저의 쿠키저장소에서 값을 가지고 올 수 있지만 약간의 제약사항이 있다. 당신은 특히 서버에서 (기본적으로 세션 쿠키의 경우로 볼 수 있는) "HttpOnly"로 보내진 쿠키값에 접근할 수 없다는 것이 하나의 좋은 이유다. 또한 발신(outgoing) 요청안에 쿠키를 설정할 수 없기 때문에  우리는 (스프링세션의 기본 쿠키이름인) "세션" 쿠키를 설정할 수 없다. 따라서 커스텀 "X-Session" 헤더를 사용해야했다. 이 둘 다 사용자 스스로의 보호를 위한 제약사항들도 악의적인 스크립트가 올바른 인증없이 당신의 리소스에 접근할 수 없도록 해준다.

UI와 리소스 서버는 공통의 원본origin을 가지지 않는다. 따라서 그들은 (우리가 그들을 스프링세션을 사용하여 세션을 공유하도록 강제함에도 불구하고) 쿠키를 공유할 수 없다.

결 론 Conclusion

우리는 이 시리즈의 두번째 섹션에서 만든 어플리케이션의 기능을 복제하였다: 원격의 백엔드로부터 불러온 greeing을 가지는 홈페이지, 네비게이션바안에 로그인과 로그아웃 링크들. 리소스 서버로 부터 오는 greeing의 차이는 UI서버안에 내장된게 아니라 독립실행상태라는 점이다. 이는 구현에 중대한 복잡성을 추가해야 했지만, 좋은 점은 우리는 대부분 설정기반configuration-based (그리고 실제론 100% 선언적) 솔루션을 가지고 있다는 점이다. 모든 새 코드들을 라이브러리 (스프링 설정과 앵귤러 커스텀 디렉티브안에서 뽑아옴으로서 솔루션을 100% 선언적으로 만들수도 있다. 몇가지 인스톨후로 이 흥미로운 작업을 잠시 뒤로 미룰 것이다. 다음 섹션에서 우리는 현재의 구현의 복잡성을 줄이는 또다른 정말 훌륭한 방법을 살펴볼 것이다: API 게이트웨이 패턴 (클라이언트는 모든 요청을 한군데로 보내고 인증은 그곳에서 처리된다)

 우리는 여기에서 스프링 세션을 논리적으로 같은 어플리케이션이 아닌 두 서버간에 세션을 공유하기 위해 사용하였다. 이는 깔끔한 트릭이다. "정식"의 JEE 배포 세션에서는 가능하지않다.


반응형

반응형

스프링 시큐리티와 앵귤러JS Spring Security and AngularJS


Part I : 단일 페이지 보안 어플리케이션 (A Secure Single Page Application)

Part II: 로그인페이지 (The Login Page)

Part III: 리소스 서버 (The Resource Server)

Part IV: API 게이트웨이 (The API Gateway)

Part V: OAuth2와 싱글사인온 (Single Sign On with OAuth2)

Part VI: 다중 UI 어플리케이션과 게이트웨이 (Multiple UI Applications and a Gateway)

Part VII: 모듈화한 AngularJS 어플리케이션  (Modular AngularJS Application)

Part VIII: Angular 어플리케이션 테스트 (Testing an Angular Application)


--------------------------------------------------------------------------------

로그인 페이지 The Login Page

이 섹션에서 "단일페이지 어플리케이션"을 스프링 시큐리티와 앵귤러JS를 어떻게 사용할지에 대한 얘기를 계속하려고 한다. 이 장에서 어떻게 앵귤러JS가 폼을 통해 유저를 인증하는지 그리고 UI에서 랜더하기위해 보안된 리소스를 가지고 오는지를 보여줄 것이다. 시리즈의 두번째 섹션으로, 당신은 어플리케이션의 기본 구성단위를 배워 나가도 되고, 첫번째 섹션에서부터 하나씩 만들어온 것을 빌드해봐도 되며, 아니면 그냥 Github의 소스코드.로 바로 가봐도 된다. 첫번째 섹션에서 우리는 백엔드 리소스를 보호하기 위해 HTTP Basic 인증을 사용하는 간단한 어플리케이션을 만들었다. 여기에 로그인 폼을 추가하고, 사용자가 인증을 할지 말지 정하할수 있게해주고 첫번째 섹션에서 다른 문제(CSRF 보호가 원칙적으로 결여됨)를 해결할 것이다.

기억하기: 샘플 어플리케이션으로 이 섹션 동안 돌릴 거면, 쿠키와 HTTP Basic credentials의 브라우저 캐시를 꼭 지워야한다. 크롬에서는 새 익명창incognito window로 여는게 최선의 방법이다.

홈페이지에 네이게이션 추가하기 Add Navigation to the Home Page

단일 페이지 어플리케이션의 핵심은 정적인 "index.html" 파일이다. 우리는 이미 정말 간단한 것 가지고 있므로 이제 이 어플리케이션에 약간의 네이게이션 기능(로그인, 로그아웃, 홈으로)을 제공해보자. 이제 ("src/main/resources/static"에서) 수정해보자:

index.html

<!doctype html>
<html>
<head>
<title>Hello AngularJS</title>
<link
	href="css/angular-bootstrap.css"
	rel="stylesheet">
<style type="text/css">
[ng\:cloak], [ng-cloak], .ng-cloak {
	display: none !important;
}
</style>
</head>

<body ng-app="hello" ng-cloak class="ng-cloak">
	<div ng-controller="navigation" class="container">
		<ul class="nav nav-pills" role="tablist">
			<li class="active"><a href="#/">home</a></li>
			<li><a href="#/login">login</a></li>
			<li ng-show="authenticated"><a href="" ng-click="logout()">logout</a></li>
		</ul>
	</div>
	<div ng-view class="container"></div>
	<script src="js/angular-bootstrap.js" type="text/javascript"></script>
	<script src="js/hello.js"></script>
</body>
</html>

사실 원래 소스와 크게 다르지않다. 아주 사소한 기능으로:

  • 네비게이션 바를 위해 <ul> 를 사용했다. 모든 링크는 홈페이지로 바로 되돌아올테지만, 일단 "라우트"설정을 했다면 앵귤러는 경로를 잘 인식할 것이다.

  • 모든 컨탠트는 "ng-view"가 있는 <div>에 부분적으로 추가될것이다.

  • "ng-cloak"은 body로 이동되었다. 우리는 앵귤러가 몇몇의 랜더링을 끝마칠때까지 전체 페이지를 숨길 것이기 때문이다. 그렇지 않으면 메뉴나 컨텐트가 페이지가 로드되는 동안 깜빡깜빡 움직일 수 있다.

  • 첫번째 섹션에서 했던대로, 프론트엔드 에셋인 "angular-bootstrap.css" 와 "angular-bootstrap.js"는 빌드시 JAR 라이브러리로부터 만들어질 것이다.


앵귤러 어플리케이션에 네비게이션 추가하기 Add Navigation to the Angular Application

이제 ("src/main/resources/public/js/hello.js" 경로에 있는) "hello" 어플리케이션을 수정하여 네비게이션 기능을 추가해보자. 일단  홈페이지의 링크를 실제로 작동하게 하는 등의 라우트를 위한 추가설정부터 하자. 

hello.js

angular.module('hello', [ 'ngRoute' ])
  .config(function($routeProvider, $httpProvider) {

	$routeProvider.when('/', {
		templateUrl : 'home.html',
		controller : 'home'
	}).when('/login', {
		templateUrl : 'login.html',
		controller : 'navigation'
	}).otherwise('/');

    $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';

  })
  .controller('home', function($scope, $http) {
    $http.get('/resource/').success(function(data) {
      $scope.greeting = data;
    })
  })
  .controller('navigation', function() {});

우리는 "ngRoute" 라 불리는 앵귤러 모듈의 의존성을 추가했다. 이는 마법과 같은 $routeProvider를 설정함수에 주입할수 있게 해준다 (앵귤러는 명명규칙naming convention에 의해 의존성 주입을 하고 당신의 함수 파라미터의 이름을 인식한다). $routeProvider는 링크들을 "/" ("home" 컨트롤러)과 "/login" ("login" 컨트롤러)에 설정하는 함수의 내부에서 사용된다. "templateUrls"은 ("/"와 같은) 라우트의 루트부터의 각각의 컨트롤러에 의해 만들어지는 모델의 랜더에 사용되는 "부분적partial" 뷰들에의 상대경로relative paths이다.

커스텀 "X-Requested-With" 는 브라우저 클라이언트가 보내는 관습적인 헤더이다. 이는 앵귤러에서 기본으로 사용되었지만 1.3.0에서 떼어냈다. 스프링 시큐리티는 401 응답에 "WWW-Authenticate" 헤더를 보내지 않음으로서 응답한다. 그러므로 브라우저는 (우리는 인증하기 원하므로 우리의 앱에 바람직한) 인증창을 띄우지 않을 것이다.

"ngRoute" 모듈을 사용하기위해선, 우리는 ("src/main/wro" 경로의) 정적 에셋을 빌드하는 "wro.xml" 설정하는 라인을 추가해줘야한다:

wro.xml
<groups xmlns="http://www.isdc.ro/wro">
  <group name="angular-bootstrap">
    ...
    <js>webjar:angularjs/1.3.8/angular-route.min.js</js>
   </group>
</groups>


환영메세지 The Greeting

("src/main/resources/static"에 있는 "index.html" 바로 옆에 있는) "home.html"에 위치한 예전의 홈페이지로부터의 환영메세지 컨탠트:

home.html
<h1>Greeting</h1>
<div ng-show="authenticated">
	<p>The ID is {{greeting.id}}</p>
	<p>The content is {{greeting.content}}</p>
</div>
<div  ng-show="!authenticated">
	<p>Login to see your greeting</p>
</div>

이제 사용자가 로그인을 할지 안할지 선택할 수 있으므로 (브라우저에 의해 제어되기 전에), 우리는 보호되야하는 것과 안해도 되는 컨탠트 사이에 UI를 구별해줘야한다. (아직 없지만) authenticated변수의 참조를 추가함으로서 실현할 수 있을것이다.


로그인 폼 The Login Form

"login.html"에 있는 로그인 폼:

login.html

<div class="alert alert-danger" ng-show="error">
	There was a problem logging in. Please try again.
</div>
<form role="form" ng-submit="login()">
	<div class="form-group">
		<label for="username">Username:</label> <input type="text"
			class="form-control" id="username" name="username" ng-model="credentials.username"/>
	</div>
	<div class="form-group">
		<label for="password">Password:</label> <input type="password"
			class="form-control" id="password" name="password" ng-model="credentials.password"/>
	</div>
	<button type="submit" class="btn btn-primary">Submit</button>
</form>

위의 코드는 사용자명과 패스워드를 입력하는 두개의 입력폼이 있고 ng-submit을 통해 제출할 수 있는 버튼이 있는 아주 표준화된 로그인폼이다. 사용자는 폼태그에 아무것도 할 필요가 없다. 아마 아무것도 입력하지 않는 편에 더 낫을 것이다. 또한 앵귤러의 $scopeerror값을 가지고 있을 때만 보이는 에러메시지가 있다. 폼 제어는 앵귤러와 HTML 컨트롤러간에 데이터를 보내기 위해 ng-model을 사용한다. 이 경우 우리는 credentials 객체에 사용자명과 패스워드를 보관할 것이다. 라우트에 의해, 우리는 아직 비어있는 "navigation" 컨트롤러를 통해 로그인폼을 링크하도록 정의했다. 이제 하나씩 채워보도록 하자.


인증 프로세스 The Authentication Process

우리가 추가한 로그인폼을 작동시키려면 약간의 기능을 더 추가해줘야 한다. 클라이언트 쪽에선 "navigation" 컨트롤러를 구현할 것이다. 서버쪽에선 스프링 시큐리티 설정을 할 것이다.


로그인 폼 제출하기 Submitting the Login Form

폼을 제출하려면, 우리가 이미 ng-submit를 통해 폼안에 이미 참조해둔 login() 함수를 정의해줘야한다. credentials객체는 ng-model을 통해 참조될 것이다. 이제 "hello.js"안에 위치한 "navigation" 컨트롤러에 살을 붙여보자 (라우트 설정과 "home" 컨트롤러는 생각한다):

hello.js
angular.module('hello', [ 'ngRoute' ]) // ... omitted code
.controller('navigation',

  function($rootScope, $scope, $http, $location) {

  var authenticate = function(credentials, callback) {

    var headers = credentials ? {authorization : "Basic "
        + btoa(credentials.username + ":" + credentials.password)
    } : {};

    $http.get('user', {headers : headers}).success(function(data) {
      if (data.name) {
        $rootScope.authenticated = true;
      } else {
        $rootScope.authenticated = false;
      }
      callback && callback();
    }).error(function() {
      $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;
        }
      });
  };
});

메뉴바에 있는 <div>는 visible과 ng-controller="navigation"로 코드되어 있으므로, 페이지가 로드될때 "navigation" 컨트롤러내의 모든 소스코드가 실행될 것이다. 이 컨트롤러는 credentials객체를 초기화하는 것 뿐만 아니라 두개의 함수를 정의하고 있다. login()함수는 폼에서 필요하고,  authenticate()는 로컬 헬퍼local helper 함수로서 백엔드에서 "user" 리소스를 불러오는데 쓰인다. authenticate()함수는 (이를테면, 만일 사용자가 세션을 설정하는 중간에 브라우저를 새로고치는 등으로) 컨트롤러가 로드될때 유저가 이미 인증을 받았는지를 확인할때 호출된다. 서버에서 이미 인증을 받았을 때 원격호출을 하기위해 authenticate()가 필요하다 그리고 우리는 이 인증을 유지하는 것을 브라우저에 맡기길 원치않는다.

authenticate()함수는 authenticated이라 부르는 어플리케이션 차원의 플래그로서 설정되는데 이는 우리가 "home.html"에서 페이지의 어느 부분을 렌더할지 제어하기 위해 이미 사용했었다. "navigation"과 "home" 컨트롤러간에 authenticated 플래그를 공유하는 목적으로 $rootScope를 사용하는 데 이는 편안하고 쉽게 따라할 수 있기 때문이다. 앵귤러 전문가라면 아마 공유되는 사용자 정의 서비스를 통해 데이터를 공유하는 편을 선호할 것이다 (하지만 이것도 결국 따지고보면 같은 메카니즘이다)

authenticate()는 (당신의 어플리케이션의 배포루트에 상대적인) 상대적 리소스 "/user" 에 GET 호출을 한다.  login() 함수가 호출될때 Base64 인코딩된 credential을 헤더에 추가한다. 서버에서 이걸로 인증을하고 돌아오는 쿠키를 되돌려준다. login() 함수 역시 인증의 결과를 받아올때 로그인폼에 에러메세지를 표시하는데 쓰이는 로컬 $scope.error 플래그를 설정한다.


현재 인증된 사용자 The Currently Authenticated User

authenticate() 함수를 서비스하려면 우리는 백엔드에 새로운 종단endpoint을 추가해야한다:

UiApplication.java
@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/user")
  public Principal user(Principal user) {
    return user;
  }

  ...

}

스프링 시큐리티 어플리케이션에서 이렇게 설정하는 것은 유용한 전략이다. "/user" 리소스에 접근할 권한이 있다면, 현재 인증된 유저(Authentication)를 되돌려줄것이다. 그렇지 않다면 스프링 시큐리티는 요청을 가로챈후 AuthenticationEntryPoint를 거쳐 401 응답을 보낸다.

서버에서 로그인 요청을 처리하기 Handling the Login Request on the Server

스프링 시큐리티로 로그인 요청을 쉽게 처리할 수 있다. 그냥 우리의 메인 어플리케이션 클래스 (inner클래스로서)에 약간의 설정만 추가해주면 된다:

UiApplication.java

@SpringBootApplication
@RestController
public class UiApplication {

  ...

  @Configuration
  @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
  protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http
        .httpBasic()
      .and()
        .authorizeRequests()
          .antMatchers("/index.html", "/home.html", "/login.html", "/").permitAll()
          .anyRequest().authenticated();
    }
  }

}

위의 코드는 단지 정적인 리소스(HTML)에의 익명의 접근을 허용하도록 커스터마이즈한 (CSS와 JS리소스는 이미 기본값으로 접근가능하다) 스프링 시큐리티를 가지고 표준화된 스프링 어플리케이션이다, HTML리소스는 스프링 시큐리티가 무시하도록 설정함으로서 익명의 사용자도 이용가능해야한다.


로그아웃 Logout

어플리케이션이 기능적으로 거의 완성되었다 마지막으로 우리는 홈페이지에서 밑그림을 그려둔 로그아웃 기능을 구현하려고한다. 다음과 같이 만들어둔 네이게이션 바를 상기해보자:

index.html

<div ng-controller="navigation" class="container">
  <ul class="nav nav-pills" role="tablist">
    <li class="active"><a href="#/">home</a></li>
    <li><a href="#/login">login</a></li>
    <li ng-show="authenticated"><a href="" ng-click="logout()">logout</a></li>
  </ul>
</div>

사용자가 인증을 받은 상태면, 로그아웃 링크를 볼 수 있다. 이는 "navigation" 컨트롤러안의 logout()함수와 묶여있다. 이 함수의 구현은 상대적으로 간단하다:

hello.js
angular.module('hello', [ 'ngRoute' ]).
// ...
.controller('navigation', function(...) {

...

$scope.logout = function() {
  $http.post('logout', {}).success(function() {
    $rootScope.authenticated = false;
    $location.path("/");
  }).error(function(data) {
    $rootScope.authenticated = false;
  });
}

...

});

이제 우리가 서버쪽을 구현해야할 "/logout"으로 HTTP POST를 보낸다.  스프링 시큐리티에 의해 이미 추가되었으므로 매우 직관적이다.(한마디로, 우리는 이 간단한 시나리오에 아무것도 할 필요없다.). 하지만 로그아웃 이후의 비지니스 로직을 돌리려고 하는 등으로 로그아웃의 동작을 좀 더 제어해야한다면 WebSecurityAdapter에 있는 HttpSecurity 콜백을 사용할 수 있다. 


CSRF 보안 CSRF Protection

이제 어플리케이션이 거의 준비되었다. 사실 실행해보면 우리가 로그아웃 링크를 제외한 지금까지 실제로 작업한 모든 것을 확인해볼 수 있다. 한번 테스트하고 브라우저의 응답을 둘러보면 왜 그런지 알 수 있다:

POST /logout HTTP/1.1
...
Content-Type: application/x-www-form-urlencoded

username=user&password=password

HTTP/1.1 403 Forbidden
Set-Cookie: JSESSIONID=3941352C51ABB941781E1DF312DA474E; Path=/; HttpOnly
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
...

{"timestamp":1420467113764,"status":403,"error":"Forbidden","message":"Expected CSRF token not found. Has your session expired?","path":"/login"}

보다시피 스프링 시큐리티의 내장 CSRF protection가 우리 스스로의 요청도 막아버리고 있으므로 잘 동작한다고 할 수 있다. CSRF protection이 원하는건 헤더에 "X-CSRF"라는 토큰이 들어있는 것이다. CSRF 토큰의 값은 홈페이지가 불러지는 첫번째 요청의 HttpRequest속성값을 가지고 서버에서 이용한다. 클라이언트가 이 값을 받으려면, 서버에서 동적 HTML 를 사용해서 랜더하거나 커스텀 종단endpoint를 통해 노출시키거나 또는 우리가 쿠키로서 보내거나 해야한다. 앵귤러가 쿠키에 기반한 ("XSRF"라 부르는) 내장된 CSRF지원을 가지고 있기 때문에 마지막 옵션이 최고의 선택이 될것이다.

서버에서는 쿠키를 보내는 커스텀 필터가 필요하다. 앵귤러는 쿠키이름을 "XSRF-TOKEN"라고 받길 원하므로 스프링 시큐리티는 요청 속성값으로 제공해줘야 한다. 요청속성값에 쿠키를 다음과 같이 전송하면 된다:

CsrfHeaderFilter.java

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);
      }
    }
    filterChain.doFilter(request, response);
  }
}

이것을 완전하게 제네릭하게 만들어 완성하려면,  ("/"를 하드코드하는 대신) 어플리케이션의 컨텍스트 경로에 쿠키 경로를 신중하게 설정해야한다. 하지만 우리가 만드는 이 어플리케이션수준에선 충분하다.

어플리케이션의 어딘가에 이 필터를 설치해야하는데 이 요청의 속성값을 사용하려면 스프링 시큐리티의 CsrfFilter이후에 불려져야한다. 이들 리소스를 보호하기 위해 스프링 시큐리티가 이미 사용중이므로 스프링 시큐리티의 Spring Security filter chain이 최적의 장소이다. 예를 들면, 위의 SecurityConfiguration를 다음과 같이 확장해보자:

SecurityConfiguration.java

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .httpBasic().and()
      .authorizeRequests()
        .antMatchers("/index.html", "/home.html", "/login.html", "/").permitAll().anyRequest()
        .authenticated().and()
      .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
  }
}

서버단에서 해줘야하는 또 다른 작업은 스프링 시큐리티에 앵귤러가 받기를 원하는 포맷 (앵귤러는 스프링의 기본값인 "X-CSRF-TOKEN" 대신 "X-XRSF-TOKEN" 이름을 헤더에 쓴다)의 CSRF토큰을 알려줘야한다.

SecurityConfiguration.java

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
    .httpBasic().and()
    ...
    .csrf().csrfTokenRepository(csrfTokenRepository());
}

private CsrfTokenRepository csrfTokenRepository() {
  HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
  repository.setHeaderName("X-XSRF-TOKEN");
  return repository;
}

위의 수정으로 이제 클라이언트 쪽의 아무런 수정없이 로그인폼이 잘 작동한다.


어떻게 작동하나? How Does it Work?

개발자 툴 (크롬에선 기본값으로 F12를 누르면 된다. 파이이폭스는 플러그인을 설치해야한다)을 통해, 브라우저와 백엔드간의 연동을 브라우저에서 확인할 수 있다. 아래 정리해두었다:

VerbPathStatusResponse

GET

/

200

index.html

GET

/css/angular-bootstrap.css

200

트위터 부트스트랩 CSS

GET

/js/angular-bootstrap.js

200

부트스트랩과 앵귤러JS

GET

/js/hello.js

200

어플리케이션 로직

GET

/user

401

Unauthorized

GET

/home.html

200

홈페이지

GET

/resource

401

Unauthorized

GET

/login.html

200

앵귤러 로그인 폼 일부

GET

/user

401

Unauthorized

GET

/user

200

인증credentials보내고 JSON받기

GET

/resource

200

JSON greeting

위에 "ignore"로 표시된 응답은 앵귤러가 XHR호출로 받은 HTML응답이다. 우리는 HTML로 떨어진 데이터를 처리하지 않으므로 "/user"리소스의 경우 인증된 유저만 볼 수 있다. 하지만 첫번째 호출에는 없기때문에 응답이 받아진다.

요청을 더 자세히 들여다보면 그들이 모두 쿠키값을 가지고 있는 것을 확인할 수 있을 것이다. (크롬의 incognito와 같은) 클린 브라우저로 시작을 하면, 맨 처음 요청에는 서버에서 보내진 쿠키가 없지만 서버는 JSESSIONID" (일반적으로HttpSession)와 X-XSRF-TOKEN" (우리가 위에서 설정한 CRSF 쿠키)를 위해 "Set-Cookie"를 돌려준다. 이 값들은 매우 중요한데 어플리케이션은 이들없이 동작하지않는다. 이 값들은 매우 기본적인 보안기능(인증과 CSRF보호)을 제공해주고 있다. 쿠키의 값은 유저가 (POST 호출로) 인증받으면 바뀐다. 이는 (session fixation attacks을 방지하는) 또 하나의 중요한 보안기능이다.

CSRF보호를 위해 서버에서 받은 쿠키에 의존하는 것은 적절하지 않다. 브라우저는 당신이 어플리케이션에서 불러진 페이지에 있지않을 때도 자동적으로 이것을 보내기 때문이다.(XSS라고 알려진 Cross Site Scripting Attack에 의해). 헤더는 자동적으로 보내지지 않으므로 원본은 지켜진다. 사용자는 어플리케이션에서 CSRF토큰이 클라이언트에 쿠키값으로 보내지는 것을 확인할 수 있다. 따라서 우리는 브라우저에 의해 자동적으로 되돌려질 것이다. 그러나 이것은 보호되고 있는 헤더이다.

도와줘,, 내 어플리케이션을 어떻게 스케일하지?

"그런데 잠시만…싱글페이지 어플리케이션에서 세션 상태을 사용하는건 안좋은거 아닌가요?" 라고 물어본다면, 대답은 "보통은 그렇다" 이다. CSRF보안과 인증에 세션상태을 사용하는 것은 확실히 매우 좋다. 이 상태는 어딘가에 저장되어야한다 그리고 사용자가 세션을 떼어내면 어딘가에 넣어두고 그것을 사용자 스스로 관리해줘야할 것이다. 서버와 클라이언트 양쪽에서 이것은 더 많은 코드와 유지보스를 필요로 하며 이것은 완전히 불필요한 일을 하는 것이다.

"하지만,, 하지만… 그럼 이제 어떻게하면 내 어플리케이션의 규모를 크게 스케일할 수 있죠?". 이 "진짜" 질문은 당신이 위에서 했던 것이지만, 세션은 나쁘다 -> 나는 상태없는stateless 상태로 가야한다고 생각한다로 수렴되는 경향이 있다. 놀랄 것없다. 여기서 받아들여지는 중요한 점은 보안은 stateful이라는 점이다. 당신은 안전한 stateless한 어플리케이션을 가질 수 없다. 그럼 어디에 state를 저장할 것인가? 이게 여기서 얘기할 수 있는 전부다. Rob Winch 는 Spring Exchange 2014 에서 state가 왜 필요한지 (또 이는 어디에서나 쓰인다. - TCP와 SSL은 statfule이고 따라서 당신이 알았던 몰랐던 당신의 시스템도 stateful 이다) 설명한 매우 유용하고 통찰력있는 (이 주제에 대해 더 깊이 파고 들고 싶어하다고 느낄만한) 얘기를 했었다

좋은 소식은 당신은 선택할 수 있다는 것이다. 가장 쉬운 선택은 세션데이터를 메모리(in-memory)에 저장하고 당신의 로드밸러서에 세션를 전적으로 맡겨서 같은 JVM에서 같은 세션을 돌려주도록 요청을 라우트하는 것이다.  이 방법은 당신이 맨땅에서 시작하기에 충분히 좋을 뿐만아니라 실제 수많은 사용예에서 잘 작동할 것이다. 또 다른 선택은 어플리케이션 인스턴스간에 세션 데이터를 공유하는 것이다. 사용자가 엄격히 보안데이터만을 저장하고 이 데이터가 작으며 드물게 바뀌는 것이라면 (유저가 로그인, 로그아웃하거나 사용자 세션이 타임아웃됬을때만) 그래서 주요 기반(infrastructure)의 문제가 없다면, 스프링 세션으로 아주 쉽게 해결할 수 있다. 우리는 이 시리즈의 다음 섹션에서 스프링 세션을 사용할 것이므로 여기서 어떻게 설정하는 지 등에 대해 자세하게 들어갈 필요는 없다. 하지만 말그대로 코드 몇줄과 레디스 서버면 된다.

 또다른 손쉬운 방법은 세션 상태를 공유하도록 설정하고 클라우드 파운더리의 Pivotal Web Services에 WAR파일로 어플리케이션을 배포한뒤 레디스 서비스에 바인드하는 것이다.

하지만 내 커스텀 토큰 구현은 어떻게 되지? (이건 Stateless인데?)

이게 이 마지막 섹션에의 당신의 질문이라면, 아마도 당신은 처음부터 알아듣지 못한 것같으니 위의 글을 다시 읽어보자. 당신이 어딘가에 토큰을 저장한다면 그것은 stateless가 아닐 것이다. 그러나 당신이 (JWT 인코드된 토큰을 사용함으로서) 따로 저장하지않는다면, 어떻게 CSRF보호를 할 것인가? 이것은 중요한 문제다. 여기 (Rob Winch가 결론지은) 경험의 법칙이 있다: 당신의 어플리케이션이나 API를 브라우저로 접근한다면, 당신은 CSRF 보호가 필요하다. 이것은 세션이 없다고 못하는 것은 아니다. 그냥 당신 스스로 코드를 만들면된다. 요점은 이미 HttpSession 기반으로 아주 완벽히 동작하게 구현되어있는게 있다는 것이다. 당신이 CSRF가 필요없고 완벽히 (세션을 사용하지않는) "stateless" 토큰 구현체를 쓰도록 결심했다 하더라도, 당신은 여전히 클라이언트에 이를 (당신이 브라우저에 위임하도록 한 것) 사용하도록 그리고 서버의 스스로 내장된 기능(기본값을 끄지않으면 브라우저는 항상 쿠키를 보내고 서버는 항상 세션을 가진다)을 가지도록 추가의 코드를 만들어야만 한다. 그 코드는 비지니스 로직도 아니고 그걸로 돈을 벌수 있는 것도 아니다. 그럴만한 가치가 있다하더라도 추가적인 비용이 들어가는 그냥 간접비용일 뿐이다. 

결 론 Conclusion

어플리케이션은 이제 사용자가 기대하는 "실제" 환경에서 돌아가는 "실전" 어플리케이션에 더 가까워졌다. 이 아키텍쳐을 기반으로 더 풍부한 기능을 더할 수 있는 템플릿으로서 사용할 수 있을 것이다. (정적 컨텐트와 JSON응답을 가지는 단일 서버). 우리는 보안데이터를 저장하기위해 클라이언트에 우리가 보낸 쿠키를 사용하도록 하여 HttpSession를 사용하고 있다. 우리는 이제 아주 편안하다 왜냐하면 우리 스스로의 비지니스 도메인에 집중하면 되기 때문이다. 다음 섹션에서 우리는 아키텍쳐를 확장하여 인증과 UI서버를 분리할 것이다. 또 JSON을 위해 독립 리소스 서버를 갖출 것이다. 이 다중 리소스 서버는 손쉽게 만들 수 있다. 또한 스프링 세션을 소개하여 어떻게 인증데이터를 공유하는데 활용하는지 보여줄 것이다.


반응형

반응형

스프링 시큐리티와 앵귤러JS - Spring Security and AngularJS


Part I : 단일 페이지 보안 어플리케이션 (A Secure Single Page Application)

Part II: 로그인페이지 (The Login Page)

Part III: 리소스 서버 (The Resource Server)

Part IV: API 게이트웨이 (The API Gateway)

Part V: OAuth2와 싱글사인온 (Single Sign On with OAuth2)

Part VI: 다중 UI 어플리케이션과 게이트웨이 (Multiple UI Applications and a Gateway)

Part VII: 모듈화한 AngularJS 어플리케이션  (Modular AngularJS Application)

Part VIII: Angular 어플리케이션 테스트 (Testing an Angular Application)


--------------------------------------------------------------------------------

단일페이지 보안어플리케이션 A Secure Single Page Application

이 섹션에서 스프링 시큐리티, 스프링 부트와 앵귤러JS의 멋진 기능을 같이 조합하여 쾌적하고 시큐어한 사용자 경험을 제공하려고 한다. 스프링과 앵귤러JS를 막 시작하려는 사람도 충분히 따라할수 있는 수준이지만, 많은 디테일한 정보를 제공하여 경력자들 또한 이롭게 할 것이다. 이 글은 스프링 시큐리티와 앵귤러JS의 새 기능을 순차적으로 연재하는 시리즈의 첫번째 섹션이다. 연재가 되면서 어플리케이션을 차츰 더 낫게 손보겠지만 이후의 주요 변화는 기능적인 것보다는 아키텍쳐 관점이 될 것이다.


스프링과 단일페이지 어플리케이션 Spring and the Single Page Application

HTML5과 "단일페이지어플리케이션"은 요즘 개발자들에게 극도의 가치가 있는 툴이지만 정적인 컨텐츠(HTML,CSS와 자바스크립트)뿐만 아니라 의미가 되는 상호작용은 백엔드서버에 들어있으므로 우리는 백엔드 서버가 필요하다. 백엔드서버는 모든 역할의 갯수 - 정적인 컨텐츠를 제공하고, 가끔 (요즘엔 많지않지만) 동적인 HTML를 랜더링하며, 보호하는 자원의 안전한 접근을 보장하며, 자바스크립트와 브라우저내에서 HTTP와 JSON을 통해 상호작동 (REST API라 언급되기도함)을 하는 - 만큼의 역할을 할 수 있다.

스프링은 백엔드 기능(특히 엔터프라이즈 환경에서)을 만드는데 있어 언제나 인기있는 기술이었고,  스프링 부트의 출현과 함께 이러한 기능을 제공하기에 이보다 더 쉬울 순 없게 되었다. 오직 스프링부트와 앵귤러JS 그리고 트위터 부트스트랩만 사용해서 어떻게 새 단일페이지 어플리케이션을 살펴보도록 하자. 이 기술세트를 선택하게 된 특별한 이유가 있는건 아니고 그냥 엔터프라이즈 자바 바닥에서 아주 인기가 많기 때문에, 이렇게 시작할 충분한 가치가 있기 때문이다. 


새 프로젝트 만들기 Create a New Project

이 어플리케이션을 만들기위한 단계마다 스프링과 앵귤러에 익숙하지않은 사람들이 어떻게 되어가는지 따라올 수 있게 약간의 부연설명을 덧붙일 것이다. 부연설명이 필요없는 사람들은 이 단락을 넘기고 어플리케이션을 동작하는 마지막 섹션으로 바로 가도 된다. 새 프로젝트를 생성할 수 있는 몇가지 옵션들이 있다:


우리가 빌드할 완성된 프로젝트의 소스코드는 여기 Github에서 볼 수 있다. 필요하다면 프로젝트를 클론하여 돌려보고 바로 다음 단계로 건너뛰어도 된다.


Spring Tool Suite 이용하기

Spring Tool Suite (이클립스 플러그인의 한종류)의 메뉴 File->New->Spring Starter Project에서 프로젝트를 만들거나 불러올 수 있다.


홈페이지 추가하기 Add a Home Page

단일페이지 어플리케이션의 핵심은 하나의 정적인 "index.html"이다. 바로 프로젝트의 "src/main/resources/static" 또는 "src/main/resources/public"의 경로에 다음과 같이 하나 만들어보자:

index.html
<!doctype html>
<html>
<head>
<title>Hello AngularJS</title>
<link href="css/angular-bootstrap.css" rel="stylesheet">
<style type="text/css">
[ng\:cloak], [ng-cloak], .ng-cloak {
  display: none !important;
}
</style>
</head>

<body ng-app="hello">
  <div class="container">
    <h1>Greeting</h1>
    <div ng-controller="home" ng-cloak class="ng-cloak">
      <p>The ID is {{greeting.id}}</p>
      <p>The content is {{greeting.content}}</p>
    </div>
  </div>
  <script src="js/angular-bootstrap.js" type="text/javascript"></script>
  <script src="js/hello.js"></script>
</body>
</html>

단지 "Hello World"를 출력하는 코드이므로 매우 간결하고 쉽다.


홈페이지의 특징 Features of the Home Page

이 홈페이지의 가장 주요한 기능은 다음과 같다:

  • <head>에서 CSS를 불러온다. 하나의 엘리먼트에는 실제로 아직 존재하지는 않지만 ("angular-bootstrap.css")와 같이 임시적으로 파일 이름을 채우며, 인라인 스타일시트가 "ng-cloak" 클래스를 정의 하고 있다.

  • "ng-cloak" 클래스는 <div> 컨탠트에 적용되어 해당 동적 컨텐트를 앵귤러JS가 처리할때까지 보지않게(hidden) 한다 - 초기 페이지 로딩중에 페이지가 깜빡거리는 현상을 막기 위해서다.

  • <body>에 마크된 ng-app="hello"는 앵귤러가 "hello"라 불리는 어플리케이션을 인식할 수 있게 만들어주는 하는 자바스크립트 모듈을 정의해야 한다는 의미다.

  • "ng-cloak"을 제외한 모든 CSS 클래스는 트위터 부트스트랩에서 불러온다. 스타일시트를 올바르게 설정해두면 페이지를 이쁘게 만들어줄것이다.

  • greeting안의 컨탠트는 {{greeting.content}}와 같이 핸들바를 이용하여 마크업되었다. 이것은 후에 앵귤러에 의해 실제 값으로 채워질 것이다. (<div>)로 둘러쌓인 ng-controller directive에 의해 "home" 이라 불리는 controller에 의해). 

  • 앵귤러JS (와 트위터 부트스트랩)은 <body>의 하단에 포함되어 브라우저가 모든 HTML을 처리한 이후 읽혀질것이다.

  • 또한 어플리케이션의 행동을 정의해둘 별도의 "hello.js"이 있다. 

곧 스크립트와 스타일시트 파일들을 만들테니 지금은 그들이 없는 파일이라는걸 무시하기로 하자.


어플리케이션 동작하기 Running the Application

일단 홈페이지 파일이 추가되면 (아직 많은 것 하지않았음에도 불구하고) 어플리케이션을 브라우저에서 실행할 수 있다. 커맨드라인상에 다음과 같이 타입해보자:

$ mvn spring-boot:run

그리고 브라우저를 열고 주소창에 http://localhost:8080를 타입한다. 홈페이지가 실행되면 브라우저 팝업창이 뜨며 아이디와 패스워드를 물어볼것이다. (아이디는 "user"이고 패스워드는 어플리케이션 시작시 콘솔로그에 출력된다). 아직 컨탠트가 없지만 인증에 성공하면 "greeting"헤더를 가지는 빈페이지를 볼 수 있을 것이다.

 패스워드 확인을 위해 콘솔창을 찾는게 싫다면 프로젝트의 "src/main/resources"의 위치에 application.properties 파일을 만들어 다음과 같이 추가하면된다: security.user.password=password (패스워드는 원하는대로 변경가능하다). "application.yml"파일을 만들어 넣어도 똑같이 동작한다.

IDE에선 그냥 어클리케이션 클래스의 main() 메소드를 실행하면 된다. 지금은 UiApplication이라는 하나의 클래스만 있다.

패키지 빌드하여 standalone JAR을 만들어 다음과 같이 실행해도 된다:

$ mvn package
$ java -jar target/*.jar

프론트엔드 에셋 Front End Assets

앵귤러JS나 다른 프론트엔드 기술의 초급 튜토리얼들은 주로 라이브러리 에셋을 직접 라이브러리와 연관된 여러개의 파일들을 다운받아 리소스 경로에 직접 넣는 것 대신 인터텟을 통해 바로 가져오라고 가르친다. (이를테면 앵귤러 JS 웹사이트에서는 Google CDN를 통해 다운받는것을 추천한다). 이것은 어플리케이션을 동작하는데 반드시 따라야하는 것은 아니지만 실제 제품화 단계에서 브라우저와 서버같의 불필요한 통신을 피할수 있는 최고의 실전 팁이다. 우리가 CSS 스타일시트를 변경하거나 커스터마이즈하지 않는다면 "angular-bootstrap.css"파일을 프로젝트에 만들어두는 것도 불필요하므로 Google CDN를 통해 정적인 에셋을 사용하면 된다. 하지만 실제 어플리케이션은 거의 대부분 스타일시트를 수정하면서도 CSS소스 자체에는 손대고 싶지않기 때문에 Less 나 Sass와 같은 상위레벨의 툴을 사용한다. 따라서 우리 또한 그렇게 해볼 것이다.

이것을 위한 많은 방법들이 있지만 이 섹션의 목적을 위해 우리는 자바기반의 프론트엔드 에센의 선처리 및 패키징 툴체인인 wro4j을 사용할 것이다. 그냥  서블릿 어플리케이션에서 JIT (Just in Time) Filter로서 사용할 수 있지만 메이븐이나 이클립스와 같은 빌드툴을 또한 매우 잘 지원한다. 그리고 우리가 이것을 쓰려는 이유다. 따라서 우리는 정적 리소스 파일들을 빌드하여 번들화시켜 우리의 어플리케이션 JAR에 넣을 것이다.

여담: Wro4j 는 아마도 하드코어 프론트엔드 개발자가 선택할 만한 툴은 아닐것이다. 그들은 통상 bower 와/또는 grunt와 같은 노드기반의 툴체인을 쓴다. 그러한 툴은 명백하게 아주 훌륭하고 인터넷상에 수많은 튜토리얼을 구할수 있다. 당신이 그것을 선호한다면 이 프로젝트내에서 자유롭게 쓰길 바란다. 그냥 프로젝트의 "src/main/resources/static" 경로에 그 툴체인으로 만들어진 결과파일/폴더를 넣으면 바로 잘 동작할것이다. 당신이 하드코어 프론트엔드 개발자가 아니라면 이 자바기반의 툴인 wro4j를 나름 편하게 활용할 수 있을 것이다.

빌드타임에 정적 리소스를 만드려면 메이븐 pom.xml에 약간의 마법을 뿌려야한다. (좀 장황하지만 boilerplate로서 메이븐의 parent pom안에서 또는 그래들의 shared task나 plugin으로 추출된다):

pom.xml

<build>
  <resources>
    <resource>
      <directory>${project.basedir}/src/main/resources</directory>
    </resource>
    <resource>
      <directory>${project.build.directory}/generated-resources</directory>
    </resource>
  </resources>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    <plugin>
      <artifactId>maven-resources-plugin</artifactId>
      <executions>
        <execution>
          <!-- Serves *only* to filter the wro.xml so it can get an absolute
            path for the project -->
          <id>copy-resources</id>
          <phase>validate</phase>
          <goals>
            <goal>copy-resources</goal>
          </goals>
          <configuration>
            <outputDirectory>${basedir}/target/wro</outputDirectory>
            <resources>
              <resource>
                <directory>src/main/wro</directory>
                <filtering>true</filtering>
              </resource>
            </resources>
          </configuration>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>ro.isdc.wro4j</groupId>
      <artifactId>wro4j-maven-plugin</artifactId>
      <version>1.7.6</version>
      <executions>
        <execution>
          <phase>generate-resources</phase>
          <goals>
            <goal>run</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <wroManagerFactory>ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory</wroManagerFactory>
        <cssDestinationFolder>${project.build.directory}/generated-resources/static/css</cssDestinationFolder>
        <jsDestinationFolder>${project.build.directory}/generated-resources/static/js</jsDestinationFolder>
        <wroFile>${project.build.directory}/wro/wro.xml</wroFile>
        <extraConfigFile>${basedir}/src/main/wro/wro.properties</extraConfigFile>
        <contextFolder>${basedir}/src/main/wro</contextFolder>
      </configuration>
      <dependencies>
        <dependency>
          <groupId>org.webjars</groupId>
          <artifactId>jquery</artifactId>
          <version>2.1.1</version>
        </dependency>
        <dependency>
          <groupId>org.webjars</groupId>
          <artifactId>angularjs</artifactId>
          <version>1.3.8</version>
        </dependency>
        <dependency>
          <groupId>org.webjars</groupId>
          <artifactId>bootstrap</artifactId>
          <version>3.2.0</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>

위의 소스 그대로 당신의 POM에 붙여넣거나 Github에 있는 소스를 가져다 쓰면된다.

주요 사항은:

  • 우리는 (CSS와 스타일링을 위한 jquery와 부트스트랩, 그리고 비지니스 로직을 위한 앵귤러JS의) 의존성으로서 몇몇 webjars 라이브러리를 추가했다. 이러한 jar 파일안의 정적 리소스들의 일부는 우리가 만든 angular-bootstarp.* 같은 파일들을 포함할 것이다. 그러나 jar 자체는 어플리케이션에 같이 패키지 될 필요없다.

  • 트위터 부트스트랩은 JQuery에 의존성을 가지므로 같이 넣을 것이다. 부트스트랩을 쓸 필요없는 앵귤러JS 어플리케이션은 이것이 필요없다. 왜냐하면 앵귤러는 자체적으로 JQuery로 쓰려는 기능을 가지고 있기때문이다.

  • <resources/> 섹션에서 선언되었기 때문에 생성된 리소스들은 "target/generated-resources"폴더에 위치되며, 프로젝트를 빌드한 JAR안에 패키지될 것이다. (이클립스 m2e같은 메이븐 툴링을 쓴다는 가정하에) IDE에서는 클래스패스를 통해 쓸 수 있다.

  • wro4j-maven-plugin은 약간 이플립스 통합기능을 가지고 있어 이클립스 마켓플레이스에서 인스톨할 수 있다. (어플리케이션이 완성되면 필요없으므로 처음이면 나중에 인스톨하라). 인스톨하면 이클립스는 이후 리소스 파일들을 주시하고 변경사항이 있으면 결과물을 자동으로 재생성한다. 디버그 모드로 동작중이라면 브라우저에 변경사항을 즉시 반영한다.

  • Wro4j는 당신의 빌드 클래스패스를 알지 못하는 XML설정을 통해 제어하며 오직 파일의 절대경로만 인식한다. 그러므로 우리는 파일의 절대 경로를 만들어 wro.xml에 추가해야한다. 이것을 위해 메이븐 리소스 필터링을 사용할 것이며 이것이 왜 명시적으로 "maven-resources-plugin" 선언을 해줘야 하는가에 대한 이유다.

필요한 POM 수정은 이게 전부이며 "src/main/wro"에 위치할 wro4j 빌드파일을 추가할일만 남았다.


Wroj4j 소스파일들 Wro4j Source Files

Github의 소스파일엔 오직 3개 파일만 볼 수 있다. (그중 차후 커스터마이징을 위해 하나는 빈파일이다)

  • wro.properties는 wro4j 엔진의 선처리preprocessing와 랜더링을 위한 설정configuration 파일이다. 이 설정파일로 이 툴체인의 다양한 기능을 켜거나 끌수있다. Less로 부터 CSS를 컴파일하고 자바스크립트를 minify하려는 우리의 경우, 결과적으로 필요한 모든 라이브러리들의 소스를 섞어 2개의 파일로 합칠것이다.

    wro.properties
    preProcessors=lessCssImport
    postProcessors=less4j,jsMin
  • wro.xml는 "angular-bootstarp"이라는 단일 "그룹"을 선언하며 생성되는 정적 리소스의 기본명으로 종료한다. 우리가 추가한 webjars의 <css> 와 <js> 엘리먼트의 참조형 및 로컬 리소스파일인 main.less를 또한 포함하고있다.

    wro.xml
    <groups xmlns="http://www.isdc.ro/wro">
      <group name="angular-bootstrap">
      <css>webjar:bootstrap/3.2.0/less/bootstrap.less</css>
        <css>file:${project.basedir}/src/main/wro/main.less</css>
        <js>webjar:jquery/2.1.1/jquery.min.js</js>
        <js>webjar:angularjs/1.3.8/angular.min.js</js>
      </group>
    </groups>
  • main.less는 예제코드로서 빈 파일이지만 UI의 Look & Feel이나 트위터 부트스트랩의 (밑의 한줄로) 기본 파란색을 옅은 핑크색으로 바꾸는 등의 기본설정을 바꾸거나, 커스터마이즈하는데 사용된다.

    main.less
    @brand-primary: #de8579;
  • 위의 파일들을 프로젝트로 복사해넣고 "mvn package"를 타입하면 JAR파일안에 "bootstarp-angular.*" 리소스 파일들을 볼 수 있을 것이다. 이제 앱을 실행하면, CSS가 작동하는것을 확인할수 있다. 하지만 아직 비지니스 로직과 페이지 이동기능이 여전히 빠져있다.


    앵귤러 어플리케이션 만들기 Create the Angular Application

    이제 "hello" 어플리케이션을 만들어보자 ("src/main/resources/static/js/hello.js" 경로에 만들어야 "index.html"의 하단의 <script/> 엘리먼트가 올바른 경로를 찾을수 있다)

    최소사양의 앵귤러JS 어플리케이션은 다음과 같다.

    hello.js
    angular.module('hello', [])
      .controller('home', function($scope) {
        $scope.greeting = {id: 'xxx', content: 'Hello World!'}
    })

    어플리케이션의 이름은 "hello"이고 빈 "config" 와 "home"이라는 이름의 "controller"를 가지고 있다. "home" controller는 "index.html"에 ng-controller="home"을 선언한 <div> 컨텐트때문에 "index.html"가 로드될때  같이 호출된다

    controller 함수안에 마법과도 같은 $scope을 주입했고 (앵귤러는 명명규칙naming convention에 따른 의존성주입 dependency injection by naming convention을 한다), 당신의 함수변수의 이름을 인식하고 있다는걸 명심하자. $scope은 컨트롤러가 책임지는 함수내에서 UI엘리먼트를 위한 컨텐트와 행동규약등을 설정해준다.

    당신이 이 파일을 "src/main/resources/static/js" 경로밑에 추가했다면 어플리케이션은 이제 안전하고 기능적이되었으며 "Hello World!"를 보여줄것이다. greeting은 HTML내에{{greeting.id}} 와 {{greeting.content}} 와 같은 placeholder들은 핸들바를 사용하는 앵귤러에 의해 랜더되었다. 


    동적 컨텐트 추가하기 Adding Dynamic Content

    지금까지 우리는 하드코드된 greeting을 가진 어플리케이션을 만들었다. 어떻게 서로가 맞추어가는지 배우면 유용할것이다. 그러나 실제로 우리가 기대한 컨텐트는 서버로부터 오는것이므로 이제 HTML endpoint를 만들어 greeting을 받아올 것이다. "src/main/java/demo"에 있는 어플리케이션의 메인클래스에  @RestController 어노테이션과 새 @RequestMapping을 정의하자:

    UiApplication.java
    @SpringBootApplication
    @RestController
    public class UiApplication {
    
      @RequestMapping("/resource")
      public Map<String,Object> home() {
        Map<String,Object> model = new HashMap<String,Object>();
        model.put("id", UUID.randomUUID().toString());
        model.put("content", "Hello World");
        return model;
      }
    
      public static void main(String[] args) {
        SpringApplication.run(UiApplication.class, args);
      }
    
    }

    새프로젝트를 어떻게 만들었냐에 따라 UiApplication이 아닐 수도 있 다. @SpringBootApplication 대신 @EnableAutoConfiguration @ComponentScan @Configuration이 있을 수도 있다.

    어플리케이션을 실행하고 curl에서 "/resource" endpoint를 타입해보면 기본적으로 보안이 작동하여 다음과 같은 메세지를 볼 수 있다:

    $ curl localhost:8080/resource
    {"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}

    앵귤러에서 동적리소스 불러오기 Loading a Dynamic Resource from Angular

    이제 브라우저에서 메세지를 받아봐보자. "home" controller를 XHR을 사용하여 보호된 리소스를 불러올수있게 수정하자:

    hello.js
    angular.module('hello', [])
      .controller('home', function($scope, $http) {
      $http.get('/resource/').success(function(data) {
        $scope.greeting = data;
      })
    });

    core기능으로 앵귤러가 제공해주는 $http서비스를 주입하고 GET메소드로 resource에 접근한다. 성공적으로 요청되면 앵귤러는 콜백으로 Response body로부터 JSON 타입을 돌려받는다.

    다시 어플리케이션을 실행하면 (또는 그냥 브라우저의 홈페이지를 다시 로드하면) Unique ID를 가진 동적 메세지를 볼수 있을것이다. 리소스가 보호된 상태라 직접적으로 curl을 사용할 순 없지만 브라우저는 컨텐트에 접근할 수 있다. 우리 이제 백줄도 안되는 코드로 단일페이지 보안 어플리케이션을 가지게 되었다!

    당신은 아마 파일 수정후 브라우저에 정적 리소스의 재실행을 강제하고 싶을 수 있다. 크롬(파이어폭스에선 플러그인을 통해)에서 "developer tools" 메뉴 (단축키 F12)를 통해 가능하며 또는 CTRL+F5를 사용하면 된다.


    어떻게 작동하는가? How Does it Work?

    브라우저와 백엔드간의 연동은 사용자의 브라우저에서 확인할 수 있다. 만일 사용자가 개발자 툴을 사용한다면 (보통 크롬에서는 기본값으로 F12를 누르면 열리고 파이어폭스에서는 플러그인을 설치해야할 것이다) 정리하자면:

    VerbPathStatusResponse

    GET

    /

    401

    인증을 위한 브라우저 팝업 프롬프트

    GET

    /

    200

    index.html

    GET

    /css/angular-bootstrap.css

    200

    트위터 부트스트랩 CSS

    GET

    /js/angular-bootstrap.js

    200

    부트스트랩과 앵귤러JS

    GET

    /js/hello.js

    200

    어플리케이션 로직

    GET

    /resource

    200

    JSON greeting

    브라우저가 단일 연동으로서 홈페이지를 로드한다고 간주하기때문에 사용자는 아마 401 응답을 볼 수 없을것이다. 그리고 /resource를 위한 두번의 요청을 볼수 있는데, 이는 CORS negotiation때문이다.

    요청들을 더 자세히 들여다 보면, 모든 요청에 다음과 같은 "인증Authorization"헤더가 있는 것을 볼 수 있다:

    Authorization: Basic dXNlcjpwYXNzd29yZA==

    브라우저는 모든 요청에 사용자명과 패스워드를 보내고 있는데 ( 그러므로 제품화단계에선 HTTPS으로만 써야하는걸 기억하자). 이런한 단계는 "앵귤러"와 무관하므로 사용자가 자바스크립트 프레임워크를 쓰던 안쓰던 잘 동작한다.


    무엇이 문제인가? What’s Wrong with That?

    표면적으로는 매우 잘 작동하는 것처럼 보인다. 간결하고 구현이 간단하며, 모든 데이터는 패스워드에 의해 안전하다. 그리고 사용자가 프로트엔드 또는 백엔드 기술세트를 바꾸더라도 여전히 잘 작동할 것이다. 하지만 약간의 이슈가 있는데:

    • 기본 인증은 사용자명과 패스워드 인증으로 제한되어있다.

    • 인증 UI는 흔하지만 매우 추하다 ( 브라우저 다이얼로그).

    • Cross Site Request Forgery (CSRF)로부터 보호되지않는다.

    CSRF는 실제로 백엔드 리소스에 GET만 사용하는 지금의 어플리케이션에는 문제가 안될 수도 있다 (예를 들면 서버의 상태변화가 없을때). 일단 사용자가 어플리케이션에 POST, PUT 또는 DELETE 를 사용한다면, 요즘의 기술로는 더 이상 안전하지 않다.

    이 시리즈의 다음 섹션에서, 우리는 HTTP Basic보다 훨씬 유연한 폼 기반의 인증을 사용하도록 어플리케이션을 확장할 것이다. 일단 우리가 폼을 사용하면 CSRF 보호를 할 필요가 있다. 스프링 시큐리티와 앵귤러 둘다 이것을 만드는데 도움을 주는 즉시 활용가능한 멋진 기능들을 가지고 있다. 약간 스포일로해보면, 우리는 HttpSession을 사용할 것이다.

    감사의 말: 이 시리즈를 만드는데 도움을 준 모든분께 감사드리고 싶다. 특히 각각의 섹션과 소스코드를 주의깊게 리뷰를 해주고, 내가 잘 알고 있다고 생각한 부분에서 조차 알지못했던 몇가지 트릭을 알게 해준 Rob Winch 와 Thorsten Spaeth.에게 감사하다.



    반응형

    반응형

    이 가이드는 어떻게 스프링 세션을 스프링 시큐리티와 함께 사용하는지 설명할 것이다. 당신의 어플리케이션에 스프링 시큐리티를 이미 적용했다는 가정하에 진행된다.

    완전한 가이드는 다음의 시큐리티 샘플 어플리케이션링크에서 확인할 수 있다.

    의존성 업데이트 하기 Updating Dependencies

    스프링 세션을 사용하기전, 의존성 업데이트를 해야한다. 메이븐을 쓴다면 다음의 의존성을 추가해주자:

    pom.xml
    <dependencies>
            <!-- ... -->
            <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>1.0.2.RELEASE</version>
            <type>pom</type>
            </dependency>
            <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.1.6.RELEASE</version>
            </dependency>
    </dependencies>

    스프링 설정 Spring Configuration

    필수 의존성을 추가하면 스프링 설정을 생성할 수 있다. 스프링 설정으로 Servlet 필터를 만들어 HttpSession 구현체를 스프링 세션의 엄호하에 사용되는 구현체로 바꿔줘야한다. 다음의 스프링 설정을 추가하자:

    @Configuration
    @EnableRedisHttpSession 1
    public class Config {
    
            @Bean
            public JedisConnectionFactory connectionFactory() {
                    return new JedisConnectionFactory(); 2
            }
    }
    1@EnableRedisHttpSession 어노테이션은 Filter를 구현한 springSessionRepositoryFilter라는 이름의 스프링 빈을 만들어준다. 이 필터는 HttpSession를 스프링 세션의 엄호하에 사용되는 구현체로 대체해준다. 이 인스턴스에서 스프링 세션은 레디스에 엄호하에 있다.
    2

    스프링 세션을 레디스 서버에 연결하는 RedisConnectionFactory를 만들었다.RedisConnectionFactory that connects Spring Session to the Redis Server. 기본포트인 6379로 연결하도록 설정한다. 스프링 데이터 레디스의 자세한 설정정보는 레퍼런스 문서를 참고하자.

    서블릿 컨테이너 초기화 Servlet Container Initialization

    우리는 스프링 설정으로 Filter를 구현한 springSessionRepositoryFilter라는 이름의 스프링 빈을 만들어 스프링 세션에 엄호하에 사용되는 커스텀 구현체로 HttpSession을 대체하는데 쓴다.

    우리의 Filter를 동작하게 하기 위해, 스프링은 우리가 설정한 Config 클래스를 불러와야한다. 어플리케이션이 이미 스프링 설정을 SecurityInitializer 클래스를 통해 실행되고 있기때문에 간단히 다음의 Config 클래스를 추가해주면된다:

    src/main/java/sample/SecurityInitializer.java
    public class SecurityInitializer extends
                    AbstractSecurityWebApplicationInitializer {
    
            public SecurityInitializer() {
                    super(SecurityConfig.class, Config.class);
            }
    }

    마지막으로 서블릿 컨테이너 (톰캣같은)가 모든 요청에 우리의springSessionRepositoryFilter 를 사용하게 만들야한다. 스프링세션의 springSessionRepositoryFilter가 스프링 시큐리티의 springSecurityFilterChain이 불러지기전에 호출되어야한다는 것은 극도로 중요하다. 스프링 시큐리티에서 사용하는 HttpSession은 반드시 스프링 세션의 엄호하에 있어야 한다. 다행스럽게도 스프링 세션은 이것을 극도로 쉽게 만들어주는 AbstractHttpSessionApplicationInitializer 이라는 유틸리티 클래스를 제공해준다. 밑의 예제를 보자:

    src/main/java/sample/Initializer.java
    public class Initializer extends AbstractHttpSessionApplicationInitializer {
    
    }

    클래스 이름 (Initializer)은 무엇이되도 상관없다. 중요한 것은 우리가 AbstractHttpSessionApplicationInitializer를 확장했다는 것이다.

    AbstractHttpSessionApplicationInitializer 를 확장함으로서, springSessionRepositoryFilter라는 이름의 스프링 빈이 스프링 시큐리티의 Security’sspringSecurityFilterChain이전에 모든 요청을 처리하도록 서블릿 컨테이너와 함께 등록될 것이다.

    시큐리티 샘플 어플리케이션 security Sample Application

    시큐리티 샘플 어플리케이션 실행하기 Running the security Sample Application

    소스 코드를 받아서 다음의 명령어를 호출함으로서 샘플을 실행할 수 있다:

    샘플을 동작하려면 먼저 localhost에 레디스 2.8 또는 그 이후버전을 인스톨하고 기본 포트(6379)로 실행하자. 설치 설정이 다르면 JedisConnectionFactory에 레디스 서버를 해주면 된다.

    $ ./gradlew :samples:security:tomcatRun

    이제 http://localhost:8080/를 통해 어플리케이션에 접근할 수 있다.

    시큐리티 샘플 어플리케이션 둘러보기 Exploring the security Sample Application

    어플리케이션을 사용해보자. 다음의 로그인 정보를 입력하자:

    • Username user

    • Password password

    Login 버튼을 누르면, 방금 입력한 사용자로 로그인됬다로 알려주는 메세지를 볼수 있다. 이 사용자 정보는 톰캣의 HttpSession 구현체가 아닌 레디스에 저장되어있다.

    어떻게 동작하는가? How does it work?

    톰캣의 HttpSession대신, 우리는 실제 레디스에 값을 저장하고 있다. 스프링 세션은 레디스의 엄호하에 있는 구현체로 HttpSession을 대체한다. 스프링 시큐리티의SecurityContextPersistenceFilter SecurityContextHttpSession 에 저장할때, 이는 레디스안에 저장된다.

    새로운 HttpSession이 만들어질때, 스프링 세션은 사용자의 브라우저에 사용자의 세션ID를 가지고 있는 SESSION이라는 쿠키를 만든다. 계속 진행해서 쿠키를 둘러보자. (크롬이나 파이어폭스에서 쿠키값을 확인해볼수 있다)

    redis-cli를 쓰면 세션값을 언제든지 레디스에서 손쉽게 삭제할 수 있다.예를 들면 리눅스 기반의 시스템에서는 다음과 같이 타입하면된다:

    $ redis-cli keys '*' | xargs redis-cli del
     redis-cli 설치는 레디스 문서를 참고하자.

    또는, 명시적인 키explicit key값을 삭제해도 된다. 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e의 값을 가지는 세션 쿠키를 삭제하려면 다음을 터미널에 타입하자:

    $ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e

    이제 다시 http://localhost:8080/ 를 방문해보면 더이상 인증되어있지 않은 걸 확인할 수 있다.


    반응형

    반응형

    스프링 부트 1.3.0 릴리즈 노트 - 2015년 11월 17일

    원문: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.3-Release-Notes

    스프링부트 1.2이후 업그레이드된 것

    스프링 부트 1.2에서 제거된것 Deprecations from Spring Boot 1.2

    스프링 부트 1.2에서 디프리케이트된 클래스, 메소드, 프로퍼티들은 이번 릴리즈에서 제거되었다.업그레이드에 앞서 디프리케이트된 메소드를 호출하는지 먼저 확인해야한다.


    잭슨 Jackson

    스프링 부터 1.2는 어플리케이션 컨텍스트내에 모든 ObjectMapper 를 가지는 잭슨 모듈에 등록했다. 이는 ObjectMapper bean의 모듈을 완전히 제어할수 없게 만들었기 때문에, 스프링 부트 1.3에서는 오직 자동설정된 Jackson2ObjectMapperBuilder.와 함께 설정되거나 생성된 ObjectMappers 만 잭슨 모듈 빈으로 등록된다. 이는 모듈설정의 방식을 스프링부트의 다른 잭슨설정과 똑같이 만들어줬다.


    로깅 Logging

    스프링 특화 설정 Spring specific configuration

    스프잉 특화 로그 설정파일의 중복 초기화를 막기위해 기본 로그 설정의 이름을 -spring 접두사를 붙여쓰도록 바꿔주는것을  (필수요구사항은 아니지만) 권장한다. 예를 들면, logback.xml 파일을 logback-spring.xml로 바꾸는 것이다.


    초기화 실패 Initialization failures

    스프링 부트 1.2에선 logging.config 를 써서 로깅 설정파일을 커스터마이즈할 수 있었다. 파일이 존재하지않으면 기본설정을 사용해서 조용히 대비할 수 있었다. 스프링 부트 1.3는 파일이 없을경우 실패하게 된다. 유사하게, 사용자가 잘못 설정된 커스텀 로그백 설정파일을 넣어뒀다면 스프링 부트 1.2는 기본 설정으로 극복할 것이다. 스프링 부트 1.3은 시스템이 에러가 나며(falls) 설정의 문제로  System.err에 리포트된다.


    스프링 HATEOAS Spring HATEOAS

    스프링 HATEOAS 자동설정이 컨텍스트의 기본 ObjectMapper에 영향을 미치지 않도록 재작업되었으므로 spring.hateoas.apply-to-primary-object-mapper 프로퍼티는 삭제되었다. 이는 spring.hateoas.use-hal-as-default-json-media-type 이름의 프로퍼티로 대체되었는데 이는 스프링 HATEOAS HTTP 메세지 컨버터가  application/hal+json요청 뿐만 아니라  application/json의 요청을 처리할 지를 제어하는데 쓰인다.


    /health 종단의 시큐리티 Security for the /health endpoint

    actuator /health endpoint의 어떠한 정보를 보여줄지에 대한 보안 설정은 더 나은 일관성을 가지도록 약간 재조정되었다. 자세한 정보는  HTTP health endpoint access restrictions 를 참고하자.


    HTTP 응답압축 HTTP response compression

    스프링 부트 1.2에서 톰캣사용자를 위한 네이티브 응답 압축 또는 제티, 톰캣, Undertow 사용자를 위한 제티의 GZipFilter 를 사용하는 압축이 지원되었다. 제티팀이 gzip필터를 디프리케이트한데 영감받아 스프링부트 1.3은 이 세 내장 컨테이너에서 네이티브 응답 압축을 지원하도록 변경되었다. 그 결과 server.tomcat.compression. 그리고 spring.http.gzip. 프로퍼티는 더이상 지원하지 않는다. 새로운  server.compression.* 프로퍼티가 대신 사용된다.


    톰캣 세션저장 Tomcat session storage

    기본값으로 톰캣은 더이상 세션 데이터를  /tmp에 저장하지않는다. 사용자가 톰캣과 persistent 세션을 사용하려면 server.session.persistent 프로퍼티를  true로 설정하자. server.session.store-dir 는 특정위치에 파일을 저장할때 쓴다.


    제티 JSP Jetty JSPs

    spring-boot-starter-jetty "Starter POM" 은 더이상 org.eclipse.jetty:jetty-jsp. 을 포함하지않는다. 제티와 JSP를 함께 사용하는 사용자는 스스로 이 의존성을 직접 추가해줘야한다.


    MVC Stacktrace출력 MVC Stacktrace output

    Stacktrace 정보는 이제 스프링 MVC가 에러 응답을 랜더할때 포함되지않는다.스프링 부트 1.2에서 쓰던대로 유지하려면 error.include-stacktrace를 on-trace-param.로 설정하자.


    타임리프의 스프링 시큐리티 통합 Thymeleaf’s Spring Security integration

    스프링 시큐리티 4로의 업그레이드때문에  스프링 1.3은 타임리프의 스프링 시큐리티 지원의 자동설정과 의존성 관리 또한 업그레이드하였다. 새 artiface은 org.thymeleaf.extras:thymeleaf-extras-springsecurity4.이다 pom.xml 또는 build.gradle 를 각각 업그레이드 해줘야한다.


    /template폴더가 없을때 나는 에러 Missing /templates folder errors

    스프링 부트 어플리케이션은  /templates 폴더가 없다고 시작에 실패하지않는다. 스프링 부트에서 지원되는 템플릿 기술을 사용중인데 /templates 폴더를 추가하지 않았다면 이제 warning으로 로그될것이다.


    그루비 템플릿 Groovy templating

    GroovyTemplateProperties 는 이제 AbstractTemplateViewResolverProperties 를 확장하여 추가적인 설정옵션을 제공한다. 사용자가 현재 커스텀 리소스 위치를 정의하기위해 prefix.spring.groovy.template.prefix 프로터리를 설정해두었다면,  prefix.spring.groovy.resource-loader-location로 이름을 바꿔줘야 한다.


    그래들 플러그인 Gradle plugin


    bootRun 리소스 bootRun resources

    스프링 부트 그래들 플러그인은 bootRun를 쓸때 클래스패스에 src/main/resources 를 직접 추가하지않는다. 만일 사용자가 실시간 즉시 편집을 원하면, Devtools 를 쓰는것을 권장한다. 스프링 부트 1.2에서 했던대로 되돌리려면 addResources 프로퍼티는 그래들 빌드에 설정할 수 있다.


    의존성 관리 Dependency management

    스프링의 그래들 플러그인이 이번 릴리즈에서 dependency management plugin를 사용할 수 있게 업데이트되었다. 대부분의 사용자는 이 변화에 무관하지만 자신이 설정한 버전을 적용하려는  versionManagement 설정을 사용하는 사용자들은 그들의  빌드 스크립트를 업그레이드해야한다.

    버전관리를 설정하기위해 프로퍼티파일을 요구하는 대신, 새로운 플러그인이 사용자가 메이븐 bom을 쓸수있도록 해준다. 예를 들면:

    dependencyManagement {
        imports {
            mavenBom 'com.example:example-bom:1.0'
        }
    } 

    어플리케이션 플러그인 Application plugin

    스프링 부트 그래들 플러그인은 더이상 그래들의  application plugin 을 기본으로 적용하지않는다. 만일 이 어플리케이션 플러그인을 쓰려면, build.gradle.에 사용자가 적용해줘야한다.

    어플리케이션 플러그인이 제공하는 기능이 필요없지만  mainClassName 나 applicationDefaultJvmArgs 프로퍼티를 쓰고싶으면 build.gradle.에 작은 업데이트만 해주면 된다.

    메인클래스는 이제 springBoot extension의 mainClass 프로퍼티로 설정가능해졌다. 예를들면:

    springBoot { mainClass = 'com.example.YourApplication' }

    applicationDefaultJvmArgs 는 사용자 프로젝트의 ext block에서 설정할 수 있다. 예를들면:

    ext {
        applicationDefaultJvmArgs = [ '-Dcom.example.property=true' ]
    }

    어플리케이션 플러그인의 run task에 있는 main 프로퍼티를 사용해서 사용자 프로젝트의 메인클래스를 설정하려면 다음의 설정을  bootRun task에 대신 설정해 줘야한다:

    bootRun {
        main = com.example.YourApplication
    }

    메이븐 플러그인 Maven plugin

    spring-boot:run 리소스 spring-boot:run resources

    스프링 부트 메이븐 플러그인은  spring-boot:run를 쓸때 클래스패스에 src/main/resources를 직접 추가하지않는다. 만일 사용자가 실시간 즉시 편집을 원하면, Devtools 를 쓰는것을 권장한다. 스프링 부트 1.2에서 했던대로 되돌리려면 addResources 프로퍼티는 pom.xml 에 설정할 수 있다.


    메이븐 리소스 필터링 Maven resources filtering

    spring-boot-starter-parent를 사용한다면, 메이븐 토큰은 오직 @ 을 사용하여 필터링 한다. 이는 사용자 설정에서 빌드시 확장되는 스프링 placeholder를 막아준다.(${foo}와 같은). 

    실제로 만일 사용자가  ${project.version}와 같은 표준화 포멧을 여전히 사용하고 있다면 @project.version@ 로 바꿔주거나 maven-resources-plugin 설정을 오버라이드 해야한다.


    CLI의존성 관리 CLI dependency management

    스프링 부트 1.3은 이제 메타데이터에 기초한 프로퍼티 파일을 대신하여 의존성관리를 설정하기 위해 메이븐 bom을 사용한다. bom의 위치를 지정하기 위한 @GrabMetadata 을 대신하여 @DependencyManagementBomsho을 사용해야한다. 예를 들면,  @DependencyManagementBom("io.spring.platform:platform-bom:1.1.2.RELEASE").


    프로퍼티 이름변경 Property renames

    다음의 application.properties 키들이일관성 차원에서 이름을 변경하였다:

    • spring.view.에서  spring.mvc.view.

    • spring.pidfile 에서 spring.pid.file

    • error.path 에서 server.error.path

    • server.session-timeout 에서 server.session.timeout

    • servet.tomcat.accessLogEnabled 에서 server.tomcat.accesslog.enabled

    • servet.tomcat.accessLogPattern 에서 server.tomcat.accesslog.pattern

    • servet.undertow.accessLogDir 에서 server.undertow.accesslog.dir

    • servet.undertow.accessLogEnabled 에서 server.undertow.accesslog.enabled

    • servet.undertow.accessLogPattern에서 server.undertow.accesslog.pattern

    • spring.oauth2. 에서 security.oauth2.

    • server.tomcat.compression 와 spring.http.gzip 에서server.compression.*

    • prefix.spring.groovy.template.prefix 에서prefix.spring.groovy.resource-loader-location


    의존성 Dependencies


    스프링 4.2 Spring 4.2

    스프링 부트 1.3은 스프링 프레임워크 4.2 또 그 이후 버전이 필요하다. 그 이전버전과는 호환되지않는다.


    스프링 시큐리티 4.0 Spring Security 4.0

    스프링 부트 1.3은 스프링 시큐리티 4.0을 사용한다. 시큐리티 3.2에서 마이그레이션에 대한 정보는 스프링 시큐리티 문서를 확인하자.


    주목할만한 새 기능

    변경된 설정를 둘러보려면 설정 변경기록 을 확인하자.


    버전 업데이트 Version Updates

    스프링 부트 1.3빌드는 스프링 프레임워크 4.2를 필요로 한다. 몇몇 서드파티 의존성이 이 릴리즈에 맞춰 업그레이드 되었다. 이번 릴리즈에는 톰캣과 제티 버전에 주요한 업데이트는 없다.


    개발자 툴 Developer Tools

    스프링부트 1.3 은 개발시간을 향상을 목적으로 하는 새로운 spring-boot-devtools 메소드를 지원한다.이 모듈이 제공하는 것들은:

    • 민감한 프로퍼티의 기본값들 (예를 들면 템플릿 캐시를 disable하는)

    • 어플리케이션 자동 재시작

    • LiveReload 지원

    • 원격 개발 지원 (HTTL터널을 통한 원격 업데이트와 원격 디비깅을 포함).

    • 재시작해도 이어지는 Persistent HTTP 세션

    자세한 정보는 업데이트된 문서를 보자.


    캐싱 자동설정 Caching Auto-configuration

    다음과 같은 캐시 기술에 대한 자동설정이 지원된다:

    • EhCache

    • Hazelcast

    • Infinispan

    • JCache (JSR 107)를 호환하는 구현체들

    • Redis

    • Guava

    더우기, 간단한 Map 기반 메모리 상주 캐싱 또한 지원된다. 캐싱은 사용자의 어플리케이션의  @Configuration이 @EnableCaching와 같이 어노테이션되어있으며 자동적으로 등록된다. 캐시 통계 또한 이제 actuator endpoint로서 노출된다 ( 관련된 근원기술을 허용할때만)

    자세한 정보는 업데이트된 문서를 보자.


    온전히 실행가능한 JAR와 서비스 지원 Fully executable JARs and service support

    스프링 부트 메이븐과 그레들 플러그인은 이제 리눅스/유닉스 시스템을 위한 full executable archives를 만들수 있다. 더우기, 사용자는 이들 JAR들은 다음과 같이 타입함으로서 init.d 또는 systemd 서비스로서 쉽게 인스톨할 수 있다:

    $ ./myapp.jar

    그리고 이렇게 init.d 서비스로 인스톨한다:

    $ sudo link -s /var/myapp/myapp.jar /etc/init.d/myapp

    추가적인 정보는 레퍼런스 문서를 참고하자


    카산드라 지원 Cassandra Support

    카산드라의 자동 설정이 지원된다. 자세한 정보는 레퍼런스 문서를 참고하자.


    OAuth2 지원 OAuth2 Support

    이제 @EnableAuthorizationServer 와 @EnableResourceServer를 사용하여 OAuth2 인증 및 리소스 서버를 재빠르게 만들 수 있다. 뿐만 아니라  @EnableOAuth2Client는 사용자 어플리케이션에  OAuth2 클라이언트로 동작할 수 있게 해준다. 자세한 정보는 재정비된 레퍼런스 문서의 보안섹션을 참고하자.


    스프링 세션 Spring Session

    클래스패스에 스프링 세션과 스프링 데이터 레디스를 넣으면 웹어플리케이션은 이제 레이스에서 사용자 세션을 저장하도록 자동설정될 것이다.  accompanying sample 에서 자세한 정보를 확인하자.


    jOOQ 지원 jOOQ Support

    jOOQ의 자동등록을 지원한다. 형변환에 안전한(type safe) 데이터베이스 쿼리를 만드려면 사용자의 bean에 jOOQ의 DSLContext를 직접  @Autowire 하면된다. 추가적인 커스터마이즈가 spring.jooq.* 어플리케이션 프로퍼티를 통해 지원된다. 

    자세한 정보는 레퍼런스 문서의 "jOOQ 사용하기" 섹션을 참고하자.


    센드그리드 SendGrid

    SendGrid 이메일 배송서비스의 자동등록을 지원한다.


    Artemis 자동설정 Artemis auto-configuration

    2015년 HornetQ가 아파치 재단에 기부하여 아파치 Artemis가 만들어졌다. 스프링 부트 1.3부터 아파치 Artemis를 완벽하게 지원하며 HornetQ와 거의 같은 방식을 사용한다. 사용자가 Artemis 를 사용하려면, spring.hornetq. 프로퍼티를  spring.artemis.로 이름 변경하면 된다.


    "Starter POM" 검증 Validation "Starter POM"

    새로운 spring-boot-starter-validation starter POM 이 추가되어 bean validation (JSR 303) support할 수 있다.


    @WebServlet, @WebFilter와 @WebListener 지원 Support for @WebServlet, @WebFilter and @WebListener

    내장 서블릿 컨테이너를 사용할때 @WebServlet,@WebFilter 그리고 @WebListener 어노테이션된 클래스를 자동등록이 @ServletComponentScan을 사용함으로서 가능해졌다.


    스프링 리소스체인 Spring resource chains

    사용자는 이제 스프링의 application properties를 통해  ResourceChainRegistration 의 기본 aspect을 설정할 수 있다. 이는 사용자가 캐시파열cache busting을 구현하기 위한 고유한 리소스 이름을 만들수 있게 해준다.  spring.resources.chain.strategy.content. 프로퍼티는 만약 사용자가 사용자의 fingerprint에 "고정된 버전"fixed version을 쓰길 원하면 리소스의 내용에 기초해  fingerprinting을 설정하는데 쓰인다. 


    JDBC

    스프링 부트는 다음과 같은 데이터베이스의 JDBC URL로 부터 드라이버 클래스 이름을 자동으로 가져온다:

    • DB2

    • Firebird

    • Teradata


    데이터소스 타입 DataSource type

    자동설정이 사용하는 커넥션 풀connection pool은 spring.datasource.type 설정키를 통해  명시할 수 있다.


    H2 웹콘솔 H2 Web Console

    H2의 web console 의 자동설정이 추가되었다. 스프링부트의 개발자 툴을 쓸때 사용자의 웹 어플리케이션에  com.h2database:h2 의존성만 추가하면 바로 사용할 수 있다. 자세한 정보는 문서를 참고하자.


    내장 몽고DB Embedded MongoDB

    내장 MongoDB 가 자동설정에 추가되었다. de.flapdoodle.embed:de.flapdoodle.embed.mongo 의존성이 시작시 필요하며, 버전과 같은 설정을 사용하려면 application.properties를 통해 설정할 수 있다. 자세한 정보는 문서를 참고하자.


    ANSI 컬러 banner.txt 파일 ANSI color banner.txt files

    이제 사용자의 banner.txt 파일에 출력물에 색깔을 입힐 수 있는 ANSI placeholders를 사용할 수 있다. 예를 들면:

    ${AnsiColor.BRIGHT_GREEN}My Application
    ${AnsiColor.BRIGHT_YELLOW}${application.formatted-version}${AnsiColor.DEFAULT}

     

    기본 프로파일 application.properties Default profile application.properties

    -default 접미사는 이제 특정 프로파일이 활성화되지않았을때 application.properties (또는 application.yml) 파일을 읽는다. 이 것은 사용자가 배포환경을 명시하는 프로파일을 사용할때 유용할 것이다.

    FileDescription

    application.properties

    항상 불러오는 공유 프로퍼티

    application-prod.properties

    prod 프로파일이 활성활됬을때 불러오는 프로퍼티

    application-staging.properties

    staging 프로파일이 활성활됬을때 불러오는 프로퍼티

    application-default.properties

    활성화된 프로파일이 없을때 불러오는 프로퍼티


    어플리케이션 인자들 Application arguments

    사용자는 이제 CommandLineRunner를 대신하는 ApplicationRunner 인터페이스를 구현할 수 있다. 같은 방식으로 동작하지만  String[]이 아닌ApplicationArguments 인터페이스로서 인자를 제공할 수 있다. 만일 사용자가 어플리케이션 인자들에 접근해야한다면, 이미 있는 아무 bean에 직접 ApplicationArguments를 주입할 수 있다.

     ApplicationArguments 인터페이스는  "option" 과 "non-option" 인자들을 위한 편리한 메소드를 제공한다. 예를 들면:

    @Autowired
    public MyBean(ApplicationArguments args) {
        boolean debug = args.containsOption("debug");
        List<String> files = args.getNonOptionArgs();
        // if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
    }

    어플리케이션 인자 접근하기 에서 자세한 정보를 확인하자.


    로깅 Logging

    로깅 패턴 Log Patterns

    logging.pattern.console 과 logging.pattern.file 프로퍼티는 이제 사용자의 application.properties파일을 통해 직접 로깅패턴을 지정할 수 있다. 사용자의  log*.xml파일을 정의할 필요없이 패턴을 커스터마이즈하길 원하는 경우 손쉽게 이용할 수 있다.


    stacktrace에서 Jar 디테일 Jar details in stacktraces

    사용자가 logback or log4j2를 사용중이라면, stack trace안의 각 클래스의 위치에 대한 정보가 로드될것이다. (이것은 logging.exception-conversion-word를 통해 바꿀 수 있다.) 

    If you are using logback or log4j2, we now include information about the location from which each class in a stack trace was loaded (this can be customized vialogging.exception-conversion-word).


    Log4J 2 출력 Log4J 2 Output

    Log4J 2’의 기본 출력이 향상되어 Logback의 출력물과 비슷해졌다.


    톰캣 접근 로그 Tomcat access logs

    톰캣 접근로그(access logs)는 더 커스커마이징할 수 있게 되었다: 디렉토리, 파일의 접두사/접미사를 이제 설정을 통해 커스터마이즈 할 수 있다.


    로그백 확장 Logback extensions

    스프링 부트 1.3은 사용자가 logback 설정파일안에서 사용할 수 있는 몇 개의 새로은 태그를 지원한다. 태그를 쓰려면 사용자는 먼저 logback.xml 설정파일을 logback-spring.xml로 이름을 바꿔줘야 한다. 일단 이름을 바꿨다면 다음의 태그를 사용할 수 있다:

    태 그

    설 명

    <springProfile>

    사용자 마음대로 활성화된 스프링 프로퍼티에 기초한 설정을 포함하던지 배제할지 허용한다.

    <springProperty>

    Logback에서 사용하는 스프링 환경변수(Spring Environment)의 프로퍼티를 불러오는 것을 허용한다.

    레퍼런스 문서의 Logback extensions 섹션에서 자세한 정보를 확인하자.


    HTTP 세션 HTTP Sessions


    Persistent 세션 Persistent sessions

    톰캣, 제티, Undertow는 어플리케이션이 멈췃을때와 재시작되어 다시 로드될때 세션데이터를 serialize하도록 업데이트되었다. Persistent  세션은 opt-in이라  ConfigurableEmbeddedServletContainer의 persistentSession 이나  server.session.persistent=true 프로퍼티를 사용하여 설정할 수 있다. (Persistent 세션은 Devtools에서 기본적으로 enabled 되어있다)

    persistent 세션 데이터를 저장하는 위치는 server.session.store-dir 프로퍼티를 사용하여 지정할 수 있다.


    향상된 HTTP 세션 설정 Advanced HTTP Session configuration

    부가적인 프로퍼티들이 세션 설정을 위해 제공되어 이제 server.session.* 프로터리를 사용하여 "tracking modes" 와 "cookie" details을 설정할 수 있다.


    X-Forwarded-For 헤더지원 X-Forwarded-For header support

    X-Forwarded-For header 지원이 제티와 Undertow에 추가되었다. 톰캣 또한 X-Forwarded-For headers를 반영되어야 한다면 단일 server.use-forward-headers 프로퍼티를 true로 설정 할 수 있도록 새로 고쳐졌다. 스프링부트는 Cloud Foundry 나 Heroku의 배포를 감지하여 자동으로 enable되도록 지원한다.


    설정 프로퍼티 Configuration properties

    Bean에서 @ConfigurationProperties 를 사용하고 있다면, 사용자는 더이상 스프링 부트의 자동설정으로의 당신의 설정파일에 @EnableConfigurationProperties 를 추가해주지 않아도 된다. 기존엔 @EnableConfigurationProperties 의 값속성을 사용하거나 정식으로 하는 @Bean 정의를 함으로서 사용자의  @ConfigurationProperties클래스를 위해 스프링에 bean을 만들라고 했었다.

     

    메세징 Messaging

    JMS와 Rabbit endpoints 둘다 이제 설정을 통해 쉽게 disabled할 수 있게 되었다. 아무 설정없이 생성된 기본 컨테이너 팩토리가 있다면 또한 설정을 통해 커스터마이즈 할 수 있다.


    국제화 Internationalization

    자동설정된 MessageSource 인스턴스의 fallbackToSystemLocale 플래그를 spring.messages.fallback-to-system-locale 설정키를 통해 제어할 수 있다.


    자동설정 Auto-configuration

    자동설정 리포트는 "Unconditional classes"라 불리는 추가적인 섹션을 가진다. 이것은 어떤 클래스레벨 조건을 가지지 않은 자동설정 클래스를 리스트해준다. 예를 들면 언제나 어플리케이션 설정의 한 부분인 클래스 @SpringBootApplication(exclude=…​)또는 @EnableAutoConfiguration(exclude=…​)를 통해 수동으로 배재excluded된 설정에 리스트되어질 것이다.

    spring.autoconfigure.excludes 프로퍼티를 통해 자동설정 클래스에서 제외하는것 또한 가능해졌다. 유사하게 새 @ImportAutoConfiguration 어노테이션을 써서 특정 자동설정 클래스를 선별적으로 import해야하는 테스트를 할 수 있다.


    MVC 에러 핸들링 MVC Error handling

    error.include-stacktrace 프로퍼티는 스택 트레이스 속성을 MVC 에러 응답에 넣을지 결정하는데 사용한다. 옵션은 neveralways 또는 on-trace-param 이 있다. (기본값은 never).


    엑츄에이터 메트릭스 Actuator Metrics

    spring-boot-actuator 메트릭스가 export 와 aggregation을 지원하도록 확장되었다. 또한 자바8 특화 GaugeService 와 CounterService 구현이 제공되어 개선된 성능을 제공해준다.

    자세한 정보는 확장된 metrics documentation for details.


    부가적인 Health Indicators

    다음의 부가적인  HealthIndicators  제공되어 자동설정된다.:

    • Elasticsearch

    • Email

    • JMS


    새로운 actuator endpoints

    다음의 부가적인 actuator endpoints 가 스프링부트 1.3에 추가되었다:

    이름설 명

    /logfile

    로그파일로 접근할 수 있다.(설정해두었을경우).

    /flyway

    적용해둔 Flyway 데이터베이스 마이그레이션의 자세한 정보를 제공한다.

    /liquibase

    적용해둔 Liquibase 데이터베이스 마이그레이션의 자세한 정보를 제공한다.


    액츄에이터 종단을 위한 CORS지원 CORS support for actuator endpoints

    Actuator’s endpoint는 이제 CORS를 지원한다. 기본값은 disabled되어 있지만 endpoints.cors.allowed-origins.설정을 enabled함으로서 사용할 수 있다.


    /env와 /metrics를 위한 정규식 지원 Regex support for /env and /metrics

    사용자는 정규식을 사용하여 /env and /metrics actuator endpoints를 필터할 수 있다. 예를들면,  http://localhost:8080/metrics/.root..


    MVC 액츄에이터 종단을 위한 하이퍼미디어 Hypermedia for MVC actuator endpoints

    Actuator HTTP endpoints는 이제 사용자가 클래스패스에 Spring HATEOAS가 있으면 (예를 들면,  spring-boot-starter-hateoas를 통해), 하이퍼링크를 보여줄 수 있게 개선되었다. 새로운 "discovery page" 또한 모든 actuator endpoints의 링크들을 제공해준다. 클래스패스에 HAL browser의 webjar가 있으면 HAL browser를 위한 지원 또한 제공된다. 자세한 정보는 "Hypermedia for MVC Endpoints"  레퍼런스 섹션을 보자.


    Actuator docs endpoint

    새로운 spring-boot-actuator-docs 모듈이 스프링 부트 1.3에 추가되어 actuator 문서를 사용자 어플리케이션에 내장하는것이 가능해졌다. 일단 해당 모듈이 클래스패스에 있다면, 사용자는 /docs를 타입하여 각각 endpoint에서 리턴받은 데이터의 샘플을 포함하는 actuator endpoints의 정보를 얻을 수 있다.


    Disabling health indicators

    management.health.defaults.enabled 프로퍼티를 통해 기본 health indicators를 전부 손쉽게 disable시킬수 있다.


    TraceWebFilter options

    (HTTP 요청/응답의 디테일을 추적하는데 쓰이는Actuator TraceWebFilter 가 이제 더 많은 정보를 로그한다.이것을 사용하려면  management.trace.include 프로퍼티를 쓰면된다. (TraceProperties.Include enum을 참고하자)


    메이븐 지원 Maven Support


    Maven start/stop support and admin features

    메이븐 플러그인에  start 과 stop goals이 추가되었다. 이들은 메이븐의 방해없이 어플리케이션을 시작할 수 있게 해준다.( 다른 goals이 어플리케이션을 가동할수 있게 해줌으로서). 이 기술은 메이븐으로부터 통합테스트를 실행할때 자주 쓰인다.

    이 작업의 부산물로서 새로운 SpringApplicationAdminMXBean 인터페이스가 추가되어 스프링 부트 어플리케이션이 JMX를 통해 제어될수있게 허용되었다 (enabled 설정했을경우)


    Profile activation

    spring-boot-maven-plugin은 spring-boot:run.과 함께 사용할 수 있는 profiles 프로퍼티를 제공한다. 사용자의 pom.xml 에서 또는 커맨드라인에서 -Drun.profiles 을 사용함으로서 프로파일을 설정할 수 있다. 자세한 정보는 updated plugin documentation 를 참고하자.


    Ant Support

    스프링 부트는 이제 Ant를 통해 실행가능한 jars를 만들 수 있는 AntLib 모듈을 제공한다. 레퍼런스 문서의 "Spring Boot AntLib module" 섹션을 보라.


    Configuration property meta-data updates

    META-INF/spring-configuration-metadata.json 파일포멧이 업데이트되어 새로운 deprecation 과 hints 속성을 지원한다. Hints는 IDE개발자가 사용하여 더 나은 컨텐트 도움말을 보여줄 수 있으며, Deprecation은 deprecation과 replacement key를 설정할 수 있다. 만일 필요하다면 그러한 정보들은 프로퍼티의 getter에 @DeprecatedConfigurationProperty 를 추가함으로서 제공할 수 있다. updated appendix 에서 자세한 정보를 확인하자.

    기본값의 인식 또한 향상되었다: 만일 프로퍼티가 단일 인자를 가지는 하나의 메소드호출을 통해 초기화되면, 해당 인자가 초기값으로 간주될 것이다. (예를들면, Charset.forName("UTF-8") 은 기본값으로 UTF-8 으로 인식된다)

    자신의 툴과 앱에서 configuration meta-data을 사용하고 싶어하는 툴개발자를 위해 새로운 spring-boot-configuration-metadata 모듈이 추가되었다. 이것은 메타데이터를 읽고 그것을 리파지토리에서 빌드할 수 있는 API가 제공된다.


    Spring Boot CLI

    CLI 는 이제 의존성 해결dependency resolution동안 메이븐 settings.xml 의 리파지토리 설정을 사용할 수 있다. 리파지토리를 쓰려면 프로파일에 활성화해줘야 한다.

    CLI 는 또한 이제 실행가능한 WAR파일을 만들수 있다. $ spring war <filename.war> <script.groovy>를 사용하자.


    Miscellaneous

    다음과 같은 잡다한 업데이트 또한 스프링 1.3에 포함되었다:

    • 자바8을 쓰면 잭슨의 자바8 모듈이 자동적으로 등록된다.

    • TransactionTemplate bean 은 이제 TransactionAutoConfiguration의 일부로 포함되었다.

    •  MailServer bean 은 이제 spring.mail.jndi-name 프로퍼티를 사용하는 JNDI를 통해 얻어진다.JNDI 

    • 사용자는 이제  server.display-name 프로퍼티를 통해 서블릿 이름을 등록할 수 있다. (내장 서블릿 컨테이너를 쓸때) 

    • Flyway migration strategies 은 이제 FlywayMigrationStrategy bean를 통해 설정할 수 있다.

    • 새로운 SpringBootVersion  클래스가 추가되었다. (코어프레임워크의 SpringVersion 과 유사하다).

    • 사용자는 이제 amcrest matchers를 OutputCapturet과 같이 사용하여 특정한 결과의 테스트 산출물을 검증할 수 있다.

    •  사용자는 이제 Elasticsearch non local nodes를 사용해서 스프링 부트를 설정할 수 있다.

    • ApplicationPidFileWriter는 이제 fail-on-write-error프로퍼티를 통해 exception 설정을 할 수 있다. (업데이트된 javadoc를 참조하자)

    • 메이븐 플러그인은  spring-boot:run과 함께 사용할 수 있는 useTestClasspath 옵션이 추가되었다.

    • DB2와 Informix를 위한 추가적인 데이터베이스 heath 쿼리가 추가되었다.

    • 프로퍼티 바인딩 실패는 이제 더 나은 Exception 메시지를 보여준다.

    • @SpringBootApplication 어노테이션에  scanBasePackages 와 scanBasePackageClasses 속성이 추가되었다.

    • 새로운 AllNestedConditions 과 NoneNestedConditions 가 추가되었다. (현존하는 AnyNestedCondition과 비슷)

    • 활성화된 프로퍼티Active profiles는 이제 어플리케이션이 시작할때 출력로그output log를 프린트한다.

    • spring.main.banner-mode 프로퍼티는 CONSOLELOG 또는 OFF output 간에 스위치하는데 사용할 수 있다.

    • 원격 DevTools 은 이제 프록시 서버뒤에서 작동한다. (spring.devtools.remote.proxy.* 프로퍼티를 보자)

    • (Java 8 에서 지원하는) 잭슨의 parameter names module가 이제 사용자의클래스패스에 있으면 자동설정된다.

    • 스프링의 웹소켓 메세지 변환기message converters는 이제 자동설정된다.

    • 새로운 DelegatingFilterProxyRegistrationBean 클래스가 추가되어 DelegatingFilterProxy.를 거친 내장 서블릿 컨테이너와 함께 등록할 수 있게 되었다.


    Deprecations in Spring Boot 1.3.0

    •  setBannerMode사용을 유도하기 위해 Application.showBanner와 ApplicationBuilder.showBanner메소드는 디프리케이트되었다.

    • @ConditionalOnMissingClass는 이제  name.보다 value 속성을 사용하여 제공되기때문에 클래스 이름이 필요하다.

    • 아파치의 log4j 1.x의 서비스 종료(EOL)선언에 따라 Log4JLoggingSystem 는 디프리케이스되었다.

    • ConfigurableEmbeddedServletContainer setJspServletClassName 과 setRegisterJspServlet 메소드는 setJspServlet로 대체되었다.

    • EndpointMBean 클래스(와 서브클래스들)은 이제 생성자에 제공되려면 ObjectMapper 이 필요하다.

    • DropwizardMetricWriter 는 DropwizardMetricService로 대체되었다.

    • String[] 을 가지는 protected SpringApplication.afterRefresh 메소드는 ApplicationArguments.을 가지는 버젼에 의해 디프리케이트되었다.

    • CloudFoundryVcapEnvironmentPostProcessor에 의해 VcapEnvironmentPostProcessor 는 디프리케이트되었다.

    • LoggingSystem initialize 메소드는 LoggingInitializationContext받는 버젼에 의해 디프리케이트 되었다.

    • org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilder 는 디프리케이트되어 새 패키지  org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder를 사용해야한다.


    반응형

    반응형

    스프링 부트 1.2 릴리즈 노트 - 2015년 5월 16일


    원문: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.2-Release-Notes

    스프링 부트 1.1이후 업그레이드 된 것

    서블릿 3.1, 톰캣8 그리고 제티9 Servlet 3.1, Tomcat 8 and Jetty 9

    스프링부트는 이제 내장 서블릿 컨테이너로 톰캣8과 제티9를 사용한다. 이들은 서블릿 3.1과 바로 사용가능한 향상된 웹소킷을 제공해준다. 예전 버전을 선호한다면 여전히 톰캣7과 제티8을 쓸수있다 다운그레이드의 예제로 spring-boot-sample-tomcat7-jsp 와 spring-boot-sample-jetty8 을 참고하자.


    로깅 출력 Logging output

    기본 로깅설정이 업데이트되 서 더이상 로그파일을 쓰지않는다. 만약 파일출력을 원하면  logging.path 나 logging.file 프로퍼티를 사용할 수 있다. 또한 사용자는 여전히 logback.xml 파일을 추가함으로서 로깅을 완전하게 커스터마이즈할 수 있다.


    지속적 HTTP URI/body 디코딩 Consistent HTTP URI/body decoding

    consistent URI/body decoding을 위해 CharacterEncodingFilter 는 이제 자동으로 등록된다. 만약 UTF-8 이외의 것이 필요하면 spring.http.encoding.charset 프로퍼티를 쓰면된다 또는 CharacterEncodingFilter  가 모두 등록하는게 싫으면 spring.http.encoding.enabled 을 false로 설정하자.


    IgnoreDefaultModelOnRedirect with Spring MVC

    스프링 MVC 자동설정은 이제 RequestMappingHandlerAdapter의 ignoreDefaultModelOnRedirect 프로퍼티가 기본적으로 true 설정된다. 사용자가 모델속성값 model attributes을 리다이렉션 URL의 일부로 필요로 한다면, application.properties에 다음과 같이 추가하면 된다:

    spring.mvc.ignore-default-model-on-redirect=false

    잭슨 기본설정값 Jackson defaults

    잭슨 자동설정은 이제 MapperFeature.DEFAULT_VIEW_INCLUSION 과 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES의 기본값이 disabled로 설정된다. 이전의 설정을 사용하려면  application.properties에 다음과 같이 추가하면된다:

    spring.jackson.mapper.default-view-inclusion=true
    spring.jackson.deserialization.fail-on-unknown-properties=true

    몽고와 몽고DbFactory Bean Mongo and MongoDbFactory Beans

    MongoAutoConfiguration 클래스는 사용자가 자신의 MongoDbFactory bean을 정의해두면 더이상 Mongo bean을 등록하지않을것이다. 따라서 Mongo 데이터베이스에 접근하려면 자신의 MongoDbFactory bean을 선언할때 사용자가 Mongo bean을 등록하던가 또는  MongoDbFactory interface만을 쓰던가 결정해야한다.


    health.* 프로퍼티는 management.heath.*로 이동 Moved health.* properties to management.health.*

    헬스 관련 설정키들은 다른 management관련 프로퍼티와의 형평성에 맞춰 health 에서 management.health로 옮겨졌다. 


    VanillaHealthIndicator 클래스의 이름변경 Renamed VanillaHealthIndicator class

    VanillaHealthIndicator 클래스는 ApplicationHealthIndicator로 이름이 바뀌었다. 대부분의 사용자는 이 클래스를 직접적으로 연동하지않을테지만 이미 코드에 org.springframework.boot.actuate.health.VanillaHealthIndicator 를 import해서 쓰고 있다면, org.springframework.boot.actuate.health.ApplicationHealthIndicator로 바꿔줘야할것이다.


    하이버네이트 Hibernate

    SpringNamingStrategy 클래스가 org.springframework.boot.orm.jpa.hibernate 패키지로 이동되었다. 기존의 org.springframework.boot.orm.jpa.SpringNamingStrategy는 그대로 남아있지만 deprecated되었고 차후 릴리즈에서 삭제될 것이다.  hibernate-envershibernate-jpamodelgen그리고  hibernate-ehcache를 위한 Managed 의존성이 추가되었다.


    PersistenceExceptionTranslationPostprocessor

    PersistenceExceptionTranslationPostProcessor 이제 기본적으로 등록된다. exception translation set이 필요없으면 spring.dao.exceptiontranslation.enabled 프로퍼티를 false설정하자.


    헬스 JSON Health JSON

    The /health actuator endpoint JSON에 단일 HealthIndicator 와 연관되어 약간의 변화가 있었다. 이미 특정 JSON경로로 쿼리했었다면 아마 모니터링 툴을 업데이트해야할 수도 있다.


    익명의 health 접근 제한 Anonymous health access restrictions

    /health actuator endpoint 는 이제 익명의 접근이 제한된다. 익명으로 접근하면 endpoint는 health의 자세한 정보를 보여주지않으며 간단히 서버가 UP인지 DOWN만을 알려준다. 또한 endpoints.health.time-to-live에의해 명시된 시간만큼 응답이 캐시된다. 이러한 제약은 disalbed 할 수 있기때문에 1.1.x 버전대로 되돌리려면 endpoints.health.sensitive false.로 설정하자.


    스프링 4.1 Spring 4.1

    스프링 부트 1.2는 이제 Spring Framework 4.1.5 버전이 필수이며 더이상 Spring Framework 4.0 버전과 호환되지않는다.


    히카리 CP Hikari CP

    스프링 부트의 의존성관리에 의해 제공되는 com.zaxxer:HikariCP 의 버전은 이제 자바 8을 써야만한다. 자바6나 자바7을 쓴다면, com.zaxxer:HikariCP-java6 로 사용자의 의존성을 업데이트 해줘야한다.


    설정 프로퍼티 Configuration Properties

    spring.data.mongo.repositories.enabled 프로퍼티는 spring.data.mongodb.repositories.enabled로 이름이 바뀌었다.


    디프리케이션 Deprecations

    • org.springframework.boot.actuate.system.ApplicationPidListener 클래스는  ApplicationPidFileWriter로 대체되었다.

    • CLI @EnableRabbitMessaging 애노테이션은 spring-rabbit 프로젝트의  @EnableRabbit로 대체되었다..

    • http.mappers. 프로퍼티는 spring.jackson.serialization과 동일하므로 디프리케이트 되었다.

    • properties are deprecated in favor of theirspring.jackson.serialization. equivalents.

    • org.springframework.boot.json.SimpleJsonParser 는 "JSON Simple"라이브러리와의 혼동을 피하기 위해 BasicJsonParser 를 권장하기 위해 디프리케이트 되었다.


    주목할만한 새 기능 New and Noteworthy

    버전 업데이트 Version Updates

    스프링 부트 1.2를 빌드하려면 스프링 프레임워크 4.1이 필요한다. Jackson, Joda Time and Hibernate Validator 등등의 몇몇 서드파티 의존성이 이 릴리즈에 맞춰 업그레이드되었다. 기본 내장 서블릿 컨테이너가 이제 톰캣8과 제티9를 사용한다.(서블릿 3.1지원에 의해)


    @SpringBootApplication annotation

    사용자가 빈번하게 사용하는 3개의 애노테이션 @Configuration + @EnableAutoConfiguration + @ComponentScan과 동일하게 작동하는 새로운 @SpringBootApplication 애노테이션이 추가되었다.


    JTA Support

    스프링 부트 1.2는 이제 Atomkos또는 Bitronix 내장 트랜잭션 매니저를 사용하는 다수의 XA리소스를 위한 분산 JTA 트랜잭션을 지원한다. JTA transactions are also supported when deploying to a suitable Java EE Application Server.

    JTA 환경이 감지되면, 스프링의 JtaTransactionManager 가 트랜잰션을 관리하는데 사용된다.JMS, DataSource와 JPA 자동설정 빈들이 XA 트랜잭션을 지원하도록 업그레이드된다. 사용자는 @Transactional같은 표준 Spring 용어로 이 분산 트랜잭션을 사용할 수 있다.

    추가적으로, @EnableAutoConfiguration를 쓰지않아도 Atomkos 와 Bitronix을 더 쉽게 설정할 수 있는 지원 클래스들이 제공된다. 자세한 정보는 레퍼런스 문서의 JTA 섹션을 참고하자.


    JNDI Lookups

    완전한 Java EE 어플리케이션을 사용중이라면 JNDI로부터 DataSource와 JMS ConnectionFactory beans 둘다 검색lookup 할 수 있다. application.properties 또는 application.yml 파일의 spring.datasource.jndi-name 와 spring.jms.jndi-name 프로퍼티를 사용하자.


    Jackson customization

    이제  spring.jackson 프로퍼티를 사용하여 Jackson ObjectMapper를 커스터마이즈 할 수 있다. Jackson의 SerializationFeatureDeserializationFeatureMapperFeature,JsonParser.Feature 그리고 JsonGenerator.Feature 객체들을 serializationdeserializationmapperparser and generator 프로퍼티를 사용하여 커스터마이즈 할 수 있다. 예를 들면, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 를 enable하려면  application.properties에 다음과 같이 추가하면된다. 

    spring.jackson.deserialization.fail-on-unknown-properties=true

    추가적으로 spring.jackson.date-format 그리고 spring.jackson.property-naming-strategy프로퍼티가 스프링 1.2에 추가되었다.


    Banner Interface

    SpringApplication.setBanner(…​) 의 조합으로 사용할 수 있는 새로운 Banner 인터페이스가 추가되여 커스터마이즈된 배너 출력을 제공한다. 간단한 배너를 위해 src/main/resources/banner.txt 를 쓰는것을 여전히 권장하지만. 더 멋있게 보이려면 새 인터페이스가 유용할 것이다.


    Banner Properties

    src/main/resources/banner.txt 파일에  ${application.version}${spring-boot.version},${application.formatted-version} 그리고  ${spring-boot.formatted-version} 변수들을 사용할 수 있어 스프링부트나 어플리케이션의 버전을 출력할 수 있다.   (v1.2.0.RELEASE) 와 같이  formatted-version 을 괄호안에서 v접두어를 포함하여 쓸 수 있다.


    JMS Support

    자동설정이 이제 스프링 프레임워크 4.1의  @JmsListener 어노테이션을 지원한다. 사용자 클래스패스에 spring-jms.jaro이 있으면 @EnableJms 어노테이션 또한 자동설정된다.


    AMQP Support

    유사하게, 자동설정이 스프링 AMQP1.4의 @RabbitListener어노테이션을 지원한다. 사용자의 클래스패스에 spring-rabbit.jar 이 있다면  @EnableRabbit 어노테이션 또한 자동설정된다. 기존의 Rabbit 자동설정도 자동적으로 RabbitMessagingTemplate을 생성하도록 확장되었다.


    Spring Cloud Connectors

    자동설정은 spring-cloud-connectors 를 추가하였다. 새로운 spring-boot-starter-cloud-connectors POM 또한 릴리즈에 포함되었다. 자동설정은 @CloudScan 어노테이션과 기능적으로 동일하게 제공될것이다.


    Email Support

    새로운 spring-boot-starter-mail-starter POM이 자동설정 지원에 맞춰 추가되었다. 이메일을 보내기위해 사용자의 서비스에  JavaMailSender bean 을 주입할 수 있다. spring.mail.* 프로퍼티는 SMTP host 같은 아이템들을 커스터마이즈하는데 쓰인다.


    Undertow Embedded Servlet Container

    톰캣과 제티뿐만 아니라 스프링부트는 이제 내장 서블릿 컨테이너로서 Undertow 도 지원한다. 자세한 정보는 레퍼런스 문서를 보자.


    CLI Updates

    Creating a new project

    spring CLI 커맨드라인 툴은 새 init 옵션을 추가하여 start.spring.io를 통해 새로운 프로젝트를 만들 수 있다. 예를 들면 새 웹 어플리케이션을 만드려면 다음과 같이 타입하자:

    $ spring init -d=web myapp.zip


    CLI extensions

    CLI extension은 이제 CLI 자체로 인스톨/ 언인스톨이 가능하다. spring install <maven coordinates> 은 원격 jar를 찾아 CLI에 인스톨할 것이다. spring uninstall 은 이전에 설치된 extension을 삭제하는데 쓰인다.


    CLI changes

    CLI 는 이제 스프링의 @Cacheable 어노테이션을 감지하고 지원한다


    제티와 톰캣의 선언적 SSL Declarative SSL with Jetty and Tomcat

    SSL은 이제 다양한 server.ssl.* 프로퍼티를 확용하여 선언적으로 설정할 수 있다. 톰캣과 제티 둘다 지원한다. 자세한정보는 레퍼런스 문서를 보자


    Actuator Endpoints

    글로벌 endpoints.enabled 프로퍼티가 추가되어 만일 endpoint의 기본값이 enabled인지 disabled인지 설정이 할 수 있다. 이것은 사용자가 현재 opt-out 모델에서 opt-in 모델로 전환할 수 있게 해준다. 예를 들면, 사용자가 application.properties에 다음과같이 추가 함으로서 health 를 제외한 모든 endpoints를 disabled 시킬 수 있다.

    endpoints.enabled=false
    endpoints.health.enabled=true

    Metrics

    System metrics

    시스템 매트릭스는 이제 힙, 쓰레드, 클래스, 가비지 콜랙션의 정보를 제공한다.


    DataSource metrics

    DataSource pool metrics 는 이제 /metrics actuator endpoint를 통해 노출된다. 동작중인 연결갯수, Tomcat, Hikari and Commons DBCP connection pools의  자세한 풀 사용현황 등이 보여진다.


    Tomcat session metrics

    내장 서블릿 컨테이너로 톰캣을 사용중이라면, 매트릭스는 작동중인 세션수, 최대 세션수를 보여줄것이다.


    Dropwizard metrics

    Dropwizard’s MetricRegistry 로 온 아이템들은 이제 자동으로 /metrics endpoint에 보여질 것이다. 게이지와 카운터들은 단일 값으로 반영된다. 타이머, 미터, 히스토그램도 숫자 타입의 프로퍼티로 메트릭스에 설정되도록 확장될것이다.


    Health indicators

    JSON format

    /health actuator endpoint 는 이제 실제로 포함된 HealthIndicators와 무관하게 일관된 JSON을 돌려준다. 이 변화는 특정아이템을 위한 JSON 쿼리를 더 쉽게 만들어준다.


    DataSourceHealthIndicator

    DataSourceHealthIndicator는 이제 spring.datasource.validation-query 프로퍼티를 사용하여 (설정했을 경우) 데이터베이스의 건강상태를 체크할 것이다.


    DiskSpaceHealthIndicator

    이제 남은 디스크용량이  /health indicator 의 일부분으로 보고되어 특정 한계치 ( 기본값은 10M) 밑으로 떨어지면 DOWN 상태로 바뀌는데 사용된다. health.diskspace.path 와 health.diskspace.threshold 프로퍼티는 indicator를 커스터마이즈하는데 사용된다.


    Conditions

    @ConditionalOnProperty 어노테이션에 새로운 havingValue 와 matchIfMissing 속성값이 추가되어 이제 훨씬 더 복잡한 프로퍼티 매칭 컨디션property matching conditions을 만드는데 사용할 수 있다. 새 AnyNestedCondition 클래스 또한 추가되어 다른 @Conditions을 작성하는데 쓸 수 있다. 마지막으로  @ConditionalOnBean 어노테이션은 이제  클래스뿐만 아니라 String 속성으로도 선언할 수 있다.


    GSON Support

    이제 Jackson 출력물을 만드려면 Jackson을 대신해 GSON을 쓸 수 있게 되었다. Jackson은 여전히 권장되는 방식이고 기본설정값이지만 사용자가 Jackson을 필요로하는 Spring Boot’s Actuator를 쓰지않는다면, 메이븐/그레들 빌드로부터 Jackson라이브러리를 제외시킴으로서 GSON을 사용할 수 있다. 스프링 부트 1.2.2버전부터 Gson과 Jackson이 동시에 클래스패스에 있을 경우,  GSON을 사용하려면 spring.http.converters.preferred-json-mapper 를 gson으로 설정해주면 된다.


    EmbeddedServerPortWriter

    spring-actuator 프로젝트에 새로운 org.springframework.boot.actuate.system.EmbeddedServerPortFileWriter클래스가 추가되었는데 어플리케이션이 시작할때 파일에 내장 서버의 포트넘버를 적어서 사용할 수 있다.


    Log4j2

    Log4J 는 이제 추가된 spring-boot-starter-log4j2 start POM 로 대체할 수 있다. Logback은 여전히 기본설정으로 남아 권장하는 로깅시스템이다.


    Jersey auto-configuration

    Jersey의 자동설정이 지원된다. 자세한 정보는 레퍼런스를 참조하자.


    Apache-Commons DBCP2 Support

    아파치 commons-dbcp2 데이터베이스 커넥션 풀 라이브러리가 Tomcat, Hikari 그리고 DBCP (v1).에 이어 지원된다.


    Maven Plugin

    spring-boot-maven-plugin 의 repackage 작업을 disabled 시킬 수 있다. 이는 FatJars 가 필요없는 사용자가 spring-boot:run 을 쓰려고 할때 유용하다.


    Configuration meta-data

    이제 spring-bootspring-boot-autoconfigure 와 spring-boot-actuator jars에 추가적인 메타데이터 파일을 넣을수 있어 툴 개발자가 `application.properties 파일에 `code completion'을 제공하는데 사용할 수 있다. 어노테이션 프로세서 또한 @ConfigurationProperties 클래스로부터 자동적으로 사용자 자신의의 파일을 만들수 있게 


    Miscellaneous

    다음의 잡다한 업데이트들 또한 스프링 부트 1.2에 포함되었다.

    • RedisProperties클래스는 이제 database 필드를 포함한다. 

    • RelaxedDataBinder클래스는 alais 프로퍼티를 지원한다. 

    • 정규식은 이제 모든 keystosanitize 프로퍼티들과 함께 사용할 수 있다. 

    • Ansi출력은 이제 spring.output.ansi.enabled 프로퍼티로 설정가능하다. 

    •  /public/static/resources폴더에 favicon.ico 파일을 넣을 수 있다. (다른 정적 웹에셋과 같이) 

    •  ApplicationPidFileWriter (이전엔 ApplicationPidListener) 에 의해 쓰여진 파일의 위치는 spring.pidfile 프로퍼티를 사용하거나 PIDFILE 환경변수를 이용하여 특정할 수 있다.

    • 톰캣 DataSource 정보는 이제 JMX를 통해 자동으로 노출된다

    •  @Configuration 와 함께 어노테이트된 SpringBootServletInitializer 서브클래스들은 더이상 자신을 소스로 등록하기 위해 .configure 메소드를 오버라이드 할 필요 없다.

    • 사용자가 선호한다면 이제 application 프로퍼티의 포멧으로 XML을 쓸 수 있다


    반응형

    반응형

    스프링 부트 1.1 릴리즈 노트 - 2014년 6월 13일

    스프링 부트 1.0이후 업그레이드된 것

    테스트 의존성 Test dependences

    spring-boot-starter-test POM 은 더이상 spring-boot-starter-parent에 자동으로 포함되어지지 않는다. 부모POM에 테스트 의존성을 기본으로 추가한것은 좋은 아이디어 같았지만, 불행하게도 이들은 쉽게 수정하거나 제거되지않았다.

    1.0에서 1.1로 업그레이드되면서 이젠  spring-boot-starter-test 의존성을 직접 추가해줘야한다.


    레디스 드라이버 Redis Driver

    스프링 부트 1.0은  Lettuce Redis 드라이버를 사용했왔으나 1.1부턴 Jedis 드라이버로 교체되었다. 대부분의 사용자는 쉽게 알아차릴수 있을것이다.


    건강지표들 HealthIndicators

    HealthIndicator 인터페이스는 더 명시적 API로 바뀌었다. 이젠 언제나 부가적인 맥락정보(contextual information.)와 커스터마이징할 수 있는  Status 정보를 가지는 Health 인스턴스를 리턴한다. ApplicationContext에 등록된 모든 HealthIndicators를 리턴받은 Health 인스턴스는 새로 소개된  HealthAggregator.인터페이스의 구현체에 의해 합쳐졌다. 디폴트 HealthAggregator 구현체는 Status.UPStatus.DOWN orStatus.OUT_OF_SERVICE 와 같은 모든 내장된 Status 타입에의 최우선 순위를 가진다. 이 순위는 새로 소개된 custom Status 코드를 지원하려고 설정하거나 확장할수 있다.

     /health MVC endpoint 은 이제 다른 Status 코드의 다른 HTTP상태코드를 리턴하도록 설정할 수 있다.  이를테면, 기본값으로 Status.DOWN은 503을 Status.UP는 200을 리턴한다. Status 와 HttpStatus 의 매핑 역시 사용자의 필요에 따라 설정을 바꾸거나 확장할 수 있다.

     SimpleHealthIndicator클래스는 DataSourceHealthIndicator로 이름이 변경되었다. 추가적으로 Mongo, Redis, Rabbit 와 Apache Solr를 지원하는 basic health check 역시 추가되었다. Spring Boot Actuator를 쓸때 이러한 health checks 들은 자동적으로 추가된다. 데이터저장장치 나 메시징시스템 역시 자동으로 찾아준다. (auto-detected). 필요시 특정 health checks의 생성을 막을수도 있다.


    JMS 업데이트 JMS Changes

    JmsTemplate 클래스는 pubSubDomain 의 기본값이 이제 기존의 true에서  false 로 바뀌었다. TheJmsTemplateAutoConfiguration 클래스는 JmsAutoConfiguration로 이름이 바뀌었다. ActiveMQAutoConfiguration 클래스는 이제 activemq 서브패키지에 포함되었다.


    에러컨트롤러 ErrorController

    ErrorController 인터페이스와 연관된 클래스는 spring-boot-actuator 에서 spring-boot-autoconfigure로 옮겨졌다.  대부분의 사용자는 쉽게 알아차릴수 있을것이다. 하지만 몇몇의 패기지이름변경이 또한 바뀌었다.


    멀티파트 설정 Multipart Config

    MultiPartConfigFactory 는 MultipartConfigFactory 로 이름이 변경되었다. (note the different case).


    스프링 데이터 자동설정 패키지 Spring Data auto-configuration packages

    스프링 데이터 자동설정 클래스는 서브패키지로 옮겨졌다. 대부분의 사용자는 이것들을 직접적으로 사용하지는 않을테니잠 만일 @EnableAutoConfigurationexclude 속성을 통해 참조한다면 아마 import를 바꾸어야 할것이다.


    리엑터 Reactor

    스프링 부트 이제 Reactor 1.1.2 를 기본으로 제공한다. (스프링부트 1.0.x에서는 Reactor 1.0.1 제공해왔다). Reactor 1.1 에서는 reactor-tcp 모듈이 reactor-net로 이름이 변경되었으므로 사용자가 reactor-tcp 를 써왔다면 reactor-net 로 의존성을 바꿔야할것이다.


    메이븐에서 앱실행하기 Running your app using Maven

    스프링 부트는 이제 앱을 실행하기위한 프로세스를 fork할수 있다. (예를 들면 mvn spring-boot:run를 사용). 이것의 명확한 의미는 커맨드라인의 인수들 arguments 은 더이상 어플리케이션에 사용할 수 없다. 자세한 정보는 예제 를 참조하자.



    주목할만한 새 기능

    버젼 업데이트 Version Updates

    몇몇의 서드파티 의존성이 곧 등장할  Spring IO Platform 릴리즈에 정의된 버젼에 맞게 업그레이드 되었다. 스프링 부트 그래들 플러그인을 통해  custom version meta-data file 또한 사용할 수 있게 되었다.


    템플릿 추가 지원 Additional Templating Support

     현재 지원하는 Thymeleaf and JSP 에 추가하여 Freemarker, Groovy 와 Velocity 템플릿을 지원한다.

     

    메트릭스와 헬스 종단 Metrics and Health Endpoints

    /metrics actuator endpoint는 이제 힙 디테일, 클래스 로딩수, 쓰레드 정보, 가비지 컬랙션 통계와 같은 더 많은 정보를 포함한다. HealthIndicator는 다수의 bean을 등록할 수 있게 향상되었으며 이미 지원하는 JDBC indicator와 함께 Redis, Mongo and RabbitMQ indicators 또한 out of the box로 지원할 수 있게 추가되었다 


    메이븐 플러그인 Maven Plugin

    스프링 부트 메이븐 플러그인은 이제 excludes 를 지원하여 사용자의 "fat" jar의 일부분으로 번들되는 라이브러리들을 필터링 할 수 있게 되었다.  플러그인 문서 또한 이제 하나의 maven site서 발행할 수 있게 되었다.


    그래들 플러그인 Gradle Plugin

    스프링 부트 그래들 플러그인은 spring-boot-dependencies POM에 기반하여 이제 자동으로 exclusion 룰을 적용한다. 자세한 정보는 레퍼런스 문서 를 참고하자.


    커스텀 배너 Custom Banner

    사용자의 클래스패스에 이제 banner.txt 파일을 추가하거나  banner.location 프로퍼티를 설정함으로서 커스텀 배터를 추가할 수 있다. 


    Flyway 데이터베이스 마이그레이션 Flyway database migrations

    Flyway 데이터베이스 마이그레이션을 위한 자동설정auto-configuration을 제공한다.


    리퀴베이스 데이터베이스 마이그레이션 Liquibase database migrations

    Liquibase 데이터베이스 마이그레이션을 위한 자동설정auto-configuration을 제공한다.


    몽고 Mongo

    몽고 자동설정이 향상되어  Mongo,MongoDbFactory 나 MongoTemplate beans을 인젝션할 수 있다. GridFS 지원또한 자동설정된  GridFsTemplate bean을 통해 추가할 수 있다.


    젬파이어 GemFire

    Spring Data GemFire를 지원하는 새로운 spring-boot-starter-data-gemfire 모듈을 추가되었다.


    호넷Q HornetQ

     새로운 spring-boot-starter-hornetq starter POM과 함께 HornetQ JMS broker를 위한 자동설정이 추가되어 클래스패스에  org.hornetq:hornetq-jms-server 를 쓰면 내장 HornetQ broker가 기본으로 자동설정된다. 자세한 정보는 문서를 참고하자.


    일레스틱 서치 Elasticsearch

     새로운 spring-boot-starter-data-elasticsearch starter POM과 함께 Elasticseach를 위한 자동설정이 추가되었다.


    아파치 Solr Apache Solr

    새로운  spring-boot-starter-data-solr starter POM과 함께 아파치 Solr를 위한 자동설정이 추가되었다.


    웹지원 향상 Web Improvements

    Spring HATEOAS와 Spring Data Rest MVC를 위한 자동설정이 추가되었다. 멀티파트 파일 업로드 지원도 기본으로 enabled되게 설정이 바뀌었다.


    스프링 소셜 Spring Social

    페이스북, 트위터, 링크드인 커넥터를 쓸수있는 스프링 소셜의 자동설정이 추가되었다.


    스프링 모바일 Spring Mobile

    SitePreferenceHandler이 스프링 모바일 자동설정에 업데이트되었다.


    스프링 인티그레이션  Spring Integration

    Spring Integration의 자동설정이 추가되었다. 사용자의 클래스패스에 Spring Integration이 있으면 Spring Integration JMX beans뿐만 아니라  @EnableIntegration 이 자동으로 설정된다.


    스프링 WS Spring-WS

    새로운 spring-boot-starter-ws이 스프링 웹서비스 지원을 통해 쓸 수 있게 되었다.


    잭슨 Jackson

    Jackson JSON marshaling 라이브러리 지원이 향상되었다. 사용자는 이제 JodaModule andJSR310Module 뿐만 아니라 Jackson의 ObjectMapper 를 위한 자동설정빈을 쓸 수 있으며 이들이 자동으로 적용된 자신만의 Module bean 또한 추가할 수 있다.


    히카리 데이터소스 HikariDataSource

    Hikari Connection Pool 라이브러리가 추가되었다. Tomcat DBCP를 쓰면 하지 못하는 사용자의 클래스패스에 위치한 HikariCP 라이브러리를 간단히 보장해준다.


    새 조건부 어노테이션 New Conditional Annotations

    두개의 새로운  @Conditional 어노테이션이 추가 되었다. @ConditionalOnProperty 는 Environment 프로퍼티의 설정을 기반으로 bean을 조건부로 enabling해주는 기능이고  @ConditionalOnJava 는  JVM 버젼을 기반으로 bean을 enable 해주는 것이다.



    반응형

    + Recent posts