REST with Spring Boot #jqfk

201
REST with Spring Boot 俊明 (@making) JavaQne 2015 #jqfk 2015-01-24

Transcript of REST with Spring Boot #jqfk

Page 1: REST with Spring Boot #jqfk

REST with Spring Boot

槙 俊明(@making) JavaQne 2015 #jqfk

2015-01-24

Page 2: REST with Spring Boot #jqfk

自己紹介

• @making

• http://blog.ik.am

•公私ともにSpringヘビーユーザー

•日本Javaユーザーグループ幹事

Page 3: REST with Spring Boot #jqfk

祝「はじめてのSpring Boot」出版

http://bit.ly/hajiboot

最近、第2版が出ました!

Page 4: REST with Spring Boot #jqfk

今日のお話

• Spring Boot概要 • RESTについて色々

• Richardson Maturity Model • Spring HATEOAS / Spring Data REST

• JSON Patch • Spring Sync

• Securing REST Serivces • Spring Security OAuth / Spring Session

Page 5: REST with Spring Boot #jqfk

Spring Bootの概要

Page 6: REST with Spring Boot #jqfk

Spring Boot概要• Springを使って簡単にモダンなアプリケーションを開発するための仕組み

• AutoConfigure + 組み込みサーバーが特徴

Page 7: REST with Spring Boot #jqfk

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.1.RELEASE</version></parent><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build><properties> <java.version>1.8</java.version></properties>

この設定を追加するだけ

Page 8: REST with Spring Boot #jqfk

package com.example;!import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;!@RestController@EnableAutoConfigurationpublic class App {! @RequestMapping("/") String home() { return "Hello World!"; }! public static void main(String[] args) { SpringApplication.run(App.class, args); }}

魔法のアノテーション

mainメソッドでアプリ実行

Page 9: REST with Spring Boot #jqfk

ログ

組込Tomcatが起動した

Page 10: REST with Spring Boot #jqfk

ログ

組込Tomcatが起動した

Page 11: REST with Spring Boot #jqfk

実行可能jarを作成

$ mvn package

Page 12: REST with Spring Boot #jqfk

jarを実行

$ java -jar target/jggug-helloworld-1.0.0-SNAPSHOT.jar

Page 13: REST with Spring Boot #jqfk

プロパティを変更して実行

$ java -jar target/jggug-helloworld-1.0.0-SNAPSHOT.jar --server.port=8888

--(プロパティ名)=(プロパティ値)

Page 14: REST with Spring Boot #jqfk

@SpringBootApplication@RestControllerpublic class App {! @RequestMapping("/") String home() { return "Hello World!"; }! public static void main(String[] args) { SpringApplication.run(App.class, args); }}

Spring Boot 1.2より

Page 15: REST with Spring Boot #jqfk

@SpringBootApplication@RestControllerpublic class App {! @RequestMapping("/") String home() { return "Hello World!"; }! public static void main(String[] args) { SpringApplication.run(App.class, args); }}

Spring Boot 1.2より

@EnableAutoConfiguration+ @Configuration+ @ComponentScan

Page 16: REST with Spring Boot #jqfk

RESTについて

Page 17: REST with Spring Boot #jqfk

REST?

•クライアントとサーバ間でデータをやりとりするためのソフトウェアアーキテクチャスタイルの一つ

•サーバーサイドで管理している情報の中からクライアントに提供すべき情報を「リソース」として抽出し、リソースをHTTPで操作

Page 18: REST with Spring Boot #jqfk

RESTに関するいろいろな話題

• Richardson Maturity Model

• JSON Patch

• Security

Page 19: REST with Spring Boot #jqfk

Richardson Maturity Model

Page 20: REST with Spring Boot #jqfk

Richardson Maturity Model

http://martinfowler.com/articles/richardsonMaturityModel.html

RESTの成熟モデル

Page 21: REST with Spring Boot #jqfk

Richardson Maturity Model

http://martinfowler.com/articles/richardsonMaturityModel.html

RESTの成熟モデル

あなたの

Page 22: REST with Spring Boot #jqfk

Level 0: Swamp of POX

• POX (Plain Old XML) • SOAP、XML-RPC

転送プロトコルとして HTTPを使っているだけ。 通常POSTオンリー

Page 23: REST with Spring Boot #jqfk

Level 0: Swamp of POX

Page 24: REST with Spring Boot #jqfk

Level 1: Resources

• /customers、/usersなど

•なんちゃってREST

URLに名詞を使う。

Page 25: REST with Spring Boot #jqfk

Level 1: Resources

Page 26: REST with Spring Boot #jqfk

Level 2: HTTP Vebs

• GET/POST/PUT/DELETEなど

•一般的にみんなが言っている”REST”

HTTPメソッドを動詞に使う。 ヘッダやステータスを活用

Page 27: REST with Spring Boot #jqfk

Level 2: HTTP Vebs

Page 28: REST with Spring Boot #jqfk

ここまでは対応している Webフレームワークは多い

Page 29: REST with Spring Boot #jqfk

