├── .gitignore
├── .mvn
└── wrapper
│ └── maven-wrapper.properties
├── README.adoc
├── api-server
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ ├── ApiApplication.java
│ │ │ ├── Member.java
│ │ │ └── MemberRepository.java
│ └── resources
│ │ └── application.yml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── ApiApplicationTests.java
├── approval.jpg
├── build.gradle
├── exam1.adoc
├── exam2.adoc
├── exam3.adoc
├── exam4.adoc
├── exam5.adoc
├── exam6.adoc
├── exam7.adoc
├── gradle
└── wrapper
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── h2-server
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── example
│ │ └── H2Application.java
│ └── resources
│ └── application.yml
├── mvnw
├── mvnw.cmd
├── oauth2-server
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── OAuth2Application.java
│ └── resources
│ │ ├── application.yml
│ │ ├── data.sql
│ │ └── schema.sql
│ └── test
│ └── java
│ └── com
│ └── example
│ └── OAuth2ApplicationTests.java
├── pom.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | *.sw?
3 | .#*
4 | *#
5 | *~
6 | /build
7 | /code
8 | .classpath
9 | .project
10 | .settings
11 | .metadata
12 | .factorypath
13 | .recommenders
14 | bin
15 | build
16 | lib/
17 | target
18 | .springBeans
19 | interpolated*.xml
20 | dependency-reduced-pom.xml
21 | build.log
22 | _site/
23 | .*.md.html
24 | manifest.yml
25 | MANIFEST.MF
26 | settings.xml
27 | activemq-data
28 | overridedb.*
29 | *.iml
30 | *.ipr
31 | *.iws
32 | .idea
33 | *.jar
34 | .DS_Store
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | include::exam1.adoc[]
2 |
3 | include::exam2.adoc[]
4 |
5 | include::exam3.adoc[]
6 |
7 | include::exam4.adoc[]
8 |
9 | include::exam5.adoc[]
10 |
11 | include::exam6.adoc[]
12 |
13 | include::exam7.adoc[]
14 |
--------------------------------------------------------------------------------
/api-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.example
8 | demo
9 | 0.0.1-SNAPSHOT
10 | ../
11 |
12 |
13 | api-server
14 | api-server
15 | API Server
16 |
17 |
18 |
19 | org.springframework.boot
20 | spring-boot-starter-data-jpa
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-data-rest
25 |
26 |
27 | org.springframework.security.oauth
28 | spring-security-oauth2
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-security
33 |
34 |
35 | org.springframework.data
36 | spring-data-rest-hal-browser
37 |
38 |
39 | org.springframework.security
40 | spring-security-jwt
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-test
45 | test
46 |
47 |
48 |
49 |
50 |
51 |
52 | org.springframework.boot
53 | spring-boot-maven-plugin
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/api-server/src/main/java/com/example/ApiApplication.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.CommandLineRunner;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
9 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
10 | import org.springframework.security.oauth2.provider.token.TokenStore;
11 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
12 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
13 |
14 | @EnableResourceServer
15 | @SpringBootApplication
16 | public class ApiApplication {
17 |
18 | @Bean
19 | public ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() {
20 | return new ResourceServerConfigurerAdapter() {
21 | @Override
22 | public void configure(HttpSecurity http) throws Exception {
23 | http.headers().frameOptions().disable();
24 | http.authorizeRequests()
25 | .antMatchers("/members", "/members/**").access("#oauth2.hasScope('read')")
26 | .anyRequest().authenticated();
27 | }
28 | };
29 | }
30 |
31 | /**
32 | * API를 조회시 출력될 테스트 데이터
33 | * @param memberRepository
34 | * @return
35 | */
36 | @Bean
37 | public CommandLineRunner commandLineRunner(MemberRepository memberRepository) {
38 | return args -> {
39 | memberRepository.save(new Member("이철수", "chulsoo", "test111"));
40 | memberRepository.save(new Member("김정인", "jungin11", "test222"));
41 | memberRepository.save(new Member("류정우", "jwryu991", "test333"));
42 | };
43 | }
44 |
45 | public static void main(String[] args) {
46 | SpringApplication.run(ApiApplication.class, args);
47 | }
48 | }
--------------------------------------------------------------------------------
/api-server/src/main/java/com/example/Member.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import lombok.Data;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.GeneratedValue;
7 | import javax.persistence.Id;
8 |
9 | @Data
10 | @Entity
11 | public class Member {
12 | @Id
13 | @GeneratedValue
14 | Long id;
15 | String name;
16 | String username;
17 | String remark;
18 | public Member() {}
19 | public Member(String name, String username, String remark) {
20 | this.name = name;
21 | this.username = username;
22 | this.remark = remark;
23 | }
24 | }
--------------------------------------------------------------------------------
/api-server/src/main/java/com/example/MemberRepository.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.data.repository.PagingAndSortingRepository;
4 | import org.springframework.data.rest.core.annotation.RepositoryRestResource;
5 |
6 | @RepositoryRestResource
7 | public interface MemberRepository extends PagingAndSortingRepository {}
8 |
9 |
--------------------------------------------------------------------------------
/api-server/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server.port: 8081
2 |
3 | spring.h2.console:
4 | enabled: true
5 | path: /h2-console
6 |
7 | # 외부 DB 설정시 아래의 주석을 활성화 시킨 후 관련 DB 설정 정보를 입력한다.
8 | #spring:
9 | # datasource:
10 | # url: jdbc:h2:tcp://localhost/~/api;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
11 | # driverClassName: org.h2.Driver
12 | # username: sa
13 | # password:
14 |
15 | security:
16 | oauth2:
17 | client:
18 | client-id: foo
19 | client-secret: bar
20 | resource:
21 | jwt.key-uri: http://localhost:8080/oauth/token_key
22 |
23 | logging.level:
24 | org.springframework:
25 | security: debug
26 | boot: debug
--------------------------------------------------------------------------------
/api-server/src/test/java/com/example/ApiApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
7 |
8 | @RunWith(SpringJUnit4ClassRunner.class)
9 | @SpringBootTest(classes = ApiApplication.class)
10 | public class ApiApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/approval.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbcoba/spring-boot-oauth2-sample/0d61b4628ae5e892c6b416d2591efd87deb2d774/approval.jpg
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | springBootVersion = '1.4.4.RELEASE'
4 | }
5 | repositories {
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
10 | classpath "org.springframework:springloaded:1.2.4.RELEASE"
11 | classpath "io.spring.gradle:dependency-management-plugin:0.5.6.RELEASE"
12 |
13 | }
14 | }
15 |
16 | configure(allprojects) {
17 |
18 | apply plugin: 'java'
19 | apply plugin: 'spring-boot'
20 | apply plugin: "io.spring.dependency-management"
21 |
22 | group = 'com.example'
23 | version = '0.0.1-SNAPSHOT'
24 |
25 | sourceCompatibility = 1.8
26 | targetCompatibility = 1.8
27 |
28 | repositories {
29 | mavenCentral()
30 | }
31 |
32 | dependencies {
33 | compile 'org.projectlombok:lombok:1.16.6'
34 | runtime 'com.h2database:h2:1.4.190'
35 | }
36 |
37 | configurations {
38 | all*.exclude group: 'commons-logging', module: 'commons-logging' // replaced with jcl-over-slf4j
39 | all*.exclude group: 'log4j', module: 'log4j' // replaced with log4j-over-slf4j
40 | }
41 | }
42 |
43 | project(':oauth2-server') {
44 | dependencies {
45 | compile 'org.springframework.boot:spring-boot-starter-web'
46 | compile 'org.springframework.boot:spring-boot-starter-jdbc'
47 | compile 'org.springframework.boot:spring-boot-starter-security'
48 |
49 | compile 'org.springframework.security.oauth:spring-security-oauth2'
50 | compile 'org.springframework.security:spring-security-jwt'
51 |
52 | testCompile 'org.springframework.boot:spring-boot-starter-test'
53 | }
54 | }
55 |
56 | project(':api-server') {
57 | dependencies {
58 | compile 'org.springframework.boot:spring-boot-starter-data-jpa'
59 | compile 'org.springframework.boot:spring-boot-starter-data-rest'
60 | compile 'org.springframework.boot:spring-boot-starter-security'
61 |
62 | compile 'org.springframework.security.oauth:spring-security-oauth2'
63 | compile 'org.springframework.data:spring-data-rest-hal-browser'
64 | compile 'org.springframework.security:spring-security-jwt'
65 |
66 | testCompile 'org.springframework.boot:spring-boot-starter-test'
67 | }
68 |
69 | }
70 |
71 | project(':h2-server') {
72 | dependencies {
73 | compile "org.springframework.boot:spring-boot-starter-web"
74 | compile 'com.h2database:h2:1.4.190'
75 | }
76 | }
77 |
78 | apply plugin: 'eclipse'
79 |
80 | eclipse {
81 | classpath {
82 | containers.remove "org.eclipse.jdt.launching.JRE_CONTAINER"
83 | containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/exam1.adoc:
--------------------------------------------------------------------------------
1 | == Spring Boot로 만드는 OAuth2 시스템 1
2 |
3 | ==== 스프링 부트와 OAuth2
4 |
5 | 최근에 웹 또는 앱을 개발하게 되면 가장 많이 접하는 인증 형태가 바로 http://oauth.net/2/[OAuth2] 형태이다.
6 |
7 | 소위 말하는 https://developers.facebook.com/products/login[페이스북 인증], https://developers.google.com/youtube/v3/guides/authentication?hl=ko[구글 인증], https://developers.daum.net/services/apis/login[다음 인증], https://developers.naver.com/docs/login/overview[네이버 인증] 등은 대부분 OAuth2 인증을 지원한다.
8 |
9 | 사실 개발자들은 클라이언트 형태로 위와 같은 인증서비스를 사용해서 자신의 시스템과 연동을 사용한 형태는 많이 접해 보았을 것이다.
10 |
11 | 하지만 OAuth2 인증서버를 직접 만들어서 구축한 형태는 사실 개발자에게 해볼 일이 잘 없다.
12 |
13 | 난이도도 쉽지 않고 국내에서 그런 요구사항이 잘 들어오지도 않는다.
14 |
15 | 하지만 어느 정도 규모 있는 회사 및 공공 기관에서 OAuth2 형태로 인증시스템을 구현하게 되면 보통 회사에서 사용하는 인증시스템 ( LDAP 또는 조금 다르지만 SSO 등 ) 보다 연동하여 개발하기가 훨씬 더 수월하고 고 관리하기 하기 쉬워진다. 그리고 다른 인증시스템과 다르게 권한도 가지고있다. 그래서 때문에 한번 구축해서 개발하여 사용해볼 만하다.
16 |
17 | 이 포스팅을 하는 이유는 OAuth2서버가 비교적 쉽게 만들 수 있기 때문에 구축하는데 부담을 줄여주는데 조금이라도 기여하기 위해서이다.
18 |
19 | 그리고 OAuth2 서버를 구축하는 부분을 설명하는 부분을 중점으로 두고 설명하려고 한다.
20 |
21 | '''
22 |
23 | 예전부터 http://projects.spring.io/spring-security/[Spring Security](이하 스프링 시큐리티)의 서브 프로젝트인 http://projects.spring.io/spring-security-oauth/[Spring Security OAuth](이하 스프링 시큐리티 OAuth)를 이용하여 OAuth(1.0a, 2) 인증 시스템(서버와 클라이언트)을 구축할 수 있는 프레임워크를 제공하고 있었다.
24 |
25 | 하지만 많은 사용자들이 샘플을 봐도 많은 설정 정보가 존재하여 내가 하고 싶은 대로 확장하려 하면 많은 시행착오를 하게 된다.
26 |
27 | (사실은 그것 조차 OAuth2 시스템의 구조의 설정에서 많이 단축하고 확장할 수 있도록 만든 형태이다.)
28 |
29 | 그런 부분에서 처음 접한 많은 개발자들이 장벽을 실감하고 손을 놓게 된다.
30 |
31 | (사실 단순하게 만들면 큰 문제없이 개발 할 수 있으나 리얼월드의 요구사항을 따라가려고 할 때 가장 많이 생긴다.)
32 |
33 | '''
34 |
35 | 최근 스프링 프레임워크(이하 스프링)에서는 http://projects.spring.io/spring-boot/[Spring Boot](이하 스프링 부트)라는 프로젝트가 나오면서 기존의 스프링 설정의 많은 부분을 https://en.wikipedia.org/wiki/Convention_over_configuration[CoC(Convention over configuration, 설정보다 관례]) 형태로 제공되며 변경이 필요한 부분만 속성(Properties) 형태로 작성하여 적은 코딩으로 간단히 개발할 수 있도록 만들어져서 많은 인기를 끌고 있다.
36 | (물론 기존 스프링처럼 Bean 형태로 직접 선언하여 확장도 가능하다. )
37 |
38 | 스프링의 여러 프로젝트 중 하나인 http://projects.spring.io/spring-security-oauth/[스프링 시큐리티 OAuth 프로젝트]가 최근 스프링 추세에 따라 스프링 부트 형태로 최소한의 설정으로 OAuth2 시스템을 개발할 수 있도록 지원하고 있다.
39 | (OAuth 1.0a는 스프링 부트에서 지원하지 않는다.)
40 |
41 | 그래서 여기서는 스프링 부트에서 기본 적으로 지원 형태를 이용하여 OAuth2 서버를 한번 만들어 보려고 한다.
42 |
--------------------------------------------------------------------------------
/exam2.adoc:
--------------------------------------------------------------------------------
1 | == Spring Boot로 만드는 OAuth2 시스템 2
2 |
3 | ==== 본격적인 개발 하기전에
4 |
5 | > Simple is Best
6 |
7 | http://projects.spring.io/spring-boot/[Spring Boot](이하 스프링 부트)는 이 문장과 가장 적합한 형태가 아닐까 생각된다.
8 | (사실은 보이는 부분만 간단하다.)
9 |
10 | 그 문장이 맞게 간단하게 스프링 부트로 OAuth2 서버를 만들 준비를 해보자.
11 |
12 | NOTE: 사실 소스 보다 설명이 더 많이 차지할 것 같다. 완성되어 있는 형태에서는 소스는 얼마 없을 것이다.
13 |
14 | '''
15 |
16 | == 시작하기 전
17 |
18 | 간단한 API 서버와 그것을 보호하는 OAuth2 서버와 그것을 테스트할 수 있는 OAuth2 클라이언트를 샘플을 개발할 예정이다.
19 |
20 | 처음에는 간단한 API 서버와 OAuth2 인증서버가 같이 있는 서버로 개발 후 차츰 두 개의 서버로 나누는 형태로 바꿀 생각이다.
21 |
22 | Gradle(이하 그레이들)과 Maven(이하 메이븐) 중 어떤 것 기준으로 설명할까 생각했었는데 그래도 아직까지 가장 많이 사용되는 **메이븐 기준**으로 이야기할 예정이다.
23 |
24 | NOTE: 특정 IDE에 종속된 설명은 생략할 예정이다.
25 |
26 | == 스프링 부트로 시작하기
27 |
28 | https://start.spring.io/[https://start.spring.io]
29 |
30 | 스프링 부트를 시작할 때 가장 먼저 접하는 사이트이다.
31 |
32 | 여기서 필요한 기술에 대해 메뉴 정하듯 선택해서 개발에 필요한 기본 형태의 파일을 다운할 수 있다.
33 |
34 | 먼저 필요한 기술 메뉴를 확인해보자.
35 |
36 | image::https://t1.daumcdn.net/thumb/R1280x0/?fname=http://t1.daumcdn.net/brunch/service/user/so6/image/T2EJbxy7N3VkPWidZWVWFdFqCtw.jpg[]
37 | 하단에서 선택된 녹색 사각형(Generate Project 버튼을 제외하고)을 살펴보자.
38 | Group과 Artifact부분은 기본 형태로 사용하겠다. ( 이 값이 자바 패키지 명을 결정한다. )
39 |
40 | 먼저 Dependencies에서 선택한 부분을 간단하게 설명해보겠다.
41 | ****
42 | Security:: http://projects.spring.io/spring-security/[Spring Security](이하 스프링 시큐리티) 사용하기 위해 필요하며 OAuth2 서버를 만들 때 필요하다.
43 |
44 | JPA:: http://projects.spring.io/spring-data-jpa/[Spring Data JPA](이하 스프링 데이터 JPA)를 스프링 부트에 맞도록 사용하기 위한 의존성을 제공하기 위해 추가하는 것으로 손쉽게 DB를 JPA로 접근하는 형태를 사용하기 위해서 추가했다.
45 |
46 | Rest Repositories:: http://projects.spring.io/spring-data-rest/[Spring Data Rest](이하 스프링 데이터 Rest)를 스프링 부트에 맞도록 쉽게 사용할 수 있는 형태 제공하는 부분이면 Rest API 서버를 쉽게 만들어 준다. ( Web을 따로 포함하지 않아도 스프링 MVC를 사용할 수 있는 의존성이 제공된다. )
47 |
48 | h2:: 샘플을 빠르게 만들 수 있도록 내장 DB를 사용하기 위해 추가한 부분으로, 추후에 Mysql 등 다른 DB로 쉽게 바꿀 수 있다.
49 |
50 | lombok:: 어노테이션을 사용해서 소스의 양을 줄어들도록 만들어 주는 라이브러리이다. 컴파일 타임에 사용되며 주로 getter, setter를 생략하는데 자주 사용된다. 처음 접한 분이라면 https://projectlombok.org/[공식 홈페이지에서] 동영상 한번 확인해보자.
51 | ****
52 | IDE에서 이클립스와 intelliJ IDEA에서는 플러그인으로 지원된다.
53 |
54 | OAuth2 설정은 수동으로 뒷부분에서 설정한다.
55 |
56 | 자 이제 Generate Project 버튼을 눌러서 생성된 파일을 받아서 원하는 디렉터리로 압축을 풀어보자.
57 |
58 | '''
59 |
60 | == 참고
61 |
62 | OSX(Mac) 또는 Linux OS 계열로 개발할 때는 아래와 같은 터미널 명령어로도 웹에서 하는 것과 같이 쉽게 샘플 프로젝트가 추가할 수 있다. (윈도에서도 curl 설치하면 가능하다고 들었는데 테스트는 해보지 못했다.)
63 | [source,sh]
64 | ----
65 | $ curl https://start.spring.io/starter.tgz -d dependencies=security,data-jpa,h2,data-rest,lombok -d baseDir=oauth2-server-sample | tar -xzvf - #baseDir 디렉터리에서 생성
66 | ----
67 |
68 | == 실행
69 |
70 | 만든 압축을 푼 디렉터리가 oauth2-server-sample 가정하고 설명하겠다.
71 | [source,sh]
72 | ----
73 | $ cd oauth2-server-sample
74 | $ mvn clean spring-boot:run
75 | ----
76 | 처음으로 실행했을 때에는 의존관계를 가진 모든 라이브러리를 다운로드하기 때문에 많은 시간이 걸린다.
77 |
78 | 소스에 아무것도 건드리지 않았지만 8080 포트로 톰캣 서버가 스타트되었다.
79 |
80 | 이제 OAuth2 사용을 위해 설정을 추가해 보자.
81 |
82 |
83 | == OAuth2 의존성 추가
84 | [source,sh]
85 | ----
86 | $ vi pom.xml
87 | ----
88 | vi 에디터(또는 IDE)를 실행시키켜서 아래와 같은 dependency를 추가해준다.
89 | [source,xml]
90 | ----
91 |
92 | ...
93 |
94 | org.springframework.security.oauth
95 | spring-security-oauth2
96 |
97 | ...
98 |
99 | ----
100 | 이상으로 기본적인 의존성 설정은 끝이 났다.
101 |
102 | NOTE: 추가적으로 설정이 필요한 부분은 그때그때 설명할 예정이다.
103 |
104 | 다음 편에서는 OAuth2 서버에 앞서 기본적인 API 서버를 만들어 보겠다.
--------------------------------------------------------------------------------
/exam3.adoc:
--------------------------------------------------------------------------------
1 | == Spring Boot로 만드는 OAuth2 시스템 3
2 |
3 | ==== API 서버 만들기
4 |
5 | OAuth2 인증을 받기 위한 API 서버를 간단하게 만들어보겠다.
6 |
7 | API 서버 자체는 OAuth2 독립된 시스템이다. 단지 API에 접근하기 위하여 인증과 권한이 필요할 때 OAuth2를 사용하여 인증을 받을 것이다. ( 물론 OAuth2 이외에도 다른 인증시스템이 있다. )
8 |
9 | 먼저 사전에 사용될 소스를 살펴보자.
10 |
11 | === API에서 사용될 도메인
12 | [source,java]
13 | ----
14 | @Data
15 | @Entity
16 | public class Member implements Serializable {
17 | @Id
18 | @GeneratedValue
19 | Long id;
20 | String name;
21 | String username;
22 | String remark;
23 | public Member() {}
24 | public Member(String name, String username, String remark) {
25 | this.name = name;
26 | this.username = username;
27 | this.remark = remark;
28 | }
29 | }
30 | ----
31 | === Spring Data JPA 레포지토리
32 | [source,java]
33 | ----
34 | interface MemberRepository extends PagingAndSortingRepository {}
35 | ----
36 | 현재 포스팅에서 주제는 OAuth2 서버 만드는 것이 목적이기 때문에 API 서버는 간단히 만들어 보려고 한다.
37 |
38 | 최근 스프링에서 진행되고 있는 프로젝트 중에 REST API 서버를 간단하게 만들어 주는 프로젝트가 있다.
39 |
40 | https://brunch.co.kr/@sbcoba/2[두 번째 포스팅]을 보면 추가된 의존성 중 **Rest Repositories를** 확인해 볼 수 있다.
41 |
42 | 보통 스프링 MVC에서는 API 형태를 만들 때에는 아래와 같은 형태로 API를 만든다.
43 |
44 | === 기존 API 형태
45 | [source,java]
46 | ----
47 | @Controller
48 | @RequestMapping("/member")
49 | public class MemberController {
50 | @Autowired
51 | MemberService memberService;
52 |
53 | @RequestMapping
54 | public List all() {
55 | return memberService.findAll();
56 | }
57 | @RequestMapping("/{id}")
58 | public Member get(@PathVariable("id") Long id) {
59 | return memberService.find(id);
60 | }
61 | ...
62 | }
63 | ----
64 | 하지만 이 포스팅은 심플한 API 서버를 만들 목적으로 http://projects.spring.io/spring-data-rest/[Spring Data Rest 프로젝트]를 사용해서 API 서버를 만들어 보려고 한다.
65 |
66 | === Spring Data Rest 형태
67 |
68 | 의존성은 앞서 추가했기 때문에 아래와 같이 위에서 추가한 [underline]#MemberRepository#에서@RepositoryRestResource 어노테이션 설정만 하면 된다.
69 | [source,java]
70 | ----
71 | @RepositoryRestResource
72 | public interface MemberRepository extends PagingAndSortingRepository {}
73 | ----
74 | MemberRepository는 알다시피 http://projects.spring.io/spring-data-jpa/[Spring Data JPA를] 사용해서 만든 Repository 형태인데 따로 @Controller를 만들지 않고 **@RepositoryRestResource **어노테이션만 붙여도 내부적으로 Rest API가 만들어진다.
75 |
76 | 어떤 요청을 만들어 주는지 살펴보겠다.
77 |
78 | ****
79 | /{repository}/{id}/{property},methods=GET
80 | /{repository}/{id}/{property}/{propertyId},methods=GET
81 | /{repository}/{id}/{property},methods=DELETE
82 | /{repository}/{id}/{property},methods=GET
83 | /{repository}/{id}/{property},methods=PATCH || PUT || POST
84 | /{repository}/{id}/{property}/{propertyId},methods=DELETE
85 | /{repository},methods=OPTIONS
86 | /{repository},methods=HEAD
87 | /{repository},methods=GET
88 | /{repository},methods=GET
89 | /{repository},methods=POST
90 | /{repository}/{id},methods=OPTIONS
91 | /{repository}/{id},methods=HEAD
92 | /{repository}/{id},methods=GET
93 | /{repository}/{id},methods=PUT
94 | /{repository}/{id},methods=PATCH
95 | /{repository}/{id},methods=DELETE
96 | /{repository}/search,methods=HEAD
97 | /{repository}/search,methods=GET
98 | /{repository}/search,methods=OPTIONS
99 | /{repository}/search/{search},methods=GET
100 | /{repository}/search/{search},methods=GET
101 | /{repository}/search/{search},methods=OPTIONS
102 | /{repository}/search/{search},methods=HEAD
103 | ****
104 |
105 | 어노테이션 설정 하나로 위와 같은 요청을 자동으로 만들어 준다.
106 |
107 | 위에서 {repository}가 의미하는 바는 어노테이션이 있는 레포지토리 인터페이스의 주 도메인 클래스명을 기준으로 해서 ( 앞글자는 소문자로 ) 복수형을 붙여 준다. [underline]#여기서는 도메인객체가 Member 이기 때문에 복수형이 자동으로 설정되어 **members**라고 들어간다.
108 |
109 | 물론 아래와 같은 형태로 직접 URI형태를 설정하여 사용할 수도 있다.
110 | [source,java]
111 | ----
112 | @RepositoryRestResource(path = "user") // "/user" URI로 변경된다.
113 | interface MemberRepository extends PagingAndSortingRepository {}
114 | ----
115 | 하지만 이 포스팅에는 최소한의 설정을 추구하려고 하기 때문에 될 수 있으면 기본설정값을 따라 갈것이다.
116 |
117 | 자세한 내용은 http://docs.spring.io/spring-data/rest/docs/current/reference/html/[공식문서]에서 확인하자. http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.configuring-the-rest-url-path[여기]에는 주소 자체를 커스터마이징 하는 방법이 잘 설명되어 있다.
118 |
119 | http://projects.spring.io/spring-data-rest/[스프링 데이터 rest]는 기본적인 형태가 조회뿐만 아니라 [underline]#**수정 및 삭제**#까지 모든 API들이 생성되는 형태이기 때문에 http://docs.spring.io/spring-data/rest/docs/current/reference/html/[ 공식문서에서] 잘 확인해서 제어를 잘하게 되면 실무에서도 활용이 가능할 것이다.
120 |
121 | (즉 위와 같은 형태는 예제에서만 사용하시라는 뜻!)
122 |
123 | OAuth2 인증을 통해 보호하려고 하는 테스트 API를 단번에(?) 만들었다. 나중에 http://projects.spring.io/spring-data-rest/[스프링 데이터 rest] 깊게 파서 사용할 일이 있으면 한 번 포스팅해보려고 한다.
124 |
125 | '''
126 |
127 | 위에서 생성된 API를 호출해보려고 하면 계정 정보를 요구하게 된다.
128 | 스프링 부트에서 스프링 시큐리티 설정하게 되면 기본적으로 서버의 모든 API를 디폴트 계정으로 접근 제한하기 때문이다.
129 |
130 | 참고로 기본 계정 정보는 아이디는 **user**, 패스워드는 **랜덤 문자열이다.**
131 | *(서버 시작 시에 패스워드가 랜덤으로 설정되며, 설정된 패스워드는 로그에서 확인 가능하다.)*
132 |
133 | 하지만 원활한 테스트를 위해 계정을 설정해보겠다.
134 |
135 | 여기서 설정 정보는 "application.properties" 이 곳에 할 것이다.
136 | [source,properties]
137 | ----
138 | # resources/application.properties
139 | security.user.name=user
140 | security.user.password=test
141 | ----
142 | 톰캣을 재시작한 후 OSX 또는 리눅스는 콘솔에서 curl, 윈도 계열이면 브라우저에서 주소를 호출해보자.
143 |
144 | https://en.wikipedia.org/wiki/Basic_access_authentication[Basic 인증] 형태이기 때문에 아래와 같이 호출할 것이다.
145 | ****
146 | http://user/[http://user]:test@localhost:8080/members
147 | 또는 curl http://user/[http://user]:test@localhost:8080/members
148 | ****
149 | 자 다음 편에서 본격적인 OAuth2 서버를 만들어 보자!
--------------------------------------------------------------------------------
/exam4.adoc:
--------------------------------------------------------------------------------
1 | == Spring Boot로 만드는 OAuth2 시스템 4
2 |
3 | ==== 간단한 OAuth2 서버 만들어 보기
4 |
5 | 이번 포스팅부터 본격적으로 OAuth2서버를 만들어 보겠다.
6 |
7 | 간단한 세팅을 시작으로 하나하나 점증적으로 확장하는 형태로 진행할 예정이다.
8 |
9 | (샘플 소스: https://github.com/sbcoba/spring-boot-oauth2-sample[https://github.com/sbcoba/spring-boot-oauth2-sample])
10 |
11 | '''
12 |
13 | == Client ID 계정 생성
14 |
15 | 먼저 기본적인 OAuth2 서버에서 가지는 Client ID와 Client Secret을 아래에서 설정 정보를 추가해보자.
16 |
17 | (https://github.com/sbcoba/spring-boot-oauth2-sample/blob/master/src/main/resources/application.properties[실제 소스])
18 | [source,properties]
19 | ----
20 | # resources/application.properties
21 | security.oauth2.client.client-id=foo
22 | security.oauth2.client.client-secret=bar
23 | # client-id : client를 식별하는 고유 정보
24 | # client-secret : 액세스 토큰을 교환하기 위한 비공개 정보 ( 보통 암호 )
25 | ----
26 | '''
27 |
28 | === Client의 의미
29 |
30 | OAuth2 서버를 통해 API에 접근을 허가한 클라이언트를 지칭하는 명칭이다.
31 |
32 | 클라이언트 종류로는 보통 웹, 아이폰 앱, 안드로이드 앱, PC 앱 등이 있다.
33 |
34 | 예를 들면 페이스북에서도 https://developers.facebook.com/apps[개발자 사이트]에서 클라이언트 계정을 아래의 버튼으로 발급할 수 있다.
35 |
36 | image::https://t1.daumcdn.net/thumb/R1280x0/?fname=http://t1.daumcdn.net/brunch/service/user/so6/image/9RyZGAEB9zQE63TDcJSH4I1BxHQ.jpeg[]
37 |
38 | 페이스 북에서는 스프링 시큐리티 OAuth2의 명칭과 약간 다르지만 동일한 의미를 지닌다고 생각하면 된다.
39 | ****
40 | Client ID -> App ID;;
41 | Client Secret -> App Secret;;
42 | ****
43 | image::https://t2.daumcdn.net/thumb/R1280x0/?fname=http://t2.daumcdn.net/brunch/service/user/so6/image/uD5KqIj1g3ieHuJhOeZNE3W1xi0.jpg[]
44 | 발급된 페이스북 App 계정
45 | 보통 OAuth2 인증을 지원하는 웹사이트에서는 클라이언트 계정을 발급해야 하는 관리자 페이지와 그것을 저장하는 저장소 등을 가지고 있는 경우가 많다. 클라이언트 정보 저장소로는 보통 Mysql과 같은 RDB과 NoSQL 같은 외부 저장소를 이용해서 저장해둔다. 하지만 이 같은 경우에는 가장 간단한 샘플 형태이기 때문에 직접 하드 코딩해두는 경우이다.
46 |
47 | 그래서 뒷부분에서는 확장하는 부분을 설명하면서 다른 저장소를 이용해서 클라이언트 정보를 관리하는 방법도 다룰 예정이다.
48 |
49 | '''
50 |
51 | == 소스 작성 (https://github.com/sbcoba/spring-boot-oauth2-sample/blob/master/src/main/java/com/example/DemoApplication.java[실제 소스])
52 | [source,java]
53 | ----
54 | // DemoApplication.java
55 | @EnableResourceServer // API 서버 인증(또는 권한 설정
56 | @EnableAuthorizationServer // OAuth2 권한 서버
57 | @SpringBootApplication
58 | public class DemoApplication {
59 | public static void main(String[] args) {
60 | SpringApplication.run(DemoApplication.class, args);
61 | }
62 | }
63 | ----
64 | OAuth2 인증서버와 API 서버를 만들었다. ( 어노테이션 두개로 말이다!! )
65 |
66 | 놀랍지 않은가? 어노테이션 두개만 설정했을 뿐인데 설정이 완료되었다. 이것이 바로 스프링 부트의 힘이다!
67 |
68 | 사실 이번 포스팅에서 코딩은 여기 까지다. ( 테스트용 부분은 제외 )
69 |
70 | 정말 OAuth2 인증이 되는지 한 번 테스트해보자.
71 |
72 | '''
73 |
74 | == OAuth2의 Access Token 발급받기 테스트
75 |
76 | 테스트를 위해 추천하는 툴은 Chrome의 앱인 https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop[Postman]을 추천한다. 무료인데도 불구하고 API테스트시에는 최고라고 생각된다.
77 | 물론 linux(unix) 계열 운영체제이면 curl로도 충분히 테스트 가능하다. 둘 다 설명하기 힘들기 때문에 텍스트로 설명이 가능한 curl 기준으로 설명할 예정이다. ( http://www.confusedbycode.com/curl/[윈도용 curl]이 있는데 직접 설치해야 한다.)
78 |
79 | 스프링 부트에서 스프링 시큐리티 OAuth2의 기본 설정이 클라이언트의 Accees Token 발급 방법을 다섯 가지 방법으로 받을 수 있도록 활성화되어 있다. 그 다섯 가지 방식에 대해 알아 보고 테스트해보려고한다.
80 |
81 | 일단 위에서 언급한 https://github.com/sbcoba/spring-boot-oauth2-sample[github에서 샘플 소스]를 받고 서버가 실행한 상태에서 테스트가 가능하다.
82 |
83 | '''
84 |
85 | === 1. 권한 코드 방식 (Authorization Code flow)
86 |
87 | 보통 서버 사이트 웹에서 인증받을 때 가장 많이 쓰는 방식으로 기본적으로 지원하고 있는 방식이다.
88 |
89 | Access Token을 받기 위한 테스트가 다른 방식에 비해 복잡하다.
90 |
91 | (따로 인증 관련 요청 페이지 부분을 생성했다.)
92 |
93 | 먼저 바로 아래의 주소로 브라우저에서 호출한다.
94 | ****
95 | http://localhost:8080/oauth/authorize?response_type=code&client_id=foo&redirect_uri=http://localhost:8080/test/authorization-code&scope=read&state=0807edf7d85e5d[http://localhost:8080/oauth/authorize?response_type=code&client_id=foo&redirect_uri=http://localhost:8080/test/authorization-code&scope=read]
96 | ****
97 | image::https://t3.daumcdn.net/thumb/R1280x0/?fname=http://t3.daumcdn.net/brunch/service/user/so6/image/yWqjPfSNnelS4nwWBT2B4xCohqc.jpg[]
98 | Client의 접근을 허가 할 것인지 여부를 묻는다.
99 | 위 화면에서 Approve체크 후 하단에 Authorize버튼을 클릭하면 아래의 주소로 리다이렉트 되면서 브라우저 화면에서 curl 명령어가 보일 것이다.
100 | ****
101 | http://localhost:8080/test/authorization-code?code=생성된코드
102 | ****
103 | [source,sh]
104 | ----
105 | $ curl -F "grant_type=authorization_code" -F "code=생성된 코드" -F "client_id=foo" -F "scope=read" -F "client_secret=bar" -F "redirect_uri=http://localhost:8080/test/authorization-code" "http://foo:bar@localhost:8080/oauth/token"
106 | ----
107 | 브라우저에 나타나 curl 명령어를 복사해서 실행하면 아래의 Access Token정보가 보일 것이다.
108 | [source,json]
109 | ----
110 | {
111 | "access_token":"1f94c2eb-99bb-412a-bc17-9630b1ae29dc",
112 | "token_type":"bearer",
113 | "refresh_token":"a4b037b7-f736-4bde-a073-7f88279df9bb",
114 | "expires_in":43199,
115 | "scope":"read"
116 | }
117 | ----
118 | '''
119 |
120 | === 2. 암묵적인 동의 방식 (Implicit Grant flow)
121 |
122 | 보통 클라이언트 사이드에서 OAuth2 인증하는 방식이다.
123 | ****
124 | http://user:test@localhost:8080/oauth/authorize?response_type=token&redirect_uri=http://localhost:8080&client_id=foo&scope=read[http://user:test@localhost:8080/oauth/authorize?response_type=token&redirect_uri=http://localhost:8080&client_id=foo&scope=read]
125 | ****
126 | 위의 주소 형태를 호출하면 redirect_uri에 입력된 주소로 리다이렉트 되면서 기본적으로 해쉬태그에서 파라메터로 Access Token을 전달해준다.
127 |
128 | '''
129 |
130 | === 3. 자원 소유자 비밀번호 (Resource Owner Password Credentials flow)
131 |
132 | 자원 소유자 즉 사용자의 아이디(username)와 비밀번호로 Access Token 발급한다.
133 | [source,sh]
134 | ----
135 | $ curl foo:bar@localhost:8080/oauth/token -d grant_type=password -d client_id=foo -d scope=read -d username=user -d password=test
136 | ----
137 | '''
138 |
139 | === 4. 클라이언트 인증 플로우 (Client Credentials flow)
140 |
141 | 클라이언트가 직접 자신의 정보를 통해 Access Token을 발급한다.
142 | [source,sh]
143 | ----
144 | $ curl -F "grant_type=client_credentials" -F "scope=read" "http://foo:bar@localhost:8080/oauth/token"
145 | ----
146 | '''
147 |
148 | === 5. Refresh Token를 통한 Access Token 재발급
149 |
150 | 기존에 저장해둔 Refresh Token이 존재할 때 Access Token 재발급받을 필요가 있을 때 사용한다.
151 |
152 | 그리고 기존 Access Token은 만료된다.
153 | [source,sh]
154 | ----
155 | $ curl -F "grant_type=refresh_token" -F "scope=read" -F "refresh_token=발급된 Refresh Token" "http://foo:bar@localhost:8080/oauth/token"
156 | ----
157 | '''
158 |
159 | === Access Token을 사용하여 API에 접근 테스트
160 |
161 | 위에서 여러 가지 방법으로 발급된 Access Token을 사용해서 API를 호출해보자.
162 | [source,sh]
163 | ----
164 | $ curl -H "Authorization: Bearer 발급된 AccessToken" "http://localhost:8080/members%22[http://localhost:8080/members"
165 | ]# e.g.
166 | $ curl -H "Authorization: Bearer 05e63e85-9614-446a-8904-aa6cc556bb1b" "http://localhost:8080/members"
167 | ----
168 | json 정보가 확인되면 성공이다.
169 |
170 | '''
171 |
172 | 다음으로 위 소스에서 설정했던 어노테이션에 대해 한 번 살펴보자.
173 |
174 | === @EnableResourceServer
175 |
176 | API 서버를 OAuth2 인증받게 만들도록 하며 하는 역할을 한다. 기본 옵션은 모든 API의 모든 요청에 대해 OAuth2 인증을 받도록 한다.
177 |
178 | 세부적인 설정을 위해서는 아래와 같이 ResourceServerConfigurerAdapter 클래스를 상속받아서 configure를 구현해야 한다.
179 | [source,java]
180 | ----
181 | // ...
182 | @EnableResourceServer
183 | @SpringBootApplication
184 | public class DemoApplication extends ResourceServerConfigurerAdapter {
185 | @Override
186 | public void configure(HttpSecurity http) throws Exception {
187 | http.authorizeRequests()
188 | .antMatchers("/api/**").authenticated();
189 | }
190 | // ...
191 | }
192 | ----
193 | 확장을 하지 않고 기본 옵션은 모든 API는 인증이 필요한 형태로 설정된다.
194 |
195 | OAuth2 인증을 확인하기 위하여 OAuth2 토큰 스토어 지정해야 하며, 직접 설정을 하지 않았으면 인메모리 형태로 지정된다. ( 위에서는 지정하지 않았으니 인메모리 형태로 된 상태이다. )
196 | 위와 같이 [underline]#OAuth2 서버#와 [underline]#API 서버#가 같은 곳에서 처리되는 형태라면 같은 기본적으로 인메모리 토큰 스토어를 서로 공유하게 된다.
197 |
198 | 여기 예제에서는 스프링 부트의 기본 설정을 사용해서 모든 API를 인증받도록 할 예정이다. ( 즉 설정을 안 할 예정이다.)
199 |
200 | 참고로 스프링 부트에서 기본적으로 설정되는 Class의 위치는 아래와 같다.
201 | ****
202 | org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerConfiguration
203 | ****
204 | '''
205 |
206 | === @EnableAuthorizationServer
207 |
208 | OAuth2 인증서버를 활성화시켜주는 어노테이션이다.
209 |
210 | OAuth2 인증을 위한 AccessToken, RefreshToken 발급과 발급된 토큰을 통한 OAuth2 인증 등 핵심기능을 활성화시켜 준다.
211 |
212 | 내부에서는 "/oauth/token", "/oauth/authorize" 등 기본적으로 OAuth2에서 사용하는 URI의 접근을 활성화 및 인증 및 내부 예외 처리 기능 등을 가진다.
213 |
214 | 세부적인 설정을 위해서는 아래와 같이 AuthorizationServerConfigurerAdapter 클래스를 상속받아서 configure를 구현해야 한다.
215 | [source,java]
216 | ----
217 | @EnableAuthorizationServer
218 | @SpringBootApplication
219 | public class DemoApplication extends AuthorizationServerConfigurerAdapter {
220 | // ...
221 | @Override
222 | public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
223 | // OAuth2 인증서버 자체의 보안 정보를 설정하는 부분
224 | }
225 |
226 | @Override
227 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
228 | // Client 에 대한 정보를 설정하는 부분
229 | }
230 |
231 | @Override
232 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
233 | // OAuth2 서버가 작동하기 위한 Endpoint에 대한 정보를 설정
234 | }
235 | // ...
236 | }
237 | ----
238 | 위와 같이 확장할 수 있지만 여기서는 기본적으로 Spring Boot에서 기본적으로 설정해주는 형태 그대로 사용할 예정이며 추후 필요한 형태로 확장할 예정이다.
239 |
240 | 참고로 Spring Boot에서 기본적으로 설정되는 Class의 위치는 아래와 같다.
241 | ****
242 | org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration
243 | ****
244 | '''
245 |
246 | Access Token 발급 테스트하는 부분이 생각보다 많이 길어졌다.
247 |
248 | 설명은 많이 했지만 사실 코드는 얼마 되지 않는다.
249 |
250 | 그리고 OAuth2 인증 자체가 복잡한 부분이 있기 때문에 테스트 조차도 복잡해진 부분이 생겼기 때문에 계속 보안할 예정이다.
251 |
252 | [sidebar]
253 | ====
254 | OAuth2 내부에 구체적인 스펙에 관심이 있으면 국내에 출시된 OAuth2 책이 존재하니 한 번 읽어보길 권한다.
255 |
256 | http://www.hanbit.co.kr/ebook/look.html?isbn=9788979149944[**안전한 API 인증과 권한 부여를 위한 클라이언트 프로그래밍 OAuth 2.0 - 이북(ebook)**]
257 | ====
258 | 5편부터는 Spring Boot에서 지원하는 OAuth기본 설정의 부족한 부분과 확장해야 될 부분 등을 살펴보려고 한다.
--------------------------------------------------------------------------------
/exam5.adoc:
--------------------------------------------------------------------------------
1 | == Spring Boot로 만드는 OAuth2 시스템 5
2 |
3 | ==== OAuth2 서버를 커스터마이징 해보자 (TokenStore 편)
4 |
5 | 앞서 포스팅에서는 최소한의 코드로 OAuth2 서버를 만들어보았다. 사실 설명은 길었으나 정작 코드가 얼마 없는 것을 보고 많이 실망했을 수도 있겠다. 당연히 테스트 형태로 사용할 수는 있어도 실제로 서버로 사용하기에는 많이 부족한 형태이다.
6 |
7 | '''
8 |
9 | === 실제로 사용하기 부족한 이유를 한번 살펴보자.
10 |
11 | 1. 기본적으로 설정하지 않으면 **인증**, **권한**, **토큰**, **권한 코드, 클라이언트** 등 영속성이 필요한 정보를 메모리에서 관리한다. 그렇기 때문에 두개 이상의 서버를 같이 운영해서 사용할 수 없는 문제가 생긴다.
12 | (인증과 보안처럼 중요한 서버라면 가용성이 많이 높아야 한다. 최소 두대 이상을 운영할 수 있어야 문제가 발생한 서버가 있더라도 바로 중지되는 사태를 막을 수 있다.)
13 | 2. 인증 형태를 모두 Basic 형태로 하기 때문에 실제로 사용하기에는 부족하다.
14 | 3. 인증서버와 API 서버가 같이 존재하기 때문에 API 서버를 추가하거나 변경할 때 인증서버도 같이 종료되는 문제가 생긴다.
15 |
16 | 일단 지금 까지 생각나는 부분은 여기까지이다.
17 |
18 | 참고로 이야기하자면 스프링 부트를 사용한다고 해서 기존 스프링에 비해 성능이 떨어지거나 내장 톰캣을 사용한다고 해서 기존 톰캣보다 성능이 떨어진다거나 하지는 않는다. 스프링 부트 이름 그대로 해주는 부분은 서버가 시작할 시에 설정 부분을 편하게 하는 역할이 대부분이다. 단순히 그냥 스프링을 사용한다고 생각하면 된다. 그래도 느리다고 생각되면 로직 자체가 느리거나 스프링 자체 문제일 것이다.
19 | 그리고 내장 톰캣은 톰캣 사이트에서 http://tomcat.apache.org/download-80.cgi[공식으로 배포하는 톰캣의 내장할 수 있는 버전]이다.
20 | (성능상 차이가 없다고 생각하면 된다.)
21 |
22 | image::https://t1.daumcdn.net/thumb/R1280x0/?fname=http://t1.daumcdn.net/brunch/service/user/so6/image/qQ4ffCErWBws38maO6DGAEv92rI.jpg[]
23 | 공식으로 배포하는 내장톰캣
24 |
25 | '''
26 |
27 | 첫 번째로 변경해볼 부분은 토큰 (AccessToken, RefreshToken)을 메모리가 아니라 외부에 저장하는 부분을 진행해보자.
28 |
29 | 토큰을 저장하는 곳 이름이 **TokenStore**라고 불린다. TokenStore명칭 자체가 토큰을 저장하는 저장소를 의미하기도 하지만 내부에서 사용하는 **인터페이스명을** 지칭하기도 한다.
30 |
31 | 현재까지 포스팅한 부분까지가 TokenStore는 JVM 내부 메모리를 사용한 저장소를 사용하는 형태이기 때문에 테스트 서버를 재부팅할 때마다 토큰 정보가 초기화된다.
32 | 그래서 발급된 AccessToken(이하 토큰)을 가지고 API 접근 테스트할 때 조차 많이 불편한 상태이다.
33 |
34 | 이 토큰 정보 부분을 외부 저장소로 저장하는 작업을 해보자. (즉 다른 TokenStore를 사용해보자.)
35 |
36 | '''
37 |
38 | === 스프링 시큐리티 OAuth2의 기본 TokenStore 종류
39 | ****
40 | 1. org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore
41 |
42 | - JAVA 내부에서 Map, Queue 구조의 메모리를 사용한 저장소 **(기본)**
43 |
44 | 2. org.springframework.security.oauth2.provider.token.store.JdbcTokenStore
45 |
46 | - JDBC를 사용해서 DB에 저장하는 방식
47 |
48 | 3. org.springframework.security.oauth2.provider.token.store.JwtTokenStore
49 |
50 | - 외부 저장소가 아닌 https://jwt.io/[JWT](Token에 JSON 정보를 인코딩 하여 저장하는 방식)를 이용하는 방식
51 |
52 | 4. org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore
53 |
54 | - http://www.redis.io/[Redis] ( Key Value 저장소 )에 Token 정보를 저장하는 방식
55 | ****
56 | '''
57 |
58 | 기본적인 설정은 InMemoryTokenStore를 사용하기 때문에 서버가 리붓되는 동시에 데이터가 초기화된다.
59 |
60 | 그래서 먼저 TokenStore 중 가장 익숙한 DB를 이용한 JdbcTokenStore를 이용해서 토큰을 저장해 보는 예제를 해보려고 한다.
61 |
62 | 기존에 H2 DB를 사용하고 있기 때문에 H2 DB를 사용한 예제를 만들어 보려고 한다. 물론 다른 DB를 사용할 때에도 해야 하는 작업에 큰 차이는 나지 않지만 관련 DBMS 설치나 세팅 등 접속하기 전 사전작업을 설명해야 하기 때문에 제외하겠다. (DB를 변경시에도 [underline]#칼럼명만 바뀌지 않으면# 비교적 쉽게 바꿀 수 있다.)
63 |
64 | '''
65 |
66 | == JdbcTokenStore를 구현하기 위한 사전 준비사항
67 |
68 | === 1. OAuth2 토큰을 저장하기 위한 DB 스키마 생성
69 |
70 | 다행히 스프링 시큐리티 OAuth 프로젝트에서 스키마를 제공하고 있다. (https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql[DB 스키마 링크])
71 |
72 | 단 H2 DB의 스키마 형태만 제공하기 때문에 만약 Mysql, 오라클 등 다른 DB를 사용할 때에는 그 DB에 맞도록 적절하게 스키마를 변경해야 할 것이다.( 대신 칼럼명을 변경하면 귀찮아진다. )
73 |
74 | 위의 스키마 데이터를 복사하여 application.properties 와 같은 경로인 "resources/" 디렉터리 아래에**schema.sql** 파일을 생성시켜 스키마를 붙여 넣기 한다.
75 | ("**schema.sql" **파일명과 동일하게 기술해야 따로 설정 없이 관련 서버 시작 시 자동으로 ddl쿼리를 실행한다.)
76 |
77 | '''
78 |
79 | === 2. 내장 H2 DB를 조회하기 위한 웹 콘솔을 접속하기 위한 세팅
80 |
81 | H2 DB에 토큰이 저장되는지 스키마가 생성되었는지 데이터 확인할 수 있는 내장 H2 DB의 웹 콘솔을 활성화시켜보자. (기본적으로는 비활성화)
82 |
83 | 스프링 부트 1.3 이후부터는 아래와 같이 설정만 해도 바로 H2 console에 접속 가능하다.
84 | [source,properties]
85 | ----
86 | # resources/application.properties
87 | spring.h2.console.enabled=true
88 | spring.h2.console.path=/h2-console
89 | ----
90 | 현재 설명하는 예제는 스프링 부트 1.3 이후 기준이기 때문에 위와 같은 설정만 하면 되지만 그 이전 버전의 스프링 부트에서는 h2 웹 콘솔을 보기 위해서는 http://java.ihoney.pe.kr/403[허니몬님 블로그]에 설명이 잘되어 있다.
91 |
92 | 웹 콘솔 활성화 설정이 완료 후 서버를 시작하여 http://localhost:8080/h2-console[http://localhost:8080/h2-console] 주소에 접속해본다.
93 |
94 | image::https://t2.daumcdn.net/thumb/R1280x0/?fname=http://t2.daumcdn.net/brunch/service/user/so6/image/kP0XaRMtDgsL8tPdthjW51T-lDk.jpg[]
95 | 웹 콘솔에 접근하면 위와 같은 화면이 나온다.
96 | 위와 같은 화면에 나왔을 때 "JDBC URL" 부분을 [underline]#**jdbc:h2:mem:testdb**#로 **반드시 **바꿔줘야 한다. 이 값이 스프링 부트 안에서 H2 데이터 소스에 접근할 수 있는 (다른 설정을 해주지 않았다면) 기본 접속 정보이다.
97 |
98 | 스프링 시큐리티를 사용하고 있기 때문에 헤더 부분이 충돌 나서 화면이 보이지 않을 시에는 아래와 같은 설정 정보를 추가해준다.
99 |
100 | https://github.com/sbcoba/spring-boot-oauth2-sample/blob/master/src/main/java/com/example/DemoApplication.java[DemoApplication.java]
101 | [source,java]
102 | ----
103 | @Override
104 | public void configure(HttpSecurity http) throws Exception {
105 | http.headers().frameOptions().disable();
106 | ...
107 | }
108 | ----
109 | '''
110 |
111 | == JdbcTokenStore 설정
112 |
113 | 앞에서 언급한 설정 부분이 문제가 없이 되었다면 다음과 아래와 같은 설정만 추가해주면 끝이다.
114 |
115 | https://github.com/sbcoba/spring-boot-oauth2-sample/blob/master/src/main/java/com/example/DemoApplication.java[DemoApplication.java]
116 | [source,java]
117 | ----
118 | @Bean
119 | public TokenStore JdbcTokenStore(DataSource dataSource) {
120 | return new JdbcTokenStore(dataSource);
121 | }
122 | ----
123 | 이 부분만 추가되면 DB에 토큰을 저장할 준비가 완료되었다.
124 | (너무 쉽게 설정되어 허무할지도 모르지만 이미 지원되고 있는 형태이기 때문에 쉽게 가능하다.)
125 |
126 | 이제 잘되는지 테스트를 해보자.
127 |
128 | '''
129 |
130 | == 테스트
131 |
132 | 테스트 시나리오 아래와 같이 단순하다.
133 |
134 | 1. Access Token이 발급해보고 DB를 확인해서 들어갔는지 확인
135 |
136 | 2. 발급된 Access Token으로 인증이 되는지 확인
137 |
138 | Access Token 발급은 이전 포스팅에서 나열한 것 중 가장 단순한 자원 소유자 비밀번호 형태로 하겠다.
139 | [source,sh]
140 | ----
141 | $ curl foo:bar@localhost:8080/oauth/token -d grant_type=password -d client_id=foo -d scope=read -d username=user -d password=test
142 | ----
143 | 실행시켜 본다.
144 | [source,json]
145 | ----
146 | {
147 | "access_token":"a870ff1f-4c07-4816-b221-73270326ec25",
148 | "token_type":"bearer",
149 | "expires_in":43199,
150 | "scope":"read"
151 | }
152 | ----
153 | 자 Access Token을 발급받았다.
154 |
155 | DB에 값이 들어갔는지 확인해보겠다. 기본 테이블명은 "OAUTH_ACCESS_TOKEN"이다.
156 |
157 | image::https://t3.daumcdn.net/thumb/R1280x0/?fname=http://t3.daumcdn.net/brunch/service/user/so6/image/nbBgb3MUlHuF4x54S6v4xmjWZBk.jpg[]
158 | OAUTH_ACCESS_TOKEN 테이블을 조회 해본다.
159 | 위와 같이 쿼리 하면 아래에 토큰이 조회된다.
160 |
161 | AccessToken 값 자체에 해당하는 값이 TOKEN_ID 칼럼이다. 하지만 보이는 값이 다른 이유는 AccessToken 자체도 중요한 값이기 때문에 패스워드처럼 인코딩 해서 보관하기 때문에 그렇다.
162 | 참고로 JdbcTokenStore 클래스의 소스를 살펴보면 MD5 알고리즘을 사용해서 내부에서 인코딩 한다.
163 |
164 | 이러한 이유로 AccessToken 값이 일치하지 않는 이유에 대해 특별히 신경을 안 써도 된다.
165 |
166 | 자 이제 발급받은 AccessToken으로 API를 조회해보자.
167 | [source,sh]
168 | ----
169 | $ curl -H "Authorization: Bearer a870ff1f-4c07-4816-b221-73270326ec25" "http://localhost:8080/members"
170 | ----
171 | 호출해보니 API가 제대로 조회되는 것을 확인 완료하였다.
172 |
173 | 그러면 저 DB값을 통해 Access Token이 인증을 받고 있는지 DB에서 데이터를 ** 제거해보자.**
174 |
175 | image::https://t4.daumcdn.net/thumb/R1280x0/?fname=http://t4.daumcdn.net/brunch/service/user/so6/image/8WlfFeaPLGNNIbvs96fqQ2ks3es.jpg[]
176 | DELETE 쿼리를 날려보자.
177 | [source,sh]
178 | ----
179 | $ curl -H "Authorization: Bearer a870ff1f-4c07-4816-b221-73270326ec25" "http://localhost:8080/members"
180 | ----
181 | 앞서 잘되던 요청과 동일한 요청을 다시 한번 날려보자.
182 | [source,json]
183 | ----
184 | {
185 | "error":"invalid_token",
186 | "error_description":"Invalid access token: a870ff1f-4c07-4816-b221-73270326ec25"
187 | }
188 | ----
189 | 예상에 맞게 바로 실패했다.
190 |
191 | 이로서 비교적 쉽게 DB를 통해 Access Token을 발급하고 사용할 수 있게 되었다.
192 |
193 | '''
194 |
195 | 위의 내용은 미리 알려드린 동일한 https://github.com/sbcoba/spring-boot-oauth2-sample[github]에 갱신되어 있다.
--------------------------------------------------------------------------------
/exam6.adoc:
--------------------------------------------------------------------------------
1 | == Spring Boot로 만드는 OAuth2 시스템 6
2 |
3 | ==== API 서버와 OAuth2 서버를 분리
4 |
5 | 앞서 포스팅 작성한 예제까지는 API 서버와 OAuth2 서버가 하나의 웹 애플리케이션에서 같이 올라가는 형태를 취하고 있다. 예제 자체를 심플하게 유지하려는 목적과 이렇게 개발도 가능하다는 것을 보여주기 위한 것이었다.
6 |
7 | 하지만 실제 서비스를 하기 위해서는 각각 다른 인스턴스 형태로 서비스를 해야 한다.
8 |
9 | 이전 포스팅에도 언급했지만 보통 API 서버 같은 경우 자주 갱신되어 배포될 일이 잦기 때문에 OAuth2 인증 서버와 같이 운영하기에는 부담이 있다. OAuth2 인증 서버 같은 경우에는 서비스가 중단되었을 때에는 연관된 모든 서비스가 인증관련으로 문제가 발생하게 된다.
10 |
11 | 그렇기 때문에 API 서비스의 인스턴스와 OAuth2 서버의 인스턴스를 각각 생성시킬 수 있도록 하여 안정성을 늘려야 한다. 물론 트래픽 분산의 효과도 같이 따라온다.
12 |
13 | 서문은 길었지만 결론은 제대로 된 서비스 하기 위해서는 API 서버와 OAuth2 서버를 분리해야 된다는 이야기이다.
14 |
15 | 그리고 아래의 소스는 기존 소스의 [underline]#**브랜치**#로 해두었다. ( https://github.com/sbcoba/spring-boot-oauth2-sample/tree/example6[브랜치 링크] )
16 |
17 | '''
18 |
19 | 먼저 https://github.com/sbcoba/spring-boot-oauth2-sample/blob/master/src/main/java/com/example/DemoApplication.java[기존 소스]를 살펴보자.
20 |
21 | https://github.com/sbcoba/spring-boot-oauth2-sample/blob/master/src/main/java/com/example/DemoApplication.java[DemoApplication.java]
22 | [source,java]
23 | ----
24 |
25 | @EnableResourceServer
26 | @EnableAuthorizationServer
27 | @SpringBootApplication
28 | public class DemoApplication extends ResourceServerConfigurerAdapter {
29 | ...
30 | }
31 | ----
32 |
33 | 위의 소스에서 어노테이션을 잘 확인해보자.
34 | **@****EnableResourceServer **어노테이션은 API 서버를 설정하기 위한 부분이다.
35 |
36 | **@EnableAuthorizationServer** 어노테이션은 OAuth2 서버를 설정하기 위한 부분이다.
37 |
38 | **"extends ResourceServerConfigurerAdapter"** 이름을 보면 **@****EnableResourceServer**와 이름이 같은 것을 보면 같은 세트(?)라고 예측할 수 있다.
39 |
40 | 먼저 위의 설정을 OAuth2를 위한 설정과 API 서버를 위한 설정으로 각각 나누어 보자.
41 | [source,java]
42 | ----
43 | /**
44 | * OAuth2 서버 설정
45 | */
46 | @Configuration
47 | @EnableResourceServer
48 | class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
49 | ...
50 | }
51 |
52 | /**
53 | * API 서버
54 | */
55 | @Configuration
56 | @EnableAuthorizationServer
57 | class AuthorizationServerConfiguration {
58 | ...
59 | }
60 | ----
61 | 한 곳에 있던 설정을 따로 두개의 클래스를 만들어서 도출시켰다.
62 |
63 | 그리고 이전 포스팅에서 설정한 JdbcTokenStore 부분은 OAuth2 서버에서만 사용하게 된다. API 서버에서는 OAuth2 서버의 [underline]#Token조회 API#를 통해서 토큰을 조회하게 된다.
64 |
65 | [source,java]
66 | ----
67 | /**
68 | * OAuth2 서버 설정
69 | */
70 | @Configuration
71 | @EnableResourceServer
72 | class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
73 | ...
74 | @Bean
75 | public TokenStore JdbcTokenStore(DataSource dataSource) {
76 | return new JdbcTokenStore(dataSource);
77 | }
78 | }
79 | /**
80 | * API 서버
81 | */
82 | @Configuration
83 | @EnableAuthorizationServer
84 | class AuthorizationServerConfiguration {
85 | ...
86 | }
87 | ----
88 |
89 | 그 이외에 설정은 대부분 API 서버 관련 설정이기 때문에 ResourceServerConfiguration 설정 부분으로 옮기면 된다.
90 |
91 | '''
92 |
93 | 그 다음 단계는 이제 프로젝트를 나눌 것이다.
94 |
95 | 지금 프로젝트에서 oauth2-server, api-server 형태로 폴더를 추가하여 아래와 같은 형태로 만들었다.
96 |
97 | image::https://t1.daumcdn.net/thumb/R1280x0/?fname=http://t1.daumcdn.net/brunch/service/user/so6/image/ha9t98i34hmWdjqUrh5hyEJ_q7s.jpg[]
98 | 폴더 구조
99 | 각각 폴더마다 스프링 부트 형태의 구조로 만든 다음 위에서 나눈 설정 형태로 해둔다.
100 |
101 | 그리고 예제를 로컬에서 실행되기 위해서는 포트가 각각 달라야 한다.
102 | 포트 설정할 때에는 중복만 안되면 된다.
103 | 여기 예제에서는 **OAuth2 서버는 8080 (기본 포트), API 서버는 8081**로 설정 후 사용할 예정이다.
104 | (만약에 포트가 변경하면 설정 부분에서도 변경이 필요하다.)
105 | ****
106 | OAuth2 서버의 포트 -> 8080;;
107 | API 서버 포트 -> 8081;;
108 | ****
109 | === OAuth2 서버 설정 부분
110 | [source,java]
111 | ----
112 | @EnableAuthorizationServer
113 | @SpringBootApplication
114 | public class OAuth2Application {
115 | @Bean
116 | public TokenStore jdbcTokenStore(DataSource dataSource) {
117 | return new JdbcTokenStore(dataSource);
118 | }
119 | // ...
120 | }
121 | ----
122 | API 서버에서 Token의 정보를 가져가기 위한 요청을 활성화시켜 줘야 한다.
123 | (기본은 비활성화되어 있다. OAuth2 서버와 API 서버가 같이 있을 때에는 필요 없는 부분이기 때문이다.)
124 | [source,yaml]
125 | ----
126 | # Token 정보를 API(/oauth/check_token)를 활성화 시킨다. (기본은 denyAll)
127 | security.oauth2.authorization.check-token-access: isAuthenticated()
128 | ----
129 | === API 서버 설정 부분
130 | [source,java]
131 | ----
132 | @EnableResourceServer
133 | @SpringBootApplication
134 | public class ApiApplication {
135 |
136 | @Bean
137 | public ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() {
138 | //...
139 | }
140 | //...
141 | }
142 | ----
143 | 전체 적인 구조는 메이븐(maven) 멀티 모듈 형태로 이루어진 형태로 자세한 부분을 https://github.com/sbcoba/spring-boot-oauth2-sample/tree/example6[github]를 참고하면 된다.
144 |
145 | API 서버에서는 따로 OAuth2 서버로부터 Access Token 정보를 얻어 와야 되기 때문에 관련 부분을 설정해야 한다.
146 | [source,yaml]
147 | ----
148 | # API 서버의 appication.yml
149 |
150 | # 서버 포트 설정
151 | server.port: 8081
152 |
153 | # OAuth2 서버에서 기본적으로 Token정보를 받아오는 URL
154 | security.resource.token-info-uri: http://localhost:8080/oauth/check_token
155 |
156 | ----
157 |
158 | === DB 구성
159 |
160 | DB는 이전 포스팅처럼 하나의 인스턴스에서는 편의상 하나의 DB에서 API를 위한 테이블과 OAuth2 서버에서 사용하는 데이터(Access Token관리 등)를 위한 위한 테이블을 관리했지만 **OAuth2 서버와 API 서버가 나누어지면서 DB도 나누려고 한다.** ( 만약 하나로 관리하려고 하면 외부의 DB를 사용해야 한다. )
161 |
162 | 프로젝트 자체가 나누어진 지기 때문에 따로 설정하지 않아도 각각 H2 DB인스턴스 생성되기 때문에 따로 설정하지 않아도 된다.
163 |
164 | '''
165 |
166 | === 서버 실행
167 |
168 | 서버 실행하는 방법은 Maven(이하 메이븐)을 사용하며, 멀티 모듈이기 때문에 아래와 같이 하면 된다.
169 |
170 | [source,sh]
171 | ----
172 | # 부모 프로젝트 폴더에 들어간 후
173 | # Oauth2 서버 실행 ( 포트 8080 )
174 | $ mvn clean -pl oauth2-server spring-boot:run &
175 |
176 | # API 서버 실행 ( 포트 8081 )
177 | $ mvn clean -pl api-server spring-boot:run &
178 |
179 | # 포트 변경이 필요하면 아래와 같은 옵션 추가 후 서버 실행
180 | mvn clean -pl api-server spring-boot:run -Dserver.port=9999 &
181 | ----
182 | === 테스트
183 |
184 | 테스트 방법은 이전 포스팅과 동일하게 진행할 것이다.(어차피 같은 OAuth2 서버이다.)
185 | 다른 점이라면 OAuth2 서버와 API를 호출 시에는 서로 다른 서버를 호출하는 정도이다. (로컬이라면 포트만 다르기 때문에 포트만 변경하여 호출하면 된다.)
186 |
187 | **Access Token 발급**
188 | [source,sh]
189 | ----
190 | $ curl -F "grant_type=client_credentials" -F "scope=read" "http://foo:bar@localhost:8080/oauth/token"
191 | ----
192 | 이전 포스팅과 OAuth2 서버와 포트가 같기 때문에 요청 와 응답 부분도 동일하다.
193 | [source,json]
194 | ----
195 | {
196 | "access_token":"6dfb79ab-46cc-49ad-9b46-b4da66e9e103",
197 | "token_type":"bearer",
198 | "expires_in":42760,
199 | "scope":"read"
200 | }
201 | ----
202 | Access Token과 함께 API 서버에서 API 호출
203 | [source,sh]
204 | ----
205 | $ curl -H "Authorization: Bearer 6dfb79ab-46cc-49ad-9b46-b4da66e9e103" "http://localhost:8081/members"
206 | ----
207 | 이전 포스팅에서 API 호출하는 부분과 동일하지만 **포트 부분이 다른 것**을 반드시 확인해야 한다.
208 |
209 | 이 모든 소스는 기존 소스의 Github의 브랜치로 해두었다. https://github.com/sbcoba/spring-boot-oauth2-sample/tree/example6[소스 링크]
210 |
211 | '''
212 |
213 | == 부록
214 |
215 | === H2 외부 DB 인스턴스 실행
216 |
217 | 이제까지 포스팅은 H2 DB를 프로젝트에 내장하여 사용하는 형태를 가지고 개발을 하였다. 그런데 H2 DB 자체가 외부로 실행시켜서 접속하는 형태가 있다. 그런 방법을 간단하게 설명해보겠다.
218 |
219 | === 설치 형태
220 |
221 | **1. OSX 계열 ( homebrew 사용 )**
222 |
223 | $ brew install h2
224 | $ h2
225 |
226 | **2. Windows 계열 **
227 |
228 | http://www.h2database.com/html/main.html[http://www.h2database.com/html/main.html]
229 |
230 | 이 사이트에서 아래의 영역에 있는 **Windows Installer** 링크를 선택하여 다운로드한다.
231 |
232 | image::https://t2.daumcdn.net/thumb/R1280x0/?fname=http://t2.daumcdn.net/brunch/service/user/so6/image/gdIlReEq0rdKMfBpn93ibDyycJ8.jpg[]
233 |
234 | **3. 그 이외에 운영체제 (리눅스 계열 및 OSX 포함)**
235 |
236 | 위의 Windows와 동일한 링크에서 **All Platforms** 링크를 선택하여 다운로드한다.
237 |
238 | 압축을 푼 후 해당 디렉터리로 이동하여 아래와 같이 실행시키면 된다.
239 |
240 | $ ./bin/h2.sh
241 |
242 | === 서버를 통하여 직접 실행
243 |
244 | 서블릿을 통해 할 수 있는 방법도 있으나 스프링 부트를 통하면 더욱 쉽게 H2 서버만 실행할 수 있다. https://github.com/sbcoba/spring-boot-oauth2-sample/blob/example6/h2-server/src/main/java/com/example/H2Application.java[소스]
245 |
246 | [source,java]
247 | ----
248 | //...
249 | @SpringBootApplication
250 | public class H2Application {
251 | @Bean public DbStarter dbStarter() {
252 | return new DbStarter();
253 | }
254 | @Bean
255 | public ServletContextInitializer initializer() {
256 | return sc -> {
257 | sc.setInitParameter("db.user", "sa");
258 | sc.setInitParameter("db.password", "");
259 | sc.setInitParameter("db.tcpServer", "-tcpAllowOthers");
260 | };
261 | }
262 | // ...
263 | }
264 |
265 | ----
266 |
267 | === 외부의 H2 DB에 접속방법
268 |
269 | H2 DB에 접속하고자 하는 스프링 부트 애플리케이션은 아래와 같이 설정하여 접속이 가능하다. (접속 정보만 맞으면 어디서나 접속 가능하다. 즉 다른 DB와 접근방법이 동일하다.)
270 |
271 | [source,yaml]
272 | ----
273 | # application.yml
274 | spring:
275 | datasource:
276 | url: jdbc:h2:tcp://localhost/~/api;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
277 | driverClassName: org.h2.Driver
278 | username: sa
279 | password:
280 | ----
--------------------------------------------------------------------------------
/exam7.adoc:
--------------------------------------------------------------------------------
1 | == Spring Boot로 만드는 OAuth2 시스템 7
2 |
3 | ==== JWT 방식으로 바꿔 보자
4 |
5 | 이전 포스팅에서 OAuth2 기본 Access Token을 사용해서 사용해서 교환하여 인증받는 방식을 이야기하였다.
6 |
7 | 이렇게 하는 방식에는 단점이 존재한다. Access Token만 교환하기 때문에 그 다시 토큰을 가지고 인증 정보를 조회하기 위해 OAuth2 서버로 다시 요청하여 인증된 정보를 얻어오는 오버헤드가 생기게 된다.
8 |
9 | > 참고로 이야기하자면 API 서버에서는 인증과 관련된 정보를 가지고 있지 않기 때문에 호출시마다 Access Token을 사용해서 OAuth2 서버로 요청하여 정보를 가져온다.
10 | > 나중에 사용자가 많아지고 트래픽이 늘어나게 되면 API 서버에서 적절하게 캐시를 사용해서 컨트롤해야 하는 부분도 있다. 하지만 요청한 Access Token이 유효(validation)한지 문제가 없는지 계속 확인해야 하기 때문에 OAuth2 서버에도 주기적으로 체크해줘야 한다. 이 부분을 잘 컨트롤해줘야 많은 트래픽에 견딜 수 있는 인증서버 API 서버를 만들 수 있다.
11 |
12 | 그러한 부분을 어느 정도 해결해주기 위해서 나온 형태가 JWT(Json Web Token 이하 JWT) 방식이다.
13 |
14 | JWT를 여기서 설명하자면 많이 길어지기 때문에 잘 설명되어 있는 **https://blog.outsider.ne.kr/[Outsider님의 블로그]**를 방문하여 한 번 읽어보길 바란다. (https://blog.outsider.ne.kr/1069[https://blog.outsider.ne.kr/1069], https://blog.outsider.ne.kr/1160[https://blog.outsider.ne.kr/1160] )
15 |
16 | 내용을 읽어보면 알겠지만 JSON 형식의 토큰 데이터를 인코딩 하여 토큰으로 이용하는 형태라고 생각하면 될 것 같다.
17 |
18 | 이전의 OAuth2 서버가 Access Token만 발급한 후 필요한 정보를 Access Token을 통해 정보를 조회하는 형태라면, JWT는 (JSON형태의) 데이터가 직접 붙어 있는 토큰을 OAuth2 서버에서 발급하는 형태이다.
19 |
20 | 그래서 직접 다시 Access Token을 이용하여 조회할 필요 없이 JWT 토큰에 붙어 있는 정보를 바로 사용하게 된다.
21 |
22 | 소스를 살펴보기 전에 발급된 토큰을 살펴보자.
23 |
24 | 먼저 앞서 포스팅했던 기존 OAuth2 서버에서 Access Token을 발급 형태를 살펴보자.
25 | [source,json]
26 | ----
27 | {
28 | "access_token":"6dfb79ab-46cc-49ad-9b46-b4da66e9e103",
29 | "token_type":"bearer",
30 | "expires_in":42760,
31 | "scope":"read"
32 | }
33 | ----
34 | 위와 같이 Access Token 정보만 발급하였다.
35 |
36 | 그럼 JWT토큰을 형태로 OAuth2 서버를 설정했을 때 발급한 형태를 살펴보자.
37 | [source,json]
38 | ----
39 | {
40 | "access_token":"eyJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTQ1NzY0OTg5NiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjY1NDI5NWJlLWQyZmEtNDkxYi1hMTQwLTU5MTY0OTIxOWM2NSIsImNsaWVudF9pZCI6ImZvbyJ9.6LH9C0EP64Nh70O6t3WIqL009VfyzfavNLQEwEILxqw",
41 | "token_type":"bearer",
42 | "expires_in":43199,
43 | "scope":"read",
44 | "jti":"654295be-d2fa-491b-a140-591649219c65"
45 | }
46 | ----
47 |
48 | 위와 구조는 거의 비슷하지만 "access_token" 안에 이전보다 많은 문자열이 들어가 있는 것이 보일 것이다.
49 |
50 | 이 문자열이 JWT 데이터이다. 이 데이터를 http://jwt.io/[jwt.io] 사이트에서 분석해보겠다.
51 |
52 | image::https://t1.daumcdn.net/thumb/R1280x0/?fname=http://t1.daumcdn.net/brunch/service/user/so6/image/-0z3twwNMYPdaYR4ncMz5cuPbQI.jpg[]
53 |
54 | 위 문자열에서 JSON 데이터가 도출된다. ( 암호화 형태가 아니라 단순 인코딩 형태이기 때문에 바로 복호화된다. 중요한 정보를 노출하면 안 되는 이유이다. )
55 |
56 | '''
57 |
58 | 이제 JWT 형태로 OAuth2 인증을 받게 하는 형태로 만들어 보자.
59 |
60 | 먼저 JWT를 사용하기 위해서는 oauth2-server, api-server 의 pom.xml에서 아래와 같은 의존성을 추가해준다.
61 | [source,xml]
62 | ----
63 |
64 |
65 | org.springframework.security
66 | spring-security-jwt
67 |
68 | ----
69 |
70 | 그리고 Access Token을 사용하는 방법이 바뀌었기 때문에 설정 부분도 바꿔 줘야 한다.
71 |
72 | 먼저 JWT 토큰에서 서명할 때 사용하는 key 값이 필요하다. (JWT는 전송 중에 데이터가 변경되지 않았다는 서명을 가지는데 HMAC이라고 부른다.)을 그때 방법이 직접 클라이언트와 서버에 RSA (private, public) 키값을 기술해주거나 서버에서 얻어오는 방법이 있다.
73 | 여기서는 심플하게 서버에서 Key값을 얻어오는 방법으로 하겠다.
74 | [source,yaml]
75 | ----
76 | # application.yml ( OAuth2 서버 )
77 | security.oauth2.authorization.token-key-access: isAuthenticated()
78 | ----
79 |
80 | 위의 설정은 JWT에서 사용된 서명(HMAC) 정보를 검증할 key를 얻어오기 위한 API를 열어주기 위한 설정이다.
81 | (기본은 설정은 denyAll()로 되어 있다.)
82 | 참고로 기본 Key 값은 OAuth2 서버가 시작할 때 랜덤으로 결정된다.
83 |
84 | [source,yaml]
85 | ----
86 | # application.yml ( API 서버 )
87 | security.oauth2.jwt.key-uri: http://localhost:8080/oauth/token_key[http://localhost:8080/oauth/token_key]
88 | ----
89 | 위 설정은 OAuth2 서버에서 열어준 검증용 key값을 얻어오기 위해서 API의 URL을 지정해주는 부분이다. (위 주소(/oauth/token_key)는 기본값이다.)
90 |
91 | 이제 소스를 살펴보자.
92 |
93 | [source,java]
94 | ----
95 | // OAuth2Application.class
96 | // ...
97 | @Configuration
98 | class JwtOAuth2AuthorizationServerConfiguration extends OAuth2AuthorizationServerConfiguration {
99 |
100 | @Override
101 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
102 | super.configure(endpoints);
103 | endpoints.accessTokenConverter(jwtAccessTokenConverter());
104 | }
105 |
106 | @Bean
107 | public TokenStore tokenStore() {
108 | return new JwtTokenStore(jwtAccessTokenConverter());
109 | }
110 |
111 | @Bean
112 | public JwtAccessTokenConverter jwtAccessTokenConverter() {
113 | return new JwtAccessTokenConverter();
114 | }
115 | }
116 | ----
117 | 이전에 있던 JdbcTokenStore 설정 부분을 제거한 후 위 소스 설정을 추가해준다.
118 |
119 | 간단하게 설명하면 기존 token을 DB로 저장했었던 부분이 JWT로 오면서 없어지게 된다.
120 |
121 | 위에서 한번 언급했지만 token 자체가 (JSON형태로) 정보를 가지고 있기 때문에 DB에서 읽어 오는 게 아니라 token에 붙어 있는 JSON을 해석해서 토큰 정보를 읽어 오게 된다. 그리고 저장은 Token정보를 JSON으로 바꾼 후 token형태로 발행하게 된다.
122 |
123 | 그런 부분 설정을 위해서 위와 같은 설정을 하게 되는 것이다.
124 |
125 | 자 이제 Access Token을 읽어보자.
126 | [source,sh]
127 | ----
128 | $ curl foo:bar@localhost:8080/oauth/token -d grant_type=password -d client_id=foo -d scope=read -d username=user -d password=test -v
129 | ----
130 | [source,json]
131 | ----
132 | # 결과
133 | {
134 | "access_token":"eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NjMxMDc1NjcsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZTAyNmIxNGItMDE4MS00M2U1LTkyOTItYzlhOWI0MDUyZTE4IiwiY2xpZW50X2lkIjoiZm9vIiwic2NvcGUiOlsicmVhZCJdfQ.uxYf_gC471N14t6HejhS_Nta9raXdXZ_zWp9oq4PZfw",
135 | "token_type":"bearer",
136 | "refresh_token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsicmVhZCJdLCJhdGkiOiJlMDI2YjE0Yi0wMTgxLTQzZTUtOTI5Mi1jOWE5YjQwNTJlMTgiLCJleHAiOjE0NjU2NTYzNjcsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiJiMzdiZDE1Ny00NDRmLTQ5ZjEtOTljYy1jYWVkYWNjZTAzZTQiLCJjbGllbnRfaWQiOiJmb28ifQ.ouV83CljkzdRMW7GBQ3EpShUwYocL2cqheF5Pb1ntP0",
137 | "expires_in":43199,
138 | "scope":"read",
139 | "jti":"e026b14b-0181-43e5-9292-c9a9b4052e18"
140 | }
141 | ----
142 | 위 토큰을 통해서 API를 호출해보자
143 | [source,sh]
144 | ----
145 | $ curl http://localhost:8081/members -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NjMxMDc1NjcsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZTAyNmIxNGItMDE4MS00M2U1LTkyOTItYzlhOWI0MDUyZTE4IiwiY2xpZW50X2lkIjoiZm9vIiwic2NvcGUiOlsicmVhZCJdfQ.uxYf_gC471N14t6HejhS_Nta9raXdXZ_zWp9oq4PZfw"
146 |
147 | # 결과
148 | // API 결과 JSON ...
149 | ----
150 | API를 호출한 후 OAuth2 서버가 반응하는지 확인해보자. 내 예상이 맞다면 아마 로그가 안 올라올 것이다.
151 |
152 | 즉 토큰만으로 API를 호출할 수 있게 된 것이다.
153 | ( API 서버에서 Access Token을 확인하기 위한 OAuth2 서버로의 요청이 사라 졌다. token자체에서 데이타를 읽기 때문에! )
154 |
155 | 전체 소스는 https://github.com/sbcoba/spring-boot-oauth2-sample[이 곳]에 있습니다.
156 |
157 | 다음화에서 계속됩니다 ~
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon May 16 00:05:45 KST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/h2-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.example
8 | demo
9 | 0.0.1-SNAPSHOT
10 | ../
11 |
12 |
13 | h2-server
14 | h2-server
15 | H2 Database Standalone Server
16 |
17 |
18 |
19 | org.springframework.boot
20 | spring-boot-starter-web
21 |
22 |
23 | com.h2database
24 | h2
25 |
26 |
27 |
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-maven-plugin
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/h2-server/src/main/java/com/example/H2Application.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.h2.server.web.DbStarter;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.boot.context.embedded.ServletContextInitializer;
7 | import org.springframework.context.annotation.Bean;
8 |
9 | @SpringBootApplication
10 | public class H2Application {
11 |
12 | @Bean
13 | public DbStarter dbStarter() {
14 | return new DbStarter();
15 | }
16 |
17 | @Bean
18 | public ServletContextInitializer initializer() {
19 | return sc -> {
20 | sc.setInitParameter("db.user", "sa");
21 | sc.setInitParameter("db.password", "");
22 | sc.setInitParameter("db.tcpServer", "-tcpAllowOthers");
23 | };
24 | }
25 |
26 | public static void main(String[] args) {
27 | SpringApplication.run(H2Application.class, args);
28 | }
29 | }
--------------------------------------------------------------------------------
/h2-server/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server.port: 8082
2 |
3 | spring.h2.console:
4 | enabled: true
5 | path: /h2-console
6 |
7 | logging.level:
8 | org.springframework.boot: debug
9 | org.h2: debug
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | #
58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look
59 | # for the new JDKs provided by Oracle.
60 | #
61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
62 | #
63 | # Apple JDKs
64 | #
65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
66 | fi
67 |
68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
69 | #
70 | # Apple JDKs
71 | #
72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
73 | fi
74 |
75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
76 | #
77 | # Oracle JDKs
78 | #
79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
80 | fi
81 |
82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
83 | #
84 | # Apple JDKs
85 | #
86 | export JAVA_HOME=`/usr/libexec/java_home`
87 | fi
88 | ;;
89 | esac
90 |
91 | if [ -z "$JAVA_HOME" ] ; then
92 | if [ -r /etc/gentoo-release ] ; then
93 | JAVA_HOME=`java-config --jre-home`
94 | fi
95 | fi
96 |
97 | if [ -z "$M2_HOME" ] ; then
98 | ## resolve links - $0 may be a link to maven's home
99 | PRG="$0"
100 |
101 | # need this for relative symlinks
102 | while [ -h "$PRG" ] ; do
103 | ls=`ls -ld "$PRG"`
104 | link=`expr "$ls" : '.*-> \(.*\)$'`
105 | if expr "$link" : '/.*' > /dev/null; then
106 | PRG="$link"
107 | else
108 | PRG="`dirname "$PRG"`/$link"
109 | fi
110 | done
111 |
112 | saveddir=`pwd`
113 |
114 | M2_HOME=`dirname "$PRG"`/..
115 |
116 | # make it fully qualified
117 | M2_HOME=`cd "$M2_HOME" && pwd`
118 |
119 | cd "$saveddir"
120 | # echo Using m2 at $M2_HOME
121 | fi
122 |
123 | # For Cygwin, ensure paths are in UNIX format before anything is touched
124 | if $cygwin ; then
125 | [ -n "$M2_HOME" ] &&
126 | M2_HOME=`cygpath --unix "$M2_HOME"`
127 | [ -n "$JAVA_HOME" ] &&
128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
129 | [ -n "$CLASSPATH" ] &&
130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
131 | fi
132 |
133 | # For Migwn, ensure paths are in UNIX format before anything is touched
134 | if $mingw ; then
135 | [ -n "$M2_HOME" ] &&
136 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
137 | [ -n "$JAVA_HOME" ] &&
138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
139 | # TODO classpath?
140 | fi
141 |
142 | if [ -z "$JAVA_HOME" ]; then
143 | javaExecutable="`which javac`"
144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
145 | # readlink(1) is not available as standard on Solaris 10.
146 | readLink=`which readlink`
147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
148 | if $darwin ; then
149 | javaHome="`dirname \"$javaExecutable\"`"
150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
151 | else
152 | javaExecutable="`readlink -f \"$javaExecutable\"`"
153 | fi
154 | javaHome="`dirname \"$javaExecutable\"`"
155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
156 | JAVA_HOME="$javaHome"
157 | export JAVA_HOME
158 | fi
159 | fi
160 | fi
161 |
162 | if [ -z "$JAVACMD" ] ; then
163 | if [ -n "$JAVA_HOME" ] ; then
164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
165 | # IBM's JDK on AIX uses strange locations for the executables
166 | JAVACMD="$JAVA_HOME/jre/sh/java"
167 | else
168 | JAVACMD="$JAVA_HOME/bin/java"
169 | fi
170 | else
171 | JAVACMD="`which java`"
172 | fi
173 | fi
174 |
175 | if [ ! -x "$JAVACMD" ] ; then
176 | echo "Error: JAVA_HOME is not defined correctly." >&2
177 | echo " We cannot execute $JAVACMD" >&2
178 | exit 1
179 | fi
180 |
181 | if [ -z "$JAVA_HOME" ] ; then
182 | echo "Warning: JAVA_HOME environment variable is not set."
183 | fi
184 |
185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
186 |
187 | # For Cygwin, switch paths to Windows format before running java
188 | if $cygwin; then
189 | [ -n "$M2_HOME" ] &&
190 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
191 | [ -n "$JAVA_HOME" ] &&
192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
193 | [ -n "$CLASSPATH" ] &&
194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
195 | fi
196 |
197 | # traverses directory structure from process work directory to filesystem root
198 | # first directory with .mvn subdirectory is considered project base directory
199 | find_maven_basedir() {
200 | local basedir=$(pwd)
201 | local wdir=$(pwd)
202 | while [ "$wdir" != '/' ] ; do
203 | if [ -d "$wdir"/.mvn ] ; then
204 | basedir=$wdir
205 | break
206 | fi
207 | wdir=$(cd "$wdir/.."; pwd)
208 | done
209 | echo "${basedir}"
210 | }
211 |
212 | # concatenates all lines of a file
213 | concat_lines() {
214 | if [ -f "$1" ]; then
215 | echo "$(tr -s '\n' ' ' < "$1")"
216 | fi
217 | }
218 |
219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
221 |
222 | # Provide a "standardized" way to retrieve the CLI args that will
223 | # work with both Windows and non-Windows executions.
224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
225 | export MAVEN_CMD_LINE_ARGS
226 |
227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
228 |
229 | exec "$JAVACMD" \
230 | $MAVEN_OPTS \
231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
233 | ${WRAPPER_LAUNCHER} "$@"
234 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | set MAVEN_CMD_LINE_ARGS=%*
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 |
121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar""
122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
123 |
124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
125 | if ERRORLEVEL 1 goto error
126 | goto end
127 |
128 | :error
129 | set ERROR_CODE=1
130 |
131 | :end
132 | @endlocal & set ERROR_CODE=%ERROR_CODE%
133 |
134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
138 | :skipRcPost
139 |
140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
142 |
143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
144 |
145 | exit /B %ERROR_CODE%
--------------------------------------------------------------------------------
/oauth2-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.example
8 | demo
9 | 0.0.1-SNAPSHOT
10 | ../
11 |
12 |
13 | oauth2-server
14 | oauth2-server
15 | OAuth2 Server
16 |
17 |
18 |
19 | org.springframework.boot
20 | spring-boot-starter-web
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-jdbc
25 |
26 |
27 | org.springframework.security.oauth
28 | spring-security-oauth2
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-security
33 |
34 |
35 | org.springframework.security
36 | spring-security-jwt
37 |
38 |
39 | com.h2database
40 | h2
41 | runtime
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-starter-test
46 | test
47 |
48 |
49 |
50 |
51 |
52 |
53 | org.springframework.boot
54 | spring-boot-maven-plugin
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/oauth2-server/src/main/java/com/example/OAuth2Application.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.beans.factory.ObjectProvider;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.boot.autoconfigure.security.oauth2.authserver.AuthorizationServerProperties;
7 | import org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.context.annotation.Primary;
11 | import org.springframework.security.authentication.AuthenticationManager;
12 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
13 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
14 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
15 | import org.springframework.security.oauth2.provider.ClientDetailsService;
16 | import org.springframework.security.oauth2.provider.client.BaseClientDetails;
17 | import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
18 | import org.springframework.security.oauth2.provider.token.TokenStore;
19 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
20 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
21 |
22 | import javax.sql.DataSource;
23 |
24 | @EnableAuthorizationServer
25 | @SpringBootApplication
26 | public class OAuth2Application {
27 |
28 | public static void main(String[] args) {
29 | SpringApplication.run(OAuth2Application.class, args);
30 | }
31 | }
32 |
33 | @Configuration
34 | class OAuth2Configuration {
35 |
36 | @Bean
37 | public TokenStore tokenStore() {
38 | return new JwtTokenStore(jwtAccessTokenConverter());
39 | }
40 |
41 | @Bean
42 | public JwtAccessTokenConverter jwtAccessTokenConverter() {
43 | return new JwtAccessTokenConverter();
44 | }
45 |
46 | @Bean
47 | @Primary
48 | public JdbcClientDetailsService jdbcClientDetailsService(DataSource dataSource) {
49 | return new JdbcClientDetailsService(dataSource);
50 | }
51 |
52 | }
53 |
54 | @Configuration
55 | class JwtOAuth2AuthorizationServerConfiguration extends OAuth2AuthorizationServerConfiguration {
56 |
57 | private final JwtAccessTokenConverter jwtAccessTokenConverter;
58 | private final ClientDetailsService clientDetailsService;
59 |
60 | public JwtOAuth2AuthorizationServerConfiguration(BaseClientDetails details,
61 | AuthenticationManager authenticationManager,
62 | ObjectProvider tokenStoreProvider,
63 | AuthorizationServerProperties properties,
64 | JwtAccessTokenConverter jwtAccessTokenConverter,
65 | ClientDetailsService clientDetailsService) {
66 | super(details, authenticationManager, tokenStoreProvider, properties);
67 | this.jwtAccessTokenConverter = jwtAccessTokenConverter;
68 | this.clientDetailsService = clientDetailsService;
69 | }
70 |
71 | @Override
72 | public void configure(AuthorizationServerEndpointsConfigurer endpoints)
73 | throws Exception {
74 | super.configure(endpoints);
75 | endpoints.accessTokenConverter(jwtAccessTokenConverter);
76 | }
77 |
78 | @Override
79 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
80 | clients.withClientDetails(clientDetailsService);
81 | /*
82 | // 이부분 주석을 풀고 위의 코드를 주석처리하면
83 | // 클라이언트 정보를 직접 기술 할 수 있다
84 | // @formatter:off
85 | clients.inMemory()
86 | // 클라이언트 아이디
87 | .withClient("my_client_id")
88 | // 클라이언트 시크릿
89 | .secret("my_client_secret")
90 | // 엑세스토큰 발급 가능한 인증 타입
91 | // 기본이 다섯개, 여기 속성이 없으면 인증 불가
92 | .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
93 | // 클라이언트에 부여된 권한
94 | .authorities("ROLE_MY_CLIENT")
95 | // 이 클라이언트로 접근할 수 있는 범위 제한
96 | // 해당 클라이언트로 API를 접근 했을때 접근 범위를 제한 시키는 속성
97 | .scopes("read", "write")
98 | // 이 클라이언트로 발급된 엑세스토큰의 시간 (단위:초)
99 | .accessTokenValiditySeconds(60 * 60 * 4)
100 | // 이 클라이언트로 발급된 리프러시토큰의 시간 (단위:초)
101 | .refreshTokenValiditySeconds(60 * 60 * 24 * 120)
102 | .and()
103 | .withClient("your_client_id")
104 | .secret("your_client_secret")
105 | .authorizedGrantTypes("authorization_code", "implicit")
106 | .authorities("ROLE_YOUR_CLIENT")
107 | .scopes("read")
108 | .and();
109 | // @formatter:on
110 | */
111 | }
112 | }
--------------------------------------------------------------------------------
/oauth2-server/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | # 외부 DB 설정시 아래의 주석을 활성화 시킨 후 관련 DB 설정 정보를 입력한다.
2 | #spring:
3 | # datasource:
4 | # url: jdbc:h2:tcp://localhost/~/test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
5 | # driverClassName: org.h2.Driver
6 | # username: sa
7 | # password:
8 |
9 | spring.h2.console:
10 | enabled: true
11 | path: /h2-console
12 |
13 | security:
14 | user:
15 | name: user
16 | password: test
17 |
18 | oauth2:
19 |
20 | client:
21 | client-id: foo
22 | client-secret: bar
23 | authorization:
24 | token-key-access: isAuthenticated()
25 |
26 |
27 | logging.level:
28 | org.springframework:
29 | security: debug
30 | boot: debug
31 |
--------------------------------------------------------------------------------
/oauth2-server/src/main/resources/data.sql:
--------------------------------------------------------------------------------
1 | insert into oauth_client_details (client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove) values ('my_client_id', 'my_client_secret', null, 'read,write', 'authorization_code,password,client_credentials,implicit,refresh_token', null, 'ROLE_MY_CLIENT', 36000, 2592000, null, null);
2 | insert into oauth_client_details (client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove) values ('your_client_id', 'your_client_secret', null, 'read', 'authorization_code,implicit', null, 'ROLE_YOUR_CLIENT', 36000, 2592000, null, null);
--------------------------------------------------------------------------------
/oauth2-server/src/main/resources/schema.sql:
--------------------------------------------------------------------------------
1 | -- used in tests that use HSQL
2 | create table oauth_client_details (
3 | client_id VARCHAR(256) PRIMARY KEY,
4 | resource_ids VARCHAR(256),
5 | client_secret VARCHAR(256),
6 | scope VARCHAR(256),
7 | authorized_grant_types VARCHAR(256),
8 | web_server_redirect_uri VARCHAR(256),
9 | authorities VARCHAR(256),
10 | access_token_validity INTEGER,
11 | refresh_token_validity INTEGER,
12 | additional_information VARCHAR(4096),
13 | autoapprove VARCHAR(256)
14 | );
15 |
16 | create table oauth_client_token (
17 | token_id VARCHAR(256),
18 | token LONGVARBINARY,
19 | authentication_id VARCHAR(256) PRIMARY KEY,
20 | user_name VARCHAR(256),
21 | client_id VARCHAR(256)
22 | );
23 |
24 | create table oauth_access_token (
25 | token_id VARCHAR(256),
26 | token LONGVARBINARY,
27 | authentication_id VARCHAR(256) PRIMARY KEY,
28 | user_name VARCHAR(256),
29 | client_id VARCHAR(256),
30 | authentication LONGVARBINARY,
31 | refresh_token VARCHAR(256)
32 | );
33 |
34 | create table oauth_refresh_token (
35 | token_id VARCHAR(256),
36 | token LONGVARBINARY,
37 | authentication LONGVARBINARY
38 | );
39 |
40 | create table oauth_code (
41 | code VARCHAR(256), authentication LONGVARBINARY
42 | );
43 |
44 | create table oauth_approvals (
45 | userId VARCHAR(256),
46 | clientId VARCHAR(256),
47 | scope VARCHAR(256),
48 | status VARCHAR(10),
49 | expiresAt TIMESTAMP,
50 | lastModifiedAt TIMESTAMP
51 | );
52 |
53 |
54 | -- customized oauth_client_details table
55 | create table ClientDetails (
56 | appId VARCHAR(256) PRIMARY KEY,
57 | resourceIds VARCHAR(256),
58 | appSecret VARCHAR(256),
59 | scope VARCHAR(256),
60 | grantTypes VARCHAR(256),
61 | redirectUrl VARCHAR(256),
62 | authorities VARCHAR(256),
63 | access_token_validity INTEGER,
64 | refresh_token_validity INTEGER,
65 | additionalInformation VARCHAR(4096),
66 | autoApproveScopes VARCHAR(256)
67 | );
--------------------------------------------------------------------------------
/oauth2-server/src/test/java/com/example/OAuth2ApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
7 |
8 | @RunWith(SpringJUnit4ClassRunner.class)
9 | @SpringBootTest(classes = OAuth2Application.class)
10 | public class OAuth2ApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 1.4.4.RELEASE
10 |
11 |
12 |
13 | com.example
14 | demo
15 | 0.0.1-SNAPSHOT
16 | pom
17 |
18 | demo
19 | Demo project for Spring Boot
20 |
21 |
22 | oauth2-server
23 | api-server
24 | h2-server
25 |
26 |
27 |
28 | UTF-8
29 | 1.8
30 |
31 |
32 |
33 |
34 | org.projectlombok
35 | lombok
36 | 1.16.6
37 |
38 |
39 | com.h2database
40 | h2
41 | runtime
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'demo'
2 | include 'oauth2-server'
3 | include 'api-server'
4 | include 'h2-server'
--------------------------------------------------------------------------------