Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JWT and OIDC security providers now support groups claim (1.x) #1325

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This is a bug fix release of Helidon.
### Fixes
- Security: OIDC redirect with cookies now uses `Same-Site` policy of `Lax` by default to prevent infinite redirects.
If the default value is use, a warning is printed explaining the change.
- Security: OIDC and JWT: `groups` claim in a JWT is now honored and a `Role` is created in subject for each such group.

## [1.4.1]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,6 +35,7 @@
import io.helidon.security.OutboundSecurityResponse;
import io.helidon.security.Principal;
import io.helidon.security.ProviderRequest;
import io.helidon.security.Role;
import io.helidon.security.SecurityEnvironment;
import io.helidon.security.SecurityResponse;
import io.helidon.security.Subject;
Expand Down Expand Up @@ -84,6 +85,7 @@ public final class JwtProvider extends SynchronousProvider implements Authentica
private final String issuer;
private final Map<OutboundTarget, JwtOutboundTarget> targetToJwtConfig = new IdentityHashMap<>();
private final Jwk defaultJwk;
private final boolean useJwtGroups;

private JwtProvider(Builder builder) {
this.optional = builder.optional;
Expand All @@ -98,6 +100,7 @@ private JwtProvider(Builder builder) {
this.issuer = builder.issuer;
this.expectedAudience = builder.expectedAudience;
this.verifySignature = builder.verifySignature;
this.useJwtGroups = builder.useJwtGroups;

if (null == atnTokenHandler) {
defaultTokenHandler = TokenHandler.builder()
Expand Down Expand Up @@ -204,12 +207,16 @@ Subject buildSubject(Jwt jwt, SignedJwt signedJwt) {
builder.addToken(Jwt.class, jwt);
builder.addToken(SignedJwt.class, signedJwt);

Optional<List<String>> scopes = jwt.scopes();

Subject.Builder subjectBuilder = Subject.builder()
.principal(principal)
.addPublicCredential(TokenCredential.class, builder.build());

if (useJwtGroups) {
Optional<List<String>> userGroups = jwt.userGroups();
userGroups.ifPresent(groups -> groups.forEach(group -> subjectBuilder.addGrant(Role.create(group))));
}

Optional<List<String>> scopes = jwt.scopes();
scopes.ifPresent(scopeList -> {
scopeList.forEach(scope -> subjectBuilder.addGrant(Grant.builder()
.name(scope)
Expand Down Expand Up @@ -599,6 +606,7 @@ public static final class Builder implements io.helidon.common.Builder<JwtProvid
private JwkKeys signKeys;
private String issuer;
private String expectedAudience;
private boolean useJwtGroups = true;

private Builder() {
}
Expand Down Expand Up @@ -789,6 +797,7 @@ public Builder config(Config config) {
config.get("sign-token").ifExists(outbound -> outboundConfig(OutboundConfig.create(outbound)));
config.get("sign-token").ifExists(this::outbound);
config.get("allow-unsigned").asBoolean().ifPresent(this::allowUnsigned);
config.get("use-jwt-groups").asBoolean().ifPresent(this::useJwtGroups);

return this;
}
Expand All @@ -802,6 +811,18 @@ public void expectedAudience(String audience) {
this.expectedAudience = audience;
}

/**
* Claim {@code groups} from JWT will be used to automatically add
* groups to current subject (may be used with {@link javax.annotation.security.RolesAllowed} annotation).
*
* @param useJwtGroups whether to use {@code groups} claim from JWT to retrieve roles
* @return updated builder instance
*/
public Builder useJwtGroups(boolean useJwtGroups) {
this.useJwtGroups = useJwtGroups;
return this;
}

private void verifyKeys(Config config) {
Resource.create(config, "jwk").ifPresent(this::verifyJwk);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -54,6 +54,7 @@
import io.helidon.security.OutboundSecurityResponse;
import io.helidon.security.Principal;
import io.helidon.security.ProviderRequest;
import io.helidon.security.Role;
import io.helidon.security.Security;
import io.helidon.security.SecurityEnvironment;
import io.helidon.security.SecurityLevel;
Expand Down Expand Up @@ -97,10 +98,12 @@ public final class OidcProvider extends SynchronousProvider implements Authentic
private final Pattern attemptPattern;
private final boolean propagate;
private final OidcOutboundConfig outboundConfig;
private final boolean useJwtGroups;

private OidcProvider(Builder builder, OidcOutboundConfig oidcOutboundConfig) {
this.oidcConfig = builder.oidcConfig;
this.propagate = builder.propagate;
this.useJwtGroups = builder.useJwtGroups;
this.outboundConfig = oidcOutboundConfig;

attemptPattern = Pattern.compile(".*?" + oidcConfig.redirectAttemptParam() + "=(\\d+).*");
Expand Down Expand Up @@ -495,12 +498,16 @@ private Subject buildSubject(Jwt jwt, SignedJwt signedJwt) {
builder.addToken(Jwt.class, jwt);
builder.addToken(SignedJwt.class, signedJwt);

Optional<List<String>> scopes = jwt.scopes();

Subject.Builder subjectBuilder = Subject.builder()
.principal(principal)
.addPublicCredential(TokenCredential.class, builder.build());

if (useJwtGroups) {
Optional<List<String>> userGroups = jwt.userGroups();
userGroups.ifPresent(groups -> groups.forEach(group -> subjectBuilder.addGrant(Role.create(group))));
}

Optional<List<String>> scopes = jwt.scopes();
scopes.ifPresent(scopeList -> scopeList.forEach(scope -> subjectBuilder.addGrant(Grant.builder()
.name(scope)
.type("scope")
Expand Down Expand Up @@ -543,6 +550,7 @@ public static final class Builder implements io.helidon.common.Builder<OidcProvi
// identity propagation is disabled by default. In general we should not reuse the same token
// for outbound calls, unless it is the same audience
private boolean propagate;
private boolean useJwtGroups = true;
private OutboundConfig outboundConfig;
private TokenHandler defaultOutboundHandler = TokenHandler.builder()
.tokenHeader("Authorization")
Expand Down Expand Up @@ -608,6 +616,7 @@ public Builder config(Config config) {
if (null == outboundConfig) {
config.get("outbound").ifExists(outbound -> outboundConfig(OutboundConfig.create(outbound)));
}
config.get("use-jwt-groups").asBoolean().ifPresent(this::useJwtGroups);

return this;
}
Expand Down Expand Up @@ -646,6 +655,18 @@ public Builder oidcConfig(OidcConfig config) {
this.oidcConfig = config;
return this;
}

/**
* Claim {@code groups} from JWT will be used to automatically add
* groups to current subject (may be used with {@link javax.annotation.security.RolesAllowed} annotation).
*
* @param useJwtGroups whether to use {@code groups} claim from JWT to retrieve roles
* @return updated builder instance
*/
public Builder useJwtGroups(boolean useJwtGroups) {
this.useJwtGroups = useJwtGroups;
return this;
}
}

private static final class OidcOutboundConfig {
Expand Down