티스토리 뷰
이 글인 spring.io 의 Tutorial (https://spring.io/guides/tutorials/spring-boot-oauth2/) 을 간단하게 번역, 정리한 글입니다.
Spring Boot 기반에서 Spring OAuth 를 사용하여 페이스북과 같은 소셜 로그인을 처리하는 간단한 샘플입니다.
클라이언트 구현에는 AngularJS 및 WebJar 가 사용되었습니다.
프로젝트 생성
STS에서 New Spring Starter Project 위자드 기능을 통해 기본 Web Project를 생성합니다.(여기 샘플은 maven기반)
기본 연동 작업
홈페이지 추가
src/main/resources/static/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>
webjars dependency 추가
<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>
어플리케이션 보안을 위한 dependency 추가
<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>
Spring Boot 메인 클래스에 @EnableOAuth2Sso 어노테이션 추가
OAuth2 를 위한 설정파일
clientId 및 clientSecret은 페이북에 앱을 등록하면 얻을 수 있다. ( https://developers.facebook.com/ )
그리고, 등록된 페이스 북 앱 설정에서 "플랫폼 추가"를 통해 웹사이트 URL을 등록해야 한다. 안 그러면 페이북 로그인 페이지로 이동 시redirction 페이지가 없다고 에러남.
여기까지만 따라하면 일단 페이스북과 로그인 연동되는 앱이 만들어진다. 앱을 실행한 후 http://localhost:8080/ 을 접속하면, 페이스북 로그인 페이지로 리다이렉트되고 로그인이 완료되면 index.html 페이지로 리다이렉트 된다.
웹컴 페이지 구성
지금까지는 앱 전체가 보안이 필요하기 때문에 index.html 페이지조차도 로그인이 필요하다.
추가로 웰컴 페이지를 구성해서 로그인 전일 경우 로그인 링크를 보여주고, 로그인 후라면 로그인 사용자명이 나타나도록 index.html 페이를 구성해보자.
index.html 수정
<div> 바인딩. 로그인 상태에 따라.
<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>
인증된 플래그가 있는 'home"컨트롤러와 인증된 사용자를 설명하는 사용자 개체 처리
<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>
서버측 수정
@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);
}
}
# 여기 샘플에서는 전체 Principal을 전달했지만, 실제는 필요한 사용자정보만 제공하도록 수정해야함.
그리고, index 페이지, login 링크, /webjars 리소스에 대해서는 보안을 적용하기 않기 위해 아래와 같이 스프링 시큐리티 설정 추가.
@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();
}
}
여기까지 적용하고 다시 앱을 실행하고, http://localhost:8080 을 접속하면 변경된 내용을 확인할 수 있다. ( 브라우저 캐시 지우고 다시 접속)
로그아웃 기능 추가
클라이언트 수정
<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>
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 endpoint 추가
스프링 시큐리티는 세션종료와 쿠키 무효화 처리를 하는 /logout 엔드포인트를 기본적으로 포함하고 있다.
이를 구성하려면 WebSecurityConfigurer에서 configure() 메소드를 확장한다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
... // existing code here
.and().logout().logoutSuccessUrl("/").permitAll();
}
/logout 은 POST로 처리되고 CSRF에서 사용자를 보호하기 위해 요청에 토큰을 포함시켜야 함.
AngularJS의 XSRF(CSRF) 지원 기능을 Spring Security와 연동하기 위해 아래 코드 추가.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
... // existing code here
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
여기까지 적용하고 앱을 다시 실행해서 테스트하면 logout 처리를 확인할 수 있다.
OAuth2 Client 수동설정
@EnableOAuth2Sso 에는 OAuth2 클라이언트와 인증 2가지의 기능있다.
클라이언트는 재사용 가능하고 OAuth2 리소드와 상호작용할 때도 사용할 수 있다.
클라이언트 부분은 Spring Security OAuth2에서 제공되고 @EnableOAuth2Client 어노테이션으로 처리한다.
기존의 @EnableOAuth2Sso를 제거하고 @EnableOAuth2Client 추가한다.
@SpringBootApplication
@EnableOAuth2Client
@RestController
public class SocialApplication extends WebSecurityConfigurerAdapter {
...
}
이렇게 하면, OAuth2ClientContext 생성되고, 이를 이용해서 인증필터를 추가한다.
@SpringBootApplication
@EnableOAuth2Client
@RestController
public class SocialApplication extends WebSecurityConfigurerAdapter {
@Autowired
OAuth2ClientContext oauth2ClientContext;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
...
.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
...
}
이 필터는 아래의 메소드 구현을 통해 만들어진다.
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId());
tokenServices.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(tokenServices);
return facebookFilter;
}
필터는 Facebook을 통한 클라이언트 등록에 대해서도 알아야 한다.
@Bean
@ConfigurationProperties("facebook.client")
public AuthorizationCodeResourceDetails facebook() {
return new AuthorizationCodeResourceDetails();
}
또, 인증을 완료하려면 페이스북에서 사용자 정보를 제공하는 endpoint를 알아야 한다.
@Bean
@ConfigurationProperties("facebook.resource")
public ResourceServerProperties facebookResource() {
return new ResourceServerProperties();
}
위 bean 생성 시 사용한 properties를 맞추가 위해 security.oauth2 대신 facebook 구성 접두어로 변경합니다.
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
위 필터 선언 시 로그인 경로를 facebook 전용으로 변경했으므로 html에도 동일하게 적용한다.
<h1>Login</h1>
<div class="container" ng-show="!home.authenticated">
<div>
With Facebook: <a href="/login/facebook">click here</a>
</div>
</div>
리다이렉트 처리
마지막으로 앱에서 facebook으로의 리다이렉션을 명시적으로 지원하도록 한다.
FilterRegistrationBean을 사용해서 주 스프링 보안 필터 전에 필터(OAuth2ClientContextFilter)를 등록하여 올바른 순서로 호출되도록한다.
이렇게 하면 인증요청 예외에 대한 리다이렉션을 처리할 수 있다.
@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
Github과 연동 추가하기
Github 링크 추가
Github 인증필터 추가하기
여기까지 수정하고 앱을 다시 실행하면 facebook과 github을 선택하여 로그인 처리할 수 있음.
- Total
- Today
- Yesterday