ID管理、シングルサインオンのインフラ構築について、商用製品を使ったプロジェクトに以前従事していましたが、最近Keycloakというオープンソースのものがあることを知りました。
ここでは、Keycloakを設定して、SpringBootで作成されたWebアプリがログイン認証され、目的の画面を表示することを確認しました。
参考)
https://www.keycloak.org/getting-started/getting-started-zip
SprintBootアプリコード
https://github.com/keycloak/keycloak-quickstarts/blob/latest/app-springboot
https://github.com/keycloak/keycloak-quickstarts/tree/latest/service-springboot-rest
しかしながら、上記アプリを動かすために最新(20.0.3)ではない下記のバージョンを使用しました。
https://www.keycloak.org/archive/downloads-15.0.2.html
※起動方法、設定画面に違いがあります。
ここでは、基本的な設定よりこのテスト固有の部分に重点をおきます。
keycloak-15.0.2の起動(port:8080)
bin/standalone.sh
keycloak-quickstarts/app-springbootの起動(port:8081 client/アクセスタイプ:public)
mvn spring-boot:run
keycloak-quickstarts/service-springboot-restの起動(port:8082 client/アクセスタイプ:bearer)
mvn spring-boot:run
またsprintgbootの起動時にErrorがでるため、pom.xmlにバージョン情報の追記が必要でした。
1 2 3 4 5 6 7 |
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.0.3.RELEASE</version> </plugin> |
Keycloak管理画面(http://localhost:8080)にアクセスして、まずadminでログインできるようにしてから(パスワード設定)、レルム(demo)の追加、ロール(user)の追加、テストユーザ(user01)の追加をします。
クライアントの設定では、下記のようにapp,serviceそれぞれ設定します。
bear-onlyの方は、インストールタブから.jsonファイルをダウンロードし、service-springboot-rest/config/keycloak.jsonに配置します。
準備ができたら、アプリ画面にアクセスします。
リンクをクリックするとログイン画面に移動します。
ここでプロパティの確認をしてみます。
app-springboot/src/main/resources/application.properties
server.connection-timeout=5000
spring.freemarker.cache=false
server.port = 8081
keycloak.realm=demo
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=external
keycloak.resource=app-springboot
keycloak.public-client=true
product.service.url=http://localhost:8082/products
service-springboot/src/main/resources/application.properties
server.compression.enabled: true
server.compression.min-response-size: 1
server.connection-timeout=5000
server.port = 8082
keycloak.realm=demo
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=external
keycloak.resource=service-springboot
keycloak.public-client=true
keycloak.bearer-only=true
keycloak.securityConstraints[0].securityCollections[0].name = protected resource
keycloak.securityConstraints[0].authRoles[0] = user
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /products
あとアプリのコードも確認をしてみます。(変更を加えています)
app-springboot/src/main/java/org/keycloak/quickstart/springboot/web
ProductController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package org.keycloak.quickstart.springboot.web; import java.security.Principal; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import net.rossillo.spring.web.mvc.CacheControl; import net.rossillo.spring.web.mvc.CachePolicy; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.quickstart.springboot.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @CacheControl(policy = CachePolicy.NO_CACHE) public class ProductController { @Autowired private ProductService productService; private @Autowired HttpServletRequest request; @RequestMapping(value = "/products", method = RequestMethod.GET) public String handleCustomersRequest(Principal principal, Model model) { model.addAttribute("products", productService.getProducts()); model.addAttribute("principal", principal); String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8080/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH) .queryParam("redirect_uri", "http://localhost:8082/products").build("quickstart").toString(); model.addAttribute("logout", logoutUri); return "products"; } @RequestMapping(value = "/logout", method = RequestMethod.GET) public String handleLogoutt() throws ServletException { request.logout(); return "landing"; } @RequestMapping(value = "/", method = RequestMethod.GET) public String landing() throws ServletException { return "landing"; } } |
app-springboot/src\main/resources/templates/products.ftl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<#import "/spring.ftl" as spring /> <#assign xhtmlCompliant = true in spring> <!DOCTYPE html> <html> <head> <title>Product Page</title> </head> <body> <header> <a href="/logout" id="logout">Logout</a> </header> <h1>Product Page</h1> <p>User ${principal.name} made this request.</p> <h2>Products</h2> <ul> <#list products as product> <li>${product}</li> </#list> </ul> </body> </html> |
service-springboot-rest/src/main/java/org/keycloak/quickstart/springboot/service
ProductService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package org.keycloak.quickstart.springboot.service; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Component; @Component public class ProductService { public List<String> getProducts() { return Arrays.asList( "alpha", "bravo", "charlie", "delta", "echo", "foxtrot"); } public String getPublic() { return "public from getPublic function"; } } |
serviceはjsonデータを返していて、appでそれを受け取り表示しているようです。
下記のように確認できます。
以上、簡単ですが動作確認のメモでした。