반응형

스프링 부트와 OAuth2

Spring Boot And OAuth2

(원문소스: https://spring.io/guides/tutorials/spring-boot-oauth2/)

Authorization 서버 돌리기 Hosting an Authorization Server

이 섹션에서 우리는 우리가 만들 앱을 여전히 페이스북과 Github을 사용하여 인증이 가능하지만 스스로 억세스 토큰을 만들 수 있는 완전무결fully-fledged한 OAuth2 인가서버Authorization Server로 만들도록 수정할 것이다. 이들 토큰은 이후 보호된secure 백엔드 리소스에서 사용되거나 똑같은 방식으로 보호되고 있는 다른 어플리케이션과의 SSO를 하는데 쓸 수 있다.

인증 설정을 정리하기 Tidying up the Authentication Configuration

인가서버Authorization server 기능을 시작하기 전에, 우리는 두개의 외부 제공자를 위한 설정코드를 깔끔하게 정리해보자. ssoFilter() 메소드안의 몇몇 코드가 중복되어있으므로 이를 공유 메소드로 가져올 것이다:

SocialApplication.java
private Filter ssoFilter() {
  CompositeFilter filter = new CompositeFilter();
  List<Filter> filters = new ArrayList<>();
  filters.add(ssoFilter(facebook(), "/login/facebook"));
  filters.add(ssoFilter(github(), "/login/github"));
  filter.setFilters(filters);
  return filter;
}

예전 메소드의 중복된 코드를 가지는 새로운 편리한 메소드이다:

SocialApplication.java
private Filter ssoFilter(ClientResources client, String path) {
  OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter(
      path);
  OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(client.getClient(),
      oauth2ClientContext);
  facebookFilter.setRestTemplate(facebookTemplate);
  facebookFilter.setTokenServices(new UserInfoTokenServices(
      client.getResource().getUserInfoUri(), client.getClient().getClientId()));
  return facebookFilter;
}

앱의 최종 버전에서 별도의 @Beans으로 선언된OAuth2ProtectedResourceDetails 그리고 ResourceServerProperties를 합친  새로운 wrapper 객체 ClientResources를 사용하였다:

SocialApplication.java
class ClientResources {

  private OAuth2ProtectedResourceDetails client = new AuthorizationCodeResourceDetails();
  private ResourceServerProperties resource = new ResourceServerProperties();

  public OAuth2ProtectedResourceDetails getClient() {
    return client;
  }

  public ResourceServerProperties getResource() {
    return resource;
  }
}

이 Wrapper와 전에 사용한 같은 YAML 설정을 함께 사용할 수 있지만, 각각 제공자를 위한 단일 메소드를 가지게 되었다:

SocialApplication.java
@Bean
@ConfigurationProperties("github")
ClientResources github() {
  return new ClientResources();
}

@Bean
@ConfigurationProperties("facebook")
ClientResources facebook() {
  return new ClientResources();
}

인가서버 돌리기 Enabling the Authorization Server

우리가 우리의 앱에 OAuth2 인가서버를 돌리려는데, 최소한 몇몇 기본기능(하나의 클라이언트와 억세스토큰을 만드는 능력)으로 시작하려고 수많은 삽질을 할 필요없다, 하나의 인가서버Authorization Server는 종단의 묶음이상이하도 아니다. 이들은 스프링 MVC 핸들러로서 스프링 OAuth2에서 구현되었다. 우리는 이미 보호된secure 어플리케이션을 가지고 있기때문에, 실제로 @EnableAuthorizationServer 어노테이션을 추가해주기만 하면 된다:

SocialApplication.java
@SpringBootApplication
@RestController
@EnableOAuth2Client
@EnableAuthorizationServer
public class SocialApplication extends WebSecurityConfigurerAdapter {

   ...

}

이 곳에 새 어노테이션을 추가함으로서, 스프링 부트는 우리가 지원하고 싶은 OAuth2 클라이언트에 약간의 디테일만 제공해주면, 그에 필요한 종단을 설치하고 그들에 시큐리티 설정을 해줄 것이다:

application.yml
security:
  oauth2:
    client:
      client-id: acme
      client-secret: acmesecret
      scope: read,write
      auto-approve-scopes: '.*'

이 클라이언트는 우리가 외부제공자로서 필요한 facebook.client* and github.client* 와 동일하다. 외부제공자를 우리 앱에서 사용하기 위해 우리는 클라이언트 id 와 secret을 받아서 등록해 주어야 한다. 우리의 경우, 이미 스스로 똑같은 기능을 제공하고 있으기 때문에 작동확인을 위한 (최소한 하나의) 클라이언트가 필요하다.

우리는 모든 스쿠프와 정규식 매칭되는 auto-approve-scopes설정을 하였다. 이는 우리가 이 앱을 실제 시스템에 돌릴때는 필요하지않지만,  스프링 OAuth2가 우리의 사용자가 억세스 토큰을 요구할 때 설정된게 없다면 사용자에게 팝업을 띄울 대체자나 whitelabel 승인페이지 없이 빠르게 무언가를 동작할 수 있게 해준다. 토큰을 승인하는 명시적 승인절차를 추가하려면 UI를 제공하여 (/oauth/confirm_access위치의) whitelabel 버전을 대체해주어야 한다.

억세스토큰 받는 법 How to Get an Access Token

억세스 토큰은 이제 우리의 새 인가서버에서 사용이 가능하다. 토큰을 얻는 가장 간단한 방법은 "acme" 클라이언트로서 하나를 긁어오는 것이다. 앱을 실행하고 다음과 같이 curl을 보내면 이것을 확인할 수 있다:

$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=client_credentials
{"access_token":"370592fd-b9f8-452d-816a-4fd5c6b4b8a6","token_type":"bearer","expires_in":43199,"scope":"read write"}

클라이언트 credential 토큰은 어떤한 환경하에서(이를테면 토큰 종단이 동작하는지 테스트하는 등) 유용하다. 하지만, 우리 서버의 모든 기능을 장점을 취하려면 사용자들이 토큰을 만들수 있게 해줘야한다. 우리 앱에 보통의 사용자 행동을 통해 토큰을 받으려면, 사용자를 인증할 수 있어야 한다. 앱이 시작할 때 로그를 주의깊게 살펴보면, 아마 기본 스프링부트 사용자 (Spring Boot User Guide 참고)를 위한 임의 패스워드에 대한 로그를 볼 수 있을 것이다. 당신은 이 패스워드를 "사용자" ID와 같이 사용하여 사용자 방식으로 토큰을 받을 수 있다:

$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=...
{"access_token":"aa49e025-c4fe-4892-86af-15af2e6b72a2","token_type":"bearer","refresh_token":"97a9f978-7aad-4af7-9329-78ff2ce9962d","expires_in":43199,"scope":"read write"}

"…​"이 있는 곳을 실제 패스워드로 바꿔줘야한다. 이것을 "패스워드 승인password grant"라고 부르는데 억세스 토큰을 위해 사용자아이디와 패스워드를 교환하는 것이다. 패스워드 승인은 당신이 credential을 저장하거나 유효한지 확인할수 있는 로컬 사용자 데이터베이스를 가지고 있는 네이티브 또는 모바일 어플리케이션에 적합하다. 웹앱 또는 어떤 "소셜"로그인을 가진 앱에서는 "인가코드 승인authorization code grant"이 필요하다. 이는 리다이렉트를 처리하기 위해 그리고 외부제공자로부터 사용자 인터페이스를 랜더할수 있는 브라우저가 필요하다는 의미이다.

클라이언트 어플리케이션 만들기 Creating a Client Application

우리 인가서버를 위한 클라이언트 어플리케이션은 그 자체로 하나의 웹어플리케이션이다. 스프링 부트로 손쉽게 만들수 있는데 다음 예제를 보자:

ClientApplication.java
@EnableAutoConfiguration
@Configuration
@EnableOAuth2Sso
@RestController
public class ClientApplication {

	@RequestMapping("/")
	public String home(Principal user) {
		return "Hello " + user.getName();
	}

	public static void main(String[] args) {
		new SpringApplicationBuilder(ClientApplication.class)
				.properties("spring.config.name=client").run(args);
	}

}

클라이언트를 위한 요소들은 (단지 사용자의 이름을 출력하는) 하나의 홈페이지와 (spring.config.name=client를 통한) 설정파일을 위한 명시적 이름이다. 우리가 이 앱을 돌리면 우리가 다음과 같이 제공한 설정파일을 찾을 것이다:

client.yml
server:
  port: 9999
security:
  oauth2:
    client:
      client-id: acme
      client-secret: acmesecret
      access-token-uri: http://localhost:8080/oauth/token
      user-authorization-uri: http://localhost:8080/oauth/authorize
    resource:
      user-info-uri: http://localhost:8080/me

이 설정은 우리가 메인 앱에서 사용한 값들과 거의 비슷한 것 같지만, 페이스북이나 Github 대신에 "acme" 클라이언트를 가진다. 이 앱은 메인 앱과의 충돌을 피하기 위해 9999포트를 통해 동작되며 우리가 아직 구현하지 않은 사용자 정보 종단인 "/me"를 참조한다.

사용자 정보 종단 보호하기 Protecting the User Info Endpoint

우리가 페이스북과 Github을 사용한 것과 같이 새 인가서버에서 싱글사인온을 사용하려면, 생성된 억세스 토큰에 의해 보호되는 /user 종단을 만들어줘야한다. 지금까지 우리는 /user 종단을 가지고 있었고 사용자가 인증할 때 만들어진 쿠키에 의해 보호되고 있었다. 이를 추가적으로 로컬에서 승인된 억세스 토큰으로 보호하려면, 현존의 종단을 재사용하여 새로운 경로로 alias를 만들어주면 된다:

SocialApplication.java
@RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
  Map<String, String> map = new LinkedHashMap<>();
  map.put("name", principal.getName());
  return map;
}

 우리는 브라우저에 노출하고 싶지 않는 정보를 숨기기 위해, 또한 두개의 외부 인증 제공자사이의 종단의 행동을 구별하기 위해 Principal을 Map 으로 변환하였다. 원칙적으로 우리는 여기에 제공자의 구제적인 고유식별자 또는 만일 이용가능하다면 이메일과 같더 자세한 정보를 추가 할 수 있다.

