티스토리 뷰
개요
API 서버는 클라이언트에게 정보를 제공하기 위해 존재한다. 우리가 사는 집에는 열쇠가 있기 때문에 열쇠를 가진 사람만 드나들 수 있다. 마찬가지로 API 서버 또한 아무 클라이언트에게 모든 정보를 줄 수는 없다. 클라이언트가 열쇠를 제출하면 그 열쇠를 인증할 수 있는 수단이 필요하다. 이러한 수단에는 여러가지 방식이 있는데 가장 대중적이고 사실상 글로벌 표준처럼 취급되는 것이 바로 OAuth 2.0
이다. 이번 글에서는 Spring Boot
기반의 프로젝트에서 OAuth 2.0 서버를 구현하는 방법을 설명하고자 한다.
사전 지식
- API의 인증과 권한부여, OAuth 2.0과 Spring Security
- OAuth 2.0 서버 설계시 고려사항 정리
- OAuth 2.0, Grant Type 개념 정리
- OAuth 2.0, Client 개념 및 스키마 정리
- OAuth 2.0, Scope 개념 및 스키마 정리
- IntelliJ IDEA에서 Spring Boot 웹 프로젝트 생성하기
build.gradle
- 이 글를 참고하여 기본
Spring Boot
프로젝트 구성을 마친 후/build.gradle
파일에 아래 내용을 추가한다.
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security'
compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2'
}
- dependencies.compile 항목으로
spring-boot-starter-security
,spring-security-oauth2
를 추가하는 것 만으로 기존 작성된 모든 엔드포인트에 대해Basic Auth
인증을 요구하는 필터가 작동하게 된다.
@EnableAuthorizationServer
- AuthorizationConfig 클래스를 아래와 같이 작성한다. 아래 설명할 규칙만 지키면 클래스명은 어떤 이름으로 해도 상관이 없다.
package com.jsonobject.oauth2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("some_client_id")
.secret("some_client_secret")
.scopes("read:current_user", "read:users")
.authorizedGrantTypes("client_credentials");
}
}
AuthorizationServerConfigurerAdapter
클래스를 상속하고 클래스 레벨에@EnableAuthorizationServer
어노테이션을 추가하면 구체적인 환경 설정이 가능하다.configure(ClientDetailsServiceConfigurer clients)
메써드에서는 API의 요청 클라이언트 정보를 설정할 수 있다.inMemory()
는 클라이언트 정보를 메모리에 저장한다. 개발 환경에 적합하다. 반면jdbc()
는 데이터베이스에 저장한다. 운영 환경에 적합하다.withClient()
로client_id
값을 설정한다.secret()
은client_secret
값을 설정한다.scopes()
는scope
값을 설정한다.authorizedGrantTypes()
는grant_type
(access_token을 획득하기 위한 4가지 인증 방법) 값을 설정한다. 복수개를 저장할 수 있다. 본 예제에서는 client_id, client_secret 만으로 access_token을 요청할 수 있는client_credentials
를 설정했다.
사용자 저장소 설정
- 클라이언트와 토큰 관리는 Spring Security OAuth 모듈이 담당하지만 사용자 관리는 Spring Security의 몫이다.
/oauth/authorize
요청을 처리하려면 사용자 저장소에 접근할 수 있어야 한다. 아래는 메모리에 사용자 정보를 등록하는 예이다.
package com.jsonobject.oauth2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER")
.and()
.withUser("admin")
.password("password")
.roles("USER", "ADMIN");
}
}
auth.userDetailsService()
로 임의의UserDetailsService
인터페이스 구현체를 설정할 수 있다.
ClientDetailsService 빈 설정
- client_id, client_secret 등을 저장하는 클라이언트 저장소에 대한 모든 CRUD는
ClientDetailsService
인터페이스로 구현하게 되어 있다. 기본 제공되는 구현체로는InMemoryClientDetailService
,JdbcClientDetailService
클래스가 제공된다. JDBC 기반의 빈 설정을 예로 들면 아래와 같다.
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
@Bean
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(someDataSource);
}
}
TokenStore 빈 설정
- Spring Security OAuth에서
access_token
,refresh_token
을 저장하는 토큰 저장소에 대한 모든 CRUD는TokenStore
인터페이스로 구현하게 되어 있다. 기본 제공되는 구현체로는InMemoryTokenStore
,JdbcTokenStore
,RedisTokenStore
클래스가 제공된다. 인터페이스만 구현하면 되므로 제3의 구현체를 작성해도 된다. - TokenStore 빈을 설정하려면 configure(AuthorizationServerEndpointsConfigurer endpoints) 오버라이드 메써드에서
endpoints.tokenStore()
를 호출하면 된다. Redis 기반의 TokenStore 빈 설정을 예로 들면 아래와 같다.
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis'
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(jedisConnectionFactory());
}
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName("localhost");
factory.setPort(6379);
factory.setPassword("");
factory.setDatabase(1);
factory.setUsePool(true);
return factory;
}
}
/oauth/authorize
GET /oauth/authorize
는 사용자의 브라우저(또는 앱)에 보여줄 리다이렉트 웹 페이지이다. 사용자에게 로그인과 사용할 데이터에 대한 동의를 요구한다.- 만약 클라이언트가 로그인한 상태라면 권한부여에 대한 동의 페이지를, 로그인하지 않은 상태라면 로그인 페이지로 리다이렉트되어야 한다.
client_id
파라메터에는 사용자의 정보를 요구하는 클라이언트(사용자가 현재 서비스를 이용하고 있는)의 client_id를 담는다.scope
파라메터에는 사용자에게 동의를 요청할 권한(허용되는 데이터의 범위)의 목록을 담는다.redirect_uri
파라메터에는 사용자 동의 후 리다렉트될 클라이언트 측의 웹 페이지 주소를 담는다.- 사용자가 권한부여에 동의하면
POST /oauth/authorize
로 리다이렉트된다. 이 때 전달되는 파라메터는 아래와 같다. 처리 후에는 클라이언트가 요청한 redirect_uri로 결과를 전달한다. code 파라메터가 첨부된다.
_csrf=2ed1dfa5-3959-4560-a72d-c9f364696fc3
authorize=Authorize
scope.read:current_user=true
scope.read:user=true
user_oauth_approval=true
- Spring Securiy는 사용자의 정보에 대한 CRUD를 수행하는
ClientDetailsService
인터페이스 구현체가 설정되어 있어야 한다.
/oauth/token
/oauth/token
엔드포인트는 클라이언트 인증 후 access_token을 발급한다. client_id, client_secret(Basic Auth로 요청), grant_type이 필수 파라메터로 요구된다.
$ curl -X POST "http://localhost:8080/oauth/token" \
-H 'authorization: Basic c2VydmljZS1hY2NvdW50LTE6c2VydmljZS1hY2NvdW50LTEtc2VjcmV0' \
-d "grant_type=client_credentials"
- 아래와 같이 응답 바디로
access_token
이 도착한 것을 확인할 수 있다.
{
"access_token": "bcf570e5-0f2f-481b-a2ae-6d57fa7f711c",
"token_type": "bearer",
"expires_in": 38010,
"scope": "read:current_user read:users"
}
- 앞서
GET /oauth/authorize
의 결과로 획득한 code 파라메터로access_token
을 발급 받을 수 있다. client_id, client_secret, grant_type, code, redirect_uri 파라메터가 필수로 요구된다. code 파라메터는 굉장히 짧은 시간 동안만 유효하며 1회 요청이 발생하면 소멸된다.
/oauth/check_token
/oauth/check_token
엔드포인트는 요청 파라메터의 access_token의 유효 여부와 유효시 해당 클라이언트 정보를 응답한다. 요청 예는 아래와 같다. 발급된 access_token을 token 파라메터에 첨부한다.
$ curl -X POST "http://localhost:8080/oauth/check_token" \
-d "token=067d81ec-6c68-42f3-afc6-97e4b6dc2dd6"
- 성공 응답은 아래와 같다.
{
"scope": [
"read:current_user",
"read:users"
],
"exp": 1499199182,
"client_id": "some_client_id"
}
- 실패 응답은 아래와 같다.
{
"error": "invalid_token",
"error_description": "Token was not recognised"
}
{
"error": "invalid_token",
"error_description": "Token has expired"
}
참고 글
- The OAuth 2.0 Authorization Framework
- Spring Boot and OAuth2
- Spring - Spring Security Reference
- Spring - OAuth 2 Developers Guide
- Spring - SSO with OAuth2: Angular JS and Spring Security Part V
- HSC - Securing RESTful Web Services Using Spring and OAuth 2.0
- Which OAuth 2.0 grant should I implement?
- Securing a Web Application
- Secure Spring REST API using OAuth2
- Spring Boot REST API (4) - Security with OAuth2
댓글
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- MySQL
- Kendo UI Web Grid
- java
- 구동계
- Spring Boot
- Spring MVC 3
- bootstrap
- Docker
- node.js
- chrome
- CentOS
- kotlin
- jsp
- spring
- jpa
- jstl
- JHipster
- Kendo UI
- Eclipse
- 태그를 입력해 주세요.
- Tomcat
- 로드바이크
- 자전거
- DynamoDB
- JavaScript
- maven
- 평속
- 로드 바이크
- graylog
- 알뜰폰
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
글 보관함