-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17896 from sberyozkin/oidc_introspection_improvem…
…ents Support the injection of OIDC introspection response
- Loading branch information
Showing
18 changed files
with
349 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -336,6 +336,18 @@ Set `quarkus.oidc.user-info-required=true` if a UserInfo JSON object from the OI | |
A request will be sent to the OpenId Provider UserInfo endpoint and an `io.quarkus.oidc.UserInfo` (a simple `javax.json.JsonObject` wrapper) object will be created. | ||
`io.quarkus.oidc.UserInfo` can be either injected or accessed as a SecurityIdentity `userinfo` attribute. | ||
|
||
[[token-introspection]] | ||
== Token Introspection | ||
|
||
An opaque token has to be introspected by sending it to the OpenId Provider token introspection endpoint. | ||
|
||
If the opaque token is active then a token introspection `username` and `scope` properties will be used to build a `Securityidentity`. Additionally, an `io.quarkus.oidc.TokenIntrospection` (a simple `javax.json.JsonObject` wrapper) object will be created and can be either injected or accessed as a SecurityIdentity `introspection` attribute. | ||
|
||
Signed JWT tokens can also be introspected when no local matching `JsonWebKey` is available. | ||
|
||
If you only work with JWT tokens then it is recommended to disable the opaque token introspection with `quarkus.oidc.token.allow-opaque-token-introspection=false`. | ||
Additionally, disabling the introspection of signed JWT tokens is also advised with `quarkus.oidc.token.allow-jwt-introspection=false` if you expect that a local `JsonWebKey` will always be available since a 7`JsonWebKeySet` containing the public verification keys is periodically refreshed when the token has no matching `JsonWebKey`. | ||
|
||
[[config-metadata]] | ||
== Configuration Metadata | ||
|
||
|
@@ -695,6 +707,7 @@ import io.quarkus.test.security.TestSecurity; | |
import io.quarkus.test.security.oidc.Claim; | ||
import io.quarkus.test.security.oidc.ConfigMetadata; | ||
import io.quarkus.test.security.oidc.OidcSecurity; | ||
import io.quarkus.test.security.oidc.OidcConfigurationMetadata; | ||
import io.quarkus.test.security.oidc.UserInfo; | ||
import io.restassured.RestAssured; | ||
|
@@ -730,7 +743,11 @@ where `ProtectedResource` class may look like this: | |
|
||
[source, java] | ||
---- | ||
@Path("/web-app") | ||
import io.quarkus.oidc.OidcConfigurationMetadata; | ||
import io.quarkus.oidc.UserInfo; | ||
import org.eclipse.microprofile.jwt.JsonWebToken; | ||
@Path("/service") | ||
@Authenticated | ||
public class ProtectedResource { | ||
|
@@ -762,6 +779,67 @@ Note that `@TestSecurity` annotation must always be used and its `user` property | |
`@OidcSecurity` annotation is optional and can be used to set the additional token claims, as well as `UserInfo` and `OidcConfigurationMetadata` properties. | ||
Additionally, if `quarkus.oidc.token.issuer` property is configured then it will be used as an `OidcConfigurationMetadata` `issuer` property value. | ||
|
||
If you work with the opaque tokens then you can test them as follows: | ||
|
||
[source, java] | ||
---- | ||
import static org.hamcrest.Matchers.is; | ||
import org.junit.jupiter.api.Test; | ||
import io.quarkus.test.common.http.TestHTTPEndpoint; | ||
import io.quarkus.test.junit.QuarkusTest; | ||
import io.quarkus.test.security.TestSecurity; | ||
import io.quarkus.test.security.oidc.OidcSecurity; | ||
import io.quarkus.test.security.oidc.TokenIntrospection; | ||
import io.restassured.RestAssured; | ||
@QuarkusTest | ||
@TestHTTPEndpoint(ProtectedResource.class) | ||
public class TestSecurityAuthTest { | ||
@Test | ||
@TestSecurity(user = "userOidc", roles = "viewer") | ||
@OidcSecurity(introspectionRequired = true, | ||
introspection = { | ||
@TokenIntrospection(key = "email", value = "[email protected]") | ||
} | ||
) | ||
public void testOidcWithClaimsUserInfoAndMetadata() { | ||
RestAssured.when().get("test-security-oidc-claims-userinfo-metadata").then() | ||
.body(is("userOidc:viewer:userOidc:viewer")); | ||
} | ||
} | ||
---- | ||
|
||
where `ProtectedResource` class may look like this: | ||
|
||
[source, java] | ||
---- | ||
import io.quarkus.oidc.TokenIntrospection; | ||
import io.quarkus.security.identity.SecurityIdentity; | ||
@Path("/service") | ||
@Authenticated | ||
public class ProtectedResource { | ||
@Inject | ||
SecurityIdentity securityIdentity; | ||
@Inject | ||
TokenIntrospection introspection; | ||
@GET | ||
@Path("test-security-oidc-opaque-token") | ||
public String testSecurityOidcOpaqueToken() { | ||
return securityIdentity.getPrincipal().getName() + ":" + securityIdentity.getRoles().iterator().next() | ||
+ ":" + introspection.getString("username") | ||
+ ":" + introspection.getString("scope") | ||
+ ":" + introspection.getString("email"); | ||
} | ||
} | ||
---- | ||
|
||
Note that `@TestSecurity` `user` and `roles` attributes are availabe as `TokenIntrospection` `username` and `scope` properties and you can use `io.quarkus.test.security.oidc.TokenIntrospection` to add the additional introspection response properties such as an `email`, etc. | ||
|
||
== How to check the errors in the logs == | ||
|
||
Please enable `io.quarkus.oidc.runtime.OidcProvider` `TRACE` level logging to see more details about the token verification errors: | ||
|
23 changes: 23 additions & 0 deletions
23
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package io.quarkus.oidc; | ||
|
||
import javax.json.JsonObject; | ||
|
||
import io.quarkus.oidc.runtime.AbstractJsonObjectResponse; | ||
|
||
/** | ||
* Represents a token introspection result | ||
* | ||
*/ | ||
public class TokenIntrospection extends AbstractJsonObjectResponse { | ||
|
||
public TokenIntrospection() { | ||
} | ||
|
||
public TokenIntrospection(String introspectionJson) { | ||
super(introspectionJson); | ||
} | ||
|
||
public TokenIntrospection(JsonObject json) { | ||
super(json); | ||
} | ||
} |
51 changes: 4 additions & 47 deletions
51
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/UserInfo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,19 @@ | ||
package io.quarkus.oidc; | ||
|
||
import java.io.StringReader; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import javax.json.Json; | ||
import javax.json.JsonArray; | ||
import javax.json.JsonObject; | ||
import javax.json.JsonReader; | ||
import javax.json.JsonValue; | ||
|
||
public class UserInfo { | ||
import io.quarkus.oidc.runtime.AbstractJsonObjectResponse; | ||
|
||
private JsonObject json; | ||
public class UserInfo extends AbstractJsonObjectResponse { | ||
|
||
public UserInfo() { | ||
} | ||
|
||
public UserInfo(String userInfoJson) { | ||
json = toJsonObject(userInfoJson); | ||
super(userInfoJson); | ||
} | ||
|
||
public UserInfo(JsonObject json) { | ||
this.json = json; | ||
} | ||
|
||
public String getString(String name) { | ||
return json.getString(name); | ||
} | ||
|
||
public JsonArray getArray(String name) { | ||
return json.getJsonArray(name); | ||
} | ||
|
||
public JsonObject getObject(String name) { | ||
return json.getJsonObject(name); | ||
} | ||
|
||
public Object get(String name) { | ||
return json.get(name); | ||
} | ||
|
||
public boolean contains(String propertyName) { | ||
return json.containsKey(propertyName); | ||
} | ||
|
||
public Set<String> getPropertyNames() { | ||
return Collections.unmodifiableSet(json.keySet()); | ||
} | ||
|
||
public Set<Map.Entry<String, JsonValue>> getAllProperties() { | ||
return Collections.unmodifiableSet(json.entrySet()); | ||
} | ||
|
||
private static JsonObject toJsonObject(String userInfoJson) { | ||
try (JsonReader jsonReader = Json.createReader(new StringReader(userInfoJson))) { | ||
return jsonReader.readObject(); | ||
} | ||
super(json); | ||
} | ||
} |
71 changes: 71 additions & 0 deletions
71
...nsions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package io.quarkus.oidc.runtime; | ||
|
||
import java.io.StringReader; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import javax.json.Json; | ||
import javax.json.JsonArray; | ||
import javax.json.JsonNumber; | ||
import javax.json.JsonObject; | ||
import javax.json.JsonReader; | ||
import javax.json.JsonValue; | ||
|
||
public class AbstractJsonObjectResponse { | ||
private JsonObject json; | ||
|
||
public AbstractJsonObjectResponse() { | ||
} | ||
|
||
public AbstractJsonObjectResponse(String introspectionJson) { | ||
this(toJsonObject(introspectionJson)); | ||
} | ||
|
||
public AbstractJsonObjectResponse(JsonObject json) { | ||
this.json = json; | ||
} | ||
|
||
public String getString(String name) { | ||
return json.getString(name); | ||
} | ||
|
||
public Boolean getBoolean(String name) { | ||
return json.getBoolean(name); | ||
} | ||
|
||
public Long getLong(String name) { | ||
JsonNumber number = json.getJsonNumber(name); | ||
return number != null ? number.longValue() : null; | ||
} | ||
|
||
public JsonArray getArray(String name) { | ||
return json.getJsonArray(name); | ||
} | ||
|
||
public JsonObject getObject(String name) { | ||
return json.getJsonObject(name); | ||
} | ||
|
||
public Object get(String name) { | ||
return json.get(name); | ||
} | ||
|
||
public boolean contains(String propertyName) { | ||
return json.containsKey(propertyName); | ||
} | ||
|
||
public Set<String> getPropertyNames() { | ||
return Collections.unmodifiableSet(json.keySet()); | ||
} | ||
|
||
public Set<Map.Entry<String, JsonValue>> getAllProperties() { | ||
return Collections.unmodifiableSet(json.entrySet()); | ||
} | ||
|
||
private static JsonObject toJsonObject(String userInfoJson) { | ||
try (JsonReader jsonReader = Json.createReader(new StringReader(userInfoJson))) { | ||
return jsonReader.readObject(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.