이제 우리의 앱에 의해 만들어진 억세스 토큰에 보호되는 "/me" 경로는 (인가서버일 뿐아니라) 하나의 리소스 서버이다. 우리는 새 설정클래스를 만들었다. (메인앱의 n 내부 클래스로서, 하지만 이는 또한 별도의 독립실행가능한standalone 클래스로 나눌 수 있다.):

SocialApplication.java
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration
    extends ResourceServerConfigurerAdapter {
  @Override
  public void configure(HttpSecurity http) throws Exception {
    http
      .antMatcher("/me")
      .authorizeRequests().anyRequest().authenticated();
  }
}

추가적으로 우리는 메인 어플리케이션의 시큐티리를 위해 @Order를 명시해줘야한다:

SocialApplication.java
@SpringBootApplication
...
@Order(6)
public class SocialApplication extends WebSecurityConfigurerAdapter {
  ...
}

@EnableResourceServer 어노테이션은 기본값으로 @Order(3)를 가지는 시큐리티 필터를 만든다. 메인 어플리케이션 시큐리티를 @Order(6)로 옮김으로서 우리는 "/me"이 우선순위를 가지도록 보장해줄 수 있다.

OAuth2 클라이언트 테스트하기 Testing the OAuth2 Client

두개의 앱을 모두 동작시키고 브라우저오 127.0.0.1:9999를 방문하여 새 기능을 테스트해보자. 클라이언트 앱을 로컬 인가서버로 리다이렉트될 것이고 이후 사용자에게 페이스북이나 Github으로 인증을 선택하도록 할 것이다.  일단 테스트 클라이언트로 제어가 완전히 되돌아오면, 로컬 억세스 토큰은 승인되고 인증이 완료된다. (브라우저에서 "Hello" 메시지를 볼 수 있을 것이다). 만인 이미 Github이나 페이스북으로 인증이 되어있다면 이 원격 인증을 아마 알아차리지 못했을 것이다.

 테스트 클라이언트 앱에서 "localhost"를 사용하지 말자. 이는 메인 앱으로부터 쿠키를 빼앗아오기 때문에 인증이 엉망이 되어버린다. 만일 127.0.0.1이 "localhost"와 매핑되어 있지 않으면, 당신은 당신의 OS에서 이를 설정해주어야한다. (이를테면 "/etc/hosts") 또는 만일 있다면 다른 로컬 주소를 사용하자.

비인증 사용자를 위한 에러페이지 추가하기 Adding an Error Page for Unauthenticated Users

이 섹션에서 우리는 이전에 만든 Github 인증으로 전환하는 logout 앱을 수정하여 인증받지 못한 사용자를 위한 약간의 처리를 추가해볼 것이다. 동시에 우리는 특정 Github 조직에 속해있는 사용자들만 인증하도록 허용하는 규칙을 포함하도록 인증로직을 확장해 볼 것이다. "조직organization"은 Github만의 특정 개념이지만 유사한 규칙을 이를테면 구글같은 당신이 특정 도메인으로부터의 사용자만을 인증하도록 다른 제공자에게 적용할 수 있다

