reworked id tokens, added SWD endpoint shell, started checkid endpoint shell (still needs signer service of some kind)

pull/59/head
Justin Richer 2012-01-25 16:58:01 -05:00
parent a81dd8d5a4
commit 505ec3d39f
14 changed files with 292 additions and 33 deletions

View File

@ -0,0 +1,5 @@
#Wed Jan 25 13:44:10 EST 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -1,4 +1,4 @@
#Wed Jan 04 13:38:09 EST 2012 #Wed Jan 25 13:44:09 EST 2012
activeProfiles= activeProfiles=
eclipse.preferences.version=1 eclipse.preferences.version=1
fullBuildGoals=process-test-resources fullBuildGoals=process-test-resources

View File

@ -1,3 +1,3 @@
#Wed Jan 04 13:45:00 EST 2012 #Wed Jan 25 13:44:10 EST 2012
com.springsource.sts.maven.maven.automatically.update=true com.springsource.sts.maven.maven.automatically.update=true
eclipse.preferences.version=1 eclipse.preferences.version=1

View File

@ -0,0 +1,5 @@
#Wed Jan 25 13:44:10 EST 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -3,8 +3,8 @@
<wb-module deploy-name="openid"> <wb-module deploy-name="openid">
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/> <wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<dependent-module archiveName="spring-security-oauth2-1.0.0.BUILD-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/spring-security-oauth2/spring-security-oauth2"> <dependent-module archiveName="spring-security-oauth2-1.0.0.BUILD-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/spring-security-oauth2/spring-security-oauth2">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>

View File

@ -2,11 +2,9 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-server</artifactId> <artifactId>openid-connect-server</artifactId>
<name>OpenIdConnect Server</name> <name>OpenIdConnect Server</name>
<packaging>war</packaging> <packaging>war</packaging>
<version>0.1</version>
<properties> <properties>
<java-version>1.6</java-version> <java-version>1.6</java-version>
<org.springframework-version>3.1.0.RELEASE</org.springframework-version> <org.springframework-version>3.1.0.RELEASE</org.springframework-version>
@ -392,7 +390,7 @@
</goals> </goals>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId> <groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>maven-replacer-plugin</artifactId> <artifactId>maven-replacer-plugin</artifactId>

View File

