在 Quarkus OIDC 的整合中,主要分成會分成 web-app 與 service 兩種 type。應用 OIDC 授權流程可以保護我們的 HTTP 端點,並且利用到 OIDC 的授權伺服器例如 Keycloak.
在 web-app 模式下,啟用 OIDC 的授權碼流程機制( Authorization Code Flow mechanism )時,使用者在未登入情況下,當遇到需要權限的頁面時,會遇到以下流程
如下圖所示
今天會作一個 /api/users/me 來取得目前的使用者,並整合 keycloak 授權碼流程機制。
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=secret
# hybrid 表示 web-app, service 兩種通吃
quarkus.oidc.application-type=hybrid
# 所有 endpont 都要驗證 /*
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated
# Tell Dev Services for Keycloak to import the realm file
quarkus.keycloak.devservices.realm-path=quarkus-realm.json
在 /src/main/kotlin/.../resource 建立 TokenResource.kt
import io.quarkus.oidc.IdToken
import org.eclipse.microprofile.jwt.JsonWebToken
import javax.inject.Inject
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
@Path("/api/tokens")
class TokenResource {
@Inject
@IdToken
lateinit var idToken: JsonWebToken
@Inject
lateinit var accessToken: JsonWebToken
@Produces("text/html")
@GET
fun getTokens() = "<html><body><h3>idToken</h3>$idToken" +
"<h3>accessToken</h3>$accessToken</body></html>"
}
http://localhost:8080/api/tokens , 會被自動轉打到 keycloak ,並且後面有 redirect url
登入後可以看到印出 token 內容
在 /src/main/kotlin/.../resource 建立 UsersResource.kt 並輸入以下內容
package tw.brandy.ironman.resource
import io.quarkus.oidc.IdToken
import org.eclipse.microprofile.jwt.JsonWebToken
import org.jboss.resteasy.reactive.NoCache
import javax.inject.Inject
import javax.ws.rs.GET
import javax.ws.rs.Path
@Path("/api/users")
class UsersResource {
@Inject
lateinit var idToken: JsonWebToken
@GET
@Path("/me")
@NoCache
fun me(): User {
return User(idToken)
}
}
class User internal constructor(idToken: JsonWebToken) {
val userName: String
init {
userName = idToken.name
}
}
http://localhost:8080/api/users/me 會看到帶出目前使用者
properites 要加上三條以供 test
%test.quarkus.oidc.application-type=service
%test.quarkus.oidc.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB
smallrye.jwt.sign.key.location=/privateKey.pem
privateKey.pem 請從這裡取得 https://raw.githubusercontent.com/quarkusio/quarkus/main/integration-tests/oidc-tenancy/src/main/resources/privateKey.pem
import io.quarkus.test.junit.QuarkusTest
import io.restassured.module.kotlin.extensions.Given
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import io.smallrye.jwt.build.Jwt
import org.hamcrest.CoreMatchers.`is`
import org.junit.jupiter.api.Test
@QuarkusTest
class UserResourceTest {
@Test
fun `test api me`() {
Given {
auth().oauth2(getAccessToken("alice"))
} When {
get("/api/users/me")
} Then {
statusCode(200)
body("userName", `is`("alice"))
}
}
private fun getAccessToken(userName: String): String {
return Jwt.preferredUserName(userName)
.issuer("https://server.example.com")
.audience("https://service.example.com")
.sign()
}
}
測試也跑過了!!收功?
發現其他測試失敗了,阿,因為都沒有驗證,明天來補補~