Spring Boot + Spring MVC@SpringBootApplication@RestController @RequestMapping("user")public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } @RequestMapping(method = RequestMethod.GET) User get() { return new User("demo", "password"); } @RequestMapping(method = RequestMethod.POST) ResponseEntity<User> post(@RequestBody User user) { // create return ResponseEntity .created(location).body(created); }}

Page 30: REST with Spring Boot #jqfk

Level 3: Hypermedia Controls

• HATEOAS (Hypermedia As The Engine Of Application State)

Hypermediaリンクを 使用してナビゲーション。 ユーザーにサービス全体の 知識を強いない。

Page 31: REST with Spring Boot #jqfk

Level 3: Hypermedia Controls

Page 32: REST with Spring Boot #jqfk

Level 3: Hypermedia Controls

{ "name": "Alice", "links": [ { "rel": "self", "href": "http://.../customer/1" } ]}

Page 33: REST with Spring Boot #jqfk

Level 3: Hypermedia Controls

{ "name": "Alice", "links": [ { "rel": "self", "href": "http://.../customer/1" }, { "rel": "user", "href": "http://.../customer/1/user" } ]}

Page 34: REST with Spring Boot #jqfk

Level 3: Hypermedia Controls

{ "name": "Alice", "links": [ { "rel": "self", "href": "http://.../customer/1" }, { "rel": "user", "href": "http://.../customer/1/user" } ]}

関連するリソースのリンクが含まれる

Page 35: REST with Spring Boot #jqfk

Spring HATEOAS

Spring MVCにHATEOASの概念を追加

•リソースのモデルにLink追加

• HAL等のデータフォーマットに対応

Page 36: REST with Spring Boot #jqfk

具体例で説明

Page 37: REST with Spring Boot #jqfk

扱うモデル

Page 38: REST with Spring Boot #jqfk

Bookmarkエンティティ@Entitypublic class Bookmark { @ManyToOne @JsonIgnore Account account; @Id @GeneratedValue Long id; String uri; String description; // omitted}

Page 39: REST with Spring Boot #jqfk

Accountエンティティ@Entitypublic class Account { @OneToMany(mappedBy = "account") Set<Bookmark> bookmarks; @Id @GeneratedValue Long id; @JsonIgnore String password; String username; // omitted}

Page 40: REST with Spring Boot #jqfk

BookmarkRepository

public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {!

Collection<Bookmark> findByAccountUsername(String username);!

}

Page 41: REST with Spring Boot #jqfk

BookmarkRepository

public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {!

Collection<Bookmark> findByAccountUsername(String username);!

}

Spring Data JPAを使用。 CRUDを簡単に使える。

Page 42: REST with Spring Boot #jqfk

BookmarkRepository

public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {!

Collection<Bookmark> findByAccountUsername(String username);!

}

Spring Data JPAを使用。 CRUDを簡単に使える。

命名規約に対応したクエリが 実行されるメソッド(実装不要)

Page 43: REST with Spring Boot #jqfk

BookmarkRepository

public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {!

Collection<Bookmark> findByAccountUsername(String username);!

}

Spring Data JPAを使用。 CRUDを簡単に使える。

命名規約に対応したクエリが 実行されるメソッド(実装不要)

SELECT b FROM Bookmark b WHERE b.account.username= :username

Page 44: REST with Spring Boot #jqfk

AccountRepository

public interface AccountRepository extends JpaRepository<Account, Long> {!

Optional<Account> findByUsername(String username);!

}

Page 45: REST with Spring Boot #jqfk

AccountRepository

public interface AccountRepository extends JpaRepository<Account, Long> {!

Optional<Account> findByUsername(String username);!

} Java SE 8のOptionalに対応。 1件取得結果の存在有無をOptionalで表現

Page 46: REST with Spring Boot #jqfk

Level 2

普通のSpring MVCプログラミング

Page 47: REST with Spring Boot #jqfk

Controller@RestController @RequestMapping("/{userId}/bookmarks")class BookmarkRestController { @Autowired BookmarkRepository bookmarkRepository; @Autowired AccountRepository accountRepository; @RequestMapping(value = "/{bookmarkId}", method = RequestMethod.GET) Bookmark readBookmark( @PathVariable String userId, @PathVariable Long bookmarkId) { this.validateUser(userId); return this.bookmarkRepository.findOne(bookmarkId); } // …}

Page 48: REST with Spring Boot #jqfk

Controller@RequestMapping(method = RequestMethod.POST)ResponseEntity<?> add(@PathVariable String userId, @RequestBody Bookmark in) { return this.accountRepository .findByUsername(userId) .map(account -> { Bookmark result = bookmarkRepository.save( new Bookmark(account, in.uri, in.description)); URI location = …; return ResponseEntity .created(location).body(result); }) .orElseThrow(() -> new UserNotFoundException(userId));}

Page 49: REST with Spring Boot #jqfk

起動@SpringBootApplication public class Application { @Bean CommandLineRunner init(AccountRepository accountRepository, BookmarkRepository bookmarkRepository) { return (evt) -> Stream.of("kis", "skrb", "making") .forEach(a -> { Account account = accountRepository.save(new Account(a, "password")); bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/1/" + a, "A description")); bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/2/" + a, "A description"));}); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

Page 50: REST with Spring Boot #jqfk

起動@SpringBootApplication public class Application { @Bean CommandLineRunner init(AccountRepository accountRepository, BookmarkRepository bookmarkRepository) { return (evt) -> Stream.of("kis", "skrb", "making") .forEach(a -> { Account account = accountRepository.save(new Account(a, "password")); bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/1/" + a, "A description")); bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/2/" + a, "A description"));}); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

起動時に実行されるクラス

Page 51: REST with Spring Boot #jqfk

Example: GET

$ curl -X GET localhost:8080/making/bookmarks[{ "description": "A description", "uri": "http://bookmark.com/1/making", "id": 5 }, { "description": "A description", "uri": "http://bookmark.com/2/making", "id": 6 }]

Page 52: REST with Spring Boot #jqfk

Example: GET

$ curl -X GET localhost:8080/making/bookmarks/5{ "description": "A description", "uri": "http://bookmark.com/1/making", "id": 5}

Page 53: REST with Spring Boot #jqfk

Example: POST$ curl -v -X POST localhost:8080/making/bookmarks -H 'Content-Type: application/json' -d '{"url":"http://bit.ly/hajiboot", "description":"はじめてのSpring Boot"}'(略)< HTTP/1.1 201 Created< Location: http://localhost:8080/making/bookmarks/7(略){"id":7,"uri":null,"description":"はじめてのSpring Boot"}

Page 54: REST with Spring Boot #jqfk

Error Handling

@ResponseStatus(HttpStatus.NOT_FOUND)class UserNotFoundException extends RuntimeException { public UserNotFoundException(String userId) { super("could not find user '" + userId + "'."); }}

Page 55: REST with Spring Boot #jqfk

Error Handling$ curl -v -X GET localhost:8080/maki/bookmarks/6(略)< HTTP/1.1 404 Not Found(略){ "path": "/maki/bookmarks/6", "message": "could not find user 'maki'.", "exception": "bookmarks.UserNotFoundException", "error": "Not Found", "status": 404, "timestamp": 1421044115740}

Page 56: REST with Spring Boot #jqfk

Level 3

<dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId></dependency>

Spring HATEOASを使用

Page 57: REST with Spring Boot #jqfk

Level 3

<dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId></dependency>

Spring HATEOASを使用

Spring Bootを使うと依存関係を定義するだけでHATEOASを使える

Page 58: REST with Spring Boot #jqfk

class BookmarkResource extends ResourceSupport { private final Bookmark bookmark; public BookmarkResource(Bookmark bookmark) { String username = bookmark.getAccount().getUsername(); this.bookmark = bookmark; this.add(new Link(bookmark.getUri(), "bookmark-uri")); this.add(linkTo(BookmarkRestController.class, username) .withRel("bookmarks")); this.add(linkTo(methodOn(BookmarkRestController.class, username) .readBookmark(username, bookmark.getId())) .withSelfRel()); } public Bookmark getBookmark() {/**/} }

Page 59: REST with Spring Boot #jqfk

class BookmarkResource extends ResourceSupport { private final Bookmark bookmark; public BookmarkResource(Bookmark bookmark) { String username = bookmark.getAccount().getUsername(); this.bookmark = bookmark; this.add(new Link(bookmark.getUri(), "bookmark-uri")); this.add(linkTo(BookmarkRestController.class, username) .withRel("bookmarks")); this.add(linkTo(methodOn(BookmarkRestController.class, username) .readBookmark(username, bookmark.getId())) .withSelfRel()); } public Bookmark getBookmark() {/**/} }

HypermediaLinkを表現するための基本的な

情報を持つ

Page 60: REST with Spring Boot #jqfk

class BookmarkResource extends ResourceSupport { private final Bookmark bookmark; public BookmarkResource(Bookmark bookmark) { String username = bookmark.getAccount().getUsername(); this.bookmark = bookmark; this.add(new Link(bookmark.getUri(), "bookmark-uri")); this.add(linkTo(BookmarkRestController.class, username) .withRel("bookmarks")); this.add(linkTo(methodOn(BookmarkRestController.class, username) .readBookmark(username, bookmark.getId())) .withSelfRel()); } public Bookmark getBookmark() {/**/} }

HypermediaLinkを表現するための基本的な

情報を持つ

ControllerLinkBuilder

Page 61: REST with Spring Boot #jqfk

class BookmarkResource extends ResourceSupport { private final Bookmark bookmark; public BookmarkResource(Bookmark bookmark) { String username = bookmark.getAccount().getUsername(); this.bookmark = bookmark; this.add(new Link(bookmark.getUri(), "bookmark-uri")); this.add(linkTo(BookmarkRestController.class, username) .withRel("bookmarks")); this.add(linkTo(methodOn(BookmarkRestController.class, username) .readBookmark(username, bookmark.getId())) .withSelfRel()); } public Bookmark getBookmark() {/**/} }

“bookmark-uri"というrelで 対象のブックマークへのlinkを追加

Page 62: REST with Spring Boot #jqfk

class BookmarkResource extends ResourceSupport { private final Bookmark bookmark; public BookmarkResource(Bookmark bookmark) { String username = bookmark.getAccount().getUsername(); this.bookmark = bookmark; this.add(new Link(bookmark.getUri(), "bookmark-uri")); this.add(linkTo(BookmarkRestController.class, username) .withRel("bookmarks")); this.add(linkTo(methodOn(BookmarkRestController.class, username) .readBookmark(username, bookmark.getId())) .withSelfRel()); } public Bookmark getBookmark() {/**/} }

"bookmarks"というrelで ブックマークコレクションの リソースへのlinkを追加

Page 63: REST with Spring Boot #jqfk

class BookmarkResource extends ResourceSupport { private final Bookmark bookmark; public BookmarkResource(Bookmark bookmark) { String username = bookmark.getAccount().getUsername(); this.bookmark = bookmark; this.add(new Link(bookmark.getUri(), "bookmark-uri")); this.add(linkTo(BookmarkRestController.class, username) .withRel("bookmarks")); this.add(linkTo(methodOn(BookmarkRestController.class, username) .readBookmark(username, bookmark.getId())) .withSelfRel()); } public Bookmark getBookmark() {/**/} }

"self"というrelで 自身へのlinkを追加

Page 64: REST with Spring Boot #jqfk

@RequestMapping(value = “/{bookmarkId}", method = RequestMethod.GET)BookmarkResource readBookmark( @PathVariable String userId, @PathVariable Long bookmarkId) { this.validateUser(userId); return new BookmarkResource( this.bookmarkRepository .findOne(bookmarkId));}

Page 65: REST with Spring Boot #jqfk

@RequestMapping(method = RequestMethod.POST) ResponseEntity<?> add(@PathVariable String userId, @RequestBody Bookmark in) { return accountRepository.findByUsername(userId) .map(account -> { Bookmark bookmark = bookmarkRepository .save(new Bookmark(account, in.uri, in.description)); Link selfLink = new BookmarkResource(bookmark) .getLink("self"); URI location = URI.create(selfLink.getHref()); return ResponseEntity .created(location).body(bookmark); }) .orElseThrow(() -> new UserNotFoundException(userId)); }

Page 66: REST with Spring Boot #jqfk

サンプル: GET$ curl -X GET localhost:8080/making/bookmarks/5{ "_links": { "self": { "href": "http://localhost:8080/making/bookmarks/5" }, "bookmarks": { "href": "http://localhost:8080/making/bookmarks" }, "bookmark-uri": { "href": "http://bookmark.com/1/making" } }, "bookmark": { "description": "A description", "uri": "http://bookmark.com/1/making", "id": 5 }}

Page 67: REST with Spring Boot #jqfk

Example: GET$ curl -v -X GET localhost:8080/making/bookmarks/6> GET /making/bookmarks/6 HTTP/1.1> User-Agent: curl/7.30.0> Host: localhost:8080> Accept: */*>< HTTP/1.1 200 OK< Server: Apache-Coyote/1.1< Content-Type: application/hal+json;charset=UTF-8< Transfer-Encoding: chunked< Date: Mon, 12 Jan 2015 05:45:40 GMT<(略)

HALという規格のフォーマットを使用

している

Page 68: REST with Spring Boot #jqfk

HAL

http://stateless.co/hal_specification.html

Hypertext Application Language

Hypermediaを表現する フォーマット仕様の1つ

Page 69: REST with Spring Boot #jqfk

@ControllerAdviceclass BookmarkControllerAdvice {! @ResponseBody @ExceptionHandler(UserNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) VndErrors userNotFoundExceptionHandler( UserNotFoundException ex) { return new VndErrors("error", ex.getMessage()); }}

Error Handling

Page 70: REST with Spring Boot #jqfk

$ curl -X GET localhost:8080/maki/bookmarks/5[ { "message": "could not find user 'maki'.", "logref": "error" }] Vnd.Errror規格の

エラーフォーマット

Error Handling

https://github.com/blongden/vnd.error

Page 71: REST with Spring Boot #jqfk

普通の人はLevel 2で十分。 こだわりたい人はLevel 3へ。

Page 72: REST with Spring Boot #jqfk

Spring Data REST

Spring Dataのリポジトリを そのままREST APIとしてExport

Page 73: REST with Spring Boot #jqfk

Spring Data

Spring Data

JPASpring D

ata M

ongoDB

Spring Data

Xxx

JPASpring Data REST

RDB

JSON

MongoDB

Xxx

Page 74: REST with Spring Boot #jqfk

Spring Bootから使う場合

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

Spring Bootを使うと依存関係を定義するだけでSpring Data RESTを使える

Page 75: REST with Spring Boot #jqfk
Page 76: REST with Spring Boot #jqfk
Page 77: REST with Spring Boot #jqfk
Page 78: REST with Spring Boot #jqfk
Page 79: REST with Spring Boot #jqfk
Page 80: REST with Spring Boot #jqfk
Page 81: REST with Spring Boot #jqfk

ALPS

Application-Level Profile Semantics

http://alps.io/

Page 82: REST with Spring Boot #jqfk
Page 83: REST with Spring Boot #jqfk
Page 84: REST with Spring Boot #jqfk

Event Handler@RepositoryEventHandler(Bookmark.class)public class BookmarkEventHandler { @HandleBeforeSave public void beforeSave(Bookmark p) { /* … */ } @HandleAfterDelete public void afterDelete(Bookmark p) { /* … */ }}

Page 85: REST with Spring Boot #jqfk

超短期間でRESTサービスをつくる 必要がある場合に強力

Page 86: REST with Spring Boot #jqfk

JSON Patch

Page 87: REST with Spring Boot #jqfk

コレクションの変更

[{"value":"a"},{"value":"b"},{"value":"c"}]

[{"value":"a"},{"value":"c"},{"value":"d"}]

Original

Modified

[+] {"value":"d"} を4番目に追加 [-] 2番目の要素を削除 …

Page 88: REST with Spring Boot #jqfk

コレクションの変更

[{"value":"a"},{"value":"b"},{"value":"c"}]

[{"value":"a"},{"value":"c"},{"value":"d"}]

Original

Modified

[+] {"value":"d"} を4番目に追加 [-] 2番目の要素を削除 …もっと効率的なデータ転送を!

Page 89: REST with Spring Boot #jqfk

動機

より効率的なデータ転送

複数のクライアント間での データ同期

オフライン作業の反映

Page 90: REST with Spring Boot #jqfk

http://www.slideshare.net/briancavalier/differential-sync-and-json-patch-s2-gx-2014/13

Page 91: REST with Spring Boot #jqfk

http://www.slideshare.net/briancavalier/differential-sync-and-json-patch-s2-gx-2014/13

Diff & Patch!

Page 92: REST with Spring Boot #jqfk

JSON PatchRFC 6902

パッチをJSONで表現

PATCHメソッドで送信

JSON Pointer (RFC 6901)で指定したJSONパスへの操作を表現

application/json-patch+json

Page 93: REST with Spring Boot #jqfk

JSON PatchRFC 6902

パッチをJSONで表現

PATCHメソッドで送信

JSON Pointer (RFC 6901)で指定したJSONパスへの操作を表現

application/json-patch+jsonpatch(diff(a, b), a) === b

を満たすこと

Page 94: REST with Spring Boot #jqfk

JSON Patch

[{"value":"a"},{"value":"b"},{"value":"c"}]

[ {"op":"add","path":"/3","value":{"value":"d"}}, {"op":"remove","path":"/1"}]

[{"value":"a"},{"value":"c"},{"value":"d"}]

Original

Modified

Patch

Page 95: REST with Spring Boot #jqfk

典型的なREST

POST /todos {"title":"fizzbuz","done":false }

PUT /todos/1 {"title":"fizzbuz","done":true }

PATCH /todos/2 {"done":true }

DELETE /todos/3

Page 96: REST with Spring Boot #jqfk

典型的なREST

POST /todos {"title":"fizzbuz","done":false }

PUT /todos/1 {"title":"fizzbuz","done":true }

PATCH /todos/2 {"done":true }

DELETE /todos/3HTTP通信回数=操作回数 リソースが増えるとさらに増える

Page 97: REST with Spring Boot #jqfk

PATCH /todos [ {"op":"add","path":"-","value": {"title":"fizzbuzz","done":false}}, {"op":"replace","path":"/1","value": {"title":"fizzbuzz","done":true}}, {"op":"replace","path":"/2/done", "value":true}, {"op":"remove","path":"/3"}]

JSON PatchがあるREST

Page 98: REST with Spring Boot #jqfk

PATCH /todos [ {"op":"add","path":"-","value": {"title":"fizzbuzz","done":false}}, {"op":"replace","path":"/1","value": {"title":"fizzbuzz","done":true}}, {"op":"replace","path":"/2/done", "value":true}, {"op":"remove","path":"/3"}]

JSON PatchがあるREST

HTTP通信回数が1回 リソースが増えても1回 操作もアトミック

Page 99: REST with Spring Boot #jqfk

Spring Sync

* https://github.com/spring-projects/spring-sync * https://github.com/spring-projects/spring-sync-samples * https://spring.io/blog/2014/10/22/introducing-spring-sync

@Configuration@EnableDifferentialSynchronizationpublic class DiffSyncConfig extends DiffSyncConfigurerAdapter { @Autowired private PagingAndSortingRepository<Todo, Long> repo; @Override public void addPersistenceCallbacks(PersistenceCallbackRegistry registry) { registry.addPersistenceCallback( new JpaPersistenceCallback<Todo>(repo, Todo.class)); }}

Spring (MVC)でJSON Patchを扱うためのプロジェクト

まだ1.0.0.RC1 乞うご期待

Page 100: REST with Spring Boot #jqfk

Securing REST Services

Page 101: REST with Spring Boot #jqfk

どっちが好き?

• HttpSessionを使わない

• HttpSessionを使う

Page 102: REST with Spring Boot #jqfk

どっちが好き?

• HttpSessionを使わない

• HttpSessionを使う

KVSにデータを保存

OAuth 2.0を利用

Page 103: REST with Spring Boot #jqfk

OAuth 2.0

•アクセストークンを使って認可する標準的な仕組み

•多くのAPIプロバイダがOAuthによるリソースアクセスを提供

Page 104: REST with Spring Boot #jqfk

OAuth2.0の基本

Resource Owner Client

Resource Server

Authorization Server

Page 105: REST with Spring Boot #jqfk

OAuth2.0の基本

Resource Owner Client

Resource Server

Authorization Server

Github APIの例

Page 106: REST with Spring Boot #jqfk

OAuth2.0の基本

Resource Owner Client

Resource Server

Authorization Server

Githubの アカウント管理

Github API

Github API を使ったサービス プロバイダ(アプリ)

エンドユーザー (Githubユーザー)

Page 107: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

Page 108: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

サービスへ リクエスト

Page 109: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

何かサービスへ リクエスト

Page 110: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

何か

アクセストークン

サービスへ リクエスト

Page 111: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

何か

アクセストークン

アクセストークン

サービスへ リクエスト

Page 112: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

何か

アクセストークン

アクセストークン

リソース

サービスへ リクエスト

Page 113: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

何か

アクセストークン

アクセストークン

リソース

サービスへ リクエスト

サービスからの レスポンス

Page 114: REST with Spring Boot #jqfk

OAuth2.0の流れ

Resource Owner Client

Resource Server

Authorization Server

何か

アクセストークン

アクセストークン

リソース

サービスへ リクエスト

サービスからの レスポンス

ここの方式 (どうやってアクセストークンを交換するか)

=GrantType

Page 115: REST with Spring Boot #jqfk

Grant Types

• Authorization Code • Resource Owner Password Credentials • Client Credentials • Refresh Token • Implicit • JWT Bearer

Page 116: REST with Spring Boot #jqfk

Grant Types

• Authorization Code • Resource Owner Password Credentials • Client Credentials • Refresh Token • Implicit • JWT Bearer

Page 117: REST with Spring Boot #jqfk

Authorization Code (grant_type=authorization_code)•認可コードとアクセストークンを交換

•一般的にOAuthと思われているやつ

画像: http://www.binarytides.com/php-add-login-with-github-to-your-website/

Page 118: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Page 119: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

サービスへ リクエスト

Page 120: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Page

サービスへ リクエスト

Page 121: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Page

サービスへ リクエスト

Page 122: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Pageログイン

サービスへ リクエスト

Page 123: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Pageログイン

サービスへ リクエスト

Page 124: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Page

認可コード

ログイン

サービスへ リクエスト

Page 125: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Page

認可コード

ログイン

サービスへ リクエスト

Page 126: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Page

認可コード

認可コード

ログイン

サービスへ リクエスト

Page 127: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Page

認可コード

認可コード

認可コード

ログイン

サービスへ リクエスト

Page 128: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Login Page

認可コード

認可コード

アクセストークン

認可コード

ログイン

サービスへ リクエスト

Page 129: REST with Spring Boot #jqfk

http://brentertainment.com/oauth2/

Page 130: REST with Spring Boot #jqfk
Page 131: REST with Spring Boot #jqfk

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

Page 132: REST with Spring Boot #jqfk

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

302 FoundLocation: https://client.example.com/cb?code=0fcfa4625502c209702e6d12fc67f4c298e44373&state=xyz

Page 133: REST with Spring Boot #jqfk

認可コード取得

Page 134: REST with Spring Boot #jqfk

認可コード取得POST /tokenAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW grant_type=authorization_code&code=0fcfa4625502c209702e6d12fc67f4c298e44373&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

client_id:client_secretをBase64エンコード

Page 135: REST with Spring Boot #jqfk

認可コード取得POST /tokenAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW grant_type=authorization_code&code=0fcfa4625502c209702e6d12fc67f4c298e44373&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

client_id:client_secretをBase64エンコード

200 OK!

{"access_token":"e651bdf91e704c0f3d060ffd4ff0403eb087f519","expires_in":3600,"token_type":"bearer"}

Page 136: REST with Spring Boot #jqfk

アクセストークン取得

Page 137: REST with Spring Boot #jqfk

アクセストークン取得

GET /api/friendsAuthorization: Bear e651bdf91e704c0f3d060ffd4ff0403eb087f519

Page 138: REST with Spring Boot #jqfk

リソース取得

Page 139: REST with Spring Boot #jqfk

Resource Owner Password Credentials (grant_type=password)

•ユーザー名・パスワードとアクセストークンを交換

• Clientが直接ユーザー名・パスワードを知ることになるので、通常公式アプリで使用される。

Page 140: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Authorization Serverと提供元が同じ

Page 141: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

ユーザー名・ パスワード

Authorization Serverと提供元が同じ

Page 142: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

ユーザー名・ パスワード

アクセストークン

Authorization Serverと提供元が同じ

Page 143: REST with Spring Boot #jqfk
Page 144: REST with Spring Boot #jqfk

POST /tokenAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW!grant_type=password&username=demouser&password=testpass

client_id:client_secretをBase64エンコード

Page 145: REST with Spring Boot #jqfk

POST /tokenAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW!grant_type=password&username=demouser&password=testpass

client_id:client_secretをBase64エンコード

200 OK!

{"access_token":"e651bdf91e704c0f3d060ffd4ff0403eb087f519","expires_in":3600,"token_type":"bearer"}

Page 146: REST with Spring Boot #jqfk

アクセストークン取得

Page 147: REST with Spring Boot #jqfk

アクセストークン取得

GET /api/friendsAuthorization: Bear e651bdf91e704c0f3d060ffd4ff0403eb087f519

Page 148: REST with Spring Boot #jqfk

リソース取得

Page 149: REST with Spring Boot #jqfk

Spring Security OAuth• Spring Securityの拡張でOAuthに対応

•認証認可に加え、トークン管理、クライアント管理等

• OAuth認可サーバー、クライアントの実装が簡単

•標準のGrantTypeは用意済み。カスタムGrantTypeも実装可能

Page 150: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

Spring Security OAuthサーバーの場合

Page 151: REST with Spring Boot #jqfk

Resource Owner Client

Resource Server

Authorization Server

OAuth2RestTem

plate

Spring Security OAuth

クライアントの場合

Page 152: REST with Spring Boot #jqfk

Resource Owner

Client (curl) Resource

Server (Bookmark)

Authorization Server

ユーザー名・ パスワード

アクセストークン

Bookmark APIの例

Page 153: REST with Spring Boot #jqfk

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

まだSpring Boot用の AutoConfigure/Starterはない

Page 154: REST with Spring Boot #jqfk

Spring Securityの認証設定@Configurationclass WebSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter { @Autowired AccountRepository accountRepository; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()); } @Bean UserDetailsService userDetailsService() { return (username) -> accountRepository .findByUsername(username) .map(a -> new User(a.username, a.password , true, true, true, true, AuthorityUtils .createAuthorityList("USER", "write"))) .orElseThrow( () -> new UsernameNotFoundException(…)); }}

Page 155: REST with Spring Boot #jqfk

Spring Securityの認証設定@Configurationclass WebSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter { @Autowired AccountRepository accountRepository; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()); } @Bean UserDetailsService userDetailsService() { return (username) -> accountRepository .findByUsername(username) .map(a -> new User(a.username, a.password , true, true, true, true, AuthorityUtils .createAuthorityList("USER", "write"))) .orElseThrow( () -> new UsernameNotFoundException(…)); }}

