以下の記事のつづきです。
KeycloakとSpringBoot/SecurityでOpenID Connect(リソースサーバー編)
KeycloakとSpringBoot/SecurityでOpenID Connect(クライアント編)
目標
Keycloakの同じレルムにクライアントアプリケーションと、リソースサーバーが出来たので、SSO(シングルサインオン)して相互に連携できることを目指します。
環境など
ツールなど | バージョンなど |
---|---|
MacbookPro | macOS Mojave 10.14.5 |
IntelliJ IDEA | Ultimate 2019.3.3 |
Java | AdoptOpenJDK 11 |
apache maven | 3.6.3 |
JUnit | 5.6.0 |
Postman | 7.19.1 |
Spring Boot | 2.2.6.RELEASE |
Spring Security | 5.2.2.RELEASE |
Keycloak | 9.0.3 |
Docker | 19.03.8, build afacb8b |
Thymeleaf | 3.0.11.RELEASE |
簡易フロー図
やろうとしていることのフローは、このようなものです。
今、必要があって作っているのはリソースサーバーにあたる(?)RESTfulAPIサービスです。
ただ、この認証と認可のフローって、微妙に言葉が違うだけで解説を読んでいても混乱してしまうもの。やっぱり全体を作って動かして、理解するのがなんぼ。
ということで、手を動かしています。
用語
微妙に用語が異なっていてそれだけで混乱すると書いたばかりですが、ここでは、以下のように言い表します。
- クライアントアプリ=Relying Party(RP)
- ユーザーが直接触るアプリです。または、その裏にあるサーバー側プログラム。
- IDプロバイダー
- Keycloakですね。
- リソースサーバー
- RESTfulAPIサービス。 RPとは現実には同じサービスかもしれないし、別のサービスかも知れない。ただ一応、分けて作ります。
リソースサーバーの設定変更
Keycloak上では、どちらも(クライアントアプリもリソースサーバーも)Clientとして登録するのですが、リソースサーバーの方は、"bearer-only"に変更します。(ここは要件次第かな?)
![]() |
---|
クライアントアプリを作る
前回のプロジェクトを編集するか、コピーしてきて名前を変えるなどすればいいでしょう。
私はsso_demo
としました。
ディレクトリ名や、pom.xml
のartifactId
,name
など忘れずに変更しましょう。
1.ポート番号の設定など
前のクライアントアプリと同じでもいいですし、変えてもいいです。同時に動かすことはないので同じでもいいでしょう。
私は念の為変えて9010
にしました。
server.port=9010
これで実行すれば、localhost:9010
でアクセスできます。
2.ページを追加
リソースサーバーには、/hello
ページがあるので、それに対応するページを作成します。
(1)helloページの作成
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "Greeting for you!");
return "hello";
}
とりあえず固定の文字列を表示します。
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
</head>
<body class="text-center">
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
<main role="main" class="inner cover">
<h1 class="cover-heading">Apache Keycloak SSO demonstration</h1>
<p class="lead">
</p>
<p class="lead">
<H1 th:text="${message}"></H1>
</p>
</main>
</div>
</body>
(2)indexページにリンクを追加
毎回手動で/hello
って入れるのも面倒なので、indexページにリンクを追加しましょう。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
</head>
<body class="text-center">
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
<main role="main" class="inner cover">
<h1 class="cover-heading">Apache Keycloak SSO demonstration</h1>
<p class="lead">
</p>
<!-- Helloページ追加ここから -->
<p class="lead">
<a href="/hello" class="btn btn-lg btn-secondary">Greeting</a>
</p>
<!-- Helloページ追加ここまで -->
<p class="lead">
<a href="/products" class="btn btn-lg btn-secondary">Search products</a>
</p>
<p class="lead">
</p>
<p class="lead">
<a href="/userinfo" class="btn btn-lg btn-secondary">User Information</a>
</p>
</main>
</div>
</body>
(3)Keycloak用の設定を追加
realmなどの設定を入れます。コピーしてきたなら入っているはずですが、keycloak.resource=
の箇所を変えるのを忘れないでください。後でKeycloakに登録するクライアント名になります。
server.port=9010
spring.thymeleaf.mode=HTML
keycloak.enabled=true
keycloak.auth-server-url=http://localhost:8088/auth
keycloak.realm=myrealm
keycloak.resource=client2
keycloak.public-client=true
server.tomcat.accesslog.enabled=true
server.tomcat.basedir=/dev
server.tomcat.accesslog.directory=stdout
server.tomcat.accesslog.suffix=
server.tomcat.accesslog.prefix=
server.tomcat.accesslog.file-date-format=
(4)ロールアクセス制御を追加
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/hello*").hasRole("user")
.antMatchers("/products*").hasRole("user")
.antMatchers("/userinfo*").hasRole("user")
.anyRequest().permitAll();
http.csrf().disable();
}
これで実行して、http://localhost:9010 から[Greeting]にアクセスすると、ログイン後、以下のようなページが開きます。
![]() |
---|
![]() |
---|
ページの準備ができました。
次は、リソースサーバーのエンドポイントにGETアクセスして、レスポンスを表示できるようにします。
2.REST API アクセス
(1)コントローラーの変更
固定値を返しているのを、リソースサーバーの各エンドポイントを叩いてレスポンスをもらって入れるように変更します。
RestTemplate restTemplate;
private final String base_url = "http://127.0.0.1:8080";
public ProductController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping("/hello")
public String hello(Model model) {
String url = base_url + "/hello";
String response = restTemplate.getForObject(url, String.class);
model.addAttribute("message", response);
return "hello";
}
@GetMapping(path = "/products")
public String getProducts(Model model){
String url = base_url + "/list";
ResponseEntity<List<String>> response = restTemplate.exchange(
url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() {
});
List<String> products = response.getBody();
model.addAttribute("products", products);
return "products";
}
...
-
RestTemplate restTemplate;
- SpringでRESTアクセスするのに使用するオブジェクト。コンストラクタインジェクションしています。
-
final String base_url
- リソースサーバーのurl。
-
localhost
を使うと、KeycloackがDockerで動いているせいかうまく動かなかったので、IPアドレス指定にしています。
-
String response = restTemplate.getForObject(url, String.class);
- リソースサーバーの
/hello
エンドポイントからレスポンスをもらっています。
- リソースサーバーの
-
getProducts
- こちらは
List<String>
が返ってくるので、restTemplate.getForObject
ではType指定が困難です。そのため、レスポンス全体をいったん直接取得し、getBody
で値を取得しています。
- こちらは
(2)KeycloakRestTemplateを使えるように設定
KeycloakRestTemplate
はアクセストークンなどを自動的にヘッダーにつけてくれるそうです。
RestTemplate
に代わってインジェクトされるようにします。
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
public final KeycloakClientRequestFactory keycloakClientRequestFactory;
public SecurityConfig(KeycloakClientRequestFactory keycloakClientRequestFactory) {
this.keycloakClientRequestFactory = keycloakClientRequestFactory;
}
// RestTemplateにかわってインジェクトされるようにする
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RestTemplate restTemplate(){
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
-
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
- インジェクションのために検索するパッケージを追加しています。
まだアクセスは出来ません。Keycloakにclient2
を追加しないと。
Keycloakにクライアントアプリを追加する
1.クライアントの登録
Keycloakの管理画面で、クライアントを追加します。
設定はほぼ前回のclient1と同じです。
![]() |
---|
-
AccessType
- publicにします
-
Valid Redirect URIs
-
http://localhost:9010*
とします
-
上記以外はデフォルトでいいかと思います。
ポート番号は自分で設定したのと合わせてください。
![]() |
---|
[Save]を忘れずに。
2.クライアントにデフォルトスコープを追加
hello
とuser
のスコープをデフォルトスコープに追加します。
![]() |
---|
リソースサーバーにアクセス!
リソースサーバーを立ち上げます。
今回作ったクライアントを立ち上げて、/hello
にアクセスします。
ログイン後、次のように表示されれば通信が成功です。
![]() |
---|
/products
にアクセスすると、ちゃんとリストが表示されるはず。
クライアント<->リソースサーバー<->IDプロバイダー(Keycloak)が実装できました!
最初の方に貼ったフロー図の動きになっているはずです(あれを作ったつもり^^;)
感想
これで、OpenId Connectの認証・認可のフローを全部実装できました。
やっぱり説明を読んでいるだけでは、概要やイメージは分かっても、「このパラメータをこうしたい」という話になったときに「あのフローのこの部分のアレだな」というのがとっさにわかりません。
ところで・・・これだけのことをやってくれるKeycloak。
IDプロバイダーを自前実装するというのは、やっぱりかなりハードル高いですよねえ・・・
Keycloakのまだ深い設定などいじってないですが、それでも、Keycloak自身をフォークしてどうこうするのも、そうとう大変そうな印象です。
途中途中でなにか特殊な仕様でどうにかしたい場合は、リバースプロキシのようなことで対応するほうが楽かもしれません。
それから、APIゲートウェイというのもありますよね。
これらもうまく組み合わせることで、自前で実装しなければならない部分は局所的にしていくほうが、今の時代に合ったアプローチかもしれません。
それと、しょっぱなから連携するものを作ろうとして、結局動かせず、一つずつ作ることにしてやっとうまくいきました。
何事も、ミニマムからやらないとね。
サンプルプロジェクト
ここまでのサンプルプロジェクトは、以下にアップしてあります。
https://github.com/le-kamba/SpringSecurityAndKeycloakSamples
参考サイト
RestTemplateのBean登録方法の参考に。
https://stackoverflow.com/questions/36151421/could-not-autowire-fieldresttemplate-in-spring-boot-application
Keycloak 4とSpring Boot 2アプリの連携 - リソースサーバー
https://www.yo1000.com/keycloak4-springboot2-resource/
リソースサーバーのurlにlocalhost
を使ってエラーになっていたときの対処の参考になりました。
https://github.com/jenkinsci/oic-auth-plugin/issues/35
RestTemplateを使ってオブジェクトのリストを取得および投稿する
https://www.codeflow.site/ja/article/spring-rest-template-list