Github으로 전환하기 Switching to Github

logout 예제는 OAuth2 제공자로서 페이스북을 사용했다. 우리는 이를 로컬 설정을 수정함으로서 손쉽게 Github으로 전환할 수 있다:

application.yml
security:
  oauth2:
    client:
      clientId: bd1c0a783ccdd1c9b9e4
      clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
      accessTokenUri: https://github.com/login/oauth/access_token
      userAuthorizationUri: https://github.com/login/oauth/authorize
      clientAuthenticationScheme: form
    resource:
      userInfoUri: https://api.github.com/user

클라이언트에서 인증실패 감지하기 Detecting an Authentication Failure in the Client

클라이언트에서 우리는 인증하지 못한 사용자들에게 약간의 피드백을 제공해주려고 한다. 이를 제공해주려고 우리는 정보 메시지를 가진 div를 추가하였다:

index.html
<div class="container text-danger" ng-show="home.error">
There was an error (bad credentials).
</div>

이 텍스트는 컨트롤러에서 "error'플래그가 설정되었을때만 보여진다. 이를 위해 다음과 같이 약간의 코드가 필요하다:

index.html
angular
  .module("app", [])
  .controller("home", function($http, $location) {
    var self = this;
    self.logout = function() {
      if ($location.absUrl().indexOf("error=true") >= 0) {
        self.authenticated = false;
        self.error = true;
      }
      ...
    };
  });

"home" 컨트롤러는 브라우저가 로드될때 그 위치를 체크한다. 만일 URL에 "error=true"가 있다면 플래그가 설정된다.

에러페이지 추가하기 Adding an Error Page

클라이언트에서 플래그설정을 지원하기 위해 인증에러를 잡아내서 쿼리 파라메터안에 설정된 이 플래그를 가진 홈페이지로 리다이렉트할 수 있어야한다. 따라서 우리는 다음과 같은 보통의 @Controller내에 하나의 종단이 필요하다:

SocialApplication.java
@RequestMapping("/unauthenticated")
public String unauthenticated() {
  return "redirect:/?error=true";
}

이 예제 앱에서 우리는 메인 어플리케이션 클래스에 이것을 넣어주었다. 이제 이는 @RestController가 아니라 @Controller로 바꿔줌으로서 이제 리다이렉트를 처리할 수 있다. 우리가 마지막으로 해주어야할 것은 비인증 응답(UNAUTHORIZED로 알려진 HTTP 401)을 "/unauthenticated" 종단으로매핑해주는 것이다. 그냥 다음과 같이 추가해주면 된다:

ServletCustomizer.java
@Configuration
public class ServletCustomizer {
  @Bean
  public EmbeddedServletContainerCustomizer customizer() {
    return container -> {
      container.addErrorPages(
          new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
    };
  }
}

(이 예제에선, 단지 간결함을 위해 이는 메인 클래스 안에 내장된 클래스nested class로서 추가된다)

서버에서 401 만들기 Generating a 401 in the Server

401 응답은 사용자가 Github으로 로그인 안하거나 못할 경우 이미 스프링 시큐리티로 부터 보내지고 있다. 따라서 앱은 사용자가 인증에 실패할 경우 이미 동작하고 있는 것이다. (예를 들어, 토큰승인을 거절함으로서)

여기에 양념을 조금 더 쳐서, 우리는 인증 규칙을 확장하여 올바른 "조직"에 속해있지않은 사용자의 인증을 거부할 것이다. 사용자에 대해 더 많은 정보는 Github API를 사용하면 쉽다. 우리는 단지 인증 처리의 올바를 부분에 이것을 플러그해주기만 하면 된다. 운좋게도 이러한 간단한 사용예를 위해 스프링 부트는 손쉬운 확장점을 제공해주고 있다: 만일 우리가 타입 AuthoritiesExtractor @Bean을 선언해주면, 이는 인증된 사용자의 인가Authorities (보통은 "역할roles")를 구성하는데 사용될 것이다. 우리는 사용자가 올바른 조직에 속해있는지 확인하는데 사용할 수 있으면, 아닐 경우 예외가 발생한다:

SocialApplication.java
@Bean
public AuthoritiesExtractor authoritiesExtractor(OAuth2RestOperations template) {
  return map -> {
    String url = (String) map.get("organizations_url");
    @SuppressWarnings("unchecked")
    List<Map<String, Object>> orgs = template.getForObject(url, List.class);
    if (orgs.stream()
        .anyMatch(org -> "spring-projects".equals(org.get("login")))) {
      return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
    }
    throw new BadCredentialsException("Not in Spring Projects origanization");
  };
}

우리가 이 메소드안에 OAuth2RestOperations을 autowired했다는걸 기억하자, 우리는 이를 인증받은 사용자의 행동을 위한 Github API에 접근하는데 사용할 수 있다. 우리는 이렇게 하여 (스프링 오픈소스 프로젝트를 저장하는데 사용되는 조직인) "spring-projects"와 매치되는 조직들중 하나를 찾는다. 당신이 스프링 엔지니어링 팀의 일원이 아니라면 성공적으로 인증을 받기 위해 당신 자신의 값으로 대체하면 된다. 매치되는 값이 없다면 BadCredentialsException 예외가 발생하여 스프링 시큐리티가 이를 받아서 401 응답을 낸다.