ユーザー名から認証ユーザーを取得するインタフェース

Page 156: REST with Spring Boot #jqfk

@Configuration @EnableResourceServerclass OAuth2ResourceConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer r) { r.resourceId("bookmarks"); } @Override public void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests() .anyRequest().authenticated(); }}

ResourceServerの設定

Page 157: REST with Spring Boot #jqfk

@Configuration @EnableResourceServerclass OAuth2ResourceConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer r) { r.resourceId("bookmarks"); } @Override public void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests() .anyRequest().authenticated(); }}

ResourceServerの設定

リソースID

Page 158: REST with Spring Boot #jqfk

@Configuration @EnableResourceServerclass OAuth2ResourceConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer r) { r.resourceId("bookmarks"); } @Override public void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests() .anyRequest().authenticated(); }}

ResourceServerの設定

リソースID

HTTPセッションを使わない!!

Page 159: REST with Spring Boot #jqfk

@Configuration @EnableResourceServerclass OAuth2ResourceConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer r) { r.resourceId("bookmarks"); } @Override public void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests() .anyRequest().authenticated(); }}

ResourceServerの設定

リソースID

HTTPセッションを使わない!!

認可設定

Page 160: REST with Spring Boot #jqfk

AuthorizationServerの設定@Configuration @EnableAuthorizationServer class OAuth2AuthorizationConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer ep) throws Exception { ep.authenticationManager(authenticationManager); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("demoapp").secret("123456") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .authorities("ROLE_USER") .scopes("write") .resourceIds("bookmarks"); }}

