Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

REST with Spring Boot #jqfk

JavaQne 2015の発表内容

  • Sé el primero en comentar

REST with Spring Boot #jqfk

  1. 1. REST with Spring Boot 槙 俊明(@making) JavaQne 2015 #jqfk 2015-01-24
  2. 2. 自己紹介 • @making • http://blog.ik.am • 公私ともにSpringヘビーユーザー • 日本Javaユーザーグループ幹事
  3. 3. 祝「はじめてのSpring Boot」出版 http://bit.ly/hajiboot 最近、第2版 が出ました!
  4. 4. 今日のお話 • Spring Boot概要 • RESTについて色々 • Richardson Maturity Model • Spring HATEOAS / Spring Data REST • JSON Patch • Spring Sync • Securing REST Serivces • Spring Security OAuth / Spring Session
  5. 5. Spring Bootの概要
  6. 6. Spring Boot概要 • Springを使って簡単にモダンな アプリケーションを開発するた めの仕組み • AutoConfigure + 組み込みサー バーが特徴
  7. 7. <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> この設定を追加 するだけ
  8. 8. 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 @EnableAutoConfiguration public class App { ! @RequestMapping("/") String home() { return "Hello World!"; } ! public static void main(String[] args) { SpringApplication.run(App.class, args); } } 魔法のアノテーション mainメソッドでアプリ実行
  9. 9. ログ 組込Tomcatが起動した
  10. 10. ログ 組込Tomcatが起動した
  11. 11. 実行可能jarを作成 $ mvn package
  12. 12. jarを実行 $ java -jar target/jggug-helloworld-1.0.0- SNAPSHOT.jar
  13. 13. プロパティを変更して実行 $ java -jar target/jggug-helloworld-1.0.0- SNAPSHOT.jar --server.port=8888 --(プロパティ名)=(プロパティ値)
  14. 14. @SpringBootApplication @RestController public class App { ! @RequestMapping("/") String home() { return "Hello World!"; } ! public static void main(String[] args) { SpringApplication.run(App.class, args); } } Spring Boot 1.2より
  15. 15. @SpringBootApplication @RestController public 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
  16. 16. RESTについて
  17. 17. REST? • クライアントとサーバ間でデータをやりと りするためのソフトウェアアーキテクチャ スタイルの一つ • サーバーサイドで管理している情報の中か らクライアントに提供すべき情報を「リソー ス」として抽出し、リソースをHTTPで操作
  18. 18. RESTに関するいろいろな話題 • Richardson Maturity Model • JSON Patch • Security
  19. 19. Richardson Maturity Model
  20. 20. Richardson Maturity Model http://martinfowler.com/articles/richardsonMaturityModel.html RESTの成熟モデル
  21. 21. Richardson Maturity Model http://martinfowler.com/articles/richardsonMaturityModel.html RESTの成熟モデル あなたの
  22. 22. Level 0: Swamp of POX • POX (Plain Old XML) • SOAP、XML-RPC 転送プロトコルとして HTTPを使っているだけ。 通常POSTオンリー
  23. 23. Level 0: Swamp of POX
  24. 24. Level 1: Resources • /customers、/usersなど • なんちゃってREST URLに名詞を使う。
  25. 25. Level 1: Resources
  26. 26. Level 2: HTTP Vebs • GET/POST/PUT/DELETEなど • 一般的にみんなが言っている”REST” HTTPメソッドを動詞に使う。 ヘッダやステータスを活用
  27. 27. Level 2: HTTP Vebs
  28. 28. ここまでは対応している Webフレームワークは多い
  29. 29. 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);
 } }
  30. 30. Level 3: Hypermedia Controls • HATEOAS (Hypermedia As The Engine Of Application State) Hypermediaリンクを 使用してナビゲーション。 ユーザーにサービス全体の 知識を強いない。
  31. 31. Level 3: Hypermedia Controls
  32. 32. Level 3: Hypermedia Controls { "name": "Alice", "links": [ { "rel": "self", "href": "http://.../customer/1" } ] }
  33. 33. Level 3: Hypermedia Controls { "name": "Alice", "links": [ { "rel": "self", "href": "http://.../customer/1" }, { "rel": "user", "href": "http://.../customer/1/user" } ] }
  34. 34. Level 3: Hypermedia Controls { "name": "Alice", "links": [ { "rel": "self", "href": "http://.../customer/1" }, { "rel": "user", "href": "http://.../customer/1/user" } ] } 関連するリソースの リンクが含まれる
  35. 35. Spring HATEOAS Spring MVCにHATEOASの概念を追加 • リソースのモデルにLink追加 • HAL等のデータフォーマットに対応
  36. 36. 具体例で説明
  37. 37. 扱うモデル
  38. 38. Bookmarkエンティティ @Entity public class Bookmark { @ManyToOne @JsonIgnore Account account; @Id @GeneratedValue Long id; String uri; String description; // omitted }
  39. 39. Accountエンティティ @Entity public class Account { @OneToMany(mappedBy = "account") Set<Bookmark> bookmarks; @Id @GeneratedValue Long id; @JsonIgnore String password; String username; // omitted }
  40. 40. BookmarkRepository public interface BookmarkRepository extends JpaRepository<Bookmark, Long> { ! Collection<Bookmark> findByAccountUsername(String username); ! }
  41. 41. BookmarkRepository public interface BookmarkRepository extends JpaRepository<Bookmark, Long> { ! Collection<Bookmark> findByAccountUsername(String username); ! } Spring Data JPAを使用。 CRUDを簡単に使える。
  42. 42. BookmarkRepository public interface BookmarkRepository extends JpaRepository<Bookmark, Long> { ! Collection<Bookmark> findByAccountUsername(String username); ! } Spring Data JPAを使用。 CRUDを簡単に使える。 命名規約に対応したクエリが 実行されるメソッド(実装不要)
  43. 43. 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
  44. 44. AccountRepository public interface AccountRepository extends JpaRepository<Account, Long> { ! Optional<Account> findByUsername(String username); ! }
  45. 45. AccountRepository public interface AccountRepository extends JpaRepository<Account, Long> { ! Optional<Account> findByUsername(String username); ! } Java SE 8のOptionalに対応。 1件取得結果の存在有無をOptionalで表現
  46. 46. Level 2 普通のSpring MVCプログラミング
  47. 47. 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);
 } // … }
  48. 48. 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)); }
  49. 49. 起動 @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);
 }
 }
  50. 50. 起動 @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);
 }
 } 起動時に実行されるクラス
  51. 51. 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 }]
  52. 52. Example: GET $ curl -X GET localhost:8080/making/bookmarks/5 { "description": "A description", "uri": "http://bookmark.com/1/making", "id": 5 }
  53. 53. 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"}
  54. 54. Error Handling @ResponseStatus(HttpStatus.NOT_FOUND) class UserNotFoundException extends RuntimeException { public UserNotFoundException(String userId) { super("could not find user '" + userId + "'."); } }
  55. 55. 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 }
  56. 56. Level 3 <dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId> </dependency> Spring HATEOASを使用
  57. 57. Level 3 <dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId> </dependency> Spring HATEOASを使用 Spring Bootを使うと依存関係を定義す るだけでHATEOASを使える
  58. 58. 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() {/**/}
 }
  59. 59. 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を表 現するための基本的な 情報を持つ
  60. 60. 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
  61. 61. 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を追加
  62. 62. 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を追加
  63. 63. 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を追加
  64. 64. @RequestMapping(value = “/{bookmarkId}", method = RequestMethod.GET) BookmarkResource readBookmark( @PathVariable String userId, @PathVariable Long bookmarkId) { this.validateUser(userId); return new BookmarkResource( this.bookmarkRepository .findOne(bookmarkId)); }
  65. 65. @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));
 }
  66. 66. サンプル: 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 } }
  67. 67. 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という規格の フォーマットを使用 している
  68. 68. HAL http://stateless.co/hal_specification.html Hypertext Application Language Hypermediaを表現する フォーマット仕様の1つ
  69. 69. @ControllerAdvice class BookmarkControllerAdvice { ! @ResponseBody @ExceptionHandler(UserNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) VndErrors userNotFoundExceptionHandler( UserNotFoundException ex) { return new VndErrors("error", ex.getMessage()); } } Error Handling
  70. 70. $ 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
  71. 71. 普通の人はLevel 2で十分。 こだわりたい人はLevel 3へ。
  72. 72. Spring Data REST Spring Dataのリポジトリを そのままREST APIとしてExport
  73. 73. SpringData SpringData JPA SpringData MongoDB SpringData Xxx JPA SpringDataREST RDB JSON MongoDB Xxx
  74. 74. Spring Bootから使う場合 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> Spring Bootを使うと依存関係を定義す るだけでSpring Data RESTを使える
  75. 75. ALPS Application-Level Profile Semantics http://alps.io/
  76. 76. Event Handler @RepositoryEventHandler(Bookmark.class) public class BookmarkEventHandler { @HandleBeforeSave public void beforeSave(Bookmark p) { /* … */ } @HandleAfterDelete public void afterDelete(Bookmark p) { /* … */ } }
  77. 77. 超短期間でRESTサービスをつくる 必要がある場合に強力
  78. 78. JSON Patch
  79. 79. コレクションの変更 [{"value":"a"},{"value":"b"},{"value":"c"}] [{"value":"a"},{"value":"c"},{"value":"d"}] Original Modified [+] {"value":"d"} を4番目に追加 [-] 2番目の要素を削除 …
  80. 80. コレクションの変更 [{"value":"a"},{"value":"b"},{"value":"c"}] [{"value":"a"},{"value":"c"},{"value":"d"}] Original Modified [+] {"value":"d"} を4番目に追加 [-] 2番目の要素を削除 … もっと効率的なデータ転送を!
  81. 81. 動機 より効率的なデータ転送 複数のクライアント間での データ同期 オフライン作業の反映
  82. 82. http://www.slideshare.net/briancavalier/differential-sync-and-json-patch-s2-gx-2014/13
  83. 83. http://www.slideshare.net/briancavalier/differential-sync-and-json-patch-s2-gx-2014/13 Diff & Patch!
  84. 84. JSON Patch RFC 6902 パッチをJSONで表現 PATCHメソッドで送信 JSON Pointer (RFC 6901)で指定し たJSONパスへの操作を表現 application/json-patch+json
  85. 85. JSON Patch RFC 6902 パッチをJSONで表現 PATCHメソッドで送信 JSON Pointer (RFC 6901)で指定し たJSONパスへの操作を表現 application/json-patch+json patch(diff(a, b), a) === b を満たすこと
  86. 86. 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
  87. 87. 典型的なREST POST /todos {"title":"fizzbuz","done":false } PUT /todos/1 {"title":"fizzbuz","done":true } PATCH /todos/2 {"done":true } DELETE /todos/3
  88. 88. 典型的なREST POST /todos {"title":"fizzbuz","done":false } PUT /todos/1 {"title":"fizzbuz","done":true } PATCH /todos/2 {"done":true } DELETE /todos/3HTTP通信回数=操作回数 リソースが増えるとさらに増える
  89. 89. 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
  90. 90. 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回 操作もアトミック
  91. 91. 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 @EnableDifferentialSynchronization public 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 乞うご期待
  92. 92. Securing REST Services
  93. 93. どっちが好き? • HttpSessionを使わない • HttpSessionを使う
  94. 94. どっちが好き? • HttpSessionを使わない • HttpSessionを使う KVSにデータを保存 OAuth 2.0を利用
  95. 95. OAuth 2.0 • アクセストークンを使って認可する 標準的な仕組み • 多くのAPIプロバイダがOAuthによ るリソースアクセスを提供
  96. 96. OAuth2.0の基本 Resource Owner Client Resource Server Authorization Server
  97. 97. OAuth2.0の基本 Resource Owner Client Resource Server Authorization Server Github APIの例
  98. 98. OAuth2.0の基本 Resource Owner Client Resource Server Authorization Server Githubの アカウント管理 Github API Github API を使ったサービス プロバイダ(アプリ) エンドユーザー (Githubユーザー)
  99. 99. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server
  100. 100. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server サービスへ リクエスト
  101. 101. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server 何か サービスへ リクエスト
  102. 102. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server 何か アクセストークン サービスへ リクエスト
  103. 103. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server 何か アクセストークン アクセストークン サービスへ リクエスト
  104. 104. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server 何か アクセストークン アクセストークン リソース サービスへ リクエスト
  105. 105. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server 何か アクセストークン アクセストークン リソース サービスへ リクエスト サービスからの レスポンス
  106. 106. OAuth2.0の流れ Resource Owner Client Resource Server Authorization Server 何か アクセストークン アクセストークン リソース サービスへ リクエスト サービスからの レスポンス ここの方式 (どうやってアクセストークンを交換するか) =GrantType
  107. 107. Grant Types • Authorization Code • Resource Owner Password Credentials • Client Credentials • Refresh Token • Implicit • JWT Bearer
  108. 108. Grant Types • Authorization Code • Resource Owner Password Credentials • Client Credentials • Refresh Token • Implicit • JWT Bearer
  109. 109. Authorization Code (grant_type=authorization_code) • 認可コードとアクセストークンを交換 • 一般的にOAuthと思われているやつ 画像: http://www.binarytides.com/php-add-login-with-github-to-your-website/
  110. 110. Resource Owner Client Resource Server Authorization Server
  111. 111. Resource Owner Client Resource Server Authorization Server サービスへ リクエスト
  112. 112. Resource Owner Client Resource Server Authorization Server Login Page サービスへ リクエスト
  113. 113. Resource Owner Client Resource Server Authorization Server Login Page サービスへ リクエスト
  114. 114. Resource Owner Client Resource Server Authorization Server Login Pageログイン サービスへ リクエスト
  115. 115. Resource Owner Client Resource Server Authorization Server Login Pageログイン サービスへ リクエスト
  116. 116. Resource Owner Client Resource Server Authorization Server Login Page 認可コード ログイン サービスへ リクエスト
  117. 117. Resource Owner Client Resource Server Authorization Server Login Page 認可コード ログイン サービスへ リクエスト
  118. 118. Resource Owner Client Resource Server Authorization Server Login Page 認可コード 認可コード ログイン サービスへ リクエスト
  119. 119. Resource Owner Client Resource Server Authorization Server Login Page 認可コード 認可コード 認可コード ログイン サービスへ リクエスト
  120. 120. Resource Owner Client Resource Server Authorization Server Login Page 認可コード 認可コード アクセストークン 認可コード ログイン サービスへ リクエスト
  121. 121. http://brentertainment.com/oauth2/
  122. 122. GET /authorize? response_type=code&client_id=s6BhdRkqt3& state=xyz&redirect_uri=https%3A%2F %2Fclient%2Eexample%2Ecom%2Fcb
  123. 123. GET /authorize? response_type=code&client_id=s6BhdRkqt3& state=xyz&redirect_uri=https%3A%2F %2Fclient%2Eexample%2Ecom%2Fcb 302 Found Location: https://client.example.com/cb? code=0fcfa4625502c209702e6d12fc67f4c298e 44373&state=xyz
  124. 124. 認可コード取得
  125. 125. 認可コード取得POST /token Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW grant_type=authorization_code&code=0fcfa4625502c20 9702e6d12fc67f4c298e44373&redirect_uri=https%3A%2F %2Fclient%2Eexample%2Ecom%2Fcb client_id:client_secret をBase64エンコード
  126. 126. 認可コード取得POST /token Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW grant_type=authorization_code&code=0fcfa4625502c20 9702e6d12fc67f4c298e44373&redirect_uri=https%3A%2F %2Fclient%2Eexample%2Ecom%2Fcb client_id:client_secret をBase64エンコード 200 OK ! {"access_token":"e651bdf91e704c0f3d060ffd 4ff0403eb087f519","expires_in": 3600,"token_type":"bearer"}
  127. 127. アクセストークン取得
  128. 128. アクセストークン取得 GET /api/friends Authorization: Bear e651bdf91e704c0f3d060ffd4ff0403eb087f519
  129. 129. リソース取得
  130. 130. Resource Owner Password Credentials (grant_type=password) • ユーザー名・パスワードとアクセストー クンを交換 • Clientが直接ユーザー名・パスワード を知ることになるので、通常公式アプ リで使用される。
  131. 131. Resource Owner Client Resource Server Authorization Server Authorization Server と提供元が同じ
  132. 132. Resource Owner Client Resource Server Authorization Server ユーザー名・ パスワード Authorization Server と提供元が同じ
  133. 133. Resource Owner Client Resource Server Authorization Server ユーザー名・ パスワード アクセストークン Authorization Server と提供元が同じ
  134. 134. POST /token Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW ! grant_type=password&username=demouser&password=tes tpass client_id:client_secret をBase64エンコード
  135. 135. POST /token Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW ! grant_type=password&username=demouser&password=tes tpass client_id:client_secret をBase64エンコード 200 OK ! {"access_token":"e651bdf91e704c0f3d060ffd 4ff0403eb087f519","expires_in": 3600,"token_type":"bearer"}
  136. 136. アクセストークン取得
  137. 137. アクセストークン取得 GET /api/friends Authorization: Bear e651bdf91e704c0f3d060ffd4ff0403eb087f519
  138. 138. リソース取得
  139. 139. Spring Security OAuth • Spring Securityの拡張でOAuthに対応 • 認証認可に加え、トークン管理、クライアン ト管理等 • OAuth認可サーバー、クライアントの実装が簡単 • 標準のGrantTypeは用意済み。カスタム GrantTypeも実装可能
  140. 140. Resource Owner Client Resource Server Authorization Server Spring Security OAuth サーバーの場合
  141. 141. Resource Owner Client Resource Server Authorization Server OAuth2RestTemplate Spring Security OAuth クライアントの場合
  142. 142. Resource Owner Client (curl) Resource Server (Bookmark) Authorization Server ユーザー名・ パスワード アクセストークン Bookmark APIの例
  143. 143. <dependency> <groupId>org.springframework.security.oauth</ groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.0.5.RELEASE</version> </dependency> まだSpring Boot用の AutoConfigure/Starterはない
  144. 144. Spring Securityの認証設定 @Configuration class 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(…)); }}
  145. 145. Spring Securityの認証設定 @Configuration class 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(…)); }} ユーザー名から認証ユーザーを 取得するインタフェース
  146. 146. @Configuration @EnableResourceServer class 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の設定
  147. 147. @Configuration @EnableResourceServer class 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
  148. 148. @Configuration @EnableResourceServer class 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セッションを使わない!!
  149. 149. @Configuration @EnableResourceServer class 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セッションを使わない!! 認可設定
  150. 150. 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");
 }}
  151. 151. 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にアクセスするクライアントを登録 (今回はデモ用にインメモリ実装)
  152. 152. 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 を設定
  153. 153. 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を指定
  154. 154. 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に許可する ロールとスコープを指定
  155. 155. 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を指定
  156. 156. リソースアクセス $ 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"}
  157. 157. トークン発行 $ curl-X POST -u demoapp:123456 http:// localhost:8080/oauth/token -d "password=password&username=making&grant_type=pa ssword&scope=write" ! {"access_token":"5f4b1353-ddd0-431b- a4b6-365267305d73","token_type":"bearer","refres h_token":"a50e4f67-373c-4f62- bdfb-560cf005d1e7","expires_in": 4292,"scope":"write"}
  158. 158. リソースアクセス $ curl -H 'Authorization: Bearer 5f4b1353- ddd0-431b-a4b6-365267305d73' http://localhost: 8080/bookmarks ! { "content": [ { "links": […], "book": {…} } ], … }
  159. 159. 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
  160. 160. いつも通り起動 $ 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/https 2014-12-13 12:07:47.836 INFO --- [mple.App.main()] com.example.App : Started App in 5.322 seconds (JVM running for 10.02)
  161. 161. いつも通り起動 $ 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/https 2014-12-13 12:07:47.836 INFO --- [mple.App.main()] com.example.App : Started App in 5.322 seconds (JVM running for 10.02)
  162. 162. Spring Security OAuthで ステートレスにRESTを セキュア化!!
  163. 163. Spring Security OAuthで ステートレスにRESTを セキュア化!! スケールブル!!
  164. 164. Spring Security OAuthで ステートレスにRESTを セキュア化!! スケールブル!! って思うやん?
  165. 165. 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
  166. 166. 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 いつまで"ステートレス" で消耗してんの? (意訳 違訳)
  167. 167. 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 いつまで"ステートレス" で消耗してんの? (意訳 違訳)
  168. 168. 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トークンとか。アクセストークンだっ て広い意味でステート)
  169. 169. これまでのHttpSession を使う場合 HttpSessionのデータを APサーバーのメモリに保存 ロードバランサのSticky Sessionで 同じSessionを同一JVMにバインド
  170. 170. Spring Session • セッションデータをJVM間で共有する新しい 選択肢 • 新しいセッションAPI • HttpSessionと統合して、以下を提供 • Clustered Session • Multiple Browser Sessions • RESTful APIs • WebSocketにも対応
  171. 171. Spring Session • セッションデータをJVM間で共有する新しい 選択肢 • 新しいセッションAPI • HttpSessionと統合して、以下を提供 • Clustered Session • Multiple Browser Sessions • RESTful APIs • WebSocketにも対応 ServletRequest/Response、 HttpSessionをラップする Servlet Filterを提供
  172. 172. Clustered Session • KVSをつかったセッション • Redis実装が提供されている • アプリを跨いだセッション共有も可能
  173. 173. @Import(EmbeddedRedisConfiguration.class) @EnableRedisHttpSession public class SessionConfig { @Bean JedisConnectionFactory connectionFactory() { return new JedisConnectionFactory(); } }
  174. 174. public class SessionInitializer extends AbstractHttpSessionApplicationInitializer { ! public SessionInitializer() { super(SessionConfig.class); } }
  175. 175. <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>
  176. 176. Multiple Browser Sessions 割愛
  177. 177. HttpSession & RESTful APIs Cookie(JSESSIONID)の代わりに HTTPヘッダ(X-AUTH-TOKEN)に セッション情報を載せる
  178. 178. @Import(EmbeddedRedisConfiguration.class) @EnableRedisHttpSession public class SessionConfig { @Bean JedisConnectionFactory connectionFactory() { return new JedisConnectionFactory(); } @Bean HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); } }
  179. 179. class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { SessionConfig.class, …}; // … }
  180. 180. $ curl -v http://localhost:8080/ -u user:password ! HTTP/1.1 200 OK (略) x-auth-token: 0dc1f6e1- c7f1-41ac-8ce2-32b6b3e57aa3 ! {"username":"user"}
  181. 181. $ curl -v http://localhost:8080/ -H "x- auth-token: 0dc1f6e1- c7f1-41ac-8ce2-32b6b3e57aa3"
  182. 182. Spring Bootを使うと @EnableRedisHttpSession
 class HttpSessionConfig {
 @Bean
 HttpSessionStrategy httpSessionStrategy() {
 return new HeaderHttpSessionStrategy();
 }
 }
  183. 183. Spring Bootを使うと @EnableRedisHttpSession
 class HttpSessionConfig {
 @Bean
 HttpSessionStrategy httpSessionStrategy() {
 return new HeaderHttpSessionStrategy();
 }
 } これだけ! (Redisの設定も不要)
  184. 184. 認可設定 @Configuration @EnableWebSecurity
 class SecurityConfig extends WebSecurityConfigurerAdapter {
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http.authorizeRequests()
 .anyRequest().authenticated()
 .and().httpBasic();
 }
 }
  185. 185. サンプルコントローラー @RestController
 class AuthController {
 @RequestMapping
 String check(Principal principal) {
 return principal.getName();
 }
 }
  186. 186. $ curl -v http://localhost:8080/ -u making:password ! HTTP/1.1 200 OK (略) x-auth-token: fe1b6d11-9867-4df2-b5bf- a33eb004ac65 ! making
  187. 187. $ curl -v http://localhost:8080/ -u making:password ! HTTP/1.1 200 OK (略) x-auth-token: fe1b6d11-9867-4df2-b5bf- a33eb004ac65 ! making
  188. 188. $ 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"}},…}]
  189. 189. APIを3rdパーティに提供したい場合以外、 Spring Session使えばいいんじゃないか?
  190. 190. まとめ • Spring Boot概要 • RESTについていろいろ • Richardson Maturity Model / HATEOAS • JSON-Patch • Security (OAuth/Spring Session)
  191. 191. Q&A? • はじめてのSpring Boot • http://bit.ly/hajiboot • 今日話した内容のチュートリアル • http://spring.io/guides/tutorials/bookmarks • 今日のソースコード • https://github.com/making/tut-bookmarks

×