 명맥하게 위의 코드는 어떤것은 Github에, 어떤것은 다른 OAuth2제공자에게 적용하도록 다른 인증 규칙로 일반화할 수 있다. 당신이 필요한건 제공자의 API에 대한 약간의 지식과 OAuth2RestOperations이 전부다.

결 론 Conclusion

우리는 스프링 부트와 스프링 시큐리티를 사용하여 최소한의 노력으로 수많은 스타일을 가진 앱을 만드는 법을 보아왔다. 모든 예제를 통해 작동하는 주요 테마는 외부 OAuth2 제공자를 사용하는 "소셜"로그인이다. 마지막 예제조차 "내부적으로" 그러한 서비스를 제공하는데 사용된다. 왜냐하면 이는 외부 제공자들이 가지고 있는 동일한 기본기능을 가지기 때문이다. 모든 예제 앱들은 더욱 구체적인 사용예를 위해 손쉽게 확장하고 재설정이 가능한데 보통은 하나의 설정파일을 수정하는것 이상도 이하도 아니다. 만일 당신이 페이스북이나 Github (또는 유사한) 제공자를 통해 등록하기하고 자신의 호스트 주소에 클라이언트 credentials을 얻으려면 자신만의 서버 버전을 사용해야한다는 것을 기억하자 또한 이들 credential을 소스컨트롤에 올리는 일이 없도록 주의하자!


반응형

반응형

스프링 부트와 OAuth2

Spring Boot And OAuth2

(원문소스: https://spring.io/guides/tutorials/spring-boot-oauth2/)

OAuth2 클라이언트의 수동설정 Manual Configuration of OAuth2 Client

이 섹션에서 우리는 @EnableOAuth2Sso 어노테이션의 '마법'으로 이미 만들어본  logout 앱을 모든 설정을 명시적으로 직접 설정하도록 수정해볼 것이다.

클라이언트와 인증 Clients and Authentication

 @EnableOAuth2Sso에는 OAuth2 클라이언트와 인증의 2가지 기능이 있다. 클라이언트는 재사용이 가능하고, 또한 당신의 인가서버Authorization Server (우리의 경우 페이스북)가 제공하는(우리의 경우 Graph API) OAuth2 리소스들과 상호작동하는데 사용할 수 있다. 이 인증 기능은 당신의 앱을 스프링시큐리티와 맞도록 맞춰준다. 일단 페이스북과 소통하는게 끝나면, 당신의 앱은 정확히 다른 보호된 스프링 앱secure Spring app과 똑같이 동작할 것이다.

클라이언트 

클라이언트 부분은 스프링 시큐리티 OAuth2에 의해 제공되며 @EnableOAuth2Client라는 다른 어노테이션을 쓴다. 따라서 이 수정의 첫 걸음은 @EnableOAuth2Sso를 삭제하고 더 낮은 레벨의 어노테이션으로 변경하는 것이다:

SocialApplication
@SpringBootApplication
@EnableOAuthClient
@RestController
public class SocialApplication extends WebSecurityConfigurerAdapter {
  ...
}

일단 이렇게 하면 우리는 우리를 위해 만들어진 유용한 몇가지 기능을 가지게 된다. 먼저 OAuth2ClientContext를 주입할 수 있어, 우리의 시큐리티 설정에 추가할 인증 필터를 만드는데 쓸 수 있다:

SocialApplication
@SpringBootApplication
@EnableOAuthClient
@RestController
public class SocialApplication extends WebSecurityConfigurerAdapter {

  @Autowired
  OAuth2ClientContext oauth2ClientContext;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/**")
      ...
      .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
  }

  ...

}

이 필터는 우리가 OAuth2ClientContext를 사용하는 새 메소드에서 생성된다:

SocialApplication
private Filter ssoFilter() {
  OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
  OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
  facebookFilter.setRestTemplate(facebookTemplate);
  facebookFilter.setTokenServices(new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()));
  return facebookFilter;
}

이 필터 역시 페이스북에 등록된 클라이언트 설정에 대해 알아야한다:

SocialApplication
  @Bean
  @ConfigurationProperties("facebook.client")
  OAuth2ProtectedResourceDetails facebook() {
    return new AuthorizationCodeResourceDetails();
  }

그리고 인증을 완료하려면 페이스북의 사용자 정보 종단user info endpoint이 어딘지 알아야 한다:

SocialApplication
  @Bean
  @ConfigurationProperties("facebook.resource")
  ResourceServerProperties facebookResource() {
    return new ResourceServerProperties();
  }

이 두개의 "정적인" 데이터 객체(facebook()과 facebookResource())에 우리가  @ConfigurationProperties. 로 설정된 @Bean 을 사용했다는 것을 알아두자. 이것은 우리가 application.yml를, 설정의 접두어를 security.oauth2대신 facebook로 사용하는 약간 새로운 포멧으로 전환할 수 있다는 것을 의미한다:

application.yml
facebook:
  client:
    clientId: 233668646673605
    clientSecret: 33b17e044ee6a4fa383f46ec6e28ea1d
    accessTokenUri: https://graph.facebook.com/oauth/access_token
    userAuthorizationUri: https://www.facebook.com/dialog/oauth
    tokenName: oauth_token
    authenticationScheme: query
    clientAuthenticationScheme: form
  resource:
    userInfoUri: https://graph.facebook.com/me

리다이렉트 처리하기 Handling the Redirects

마지막 수정은 우리의 앱에서 페이스북으로 리다이렉트를 명시적으로 지원하게 만들어주는 것이다. 이는 서블릿 Filter를 가진 Spring OAuth2에서 처리되어진다. 이 필터는 어플리케이션 컨텍스트application context에서 이미 사용가능한데 우리가 @EnableOAuthClient선언을 해두었기 때문이다. 우리가 이 필터를 엮어서 사용하려면 그냥 스프링부트 어플리케이션의 올바른 순서right order안에서 호출해 주기만 하면 된다. 이것을 위해 우리는 FilterRegistrationBean이 필요하다:

SocialApplication.java
@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(
    OAuth2ClientContextFilter filter) {
  FilterRegistrationBean registration = new FilterRegistrationBean();
  registration.setFilter(filter);
  registration.setOrder(-100);
  return registration;
}

우리는 이미 사용가능할 필터를 Autowire해두었고, 이 필터를 메인 스프링 시큐리티 필터가 불러지기 전에 호출되도록 충분히 낮은 순서로 등록하였다. 이 방법으로 우리는 인증요청의 예외exception발생을 통해 리다이렉트를 처리할 수 있다.

이 단계까지의 수정을 통해 앱은 동작이 가능하며, 실행시 지난 섹션에서 만드 logout예제와 동일하게 동작한다. 설정을 단계별로 쪼개고 몇시적으로 우리에게 가르쳐줌으로서 스프링 부트가 해주는 마법과 같은 자동화는 더이상 없다. (이는 단지 설정의 뼈대일 뿐이다). 그리고 이는 막바로 사용가능하게 자동으로 제공되었던 기능을 확장하기 위한, 우리 자신의 의사과 비지니스 요구사항을 추가하기 위한 준비가 된다.

Github으로 로그인하기 Login with Github

이 섹션에서 우리가 이미 페이스북 링크를 통해 로그인이 가능했던 앱을 Github 인증을 추가하여 사용자가 선택할 수 있는 링크를 추가하도록 기존의 app을 수정할 것이다. 

Github 링크 추가하기 Adding the Github Link

클라이언트에서의 수정은 매우 사소하다. 단지 또다른 링크만 추가해주면 된다:

index.html
<div class="container" ng-show="!home.authenticated">
  <div>
    With Facebook: <a href="/login/facebook">click here</a>
  </div>
  <div>
    With Github: <a href="/login/github">click here</a>
  </div>
</div>

원칙적으로, 일단 우리가 인증 제공자를 추가하려면 "/user" 종단으로부터 되돌아오는 데이터에 대해 더 신중해져야 한다. Github과 페이스북 둘다 사용자 정보안에 "name"필드가 똑같이 있다. 따라서 우리의 종단에 실제로 수정을 해줄 필요가 없다.

Github 인증필터 추가하기 Adding the Github Authentication Filter

서버단의 주요 수정은 우리의 새 링크로 부터 오는 "/login/github" 요청을 처리하는 부가적인 시큐리티 필터를 추가하는 것이다. 이미 우리는 ssoFilter() 메소드에서 만들어진 페이스북을 위한 커스텀 인증 필터를 가지고 있으므로, 인증 경로를 하나이상 처리할 수 있도록 그냥 기존의 것을 Composite필터를 써서 바꿔주기만 하면 된다

SocialApplication.java
private Filter ssoFilter() {

  CompositeFilter filter = new CompositeFilter();
  List<Filter> filters = new ArrayList<>();

  OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
  OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
  facebookFilter.setRestTemplate(facebookTemplate);
  facebookFilter.setTokenServices(new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()));
  filters.add(facebookFilter);

  OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/github");
  OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext);
  githubFilter.setRestTemplate(githubTemplate);
  githubFilter.setTokenServices(new UserInfoTokenServices(githubResource().getUserInfoUri(), github().getClientId()));
  filters.add(githubFilter);

  filter.setFilters(filters);
  return filter;

}

우리의 예전 ssoFilter()의 코드는 하나는 페이스북, 다른 하나는 Github용으로 중복된다. 이 두개의 필터는 하나의 컴포지트composite로 합쳐진다.

facebook()과 facebookResource() 메소드 역시 유사하게 github()과 githubResource()로 추가해줘야한다:

SocialApplication.java
@Bean
@ConfigurationProperties("github.client")
OAuth2ProtectedResourceDetails github() {
	return new AuthorizationCodeResourceDetails();
}

@Bean
@ConfigurationProperties("github.resource")
ResourceServerProperties githubResource() {
	return new ResourceServerProperties();
}

그리고 이에 상응하는 설정들 역시 추가해준다:

application.yml
github:
  client:
    clientId: bd1c0a783ccdd1c9b9e4
    clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
    accessTokenUri: https://github.com/login/oauth/access_token
    userAuthorizationUri: https://github.com/login/oauth/authorize
    clientAuthenticationScheme: form
  resource:
    userInfoUri: https://api.github.com/user

여기의 클라이언트 디테일들은 Github에 등록된 정보여야 하며 (페이스북과 동일하게) localhost:8080주소를 가르켜야한다.

앱은 이제 준비되었고 사용자가 페이스북 또는 Github으로 인증을 선택하도록 동작할 것이다.

로컬 사용자 데이터베이스를 추가하기 How to Add a Local User Database

많은 어플리케이션은 인증을 외부 제공자에의해 위임할 경우에도, 그들 사용자의 데이터를 로컬에서 가지고 있어야 한다. 여기에 그 코드를 보여주진 않을 것이지만 이를 쉽게 해주는 두가지 단계가 있다:

  1. 당신의 데이터베이스를 위한 백엔드를 선택한 후, 외부 인증으로부터 일부 또는 전체를 불러올 수 오는데 사용하는 커스텀 User 객체를 위한 (스프링 데이터 등을 사용하여) 리파지토리repository를 설정하라

  2. 당신의 /user 종단에서 리파지토리 검사에 의해 로그인된 각각 고유한 사용자를 위한  User 객체를 규정하자. 만일 현재의 Principal의 식별자를 가진 사용자가 이미 있다면, 업데이트하거나, 그렇지않다면 생성할 수 있다.

힌트: User 객체에 필드 하나를 추가하여 외부 제공자의 고유 식별자를 링크해주자 (사용자의 이름같은 게 아닌 무언가 고유한 것)


반응형

반응형

스프링 부트와 OAuth2

Spring Boot And OAuth2

(원문소스: https://spring.io/guides/tutorials/spring-boot-oauth2/)

기본페이지 추가하기 Add a Welcome Page

이 섹션에서 우리는 '페이스북에 로그인하기'라는 명시적 링크를 추가함으로서 방금 만든 simple앱을 수정할 것이다.  즉시 리다이렉트되는 대신, 새 링크는 홈페이지에 보여져 사용자가 로그인을 할지 안할지 선택할 수 있게 된다. 사용자가 링크를 클릭했을 때만 인증된 컨탠트가 보여질 것이다.

홈페이지의 조건적 컨탠트 Conditional Content in Home Page

어떠한 컨탠트를 사용자가 인증했을 때 또는 우리가 서버측 랜더링(이를테면 Freemaker또는 Thymeleaf)을 사용하지 않을지 조건적으로 랜더하거나 또는 자바스크립트를 조금 써서 우리가 그냥 브라우저에게 그것하라고 물어보게 할 수 있다. 이것을 위해 우리는 AngularJS를 쓸 것이다. 만약 당신이 선호하는 다른 프레임워크가 있다면 클라이언트 코드를 옮기는게 그다지 어렵지 않을 것이다.

앵귤러JS를 쓰기위해 우리는 앵귤러 앱 컨테이너로서 <body>에 마크해 주었다.

index.html
<body ng-app="app" ng-controller="home as home">
...
</body>

Body안의 <div> 엘리먼트들은 그것이 보여지는 부분들 제어하는 모델에 묶여bound질 수 있다.

index.html
<div class="container" ng-show="!home.authenticated">
	Login with: <a href="/login">Facebook</a>
</div>
<div class="container" ng-show="home.authenticated">
	Logged in as: <span ng-bind="home.user"></span>
</div>

이 HTML은 authenticated 플래그와 인증받은 사용자를 위한 user객체를 가진 "home" 컨트롤러를 필요로 한다. 여기 이들 기능의 간단한 구현체를 보자 (<body>)의 끝 줄에 이들을 넣자):

index.html
<script type="text/javascript" src="/webjars/angularjs/angular.min.js"></script>
<script type="text/javascript">
  angular.module("app", []).controller("home", function($http) {
    var self = this;
    $http.get("/user").success(function(data) {
      self.user = data.userAuthentication.details.name;
      self.authenticated = true;
    }).error(function() {
      self.user = "N/A";
      self.authenticated = false;
    });
  });
</script>

서버측 수정 Server Side Changes

이를 동작시키려면, 서버쪽에 약간 수정을 해줘야한다. "home" 컨트롤러는 현재 인증받은 사용자정보를 받아오는  "/user"라는 종단endpoint이 필요하다. 메인 클래스에서 다음과 같이 아주 쉽게할 수 있다:

SocialApplication
@SpringBootApplication
@EnableOAuth2Sso
@RestController
public class SocialApplication {

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

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

}

우리가 핸들러 메소드에 주입한 @RestController 와 @RequestMapping 그리고 java.util.Principal 등을 알아두자

위와 같이 /user 종단에 Principal 전체를 리턴하는 것은 매우 안좋은 발상이다. (브라우저 클라이언트에 노출해서는 안되는 정보를 포함하고 있기 때문이다.) 재빠른 동작을 위해 이렇게 했지만 이 가이드의 후반에 이 종단의 정보를 숨기도록 바꿀 것이다.

이 앱은 이제 사용자가 우리가 제공한 링크를 클릭함으로서 전처럼 인증할 수 있게 잘 동작할 것이다. 이 링크를 보이도록 만드려면 WebSecurityConfigurer를 추가하여 홈페이지의 보안을 꺼주어야 한다:

SocialApplication
@SpringBootApplication
@EnableOAuth2Sso
@RestController
public class SocialApplication extends WebSecurityConfigurerAdapter {

  ...

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .antMatcher("/**")
      .authorizeRequests()
        .antMatchers("/", "/login**", "/webjars/**")
        .permitAll()
      .anyRequest()
        .authenticated();
  }

}

스프링 부트는 @EnableOAuth2Sso 어노테이션을 가진 클래스의 WebSecurityConfigurer에 특별한 의미를 부여한다: OAuth2 authentication processor를 가진 시큐리티 필터체인security filter chain을 쓸 수 있도록 설정한다. 홈페이지를 보이도록 만드는 데 필요한건 홈페이지와 관련된 정적인 리소스를 authorizeRequests()에 명시하는 것이다. (우리는 인증을 다루는데 필요한 로그인 종단의 접근 또한 포함할 것이다.). 모든 이외의 요청들(예를 들어, /user종단) 인증이 필요하다.

이 단계의 수정을 거쳐 어플리케이션은 완성되었다. 서버를 돌리고 홈페이지를 방문하면 "Login with Facebook"이라는 멋지게 스타일된 HTML링크를 볼 수 있을 것이다. 이 링크는 당신을 페이스북으로 직접 가게해주는게 아니라, 인증을 처리하는 로컬 경로로 데려다준다.(그리고 페이스북으로 리다이렉트를 보낸다). 일단 인증을 받았다면, 로컬앱으로 다시 리다이렉트 되어, 이제 당신의 이름을 보여줄 것이다. ( 당신이 페이스북에 데이터접근을 허락한다고 설정했을 경우)

로그아웃 버튼 추가하기 Add a Logout Button

이 섹션에서 우리는 사용자가 앱을 로그아웃하도록 버튼을 추가함으로서 click 앱을 수정할것이다. 이는 간단한 기능같이 보일지 모르지만 사실 구현에 매우 신중해야한다. 정확히 어떻게 이렇게 하는지 약간의 시간을 들여 곱씹어볼 가치가 있다. 사실 대부분의 수정은 읽기 전용 리소스에서 읽고 쓰기가 가능한 리소스로 (로그아웃을 한다는 것은 상태변경을 해주어야한다) 앱을 변환하는 작업을 해주는 것이다. 단지 정적인 컨텐트가 아닌 어떤 실제 어플리케이션에서 같은 변경을 해주어야한다.

클라이언트측 변경 Client Side Changes

클라이언트에서 우리는 그냥 로그아웃 버튼과 자바스크립트로 서버에 인증을 취소할지 물어보는 호출만 제공해주기만 하면 된다. 먼저, UI의 "인증받은authenticated" 섹션에서 버튼을 추가하자: 

index.html
<div class="container" ng-show="home.authenticated">
  Logged in as: <span ng-bind="home.user"></span>
  <div>
    <button ng-click="home.logout()" class="btn btn-primary">Logout</button>
  </div>
</div>

그다음 logout() 함수를 제공하여 자바스크립트를 참조한다:

index.html
angular
  .module("app", [])
  .controller("home", function($http, $location) {
    var self = this;
    self.logout = function() {
      $http.post('/logout', {}).success(function() {
        self.authenticated = false;
        $location.path("/");
      }).error(function(data) {
        console.log("Logout failed")
        self.authenticated = false;
      });
    };
  });

logout() 함수는 /logout 로  POST 호출을 한뒤 authenticated 플래그를 초기화한다. 이제 우리는 종단의 구현을 위해 서버측으로 옮겨가보자.

로그아웃 종단 추가하기 Adding a Logout Endpoint

스프링 시큐리티는 우리가 하려고 하는 일(세션을 정리하고 쿠키를 무효화invalidate함)을 위한 /logout 종단 지원을 내장하고 있다. 이 종단을 설정하려면, 간단히 우리의 WebSecurityConfigurer에 있는 configure method 메소드를 다음과 같이 확장하면 된다:

SocialApplication.java
@Override
protected void configure(HttpSecurity http) throws Exception {
  http.antMatcher("/**")
    ... // existing code here
    .and().logout().logoutSuccessUrl("/").permitAll();
}

/logout종단은 우리에게 이 요청을 POST로 보낼것을 요구한다. 그리고 Cross Site Request Forgery (씨서프sea surf라 발음하는 CSRF)로 부터 사용자를 보호하기 위해 이 요청안에 하나의 토큰을 넣으라고 요구한다. 이 토큰의 값은 현재 세션으로 링크되어있는데 이는 무엇을 보호하는지 제공해주는 일을 한다. 이제 우리의 자바스크립트 앱에 이 데이터를 넣는 방법을 알아야한다.

앵귤러JS 또한 (그들이 XSRF라 부르는) CSRF 지원을 내장하고 있다. 하지만 스프링 시큐리티의 통상적인 방법과 약간 다르게 구현되어있다. 앵귤러가 서버에게 원하는 것은 "XSRF-TOKEN"이라 부르는 쿠키를 보내주는 것이다. 이것이 들어있으면 앵귤러는 "X-XSRF-TOKEN"이라는 이름의 헤더를 되돌려준다. 스프링 시큐리티에서 이것을 알려주기 위해, 우리는 쿠키를 만드는 필터를 하나 추가해주어야한다, 또한 현재의 CSRF 필터에 이 헤더이름을 알려주어야한다. WebSecurityConfigurer에서:

SocialApplication.java
@Override
protected void configure(HttpSecurity http) throws Exception {
  http.antMatcher("/**")
    ... // existing code here
    .and().csrf().csrfTokenRepository(csrfTokenRepository())
    .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}

csrfHeaderFilter()는 다음의 커스텀 필터이다:

SocialApplication.java
private Filter csrfHeaderFilter() {
  return new 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);
    }
  };
}

그리고 csrfTokenRepository()는 여기 정의되어있다.:

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

출동준비완료! Ready To Roll!

여기까지의 수정으로, 우리는 서버를 동작하고 새로운 로그아웃 버튼을 시도할 준비를 마쳤다. 앱을 시작하고 새 브라우저 창에 홈페이지를 열어서 "login"링크를 클릭하면 당신을 페이스북으로 데려다 줄 것이다. (이미 로그인 되어있다면 아마 리다이렉트를 알아차리지 못할 것이다.). 현재 세션을 종료하기위해 "Logout"버튼을 클릭하면 앱은 비인증 상태로 되돌아 온다. 궁금하면 브라우저가 로컬서버에서 받아온 요청안에 새 쿠키와 헤더를 확인할 수 있을 것이다.

이제 브라우저 클라이언트에서 로그아웃 종단이 동작하는 것을 기억하자, 이제 모든 HTTP요청들((POST,PUT,DELETE,등등)에서 또한 잘 동작할 것이다. 이 앱은 이제 약간은 더 실전적 기능을 가진 좋은 플랫폼이 되었다.


반응형

반응형

스프링 부트와 OAuth2

Spring Boot And OAuth2

(원문소스: https://spring.io/guides/tutorials/spring-boot-oauth2/)


이 가이드에서 OAuth2와 Spring Boot를 사용해 어떻게 하면 "소셜 로그인"의 다양한 일을 하는 예제를 만들 수 있는 지 보여줄 것이다. 간단한 하나의 싱글사인온 제공자에 연결해보는 걸로 시작하여, Facebook 이나 Github과 같은 authentication 제공자와 함께 OAuth2 Authorization Server를 스스로 돌리는 일을 해볼것이다. 예제들은 백엔드로 모두 스프링 부트와 스프링 OAuth를 사용한 단일 페이지 앱이 될 것이며 프론트엔드로 모두 AngularJS 를 사용하였다. 하지만 자바스크립트단의 코드나 서버의 랜더링사용은 최소화 하였다.

각각 새로운 기능을 추가해놓은 몇가지 예제들이다:

  • simple: 매우 기본적인 정적 앱으로 그냥 하나의 홈페이지와 스프링부트의 @EnableOAuth2Sso를 통해 무작위 로그인을 한다 (홈페이지를 방문하면 자동으로 페이스북으로 리다이렉트 될 것이다)

  • click: 사용자가 로그인을 클릭할 수 있는 명시적 링크를 추가하였다.

  • logout: 인증받은 사용자를 위한 로그아웃 링크를 추가하였다.

  • manual @EnableOAuth2Sso 어노테이션을 사용하지않고 수동으로 설정하여 똑같이 동작시켜본다.

  • gitub: 두번째 로그인 제공자로서 Github을 추가하여 사용자는 홈페이지에서 어느것을 사용할 지 고를 수 있다.

  • auth-server스스로 토큰을 발행할 수 있지만 여전히 인증을 위해 외부 OAuth2 제공자를 사용할 수 있는 완전무결한fully-fledged OAuth2  Authorization 서버를 돌린다. 

  • custom-error Github API에 기반한 커스텀 인증과, 비인증 사용자를 위한 에러 메세지를 추가하였다.

이들은 각각 IDE에서 불러올 수 있으며, 앱을 시작할 수 있는 SocialApplication 메인 클래스가 있다. 이들 모두 http://localhost:8080 로 홈페이지에 접근할 수 있다.( 로그인을 하고 그 내용을 보려면 최소한 페이스북 계정이 필요하다). 또한, gmvn spring-boot:run를 사용하여 커맨드라인상에서 앱을 돌리거나, mvn package 를 통해 jar파일을 빌드하고 java -jar target/*.jar 로도 돌릴 수 있다. 프로젝트 상위레벨에 있는 wrapper를 사용하면 메이븐을 설치하지않다도 된다. 예를 들면:

$ cd simple
$ ../mvnw package
$ java -jar target/*.jar
 모든 앱은 localhost:8080

에서 동작한다. 페이스북과 Github에 이 주소로 OAuth2 클라이언트를 등록했기 때문이다. 다른 호스트나 포트로 돌리려면 스스로 각각 앱을 등록하고 config 파일에 해당 인증정보들을 넣어줘야한다. 기본설정값을 사용하면 localhost로 돌린다고 당신의 페이스북이나 Github 인증정보가 누출되거나 위험해지지않는다. 하지만 당신이 인터넷에 무엇을 노출할지 조심해야한다. 공개 소스 컨트롤에 당신의 앱을 등록하지 않아야한다.

페이스북으로 싱글 사인온 하기 Single Sign On With Facebook

이 섹션에서, 우리는 인증을 위해 페이스북을 사용하는 최소한의 어플리케이션을 만들것이다. 이는 스프링 부트의 자동설정을 사용하면 매우 쉽다. 

새 프로젝트 만들기

먼저 스프링 부트 어플리케이션을 만들어야 한다. 이를 위한 수많은 방법이 있지만, 가장 쉬운 것은 http://start.spring.io 를 방문하여 빈 프로젝트를 만드는 것이다. (시작점으로서 "Web" 의존성을 골라주자). 커맨드라인으로 아래와 같이하면 똑같이 만들 수 있다:

$ mkdir ui && cd ui
$ curl https://start.spring.io/starter.tgz -d style=web -d name=simple | tar -xzvf -

이제 당신이 선호하는 IDE에서 이 프로젝트를 불러올 수 있다. (기본적으로 보통의 메이븐 자바프로젝트다) 또는 파일 작업을 하고 그냥 커맨드라인에서 "mvn"을 사용해도 된다.

홈페이지 추가하기

새 프로젝트의 "src/main/resources/static" 폴더에 index.html파일을 만들자. 몇몇의 스타일시트와 자바스크립트를 링크해줘야하며 그 결과는 다음과 같다:

index.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>Demo</title>
    <meta name="description" content=""/>
    <meta name="viewport" content="width=device-width"/>
    <base href="/"/>
    <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css"/>
    <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script>
    <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
	<h1>Demo</h1>
	<div class="container"></div>
</body>
</html>

이 소스의 무엇도 OAuth2 로그인 기능과 무관하지만 우리는 홈페이지에 약간의 기본적인 기능을 시작할 수 있을 뿐아니라 멋지게 보이는 UI 를 원한다.

앱을 시작하고 홈페이지를 로드하면, 스타일 시트가 로드되지 않았다는 걸 알 수 있을 것이다. 이제 그들을 아래와 같이 의존성을 추가해줌으로서 해결할 수 있다:

pom.xml
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>angularjs</artifactId>
	<version>1.4.3</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>jquery</artifactId>
	<version>2.1.1</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>bootstrap</artifactId>
	<version>3.2.0</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>webjars-locator</artifactId>
</dependency>

우리는 트위터 부트스트랩과 (우리가 지금 당장 써야하는) jQuery, 거기에 나중에 쓸 AngularJS를 추가했다. 다른 의존성은 webjars "locator"로 webjars 사이트에 의해 라이브러리로서 제공받는 것이고 이것은 스프링에 의해 정확한 버전을 알 필요없이(그렇기 때문에 index.html에서 버전없는 /webjar/all}링크를 사용했다webjars의 정적인 어셋을 위치locate하는데 사용된다. 이 webjar locator를 활성화하려면 어플리케이션의 application.properties에서 Spring MVC 리소스 체인을 활성화시켜줘야한다:

application.properties
spring.resources.chain.enabled: true

이 수정을 통해 이제 우리는 이뻐보이는 홈페이지를 가지게 되었다.

어플리케이션 보호하기 Securing the Application

어플리케이션을 안전하게 만드려면, 스프링 시큐리티 의존성을 추가해주면 된다. 그러면 기본적으로 HTTP Basic으로 보호될 것이다. 따라서 우리가 (페이스북으로 위임하는) "소셜" 로그인을 만드려고 하므로, Spring Security OAuth2 의존성 또한 추가해줘야한다:

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

페이스북으로의 링크를 만드면, 메인클래스에서 @EnableOAuth2Sso 어노테이션을 해줘야한다:

SocialApplication.java
@SpringBootApplication
@EnableOAuth2Sso
public class SocialApplication {

  ...

}

그리고 약간의 설정이 필요하다 (더 나은 식별성을 위해 application.properties을 YAML 으로 변환하였다):

application.yml
security:
  oauth2:
    client:
      clientId: 233668646673605
      clientSecret: 33b17e044ee6a4fa383f46ec6e28ea1d
      accessTokenUri: https://graph.facebook.com/oauth/access_token
      userAuthorizationUri: https://www.facebook.com/dialog/oauth
      tokenName: oauth_token
      authenticationScheme: query
      clientAuthenticationScheme: form
    resource:
      userInfoUri: https://graph.facebook.com/me
...

이 설정은 페이스북의 개발자사이트에 등록된 클라이언트 앱을 참조한다. 당신은 당신의 앱에서 리다이렉트할 곳을 등록해줘야한다. (우리의 경우 Home page). 이것은 "localhost:8080"으로 등록되었는데, 이 주소로 돌아가는 앱에서만 작동한다.

이 수정으로 앱을 다시 돌리고 http://localhost:8080의 홈페이지를 방문해보자. 홈페이지 대신, 당신은 페이스북 로그인으로 리다이렉트될 것이다. 로그인하려고 인가authorization를 수락하면, 당신의 로컬 앱으로 다시 리다이렉트되어 돌아와지고 홈페이지가 보여질 것이다. 만일 페이스북 로그인이 되어있는 상태면, 브라우저를 완전히 새롭게 열어 쿠키도 없고 캐시된 데이터가 없더라고 이 로컬앱에서 재인증할 필요없다. (이것이 싱글사이온의 의미이다)

당신이 이 섹션의 예제 어플리케이션을 돌리는 중이라면, 브라우저의 쿠키와 HTTP Basic credentials의 캐시를 지웠는지 먼저 확인하자. 크롬의 incognito window를 사용하는 것이 가장 좋은 방법이다.

이 예제의 접근을 승인하는 것grant access은 안전하다. 로컬에서만 동작하고 있는 이 앱에서만 토큰을 사용할 수 있고 범위scope도 제약적이기 때문이다. 당신이 이와 같은 절차를 거치는 앱을 로그인할 때, 무엇을 승인해야하는지 반드시 살펴봐야한다: 당신이 불편해할 만큼의 권한을 요구할 수도 있기때문이다. (예를 들면, 당신의 개인정보를 수정할 수 있는 권한을 요구할 수 있다.)

방금 뭔일 있었나? What Just Happened?

당신이 방금 만든 이 앱은, OAuth2 용어로, 클라이언트 어플리케이션이다. 그리고 이 앱은 페이스북(인가서버Authorization Server)로부터 억세스토큰access token을 받기위해 authorization code grant를 사용했다. 그 후 당신의 로그인ID와 이름을 포함한 (당신이 허용해준) 몇가지 개인 정보를 페이스북에 요청하기 위해 억세스토큰을 사용했다. 이 단계까지 페이스북은 당신이 보낸 토큰을 디코딩하고 사용자의 정보에 접근하는 앱의 권한을 체크하는 등등의 리소스 서버로서 역할을 하고 있다. 이 단계가 성공적으로 완료되면, 당신의 앱은 사용자 정보를 스프링 시큐리티 컨텍스트Spring security context안에 넣음으로서 당신을 인증할 것이다.

(크롬의 F12누르면 보이는) 브라우저 도구를 열고 네트워크 트래픽을 따라가보면, 페이스북으로 리다이렉트되어 가고오고 하는것을 확인할 수 있으며 최종적으로 새 Set-Cookie 헤더를 가지고 홈페이지로 되돌아와진다. 이 쿠키(기본값으로JSESSIONID)는 스프링(또는 어느 서블릿 기반의) 어플리케이션에서 당신의 인증 상세정보를 위한 하나의 토큰이다.

이제 우리는, 어떠한 정보를 보기위해 사용자가 페이스북과 같은 외부 제공자로 인증해야하는 측면에서, 안전한 어플리케이션을 가지게 되었다. 우리는 웹사이트에 매번 이를 사용하려는게 아니라 기본 식별, 그리고 당신의 사이트에 서로 다른 사용자간에 컨텐트를 구별하기 위한 목적으로 이는 오늘날 이러한 종류의 인증이 왜 인기가 많은가를 설명하는 훌륭한 출발점이 될 것이다. 다음 섹션에서 우리는 어플리케이션에 약간의 기본 기능들을 추가하고, 사용자가 처음 페이스북으로 리다이렉트 할 때 언제 그리고 무엇을 하는지 조금더 구체적으로 알아볼 것이다.


반응형

+ Recent posts