Page 161: REST with Spring Boot #jqfk

AuthorizationServerの設定@Configuration @EnableAuthorizationServer class OAuth2AuthorizationConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer ep) throws Exception { ep.authenticationManager(authenticationManager); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("demoapp").secret("123456") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .authorities("ROLE_USER") .scopes("write") .resourceIds("bookmarks"); }}

APIにアクセスするクライアントを登録 (今回はデモ用にインメモリ実装)

Page 162: REST with Spring Boot #jqfk

AuthorizationServerの設定@Configuration @EnableAuthorizationServer class OAuth2AuthorizationConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer ep) throws Exception { ep.authenticationManager(authenticationManager); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("demoapp").secret("123456") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .authorities("ROLE_USER") .scopes("write") .resourceIds("bookmarks"); }}

client_idとclient_secret を設定

Page 163: REST with Spring Boot #jqfk

AuthorizationServerの設定@Configuration @EnableAuthorizationServer class OAuth2AuthorizationConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer ep) throws Exception { ep.authenticationManager(authenticationManager); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("demoapp").secret("123456") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .authorities("ROLE_USER") .scopes("write") .resourceIds("bookmarks"); }}

対象のclientに許可するgrant_typeを指定

Page 164: REST with Spring Boot #jqfk

AuthorizationServerの設定@Configuration @EnableAuthorizationServer class OAuth2AuthorizationConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer ep) throws Exception { ep.authenticationManager(authenticationManager); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("demoapp").secret("123456") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .authorities("ROLE_USER") .scopes("write") .resourceIds("bookmarks"); }}

