Merge upstream-master

This commit is contained in:
Tomas Kyjovsky committed 2015-11-30 16:48:25 +01:00
commit 0d4771a763
273 files changed
+7821 -916

No files matched your search

+1 -1
View File
@@ -8,6 +8,6 @@ install:
script:
- mvn test -B
- mvn -file testsuite/integration-arquillian test -B
- mvn -file testsuite/integration-arquillian test -B -Pno-console
sudo: false
@@ -5,6 +5,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessTokenResponse;
@@ -51,7 +52,13 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
@POST
@Path(AdapterConstants.K_LOGOUT)
public Response backchannelLogout(String input) {
JWSInput token = new JWSInput(input);
JWSInput token = null;
try {
token = new JWSInput(input);
} catch (JWSInputException e) {
logger.warn("Failed to verify logout request");
return Response.status(400).build();
}
PublicKey key = getExternalIdpKey();
if (key != null) {
if (!verify(token, key)) {
@@ -29,6 +29,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
@@ -282,40 +283,41 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
throw new IdentityBrokerException("No token from server.");
}
JsonWebToken token;
try {
JWSInput jws = new JWSInput(encodedToken);
if (!verify(jws, key)) {
throw new IdentityBrokerException("token signature validation failed");
}
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
String iss = token.getIssuer();
if (!token.hasAudience(getConfig().getClientId())) {
throw new IdentityBrokerException("Wrong audience from token.");
}
if (!token.isActive()) {
throw new IdentityBrokerException("Token is no longer valid");
}
String trustedIssuers = getConfig().getIssuer();
if (trustedIssuers != null) {
String[] issuers = trustedIssuers.split(",");
for (String trustedIssuer : issuers) {
if (iss != null && iss.equals(trustedIssuer.trim())) {
return token;
}
}
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
return token;
} catch (IOException e) {
throw new IdentityBrokerException("Could not decode token.", e);
token = jws.readJsonContent(JsonWebToken.class);
} catch (JWSInputException e) {
throw new IdentityBrokerException("Invalid token", e);
}
String iss = token.getIssuer();
if (!token.hasAudience(getConfig().getClientId())) {
throw new IdentityBrokerException("Wrong audience from token.");
}
if (!token.isActive()) {
throw new IdentityBrokerException("Token is no longer valid");
}
String trustedIssuers = getConfig().getIssuer();
if (trustedIssuers != null) {
String[] issuers = trustedIssuers.split(",");
for (String trustedIssuer : issuers) {
if (iss != null && iss.equals(trustedIssuer.trim())) {
return token;
}
}
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
return token;
}
@Override
@@ -89,7 +89,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
} else if (value instanceof List) {
List list = (List)value;
for (Object val : list) {
return valueEquals(desiredValue, val);
if (valueEquals(desiredValue, val)) return true;
}
}
return false;
@@ -341,6 +341,17 @@ public class KeycloakUriBuilder {
return this;
}
/**
* Set fragment, but not encode it. It assumes that given fragment was already properly encoded
*
* @param fragment
* @return
*/
public KeycloakUriBuilder encodedFragment(String fragment) {
this.fragment = fragment;
return this;
}
/**
* Only replace path params in path of URI. This changes state of URIBuilder.
*
@@ -51,6 +51,11 @@
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="REALM">
<column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
</addColumn>
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
@@ -69,11 +74,37 @@
<addUniqueConstraint columnNames="GROUP_ID" constraintName="CON_GROUP_ID_DEF_GROUPS" tableName="REALM_DEFAULT_GROUPS"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
<addColumn tableName="CLIENT">
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
<column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
</addColumn>
<update tableName="CLIENT">
<column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
<where>DIRECT_GRANTS_ONLY = :value</where>
<whereParams>
<param valueBoolean="true" />
</whereParams>
</update>
<dropDefaultValue tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY" />
<dropColumn tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY"/>
<modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>
<!-- Sybase specific hacks -->
<modifySql dbms="sybase">
<regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
</modifySql>
</changeSet>
</databaseChangeLog>
@@ -28,7 +28,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
Update1_2_0_Beta1.class,
Update1_2_0_CR1.class,
Update1_3_0.class,
Update1_4_0.class
Update1_4_0.class,
Update1_7_0.class
};
@Override
@@ -0,0 +1,39 @@
package org.keycloak.connections.mongo.updater.impl.updates;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class Update1_7_0 extends Update {
@Override
public String getId() {
return "1.7.0";
}
@Override
public void update(KeycloakSession session) throws ClassNotFoundException {
DBCollection clients = db.getCollection("clients");
DBCursor clientsCursor = clients.find();
try {
while (clientsCursor.hasNext()) {
BasicDBObject client = (BasicDBObject) clientsCursor.next();
boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
client.append("standardFlowEnabled", !directGrantsOnly);
client.append("implicitFlowEnabled", false);
client.append("directAccessGrantsEnabled", true);
client.removeField("directGrantsOnly");
clients.save(client);
}
} finally {
clientsCursor.close();
}
}
}
@@ -2,6 +2,7 @@ package org.keycloak;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken;
import org.keycloak.util.TokenUtil;
@@ -22,7 +23,7 @@ public class RSATokenVerifier {
JWSInput input = null;
try {
input = new JWSInput(tokenString);
} catch (Exception e) {
} catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token", e);
}
if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
@@ -30,7 +31,7 @@ public class RSATokenVerifier {
AccessToken token;
try {
token = input.readJsonContent(AccessToken.class);
} catch (IOException e) {
} catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token signature", e);
}
String user = token.getSubject();
@@ -21,14 +21,14 @@ public class JWSInput {
byte[] signature;
public JWSInput(String wire) {
this.wireString = wire;
String[] parts = wire.split("\\.");
if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
encodedHeader = parts[0];
encodedContent = parts[1];
encodedSignatureInput = encodedHeader + '.' + encodedContent;
public JWSInput(String wire) throws JWSInputException {
try {
this.wireString = wire;
String[] parts = wire.split("\\.");
if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
encodedHeader = parts[0];
encodedContent = parts[1];
encodedSignatureInput = encodedHeader + '.' + encodedContent;
content = Base64Url.decode(encodedContent);
if (parts.length > 2) {
encodedSignature = parts[2];
@@ -37,8 +37,8 @@ public class JWSInput {
}
byte[] headerBytes = Base64Url.decode(encodedHeader);
header = JsonSerialization.readValue(headerBytes, JWSHeader.class);
} catch (Exception e) {
throw new RuntimeException(e);
} catch (Throwable t) {
throw new JWSInputException(t);
}
}
@@ -80,8 +80,12 @@ public class JWSInput {
return header.getAlgorithm().getProvider().verify(this, key);
}
public <T> T readJsonContent(Class<T> type) throws IOException {
return JsonSerialization.readValue(content, type);
public <T> T readJsonContent(Class<T> type) throws JWSInputException {
try {
return JsonSerialization.readValue(content, type);
} catch (IOException e) {
throw new JWSInputException(e);
}
}
public String readContentAsString() {
@@ -0,0 +1,18 @@
package org.keycloak.jose.jws;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWSInputException extends Exception {
public JWSInputException(String s) {
super(s);
}
public JWSInputException() {
}
public JWSInputException(Throwable throwable) {
super(throwable);
}
}
@@ -64,7 +64,7 @@ public class RSAProvider implements SignatureProvider {
verifier.update(input.getEncodedSignatureInput().getBytes("UTF-8"));
return verifier.verify(input.getSignature());
} catch (Exception e) {
throw new RuntimeException(e);
return false;
}
}
@@ -28,6 +28,7 @@ public class RefreshToken extends AccessToken {
this.subject = token.subject;
this.issuedFor = token.issuedFor;
this.sessionState = token.sessionState;
this.nonce = token.nonce;
if (token.realmAccess != null) {
realmAccess = token.realmAccess.clone();
}
@@ -26,6 +26,9 @@ public class ClientRepresentation {
protected Integer notBefore;
protected Boolean bearerOnly;
protected Boolean consentRequired;
protected Boolean standardFlowEnabled;
protected Boolean implicitFlowEnabled;
protected Boolean directAccessGrantsEnabled;
protected Boolean serviceAccountsEnabled;
protected Boolean directGrantsOnly;
protected Boolean publicClient;
@@ -181,6 +184,30 @@ public class ClientRepresentation {
this.consentRequired = consentRequired;
}
public Boolean isStandardFlowEnabled() {
return standardFlowEnabled;
}
public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
this.standardFlowEnabled = standardFlowEnabled;
}
public Boolean isImplicitFlowEnabled() {
return implicitFlowEnabled;
}
public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
this.implicitFlowEnabled = implicitFlowEnabled;
}
public Boolean isDirectAccessGrantsEnabled() {
return directAccessGrantsEnabled;
}
public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
}
public Boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
@@ -12,6 +12,7 @@ public class RealmRepresentation {
protected Integer notBefore;
protected Boolean revokeRefreshToken;
protected Integer accessTokenLifespan;
protected Integer accessTokenLifespanForImplicitFlow;
protected Integer ssoSessionIdleTimeout;
protected Integer ssoSessionMaxLifespan;
protected Integer offlineSessionIdleTimeout;
@@ -84,6 +85,8 @@ public class RealmRepresentation {
private List<IdentityProviderRepresentation> identityProviders;
private List<IdentityProviderMapperRepresentation> identityProviderMappers;
private List<ProtocolMapperRepresentation> protocolMappers;
@Deprecated
private Boolean identityFederationEnabled;
protected Boolean internationalizationEnabled;
protected Set<String> supportedLocales;
protected String defaultLocale;
@@ -185,6 +188,14 @@ public class RealmRepresentation {
this.accessTokenLifespan = accessTokenLifespan;
}
public Integer getAccessTokenLifespanForImplicitFlow() {
return accessTokenLifespanForImplicitFlow;
}
public void setAccessTokenLifespanForImplicitFlow(Integer accessTokenLifespanForImplicitFlow) {
this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
}
public Integer getSsoSessionIdleTimeout() {
return ssoSessionIdleTimeout;
}
@@ -612,6 +623,11 @@ public class RealmRepresentation {
identityProviders.add(identityProviderRepresentation);
}
@Deprecated
public boolean isIdentityFederationEnabled() {
return identityProviders != null && !identityProviders.isEmpty();
}
public List<ProtocolMapperRepresentation> getProtocolMappers() {
return protocolMappers;
}
@@ -4,6 +4,7 @@ import java.io.IOException;
import org.keycloak.OAuth2Constants;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.RefreshToken;
/**
@@ -41,11 +42,15 @@ public class TokenUtil {
* @param decodedToken
* @return
*/
public static RefreshToken getRefreshToken(byte[] decodedToken) throws IOException {
return JsonSerialization.readValue(decodedToken, RefreshToken.class);
public static RefreshToken getRefreshToken(byte[] decodedToken) throws JWSInputException {
try {
return JsonSerialization.readValue(decodedToken, RefreshToken.class);
} catch (IOException e) {
throw new JWSInputException(e);
}
}
public static RefreshToken getRefreshToken(String refreshToken) throws IOException {
public static RefreshToken getRefreshToken(String refreshToken) throws JWSInputException {
byte[] encodedContent = new JWSInput(refreshToken).getContent();
return getRefreshToken(encodedContent);
}
@@ -56,13 +61,9 @@ public class TokenUtil {
* @param refreshToken
* @return
*/
public static boolean isOfflineToken(String refreshToken) {
try {
RefreshToken token = getRefreshToken(refreshToken);
return token.getType().equals(TOKEN_TYPE_OFFLINE);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
public static boolean isOfflineToken(String refreshToken) throws JWSInputException {
RefreshToken token = getRefreshToken(refreshToken);
return token.getType().equals(TOKEN_TYPE_OFFLINE);
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
<module>wf8-adapter</module>
<module>wf9-adapter</module>
<module>wildfly-adapter</module>
</modules>
</project>
@@ -6,15 +6,15 @@
<version>1.7.0.Final-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Wildfly 9 Adapter</name>
<name>Keycloak Wildfly Adapter</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-wf9-adapter-dist-pom</artifactId>
<artifactId>keycloak-wildfly-adapter-dist-pom</artifactId>
<packaging>pom</packaging>
<modules>
<module>wf9-modules</module>
<module>wf9-adapter-zip</module>
<module>wildfly-modules</module>
<module>wildfly-adapter-zip</module>
</modules>
</project>
@@ -18,7 +18,7 @@
<include>org/keycloak/keycloak-jboss-adapter-core/**</include>
<include>org/keycloak/keycloak-undertow-adapter/**</include>
<include>org/keycloak/keycloak-wildfly-adapter/**</include>
<include>org/keycloak/keycloak-wf9-subsystem/**</include>
<include>org/keycloak/keycloak-wildfly-subsystem/**</include>
<include>org/keycloak/keycloak-adapter-subsystem/**</include>
<include>org/keycloak/keycloak-servlet-oauth-client/**</include>
</includes>
@@ -8,15 +8,15 @@
<relativePath>../../../../pom.xml</relativePath>
</parent>
<artifactId>keycloak-wf9-adapter-dist</artifactId>
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
<packaging>pom</packaging>
<name>Keycloak Wildfly 9 Adapter Distro</name>
<name>Keycloak Wildfly Adapter Distro</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-modules</artifactId>
<artifactId>keycloak-wildfly-modules</artifactId>
<type>zip</type>
</dependency>
</dependencies>
@@ -37,7 +37,7 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-modules</artifactId>
<artifactId>keycloak-wildfly-modules</artifactId>
<type>zip</type>
<outputDirectory>${project.build.directory}/unpacked</outputDirectory>
</artifactItem>
@@ -70,8 +70,8 @@
<maven-resource group="org.keycloak" artifact="keycloak-wildfly-adapter"/>
</module-def>
<module-def name="org.keycloak.keycloak-wf9-subsystem">
<maven-resource group="org.keycloak" artifact="keycloak-wf9-subsystem"/>
<module-def name="org.keycloak.keycloak-wildfly-subsystem">
<maven-resource group="org.keycloak" artifact="keycloak-wildfly-subsystem"/>
</module-def>
<module-def name="org.keycloak.keycloak-servlet-oauth-client">
@@ -12,9 +12,9 @@
<relativePath>../../../../pom.xml</relativePath>
</parent>
<artifactId>keycloak-wf9-modules</artifactId>
<artifactId>keycloak-wildfly-modules</artifactId>
<name>Keycloak Wildfly 9 Modules</name>
<name>Keycloak Wildfly Modules</name>
<packaging>pom</packaging>
<dependencies>
<dependency>
@@ -51,7 +51,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-subsystem</artifactId>
<artifactId>keycloak-wildfly-subsystem</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
@@ -30,6 +30,6 @@
</resources>
<dependencies>
<module name="org.keycloak.keycloak-wf9-subsystem" export="true" services="export"/>
<module name="org.keycloak.keycloak-wildfly-subsystem" export="true" services="export"/>
</dependencies>
</module>
@@ -22,7 +22,7 @@
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-wf9-subsystem">
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-wildfly-subsystem">
<resources>
<resource-root path="."/>
+2 -2
View File
@@ -33,14 +33,14 @@
</excludes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/keycloak-wf9-adapter-${project.version}</directory>
<directory>${project.build.directory}/unpacked/keycloak-wildfly-adapter-${project.version}</directory>
<outputDirectory>keycloak</outputDirectory>
<excludes>
<exclude>standalone/configuration/standalone-keycloak.xml</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/keycloak-saml-wf9-adapter-${project.version}</directory>
<directory>${project.build.directory}/unpacked/keycloak-saml-wildfly-adapter-${project.version}</directory>
<outputDirectory>keycloak</outputDirectory>
<excludes>
<exclude>standalone/configuration/standalone-keycloak.xml</exclude>
+6 -6
View File
@@ -21,12 +21,12 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-adapter-dist</artifactId>
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
<artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
<dependency>
@@ -99,9 +99,9 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-adapter-dist</artifactId>
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
<outputDirectory>${project.build.directory}/unpacked/keycloak-wf9-adapter-${project.version}</outputDirectory>
<outputDirectory>${project.build.directory}/unpacked/keycloak-wildfly-adapter-${project.version}</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
@@ -116,9 +116,9 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
<artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>zip</type>
<outputDirectory>${project.build.directory}/unpacked/keycloak-saml-wf9-adapter-${project.version}</outputDirectory>
<outputDirectory>${project.build.directory}/unpacked/keycloak-saml-wildfly-adapter-${project.version}</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
@@ -44,7 +44,7 @@
<web-context>auth</web-context>
</subsystem>
<subsystem xmlns="urn:jboss:domain:keycloak:1.1"/>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.6"/>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1"/>
</xsl:copy>
</xsl:template>
+4 -4
View File
@@ -239,12 +239,12 @@
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-adapter-dist</artifactId>
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-adapter-dist</artifactId>
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>tar.gz</type>
</artifactItem>
</artifactItems>
@@ -338,12 +338,12 @@
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
<artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>zip</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
<artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>tar.gz</type>
</artifactItem>
</artifactItems>
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-subsystem</artifactId>
<artifactId>keycloak-wildfly-subsystem</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
@@ -28,6 +28,6 @@
</resources>
<dependencies>
<module name="org.keycloak.keycloak-wf9-subsystem" export="true" services="export"/>
<module name="org.keycloak.keycloak-wildfly-subsystem" export="true" services="export"/>
</dependencies>
</module>
@@ -22,10 +22,10 @@
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wf9-subsystem">
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wildfly-subsystem">
<resources>
<artifact name="${org.keycloak:keycloak-wf9-subsystem}"/>
<artifact name="${org.keycloak:keycloak-wildfly-subsystem}"/>
</resources>
<dependencies>
@@ -29,7 +29,7 @@
</fileSets>
<files>
<file>
<source>../../shared-cli/adapter-install.cli</source>
<source>../../shared-cli/adapter-install-saml.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
@@ -29,7 +29,7 @@
</fileSets>
<files>
<file>
<source>../../shared-cli/adapter-install.cli</source>
<source>../../shared-cli/adapter-install-saml.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
+1 -1
View File
@@ -15,7 +15,7 @@
<packaging>pom</packaging>
<modules>
<module>wf9-adapter</module>
<module>wildfly-adapter</module>
<module>tomcat6-adapter-zip</module>
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
@@ -6,15 +6,15 @@
<version>1.7.0.Final-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Wildfly 9 SAML Adapter</name>
<name>Keycloak Wildfly SAML Adapter</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-saml-wf9-adapter-dist-pom</artifactId>
<artifactId>keycloak-saml-wildfly-adapter-dist-pom</artifactId>
<packaging>pom</packaging>
<modules>
<module>wf9-modules</module>
<module>wf9-adapter-zip</module>
<module>wildfly-modules</module>
<module>wildfly-adapter-zip</module>
</modules>
</project>
@@ -18,7 +18,7 @@
<include>org/keycloak/keycloak-jboss-adapter-core/**</include>
<include>org/keycloak/keycloak-saml-undertow-adapter/**</include>
<include>org/keycloak/keycloak-saml-wildfly-adapter/**</include>
<include>org/keycloak/keycloak-saml-wf9-subsystem/**</include>
<include>org/keycloak/keycloak-saml-wildfly-subsystem/**</include>
<include>org/keycloak/keycloak-saml-adapter-subsystem/**</include>
</includes>
<excludes>
@@ -29,7 +29,7 @@
</fileSets>
<files>
<file>
<source>../../shared-cli/adapter-install.cli</source>
<source>../../shared-cli/adapter-install-saml.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
@@ -8,15 +8,15 @@
<relativePath>../../../../pom.xml</relativePath>
</parent>
<artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
<artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<packaging>pom</packaging>
<name>Keycloak SAML Wildfly 9 Adapter Distro</name>
<name>Keycloak SAML Wildfly Adapter Distro</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wf9-modules</artifactId>
<artifactId>keycloak-saml-wildfly-modules</artifactId>
<type>zip</type>
</dependency>
</dependencies>
@@ -37,7 +37,7 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wf9-modules</artifactId>
<artifactId>keycloak-saml-wildfly-modules</artifactId>
<type>zip</type>
<outputDirectory>${project.build.directory}/unpacked</outputDirectory>
</artifactItem>
@@ -69,8 +69,8 @@
<maven-resource group="org.keycloak" artifact="keycloak-saml-wildfly-adapter"/>
</module-def>
<module-def name="org.keycloak.keycloak-saml-wf9-subsystem">
<maven-resource group="org.keycloak" artifact="keycloak-saml-wf9-subsystem"/>
<module-def name="org.keycloak.keycloak-saml-wildfly-subsystem">
<maven-resource group="org.keycloak" artifact="keycloak-saml-wildfly-subsystem"/>
</module-def>
</target>
@@ -12,9 +12,9 @@
<relativePath>../../../../pom.xml</relativePath>
</parent>
<artifactId>keycloak-saml-wf9-modules</artifactId>
<artifactId>keycloak-saml-wildfly-modules</artifactId>
<name>Keycloak SAML Wildfly 9 Modules</name>
<name>Keycloak SAML Wildfly Modules</name>
<packaging>pom</packaging>
<dependencies>
<dependency>
@@ -51,7 +51,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wf9-subsystem</artifactId>
<artifactId>keycloak-saml-wildfly-subsystem</artifactId>
</dependency>
</dependencies>
@@ -30,6 +30,6 @@
</resources>
<dependencies>
<module name="org.keycloak.keycloak-saml-wf9-subsystem" export="true" services="export"/>
<module name="org.keycloak.keycloak-saml-wildfly-subsystem" export="true" services="export"/>
</dependencies>
</module>
@@ -22,7 +22,7 @@
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-saml-wf9-subsystem">
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-saml-wildfly-subsystem">
<resources>
<resource-root path="."/>
@@ -71,6 +71,10 @@
<maven-resource group="com.google.zxing" artifact="core"/>
</module-def>
<module-def name="org.jboss.aesh" slot="0.65">
<maven-resource group="org.jboss.aesh" artifact="aesh"/>
</module-def>
<module-def name="com.google.zxing.javase">
<maven-resource group="com.google.zxing" artifact="javase"/>
</module-def>
@@ -246,6 +250,10 @@
<maven-resource group="org.keycloak" artifact="keycloak-saml-protocol"/>
</module-def>
<module-def name="org.keycloak.keycloak-wildfly-adduser">
<maven-resource group="org.keycloak" artifact="keycloak-wildfly-adduser"/>
</module-def>
<!-- mongo -->
<module-def name="org.keycloak.keycloak-connections-mongo">
@@ -38,6 +38,10 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-extensions</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-adduser</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
@@ -46,6 +50,10 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.aesh</groupId>
<artifactId>aesh</artifactId>
</dependency>
</dependencies>
<build>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2010, Red Hat, Inc., and individual contributors
~ as indicated by the @author tags. See the copyright.txt file in the
~ distribution for a full listing of individual contributors.
~
~ This is free software; you can redistribute it and/or modify it
~ under the terms of the GNU Lesser General Public License as
~ published by the Free Software Foundation; either version 2.1 of
~ the License, or (at your option) any later version.
~
~ This software is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
~ Lesser General Public License for more details.
~
~ You should have received a copy of the GNU Lesser General Public
~ License along with this software; if not, write to the Free
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<module xmlns="urn:jboss:module:1.3" name="org.jboss.aesh" slot="0.65">
<properties>
<property name="jboss.api" value="private"/>
</properties>
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="org.fusesource.jansi" />
</dependencies>
</module>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wildfly-adduser">
<main-class name="org.keycloak.wildfly.adduser.AddUser"/>
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.jboss.aesh" slot="0.65"/>
<module name="org.jboss.as.domain-management"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
</dependencies>
</module>
@@ -52,6 +52,11 @@
<source>src/main/providers/README.txt</source>
<outputDirectory>standalone/configuration/providers</outputDirectory>
</file>
<file>
<source>../../../feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh</source>
<outputDirectory>bin</outputDirectory>
<destName>add-user-keycloak.sh</destName>
</file>
</files>
</assembly>
@@ -15,6 +15,7 @@
<includes>
<include>com/google/zxing/**</include>
<include>org/freemarker/**</include>
<include>org/jboss/aesh/**</include>
<include>org/keycloak/**</include>
<include>org/liquibase/**</include>
<include>org/mongodb/**</include>
@@ -71,6 +72,11 @@
<source>${project.build.directory}/unpacked/keycloak-${project.version}/standalone/configuration/keycloak-server.json</source>
<outputDirectory>standalone/configuration</outputDirectory>
</file>
<file>
<source>${project.build.directory}/unpacked/keycloak-${project.version}/bin/add-user.sh</source>
<outputDirectory>bin</outputDirectory>
<destName>add-user-keycloak.sh</destName>
</file>
</files>
</assembly>
@@ -14,10 +14,10 @@
the Keycloak download site. They are also available as a maven artifact.
</para>
<para>
Install on Wildfly 9:
Install on Wildfly 9 or 10:
<programlisting>
$ cd $WILDFLY_HOME
$ unzip keycloak-wf9-adapter-dist.zip
$ unzip keycloak-wildfly-adapter-dist.zip
</programlisting>
</para>
<para>
@@ -42,6 +42,12 @@
initial login. The value of this configuration option should be however long you feel comfortable with the
application not knowing if the user's permissions have changed. This value is usually in minutes.
</para>
<para>
The <code class="literal">Access Token Lifespan For Implicit Flow</code> is how long an access token is valid for when using OpenID Connect implicit flow.
With implicit flow, there is no refresh token available, so that's why the lifespan is usually bigger than default Access Token Lifespan mentioned above.
See <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth">OpenID Connect specification</ulink> for details about implicit flow and
<link linkend="javascript-adapter">Javascript Adapter</link> for some additional details.
</para>
<para>
The <literal>Client login timeout</literal> is how long an access code is valid for. An access code is obtained
on the 1st leg of the OAuth 2.0 redirection protocol. This should be a short time limit. Usually seconds.
@@ -3,7 +3,7 @@
<para>
To be able to secure WAR apps deployed on JBoss EAP 6.x or Wildfly, you must install and
configure the Keycloak SAML Adapter Subsystem. You then provide a keycloak
config, <literal>/WEB-INF/keycloak-saml</literal> file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml.
config, <literal>/WEB-INF/keycloak-saml.xml</literal> file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml.
Both methods are described in this section.
</para>
<section id="jboss-adapter-installation">
@@ -13,10 +13,10 @@
the Keycloak download site. They are also available as a maven artifact.
</para>
<para>
Install on Wildfly 9:
Install on Wildfly 9 or 10:
<programlisting>
$ cd $WILDFLY_HOME
$ unzip keycloak-saml-wf9-adapter-dist.zip
$ unzip keycloak-saml-wildfly-adapter-dist.zip
</programlisting>
</para>
<para>
@@ -38,7 +38,7 @@ $ unzip keycloak-saml-eap6-adapter-dist.zip
from the server's bin directory:
<programlisting>
$ cd $JBOSS_HOME/bin
$ jboss-cli.sh -c --file=adapter-install.cli
$ jboss-cli.sh -c --file=adapter-install-saml.cli
</programlisting>
The script will add the extension, subsystem, and optional security-domain as described below.
</para>
@@ -52,7 +52,7 @@ $ jboss-cli.sh -c --file=adapter-install.cli
</extensions>
<profile>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.6"/>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1"/>
...
</profile>
]]>
@@ -185,4 +185,92 @@ public class CustomerService {
</programlisting>
</para>
</section>
<section>
<title>Securing WARs via Keycloak SAML Subsystem</title>
<para>
You do not have to crack open a WAR to secure it with Keycloak. Alternatively, you can externally secure
it via the Keycloak SAML Adapter Subsystem. While you don't have to specify KEYCLOAK-SAML as an <literal>auth-method</literal>,
you still have to define the <literal>security-constraints</literal> in <literal>web.xml</literal>. You do
not, however, have to create a <literal>WEB-INF/keycloak-saml.xml</literal> file. This metadata is instead defined
within XML in your server's <literal>domain.xml</literal> or <literal>standalone.xml</literal> subsystem
configuration section.
</para>
<para>
<programlisting><![CDATA[
<extensions>
<extension module="org.keycloak.keycloak-saml-adapter-subsystem"/>
</extensions>
<profile>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
<secure-deployment name="WAR MODULE NAME.war">
<SP entityID="APPLICATION URL">
...
</SP>
</secure-deployment>
</subsystem>
</profile>
]]>
</programlisting>
</para>
<para>
The <literal>secure-deployment</literal> <literal>name</literal> attribute identifies the WAR you want
to secure. Its value is the <literal>module-name</literal> defined in <literal>web.xml</literal> with
<literal>.war</literal> appended. The rest of the configuration uses the same XML syntax as
<literal>keycloak-saml.xml</literal> configuration defined in <link linkend='adapter-config'>general adapter configuration</link>.
</para>
<para>
An example configuration:
</para>
<para>
<programlisting><![CDATA[
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
<secure-deployment name="saml-post-encryption.war">
<SP entityID="http://localhost:8080/sales-post-enc/"
sslPolicy="EXTERNAL"
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
logoutPage="/logout.jsp"
forceAuthentication="false">
<Keys>
<Key signing="true" encryption="true">
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
<Certificate alias="http://localhost:8080/sales-post-enc/"/>
</KeyStore>
</Key>
</Keys>
<PrincipalNameMapping policy="FROM_NAME_ID"/>
<RoleIdentifiers>
<Attribute name="Role"/>
</RoleIdentifiers>
<IDP entityID="idp">
<SingleSignOnService signRequest="true"
validateResponseSignature="true"
requestBinding="POST"
bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
<SingleLogoutService
validateRequestSignature="true"
validateResponseSignature="true"
signRequest="true"
signResponse="true"
requestBinding="POST"
responseBinding="POST"
postBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"
redirectBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
<Keys>
<Key signing="true" >
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
<Certificate alias="saml-demo"/>
</KeyStore>
</Key>
</Keys>
</IDP>
</SP>
</secure-deployment>
</subsystem>
]]>
</programlisting>
</para>
</section>
</chapter>
@@ -11,6 +11,7 @@ public interface Details {
String CODE_ID = "code_id";
String REDIRECT_URI = "redirect_uri";
String RESPONSE_TYPE = "response_type";
String RESPONSE_MODE = "response_mode";
String AUTH_TYPE = "auth_type";
String AUTH_METHOD = "auth_method";
String IDENTITY_PROVIDER = "identity_provider";
@@ -23,6 +23,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization;
@@ -49,40 +50,44 @@ public class OfflineAccessPortalServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
if (req.getRequestURI().endsWith("/login")) {
storeToken(req);
req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp);
return;
}
if (req.getRequestURI().endsWith("/login")) {
storeToken(req);
req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp);
return;
String refreshToken = RefreshTokenDAO.loadToken();
String refreshTokenInfo;
boolean savedTokenAvailable;
if (refreshToken == null) {
refreshTokenInfo = "No token saved in database. Please login first";
savedTokenAvailable = false;
} else {
RefreshToken refreshTokenDecoded = null;
refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
savedTokenAvailable = true;
}
req.setAttribute("tokenInfo", refreshTokenInfo);
req.setAttribute("savedTokenAvailable", savedTokenAvailable);
String customers;
if (req.getRequestURI().endsWith("/loadCustomers")) {
customers = loadCustomers(req, refreshToken);
} else {
customers = "";
}
req.setAttribute("customers", customers);
req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
} catch (JWSInputException e) {
throw new ServletException(e);
}
String refreshToken = RefreshTokenDAO.loadToken();
String refreshTokenInfo;
boolean savedTokenAvailable;
if (refreshToken == null) {
refreshTokenInfo = "No token saved in database. Please login first";
savedTokenAvailable = false;
} else {
RefreshToken refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
savedTokenAvailable = true;
}
req.setAttribute("tokenInfo", refreshTokenInfo);
req.setAttribute("savedTokenAvailable", savedTokenAvailable);
String customers;
if (req.getRequestURI().endsWith("/loadCustomers")) {
customers = loadCustomers(req, refreshToken);
} else {
customers = "";
}
req.setAttribute("customers", customers);
req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
}
private void storeToken(HttpServletRequest req) throws IOException {
private void storeToken(HttpServletRequest req) throws IOException, JWSInputException {
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
String refreshToken = ctx.getRefreshToken();
+16 -3
View File
@@ -67,8 +67,11 @@
var o = 'Token Expires:\t\t' + new Date((keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds\n';
o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
if (keycloak.refreshTokenParsed) {
o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
}
output(o);
}
@@ -106,7 +109,17 @@
event('Auth Logout');
};
keycloak.init().success(function(authenticated) {
keycloak.onTokenExpired = function () {
event('Access token expired.');
};
// Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too
var initOptions = {
responseMode: 'fragment',
flow: 'standard'
};
keycloak.init(initOptions).success(function(authenticated) {
output('Init Success (' + (authenticated ? 'Authenticated' : 'Not Authenticated') + ')');
}).error(function() {
output('Init Error');
@@ -49,7 +49,6 @@
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
<div class="">
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
</div>
</div>
</div>
@@ -80,6 +80,8 @@ offline-session-idle=Offline Session Idle
offline-session-idle.tooltip=Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire.
access-token-lifespan=Access Token Lifespan
access-token-lifespan.tooltip=Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
access-token-lifespan-for-implicit-flow=Access Token Lifespan For Implicit Flow
access-token-lifespan-for-implicit-flow.tooltip=Max time before an access token issued during OpenID Connect Implicit Flow is expired. This value is recommended to be shorter than SSO timeout. There is no possibility to refresh token during implicit flow, that's why there is separate timeout different to 'Access Token Lifespan'.
client-login-timeout=Client login timeout
client-login-timeout.tooltip=Max time an client has to finish the access token protocol. This should normally be 1 minute.
login-timeout=Login timeout
@@ -169,14 +171,18 @@ client.name.tooltip=Specifies display name of the client. For example 'My Client
client.enabled.tooltip=Disabled clients cannot initiate a login or have obtain access tokens.
consent-required=Consent Required
consent-required.tooltip=If enabled users have to consent to client access.
direct-grants-only=Direct Grants Only
direct-grants-only.tooltip=When enabled, client can only obtain grants from grant REST API.
client-protocol=Client Protocol
client-protocol.tooltip='OpenID connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.
access-type=Access Type
access-type.tooltip='Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.
standard-flow-enabled=Standard Flow Enabled
standard-flow-enabled.tooltip=This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.
implicit-flow-enabled=Implicit Flow Enabled
implicit-flow-enabled.tooltip=This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client.
direct-access-grants-enabled=Direct Access Grants Enabled
direct-access-grants-enabled.tooltip=This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client.
service-accounts-enabled=Service Accounts Enabled
service-accounts-enabled.tooltip=Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.
service-accounts-enabled.tooltip=Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client. In terms of OAuth2 specification, this enables support of 'Client Credentials Grant' for this client.
include-authnstatement=Include AuthnStatement
include-authnstatement.tooltip=Should a statement specifying the method and timestamp be included in login responses?
sign-documents=Sign Documents
@@ -799,13 +799,75 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
} else if ($scope.client.attributes['saml_name_id_format'] == 'persistent') {
$scope.nameIdFormat = $scope.nameIdFormats[3];
}
if ($scope.client.attributes["saml.server.signature"]) {
if ($scope.client.attributes["saml.server.signature"] == "true") {
$scope.samlServerSignature = true;
} else {
$scope.samlServerSignature = false;
}
}
if ($scope.client.attributes["saml.assertion.signature"]) {
if ($scope.client.attributes["saml.assertion.signature"] == "true") {
$scope.samlAssertionSignature = true;
} else {
$scope.samlAssertionSignature = false;
}
}
if ($scope.client.attributes["saml.client.signature"]) {
if ($scope.client.attributes["saml.client.signature"] == "true") {
$scope.samlClientSignature = true;
} else {
$scope.samlClientSignature = false;
}
}
if ($scope.client.attributes["saml.encrypt"]) {
if ($scope.client.attributes["saml.encrypt"] == "true") {
$scope.samlEncrypt = true;
} else {
$scope.samlEncrypt = false;
}
}
if ($scope.client.attributes["saml.authnstatement"]) {
if ($scope.client.attributes["saml.authnstatement"] == "true") {
$scope.samlAuthnStatement = true;
} else {
$scope.samlAuthnStatement = false;
}
}
if ($scope.client.attributes["saml_force_name_id_format"]) {
if ($scope.client.attributes["saml_force_name_id_format"] == "true") {
$scope.samlForceNameIdFormat = true;
} else {
$scope.samlForceNameIdFormat = false;
}
}
if ($scope.client.attributes["saml.multivalued.roles"]) {
if ($scope.client.attributes["saml.multivalued.roles"] == "true") {
$scope.samlMultiValuedRoles = true;
} else {
$scope.samlMultiValuedRoles = false;
}
}
if ($scope.client.attributes["saml.force.post.binding"]) {
if ($scope.client.attributes["saml.force.post.binding"] == "true") {
$scope.samlForcePostBinding = true;
} else {
$scope.samlForcePostBinding = false;
}
}
}
if (!$scope.create) {
$scope.client = angular.copy(client);
updateProperties();
} else {
$scope.client = { enabled: true, attributes: {}};
$scope.client = {
enabled: true,
standardFlowEnabled: true,
directAccessGrantsEnabled: true,
attributes: {}
};
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
$scope.client.redirectUris = [];
$scope.accessType = $scope.accessTypes[0];
@@ -816,63 +878,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
$scope.samlForceNameIdFormat = false;
}
if ($scope.client.attributes["saml.server.signature"]) {
if ($scope.client.attributes["saml.server.signature"] == "true") {
$scope.samlServerSignature = true;
} else {
$scope.samlServerSignature = false;
}
}
if ($scope.client.attributes["saml.assertion.signature"]) {
if ($scope.client.attributes["saml.assertion.signature"] == "true") {
$scope.samlAssertionSignature = true;
} else {
$scope.samlAssertionSignature = false;
}
}
if ($scope.client.attributes["saml.client.signature"]) {
if ($scope.client.attributes["saml.client.signature"] == "true") {
$scope.samlClientSignature = true;
} else {
$scope.samlClientSignature = false;
}
}
if ($scope.client.attributes["saml.encrypt"]) {
if ($scope.client.attributes["saml.encrypt"] == "true") {
$scope.samlEncrypt = true;
} else {
$scope.samlEncrypt = false;
}
}
if ($scope.client.attributes["saml.authnstatement"]) {
if ($scope.client.attributes["saml.authnstatement"] == "true") {
$scope.samlAuthnStatement = true;
} else {
$scope.samlAuthnStatement = false;
}
}
if ($scope.client.attributes["saml_force_name_id_format"]) {
if ($scope.client.attributes["saml_force_name_id_format"] == "true") {
$scope.samlForceNameIdFormat = true;
} else {
$scope.samlForceNameIdFormat = false;
}
}
if ($scope.client.attributes["saml.multivalued.roles"]) {
if ($scope.client.attributes["saml.multivalued.roles"] == "true") {
$scope.samlMultiValuedRoles = true;
} else {
$scope.samlMultiValuedRoles = false;
}
}
if ($scope.client.attributes["saml.force.post.binding"]) {
if ($scope.client.attributes["saml.force.post.binding"] == "true") {
$scope.samlForcePostBinding = true;
} else {
$scope.samlForcePostBinding = false;
}
}
$scope.importFile = function(fileContent){
console.debug(fileContent);
@@ -1039,7 +1044,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
$scope.client.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm;
$scope.client.attributes['saml_name_id_format'] = $scope.nameIdFormat;
if ($scope.client.protocol != 'saml' && !$scope.client.bearerOnly && !$scope.client.directGrantsOnly && (!$scope.client.redirectUris || $scope.client.redirectUris.length == 0)) {
if ($scope.client.protocol != 'saml' && !$scope.client.bearerOnly && ($scope.client.standardFlowEnabled || $scope.client.implicitFlowEnabled) && (!$scope.client.redirectUris || $scope.client.redirectUris.length == 0)) {
Notifications.error("You must specify at least one redirect uri");
} else {
if ($scope.create) {
@@ -899,6 +899,12 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.realm.accessTokenLifespan = TimeUnit.convert($scope.realm.accessTokenLifespan, from, to);
});
$scope.realm.accessTokenLifespanForImplicitFlowUnit = TimeUnit.autoUnit(realm.accessTokenLifespanForImplicitFlow);
$scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit.toUnit(realm.accessTokenLifespanForImplicitFlow, $scope.realm.accessTokenLifespanForImplicitFlowUnit);
$scope.$watch('realm.accessTokenLifespanForImplicitFlowUnit', function(to, from) {
$scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit.convert($scope.realm.accessTokenLifespanForImplicitFlow, from, to);
});
$scope.realm.ssoSessionIdleTimeoutUnit = TimeUnit.autoUnit(realm.ssoSessionIdleTimeout);
$scope.realm.ssoSessionIdleTimeout = TimeUnit.toUnit(realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit);
$scope.$watch('realm.ssoSessionIdleTimeoutUnit', function(to, from) {
@@ -947,6 +953,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.save = function() {
var realmCopy = angular.copy($scope.realm);
delete realmCopy["accessTokenLifespanUnit"];
delete realmCopy["accessTokenLifespanForImplicitFlowUnit"];
delete realmCopy["ssoSessionMaxLifespanUnit"];
delete realmCopy["offlineSessionIdleTimeoutUnit"];
delete realmCopy["accessCodeLifespanUnit"];
@@ -955,6 +962,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
delete realmCopy["accessCodeLifespanLoginUnit"];
realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
realmCopy.accessTokenLifespanForImplicitFlow = TimeUnit.toSeconds($scope.realm.accessTokenLifespanForImplicitFlow, $scope.realm.accessTokenLifespanForImplicitFlowUnit)
realmCopy.ssoSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit)
realmCopy.ssoSessionMaxLifespan = TimeUnit.toSeconds($scope.realm.ssoSessionMaxLifespan, $scope.realm.ssoSessionMaxLifespanUnit)
realmCopy.offlineSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.offlineSessionIdleTimeout, $scope.realm.offlineSessionIdleTimeoutUnit)
@@ -59,13 +59,6 @@
</div>
<kc-tooltip>{{:: 'consent-required.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="directGrantsOnly">{{:: 'direct-grants-only' | translate}}</label>
<div class="col-sm-6">
<input ng-model="client.directGrantsOnly" name="directGrantsOnly" id="directGrantsOnly" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
<kc-tooltip>{{:: 'direct-grants-only.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="protocol">{{:: 'client-protocol' | translate}}</label>
<div class="col-sm-6">
@@ -92,6 +85,27 @@
</div>
<kc-tooltip>{{:: 'access-type.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.bearerOnly">
<label class="col-md-2 control-label" for="standardFlowEnabled">{{:: 'standard-flow-enabled' | translate}}</label>
<kc-tooltip>{{:: 'standard-flow-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="client.standardFlowEnabled" name="standardFlowEnabled" id="standardFlowEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && client.publicClient && !client.bearerOnly">
<label class="col-md-2 control-label" for="implicitFlowEnabled">{{:: 'implicit-flow-enabled' | translate}}</label>
<kc-tooltip>{{:: 'implicit-flow-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="client.implicitFlowEnabled" name="implicitFlowEnabled" id="implicitFlowEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.bearerOnly">
<label class="col-md-2 control-label" for="directAccessGrantsEnabled">{{:: 'direct-access-grants-enabled' | translate}}</label>
<kc-tooltip>{{:: 'direct-access-grants-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="client.directAccessGrantsEnabled" name="directAccessGrantsEnabled" id="directAccessGrantsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.publicClient && !client.bearerOnly">
<label class="col-md-2 control-label" for="serviceAccountsEnabled">{{:: 'service-accounts-enabled' | translate}}</label>
<kc-tooltip>{{:: 'service-accounts-enabled.tooltip' | translate}}</kc-tooltip>
@@ -202,7 +216,7 @@
<kc-tooltip>{{:: 'root-url.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block" data-ng-hide="client.bearerOnly || client.directGrantsOnly">
<div class="form-group clearfix block" data-ng-hide="client.bearerOnly || (!client.standardFlowEnabled && !client.implicitFlowEnabled)">
<label class="col-md-2 control-label" for="newRedirectUri"><span class="required" data-ng-show="protocol != 'saml'">*</span> {{:: 'valid-redirect-uris' | translate}}</label>
<div class="col-sm-6">
@@ -15,7 +15,7 @@
ng-options="r.name for r in realmRoles | orderBy:'toString()'">
<option style="display:none" value="">Select a role</option>
</select>
<button class="btn btn-default" type="submit" ng-click="selectRealmRole()" tooltip-trigger="mouseover mouseout" tooltip="Select realm role" tooltip-placement="right">
<button class="btn btn-default" type="submit" data-ng-disabled="!selectedRealmRole.role" ng-click="selectRealmRole()" tooltip-trigger="mouseover mouseout" tooltip="Select realm role" tooltip-placement="right">
Select Realm Role</i>
</button>
</div>
@@ -32,7 +32,7 @@
ng-options="r.name for r in clientRoles | orderBy:'toString()'">
<option style="display:none" value="">Select a role</option>
</select>
<button class="btn btn-default" type="submit" ng-click="selectClientRole()" tooltip-trigger="mouseover mouseout" tooltip="Select client role" tooltip-placement="right">
<button class="btn btn-default" type="submit" data-ng-disabled="!selectedClientRole.role" ng-click="selectClientRole()" tooltip-trigger="mouseover mouseout" tooltip="Select client role" tooltip-placement="right">
Select Client Role
</button>
</div>
@@ -31,7 +31,7 @@
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="name">{{:: 'name' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create">
<input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create" required>
</div>
<kc-tooltip>{{:: 'mapper.name.tooltip' | translate}}</kc-tooltip>
</div>
@@ -56,7 +56,8 @@
<div>
<select class="form-control" id="mapperTypeCreate"
ng-model="mapperType"
ng-options="mapperType.name for mapperType in mapperTypes">
ng-options="mapperType.name for mapperType in mapperTypes"
required>
</select>
</div>
</div>
@@ -82,6 +82,23 @@
<kc-tooltip>{{:: 'access-token-lifespan.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="accessTokenLifespanForImplicitFlow">{{:: 'access-token-lifespan-for-implicit-flow' | translate}}</label>
<div class="col-md-6 time-selector">
<input class="form-control" type="number" required min="1"
max="31536000" data-ng-model="realm.accessTokenLifespanForImplicitFlow"
id="accessTokenLifespanForImplicitFlow" name="accessTokenLifespanForImplicitFlow"/>
<select class="form-control" name="accessTokenLifespanForImplicitFlowUnit" data-ng-model="realm.accessTokenLifespanForImplicitFlowUnit">
<option data-ng-selected="!realm.accessTokenLifespanForImplicitFlowUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
<option value="Minutes">{{:: 'minutes' | translate}}</option>
<option value="Hours">{{:: 'hours' | translate}}</option>
<option value="Days">{{:: 'days' | translate}}</option>
</select>
</div>
<kc-tooltip>{{:: 'access-token-lifespan-for-implicit-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="accessCodeLifespan">{{:: 'client-login-timeout' | translate}}</label>
@@ -176,7 +176,8 @@ failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
directGrantsOnlyMessage=Direct-grants-only clients are not allowed to initiate browser login
standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
invalidRedirectUriMessage=Invalid redirect uri
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
invlidRequesterMessage=Invalid requester
@@ -5,6 +5,7 @@
.kc-dropdown{
position: relative;
z-index: 9999;
}
.kc-dropdown > a{
position: absolute;
@@ -9,6 +9,7 @@ import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder;
@@ -58,10 +59,10 @@ public class CookieTokenStore {
AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
IDToken idToken;
if (idTokenString != null && idTokenString.length() > 0) {
JWSInput input = new JWSInput(idTokenString);
try {
JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) {
} catch (JWSInputException e) {
throw new VerificationException(e);
}
} else {
@@ -11,6 +11,7 @@ import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.enums.TokenStore;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@@ -313,10 +314,10 @@ public class OAuthRequestAuthenticator {
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) {
JWSInput input = new JWSInput(idTokenString);
try {
JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) {
} catch (JWSInputException e) {
throw new VerificationException();
}
}
@@ -3,6 +3,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput;
@@ -178,18 +179,17 @@ public class PreAuthActionsHandler {
return null;
}
JWSInput input = new JWSInput(token);
boolean verified = false;
try {
verified = RSAProvider.verify(input, deployment.getRealmKey());
} catch (Exception ignore) {
JWSInput input = new JWSInput(token);
if (RSAProvider.verify(input, deployment.getRealmKey())) {
return input;
}
} catch (JWSInputException ignore) {
}
if (!verified) {
log.warn("admin request failed, unable to verify token");
facade.getResponse().sendError(403, "no token");
return null;
}
return input;
log.warn("admin request failed, unable to verify token");
facade.getResponse().sendError(403, "no token");
return null;
}
@@ -8,6 +8,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@@ -195,10 +196,10 @@ public class KeycloakInstalled {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) {
JWSInput input = new JWSInput(idTokenString);
try {
JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) {
} catch (JWSInputException e) {
throw new VerificationException();
}
}
+200 -65
View File
@@ -36,6 +36,39 @@
if (initOptions.onLoad === 'login-required') {
kc.loginRequired = true;
}
if (initOptions.responseMode) {
if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') {
kc.responseMode = initOptions.responseMode;
} else {
throw 'Invalid value for responseMode';
}
}
if (initOptions.flow) {
switch (initOptions.flow) {
case 'standard':
kc.responseType = 'code';
break;
case 'implicit':
kc.responseType = 'id_token token';
break;
case 'hybrid':
kc.responseType = 'code id_token token';
break;
default:
throw 'Invalid value for flow';
}
kc.flow = initOptions.flow;
}
}
if (!kc.responseMode) {
kc.responseMode = 'fragment';
}
if (!kc.responseType) {
kc.responseType = 'code';
kc.flow = 'standard';
}
var promise = createPromise();
@@ -95,7 +128,7 @@
return;
} else if (initOptions) {
if (initOptions.token || initOptions.refreshToken) {
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
if (loginIframe.enable) {
setupCheckLoginIframe().success(function() {
@@ -132,13 +165,14 @@
kc.createLoginUrl = function(options) {
var state = createUUID();
var nonce = createUUID();
var redirectUri = adapter.redirectUri(options);
if (options && options.prompt) {
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
}
sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) });
sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
var action = 'auth';
if (options && options.action == 'register') {
@@ -150,7 +184,9 @@
+ '?client_id=' + encodeURIComponent(kc.clientId)
+ '&redirect_uri=' + encodeURIComponent(redirectUri)
+ '&state=' + encodeURIComponent(state)
+ '&response_type=code';
+ '&nonce=' + encodeURIComponent(nonce)
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
+ '&response_type=' + encodeURIComponent(kc.responseType);
if (options && options.prompt) {
url += '&prompt=' + encodeURIComponent(options.prompt);
@@ -277,7 +313,7 @@
}
kc.isTokenExpired = function(minValidity) {
if (!kc.tokenParsed || !kc.refreshToken) {
if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) {
throw 'Not authenticated';
}
@@ -327,7 +363,7 @@
timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
@@ -365,7 +401,7 @@
kc.clearToken = function() {
if (kc.token) {
setToken(null, null, null);
setToken(null, null, null, true);
kc.onAuthLogout && kc.onAuthLogout();
if (kc.loginRequired) {
kc.login();
@@ -394,7 +430,21 @@
var error = oauth.error;
var prompt = oauth.prompt;
if (code) {
var timeLocal = new Date().getTime();
if (error) {
if (prompt != 'none') {
kc.onAuthError && kc.onAuthError();
promise && promise.setError();
} else {
promise && promise.setSuccess();
}
return;
} else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) {
authSuccess(oauth.access_token, null, oauth.id_token, true);
}
if ((kc.flow != 'implicit') && code) {
var params = 'code=' + code + '&grant_type=authorization_code';
var url = getRealmUrl() + '/protocol/openid-connect/token';
@@ -412,20 +462,12 @@
req.withCredentials = true;
var timeLocal = new Date().getTime();
req.onreadystatechange = function() {
if (req.readyState == 4) {
if (req.status == 200) {
timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess();
authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard');
} else {
kc.onAuthError && kc.onAuthError();
promise && promise.setError();
@@ -434,14 +476,30 @@
};
req.send(params);
} else if (error) {
if (prompt != 'none') {
kc.onAuthError && kc.onAuthError();
}
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
timeLocal = (timeLocal + new Date().getTime()) / 2;
setToken(accessToken, refreshToken, idToken, true);
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
console.log('invalid nonce!');
kc.clearToken();
promise && promise.setError();
} else {
promise && promise.setSuccess();
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
if (fulfillPromise) {
kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess();
}
}
}
}
function loadConfig(url) {
@@ -507,7 +565,12 @@
return promise.promise;
}
function setToken(token, refreshToken, idToken) {
function setToken(token, refreshToken, idToken, useTokenTime) {
if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle);
kc.tokenTimeoutHandle = null;
}
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
@@ -520,6 +583,13 @@
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
if (kc.onTokenExpired) {
var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
var expiresIn = kc.tokenParsed.exp - start;
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
}
} else {
delete kc.token;
delete kc.tokenParsed;
@@ -597,53 +667,21 @@
}
function parseCallback(url) {
if (url.indexOf('?') != -1) {
var oauth = {};
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
oauth.newUrl = url.split('?')[0];
var paramString = url.split('?')[1];
var fragIndex = paramString.indexOf('#');
if (fragIndex != -1) {
paramString = paramString.substring(0, fragIndex);
}
var params = paramString.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
switch (decodeURIComponent(p[0])) {
case 'code':
oauth.code = p[1];
break;
case 'error':
oauth.error = p[1];
break;
case 'state':
oauth.state = decodeURIComponent(p[1]);
break;
case 'redirect_fragment':
oauth.fragment = decodeURIComponent(p[1]);
break;
case 'prompt':
oauth.prompt = p[1];
break;
default:
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1];
break;
}
var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
delete sessionStorage.oauthState;
oauth.redirectUri = sessionState.redirectUri;
oauth.storedNonce = sessionState.nonce;
if (oauth.fragment) {
oauth.newUrl += '#' + oauth.fragment;
}
var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) {
delete sessionStorage.oauthState;
oauth.redirectUri = sessionState.redirectUri;
if (oauth.fragment) {
oauth.newUrl += '#' + oauth.fragment;
}
return oauth;
}
return oauth;
}
}
@@ -907,6 +945,103 @@
throw 'invalid adapter type: ' + type;
}
var CallbackParser = function(uriToParse, responseMode) {
if (!(this instanceof CallbackParser)) {
return new CallbackParser(uriToParse, responseMode);
}
var parser = this;
var initialParse = function() {
var baseUri = null;
var queryString = null;
var fragmentString = null;
var questionMarkIndex = uriToParse.indexOf("?");
var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1);
if (questionMarkIndex == -1 && fragmentIndex == -1) {
baseUri = uriToParse;
} else if (questionMarkIndex != -1) {
baseUri = uriToParse.substring(0, questionMarkIndex);
queryString = uriToParse.substring(questionMarkIndex + 1);
if (fragmentIndex != -1) {
fragmentIndex = queryString.indexOf("#");
fragmentString = queryString.substring(fragmentIndex + 1);
queryString = queryString.substring(0, fragmentIndex);
}
} else {
baseUri = uriToParse.substring(0, fragmentIndex);
fragmentString = uriToParse.substring(fragmentIndex + 1);
}
return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString };
}
var parseParams = function(paramString) {
var result = {};
var params = paramString.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
var paramName = decodeURIComponent(p[0]);
var paramValue = decodeURIComponent(p[1]);
result[paramName] = paramValue;
}
return result;
}
var handleQueryParam = function(paramName, paramValue, oauth) {
var supportedOAuthParams = [ 'code', 'error', 'state' ];
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
if (paramName === supportedOAuthParams[i]) {
oauth[paramName] = paramValue;
return true;
}
}
return false;
}
parser.parseUri = function() {
var parsedUri = initialParse();
var queryParams = {};
if (parsedUri.queryString) {
queryParams = parseParams(parsedUri.queryString);
}
var oauth = { newUrl: parsedUri.baseUri };
for (var param in queryParams) {
switch (param) {
case 'redirect_fragment':
oauth.fragment = queryParams[param];
break;
case 'prompt':
oauth.prompt = queryParams[param];
break;
default:
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
}
break;
}
}
if (responseMode === 'fragment') {
var fragmentParams = {};
if (parsedUri.fragmentString) {
fragmentParams = parseParams(parsedUri.fragmentString);
}
for (var param in fragmentParams) {
oauth[param] = fragmentParams[param];
}
}
return oauth;
}
}
}
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
@@ -9,6 +9,7 @@ import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder;
@@ -153,10 +154,10 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
public static IDToken extractIdToken(String idToken) {
if (idToken == null) return null;
JWSInput input = new JWSInput(idToken);
try {
JWSInput input = new JWSInput(idToken);
return input.readJsonContent(IDToken.class);
} catch (IOException e) {
} catch (JWSInputException e) {
throw new RuntimeException(e);
}
}
+1 -1
View File
@@ -16,6 +16,6 @@
<modules>
<module>wildfly-adapter</module>
<module>wf8-subsystem</module>
<module>wf9-subsystem</module>
<module>wildfly-subsystem</module>
</modules>
</project>
@@ -24,8 +24,8 @@
<relativePath>../../../pom.xml</relativePath>
</parent>
<artifactId>keycloak-wf9-subsystem</artifactId>
<name>Keycloak Wildfly 9 Adapter Subsystem</name>
<artifactId>keycloak-wildfly-subsystem</artifactId>
<name>Keycloak Wildfly Adapter Subsystem</name>
<description/>
<packaging>jar</packaging>
Loaded 100 of 273 files, more files were not shown because too many files have changed in this diff. Show more