@ -1,11 +1,17 @@
package org.mitre.jwt.model; package org.mitre.jwt.model;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.codec.binary.Base64;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
/** /**
@ -20,7 +26,19 @@ public class ClaimSet {
// the LinkedHashMap preserves insertion order // the LinkedHashMap preserves insertion order
private Map<String, Object> claims = new LinkedHashMap<String, Object>(); private Map<String, Object> claims = new LinkedHashMap<String, Object>();
/** public ClaimSet() {
}
public ClaimSet(JsonObject json) {
loadFromJsonObject(json);
}
public ClaimSet(String b64) {
loadFromBase64JsonObjectString(b64);
}
/**
* Get an extension claim * Get an extension claim
*/ */
public Object getClaim(String key) { public Object getClaim(String key) {
@ -86,6 +104,14 @@ public class ClaimSet {
/** /**
* Clear all claims from this ClaimSet
* @see java.util.Map#clear()
*/
public void clear() {
claims.clear();
}
/**
* Get a copy of this claim set as a JsonObject. The JsonObject is not * Get a copy of this claim set as a JsonObject. The JsonObject is not
* backed by a live copy of this ClaimSet. * backed by a live copy of this ClaimSet.
* @return a copy of the data in this header in a JsonObject * @return a copy of the data in this header in a JsonObject
@ -128,5 +154,32 @@ public class ClaimSet {
return o; return o;
} }
/**
* Load new claims from the given json object. Will replace any existing claims, but does not clear claim set.
* @param json
*/
public void loadFromJsonObject(JsonObject json) {
for (Entry<String, JsonElement> element : json.entrySet()) {
if (element.getValue().isJsonPrimitive()){
// we handle all primitives in here
JsonPrimitive prim = element.getValue().getAsJsonPrimitive();
setClaim(element.getKey(), prim);
} else {
setClaim(element.getKey(), element.getValue());
}
}
}
/**
* Load a new claims set from a Base64 encoded JSON Object string
*/
public void loadFromBase64JsonObjectString(String b64) {
byte[] b64decoded = Base64.decodeBase64(b64);
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(new InputStreamReader(new ByteArrayInputStream(b64decoded))).getAsJsonObject();
loadFromJsonObject(json);
}
} }

View File

@ -139,12 +139,8 @@ public class Jwt {
String c64 = parts.get(1); String c64 = parts.get(1);
String s64 = parts.get(2); String s64 = parts.get(2);
JsonParser parser = new JsonParser();
JsonObject hjo = parser.parse(new InputStreamReader(new ByteArrayInputStream(Base64.decodeBase64(h64)))).getAsJsonObject();
JsonObject cjo = parser.parse(new InputStreamReader(new ByteArrayInputStream(Base64.decodeBase64(c64)))).getAsJsonObject();
// shuttle for return value // shuttle for return value
Jwt jwt = new Jwt(new JwtHeader(hjo), new JwtClaims(cjo), s64); Jwt jwt = new Jwt(new JwtHeader(h64), new JwtClaims(c64), s64);
// TODO: save the wire-encoded string in the Jwt object itself? // TODO: save the wire-encoded string in the Jwt object itself?

View File

@ -29,10 +29,21 @@ public class JwtClaims extends ClaimSet {
//public static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); //public static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
public JwtClaims() { public JwtClaims() {
super();
} }
public JwtClaims(JsonObject json) { public JwtClaims(JsonObject json) {
super(json);
}
public JwtClaims(String b64) {
super(b64);
}
@Override
public void loadFromJsonObject(JsonObject json) {
JsonObject pass = new JsonObject();
for (Entry<String, JsonElement> element : json.entrySet()) { for (Entry<String, JsonElement> element : json.entrySet()) {
if (element.getKey().equals(EXPIRATION)) { if (element.getKey().equals(EXPIRATION)) {
setExpiration(new Date(element.getValue().getAsLong() * 1000L)); setExpiration(new Date(element.getValue().getAsLong() * 1000L));
@ -51,15 +62,12 @@ public class JwtClaims extends ClaimSet {
} else if (element.getKey().equals(TYPE)) { } else if (element.getKey().equals(TYPE)) {
setType(element.getValue().getAsString()); setType(element.getValue().getAsString());
} else { } else {
if (element.getValue().isJsonPrimitive()){ pass.add(element.getKey(), element.getValue());
// we handle all primitives in here
JsonPrimitive prim = element.getValue().getAsJsonPrimitive();
setClaim(element.getKey(), prim);
} else {
setClaim(element.getKey(), element.getValue());
}
} }
} }
// load all the generic claims into this object
super.loadFromJsonObject(pass);
} }
/** /**

View File

@ -18,7 +18,7 @@ public class JwtHeader extends ClaimSet {
* Make an empty header * Make an empty header
*/ */
public JwtHeader() { public JwtHeader() {
super();
} }
/** /**
@ -26,7 +26,22 @@ public class JwtHeader extends ClaimSet {
* @param json * @param json
*/ */
public JwtHeader(JsonObject json) { public JwtHeader(JsonObject json) {
super(json);
}
public JwtHeader(String b64) {
super(b64);
}
/**
* Load all claims from the given json object into this object
*/
@Override
public void loadFromJsonObject(JsonObject json) {
JsonObject pass = new JsonObject();
for (Entry<String, JsonElement> element : json.entrySet()) { for (Entry<String, JsonElement> element : json.entrySet()) {
if (element.getKey().equals(TYPE)) { if (element.getKey().equals(TYPE)) {
this.setType(json.get(TYPE).getAsString()); this.setType(json.get(TYPE).getAsString());
@ -35,15 +50,12 @@ public class JwtHeader extends ClaimSet {
} else if (element.getKey().equals(ENCRYPTION_METHOD)) { } else if (element.getKey().equals(ENCRYPTION_METHOD)) {
this.setEncryptionMethod(json.get(ENCRYPTION_METHOD).getAsString()); this.setEncryptionMethod(json.get(ENCRYPTION_METHOD).getAsString());
} else { } else {
if (element.getValue().isJsonPrimitive()){ pass.add(element.getKey(), element.getValue());
// we handle all primitives in here
JsonPrimitive prim = element.getValue().getAsJsonPrimitive();
setClaim(element.getKey(), prim);
} else {
setClaim(element.getKey(), element.getValue());
}
} }
} }
// now load all the ones we didn't handly specially
super.loadFromJsonObject(pass);
} }
/** /**

View File

@ -1,5 +1,7 @@
package org.mitre.openid.connect.model; package org.mitre.openid.connect.model;
import java.util.List;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
@ -10,6 +12,11 @@ import javax.persistence.Table;
import javax.persistence.Transient; import javax.persistence.Transient;
import org.mitre.jwt.model.Jwt; import org.mitre.jwt.model.Jwt;
import org.mitre.jwt.model.JwtClaims;
import org.mitre.jwt.model.JwtHeader;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
@Entity @Entity
@Table(name="idtoken") @Table(name="idtoken")
@ -18,6 +25,24 @@ import org.mitre.jwt.model.Jwt;
}) })
public class IdToken extends Jwt { public class IdToken extends Jwt {
/**
* Create a blank IdToken
*/
public IdToken() {
super();
}
/**
* Create an IdToken from the requisite pieces.
* @param header
* @param claims
* @param signature
*/
public IdToken(JwtHeader header, JwtClaims claims, String signature) {
super(header, claims, signature);
}
private Long id; private Long id;
/** /**
@ -52,5 +77,33 @@ public class IdToken extends Jwt {
} }
/**
* Parse a wire-encoded IdToken.
*
*/
public static IdToken parse(String s) {
// TODO: this code was copied nearly verbatim from Jwt.parse, and
// we should figure out how to re-use and abstract bits, likely
// split on the dots
List<String> parts = Lists.newArrayList(Splitter.on(".").split(s));
if (parts.size() != 3) {
throw new IllegalArgumentException("Invalid JWT format.");
}
String h64 = parts.get(0);
String c64 = parts.get(1);
String s64 = parts.get(2);
// shuttle for return value
IdToken idToken = new IdToken(new JwtHeader(h64), new IdTokenClaims(c64), s64);
// TODO: save the wire-encoded string in the Jwt object itself?
return idToken;
}
} }

View File

@ -1,7 +1,9 @@
package org.mitre.openid.connect.model; package org.mitre.openid.connect.model;
import java.util.Date; import java.util.Date;
import java.util.Map.Entry;
import javax.persistence.Basic;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
@ -11,6 +13,10 @@ import javax.persistence.Transient;
import org.mitre.jwt.model.JwtClaims; import org.mitre.jwt.model.JwtClaims;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@Entity @Entity
@Table(name="idtokenclaims") @Table(name="idtokenclaims")
@ -23,6 +29,20 @@ public class IdTokenClaims extends JwtClaims {
private Long id; private Long id;
public IdTokenClaims() {
super();
}
public IdTokenClaims(JsonObject json) {
super(json);
}
public IdTokenClaims(String b64) {
super(b64);
}
/** /**
* @return the id * @return the id
*/ */
@ -74,4 +94,55 @@ public class IdTokenClaims extends JwtClaims {
setClaim(AUTH_TIME, authTime); setClaim(AUTH_TIME, authTime);
} }
/**
* Get the seraialized form of this claim set
*/
@Basic
public String getSerializedForm() {
// TODO Auto-generated method stub
JsonObject o = super.getAsJsonObject();
return o.toString();
}
/**
* Set up the claims in this object from the serialized form. This clears all current claims from the object.
* @param s a JSON Object string to load into this object
* @throws IllegalArgumentException if s is not a valid JSON object string
*/
public void setSerializedForm(String s) {
JsonParser parser = new JsonParser();
JsonElement json = parser.parse(s);
if (json != null && json.isJsonObject()) {
loadFromJsonObject(json.getAsJsonObject());
} else {
throw new IllegalArgumentException("Could not parse: " + s);
}
}
/**
* Load this IdToken from a JSON Object
*/
@Override
public void loadFromJsonObject(JsonObject json) {
JsonObject pass = new JsonObject();
for (Entry<String, JsonElement> element : json.entrySet()) {
if (element.getKey().equals(USER_ID)) {
setUserId(element.getValue().getAsString());
} else if (element.getKey().equals(AUTHENTICATION_CONTEXT_CLASS_REFERENCE)) {
setAuthContext(element.getValue().getAsString());
} else if (element.getKey().equals(NONCE)) {
setNonce(element.getValue().getAsString());
} else if (element.getKey().equals(AUTH_TIME)) {
setAuthTime(new Date(element.getValue().getAsLong() * 1000L));
} else {
pass.add(element.getKey(), element.getValue());
}
}
super.loadFromJsonObject(pass);
}
} }

View File

@ -1,5 +1,6 @@
package org.mitre.openid.connect.web; package org.mitre.openid.connect.web;
import org.mitre.openid.connect.model.IdToken;
import org.mitre.openid.connect.model.IdTokenClaims; import org.mitre.openid.connect.model.IdTokenClaims;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -7,15 +8,17 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@Controller @Controller
@RequestMapping("/checkid")
public class CheckIDEndpoint { public class CheckIDEndpoint {
@RequestMapping("/")
public ModelAndView checkID(@RequestParam("id_token") String idToken, ModelAndView mav) {
@RequestMapping("/checkid")
public ModelAndView checkID(@RequestParam("id_token") String tokenString, ModelAndView mav) {
IdToken token = IdToken.parse(tokenString);
IdTokenClaims token = new IdTokenClaims();
//TODO: Set claims
return new ModelAndView("jsonIdTokenView", "checkId", token); return new ModelAndView("jsonIdTokenView", "checkId", token);
} }

View File

@ -0,0 +1,55 @@
package org.mitre.swd.web;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class SimpleWebDiscoveryEndpoint {
@RequestMapping(value="/.well-known/simple-web-discovery",
params={"principal", "service=http://openid.net/specs/connect/1.0/issuer"})
public ModelAndView openIdConnectIssuerDiscovery(@RequestParam("principal") String principal, ModelAndView modelAndView) {
return modelAndView;
}
/*
* version string Version of the provider response. "3.0" is the default.
* issuer string The https: URL with no path component that the OP asserts as its Issuer Identifier
* authorization_endpoint string URL of the OP's Authentication and Authorization Endpoint [OpenID.Messages]
* token_endpoint string URL of the OP's OAuth 2.0 Token Endpoint [OpenID.Messages]
* userinfo_endpoint string URL of the OP's UserInfo Endpoint [OpenID.Messages]
* check_id_endpoint string URL of the OP's Check ID Endpoint [OpenID.Messages]
* refresh_session_endpoint string URL of the OP's Refresh Session Endpoint [OpenID.Session]
* end_session_endpoint string URL of the OP's End Session Endpoint [OpenID.Session]
* jwk_url string URL of the OP's JSON Web Key [JWK] document. Server's signing Key
* jwk_encryption_url string URL of the OP's JSON Web Key [JWK] document. Server's Encryption Key, if not present, its value is the same as the URL provided by jwk_url
* x509_url string URL of the OP's X.509 certificates in PEM format.
* x509_encryption_url string URL of the OP's X.509 certificates in PEM format. Server's Encryption Key, if not present its value is the same as the URL provided by x509_url
* registration_endpoint string URL of the OP's Dynamic Client Registration Endpoint [OpenID.Registration]
* scopes_supported array A JSON array containing a list of the OAuth 2.0 [OAuth2.0] scope values that this server supports. The server MUST support the openid scope value.
* response_types_supported array A JSON array containing a list of the OAuth 2.0 response_type that this server supports. The server MUST support the code response_type.
* acrs_supported array A JSON array containing a list of the Authentication Context Class References that this server supports.
* user_id_types_supported array A JSON array containing a list of the user identifier types that this server supports. Valid types include pairwise and public.
* userinfo_algs_supported array A JSON array containing a list of the JWS [JWS] and JWE [JWE] signing and encryption algorithms supported by the UserInfo Endpoint to encode the JWT [JWT].
* id_token_algs_supported array A JSON array containing a list of the JWS [JWS] and JWE [JWE] signing and encryption algorithms supported by the Authorization Server for the ID Token to encode the JWT [JWT].
* request_object_algs_supported array A JSON array containing a list of the JWS [JWS] and JWE [JWE] signing and encryption algorithms supported by the Authorization Server for the OpenID Request Object described in Section 2.1.2.1 of OpenID Connect Messages 1.0 [OpenID.Messages] to encode the JWT [JWT]. Servers SHOULD support HS256.
* token_endpoint_auth_types_supported array A JSON array containing a list of authentication types supported by this Token Endpoint. The options are client_secret_post, client_secret_basic, client_secret_jwt, and private_key_jwt, as described in Section 2.2.1 of OpenID Connect Messages 1.0 [OpenID.Messages]. Other Authentication types may be defined by extension. If unspecified or omitted, the default is client_secret_basic HTTP Basic Authentication Scheme as specified in section 2.3.1 of OAuth 2.0 [OAuth2.0].
* token_endpoint_auth_algs_supported array A JSON array containing a list of the JWS [JWS] signing algorithms supported by the Token Endpoint for the private_key_jwt method to encode the JWT [JWT]. Servers SHOULD support RS256.
*/
@RequestMapping("/.well-known/openid-configuration")
public ModelAndView providerConfiguration(ModelAndView modelAndView) {
Map m = modelAndView.getModel();
m.put("version", "3.0");
// TODO: everything in the list up there
return modelAndView;
}
}