対象のclientに許可するロールとスコープを指定

Page 165: REST with Spring Boot #jqfk

AuthorizationServerの設定@Configuration @EnableAuthorizationServer class OAuth2AuthorizationConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer ep) throws Exception { ep.authenticationManager(authenticationManager); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("demoapp").secret("123456") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .authorities("ROLE_USER") .scopes("write") .resourceIds("bookmarks"); }}

clientに対応する リソースIDを指定

Page 166: REST with Spring Boot #jqfk

リソースアクセス$ curl -v http://localhost:8080/bookmarks(略)< HTTP/1.1 401 Unauthorized(略)< WWW-Authenticate: Bearer realm="bookmarks", error="unauthorized", error_description="An Authentication object was not found in the SecurityContext"(略){"error_description": "An Authentication object was not found in the SecurityContext","error": "unauthorized"}

Page 167: REST with Spring Boot #jqfk

トークン発行$ curl-X POST -u demoapp:123456 http://localhost:8080/oauth/token -d "password=password&username=making&grant_type=password&scope=write"!

{"access_token":"5f4b1353-ddd0-431b-a4b6-365267305d73","token_type":"bearer","refresh_token":"a50e4f67-373c-4f62-bdfb-560cf005d1e7","expires_in":4292,"scope":"write"}

Page 168: REST with Spring Boot #jqfk

リソースアクセス$ curl -H 'Authorization: Bearer 5f4b1353-ddd0-431b-a4b6-365267305d73' http://localhost:8080/bookmarks!

{ "content": [ { "links": […], "book": {…} } ], …}

Page 169: REST with Spring Boot #jqfk

HTTPS対応

$ keytool -genkeypair -alias mytestkey -keyalg RSA -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" -keypass changeme -keystore server.jks -storepass letmein

•設定ファイル(application.yml)に設定を書くだけで簡単SSL対応

server: port: 8443 ssl: key-store: server.jks key-store-password: letmein key-password: changeme

Page 170: REST with Spring Boot #jqfk

いつも通り起動$ mvn spring-boot:run… (略)2014-12-13 12:07:47.833 INFO --- [mple.App.main()] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8443/https2014-12-13 12:07:47.836 INFO --- [mple.App.main()] com.example.App : Started App in 5.322 seconds (JVM running for 10.02)

Page 171: REST with Spring Boot #jqfk

いつも通り起動$ mvn spring-boot:run… (略)2014-12-13 12:07:47.833 INFO --- [mple.App.main()] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8443/https2014-12-13 12:07:47.836 INFO --- [mple.App.main()] com.example.App : Started App in 5.322 seconds (JVM running for 10.02)

Page 172: REST with Spring Boot #jqfk

Spring Security OAuthで ステートレスにRESTを

セキュア化!!

Page 173: REST with Spring Boot #jqfk

Spring Security OAuthで ステートレスにRESTを

セキュア化!!

スケールブル!!

Page 174: REST with Spring Boot #jqfk

Spring Security OAuthで ステートレスにRESTを

セキュア化!!

スケールブル!!

って思うやん?

Page 175: REST with Spring Boot #jqfk

https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii#help-how-is-my-application-going-to-scale

Page 176: REST with Spring Boot #jqfk

https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii#help-how-is-my-application-going-to-scale

いつまで"ステートレス"で消耗してんの?

(意訳 違訳)

Page 177: REST with Spring Boot #jqfk

https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii#help-how-is-my-application-going-to-scale

いつまで"ステートレス"で消耗してんの?

(意訳 違訳)

Page 178: REST with Spring Boot #jqfk

https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii#help-how-is-my-application-going-to-scale

いつまで"ステートレス"で消耗してんの?

(意訳 違訳)セキュリティ対策は何やかんやでステートフル

(CSRFトークンとか。アクセストークンだって広い意味でステート)

Page 179: REST with Spring Boot #jqfk

これまでのHttpSession を使う場合

HttpSessionのデータを APサーバーのメモリに保存

ロードバランサのSticky Sessionで 同じSessionを同一JVMにバインド

Page 180: REST with Spring Boot #jqfk

Spring Session•セッションデータをJVM間で共有する新しい選択肢

•新しいセッションAPI

• HttpSessionと統合して、以下を提供

• Clustered Session • Multiple Browser Sessions • RESTful APIs

• WebSocketにも対応

Page 181: REST with Spring Boot #jqfk

Spring Session•セッションデータをJVM間で共有する新しい選択肢

•新しいセッションAPI

• HttpSessionと統合して、以下を提供

• Clustered Session • Multiple Browser Sessions • RESTful APIs

• WebSocketにも対応

ServletRequest/Response、HttpSessionをラップする

Servlet Filterを提供

Page 182: REST with Spring Boot #jqfk

Clustered Session

• KVSをつかったセッション

• Redis実装が提供されている

•アプリを跨いだセッション共有も可能

Page 183: REST with Spring Boot #jqfk

@Import(EmbeddedRedisConfiguration.class)@EnableRedisHttpSession public class SessionConfig { @Bean JedisConnectionFactory connectionFactory() { return new JedisConnectionFactory(); }}

Page 184: REST with Spring Boot #jqfk

public class SessionInitializer extends AbstractHttpSessionApplicationInitializer { !

public SessionInitializer() { super(SessionConfig.class); }}

Page 185: REST with Spring Boot #jqfk

<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>1.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.0.0.RELEASE</version> </dependency>

Page 186: REST with Spring Boot #jqfk

Multiple Browser Sessions

割愛

Page 187: REST with Spring Boot #jqfk

HttpSession & RESTful APIs

Cookie(JSESSIONID)の代わりに HTTPヘッダ(X-AUTH-TOKEN)に セッション情報を載せる

Page 188: REST with Spring Boot #jqfk

@Import(EmbeddedRedisConfiguration.class)@EnableRedisHttpSession public class SessionConfig { @Bean JedisConnectionFactory connectionFactory() { return new JedisConnectionFactory(); } @Bean HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); }}

Page 189: REST with Spring Boot #jqfk

class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { SessionConfig.class, …}; // …}

Page 190: REST with Spring Boot #jqfk

$ curl -v http://localhost:8080/ -u user:password!

HTTP/1.1 200 OK(略)x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3!

{"username":"user"}

Page 191: REST with Spring Boot #jqfk

$ curl -v http://localhost:8080/ -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"

Page 192: REST with Spring Boot #jqfk

Spring Bootを使うと

@EnableRedisHttpSession class HttpSessionConfig { @Bean HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); } }

Page 193: REST with Spring Boot #jqfk

Spring Bootを使うと

@EnableRedisHttpSession class HttpSessionConfig { @Bean HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); } }

これだけ! (Redisの設定も不要)

Page 194: REST with Spring Boot #jqfk

認可設定@Configuration @EnableWebSecurity class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and().httpBasic(); } }

Page 195: REST with Spring Boot #jqfk

サンプルコントローラー

@RestController class AuthController { @RequestMapping String check(Principal principal) { return principal.getName(); } }

Page 196: REST with Spring Boot #jqfk

$ curl -v http://localhost:8080/ -u making:password!

HTTP/1.1 200 OK(略)x-auth-token: fe1b6d11-9867-4df2-b5bf-a33eb004ac65!

making

Page 197: REST with Spring Boot #jqfk

$ curl -v http://localhost:8080/ -u making:password!

HTTP/1.1 200 OK(略)x-auth-token: fe1b6d11-9867-4df2-b5bf-a33eb004ac65!

making

Page 198: REST with Spring Boot #jqfk

$ curl -v -H 'x-auth-token: fe1b6d11-9867-4df2-b5bf-a33eb004ac65' http://localhost:8080/bookmarks!

{"_embedded": {"bookmarkResourceList": [{"_links": {…,"bookmark-uri": { "href": "http://bookmark.com/1/making"}},…}]

Page 199: REST with Spring Boot #jqfk

APIを3rdパーティに提供したい場合以外、 Spring Session使えばいいんじゃないか?

Page 200: REST with Spring Boot #jqfk

まとめ

• Spring Boot概要

• RESTについていろいろ

• Richardson Maturity Model / HATEOAS • JSON-Patch • Security (OAuth/Spring Session)

Page 201: REST with Spring Boot #jqfk

Q&A?

• はじめてのSpring Boot

• http://bit.ly/hajiboot • 今日話した内容のチュートリアル

• http://spring.io/guides/tutorials/bookmarks • 今日のソースコード

• https://github.com/making/tut-bookmarks