diff --git a/pom.xml b/pom.xml
index c8f498bed..967761740 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,10 +7,20 @@
     <name>OpenIdConnect</name>
     <packaging>pom</packaging>
     <version>0.1</version>
-    <modules>
-    	<module>spring-security-oauth/spring-security-oauth2</module>
-	<module>server</module>
-    </modules>
+    
+    <profiles>
+    	<profile>
+    		<activation>
+    			<activeByDefault>true</activeByDefault>
+    		</activation>
+    		<id>default</id>
+		    <modules>
+		    	<module>spring-security-oauth/spring-security-oauth2</module>
+				<module>server</module>
+		    </modules>
+    	</profile>
+    </profiles>
+    
     <properties>
         <java-version>1.6</java-version>
         <spring.version>3.1.0.RELEASE</spring.version>
diff --git a/server/src/main/java/org/mitre/oauth2/exception/ClientNotFoundException.java b/server/src/main/java/org/mitre/oauth2/exception/ClientNotFoundException.java
new file mode 100644
index 000000000..94fe88eff
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/exception/ClientNotFoundException.java
@@ -0,0 +1,50 @@
+package org.mitre.oauth2.exception;
+/**
+ * 
+ */
+
+
+/**
+ * @author aanganes
+ *
+ */
+public class ClientNotFoundException extends RuntimeException {
+
+	/**
+	 * 
+	 */
+	private static final Long serialVersionUID = 1L;
+
+	/**
+	 * 
+	 */
+	public ClientNotFoundException() {
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 */
+	public ClientNotFoundException(String message) {
+		super(message);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param cause
+	 */
+	public ClientNotFoundException(Throwable cause) {
+		super(cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 * @param cause
+	 */
+	public ClientNotFoundException(String message, Throwable cause) {
+		super(message, cause);
+		// TODO Auto-generated constructor stub
+	}
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java b/server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java
new file mode 100644
index 000000000..78ff82b2c
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java
@@ -0,0 +1,15 @@
+package org.mitre.oauth2.exception;
+
+public class DuplicateClientIdException extends RuntimeException {
+
+	public DuplicateClientIdException(String clientId) {
+	    super("Duplicate client id: " + clientId);
+    }
+
+	/**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+
+    
+}
diff --git a/server/src/main/java/org/mitre/oauth2/exception/PermissionDeniedException.java b/server/src/main/java/org/mitre/oauth2/exception/PermissionDeniedException.java
new file mode 100644
index 000000000..c6f2bea6e
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/exception/PermissionDeniedException.java
@@ -0,0 +1,49 @@
+/**
+ * 
+ */
+package org.mitre.oauth2.exception;
+
+/**
+ * @author AANGANES
+ *
+ */
+public class PermissionDeniedException extends RuntimeException {
+
+	/**
+	 * 
+	 */
+	private static final Long serialVersionUID = 1L;
+
+	/**
+	 * 
+	 */
+	public PermissionDeniedException() {
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 */
+	public PermissionDeniedException(String message) {
+		super(message);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param cause
+	 */
+	public PermissionDeniedException(Throwable cause) {
+		super(cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	/**
+	 * @param message
+	 * @param cause
+	 */
+	public PermissionDeniedException(String message, Throwable cause) {
+		super(message, cause);
+		// TODO Auto-generated constructor stub
+	}
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java
new file mode 100644
index 000000000..247515d7b
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java
@@ -0,0 +1,473 @@
+/**
+ * 
+ */
+package org.mitre.oauth2.model;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.Basic;
+import javax.persistence.CollectionTable;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.provider.ClientDetails;
+
+/**
+ * @author jricher
+ * 
+ */
+@Entity
+@Table(name="clientdetails")
+@NamedQueries({
+	@NamedQuery(name = "ClientDetailsEntity.findAll", query = "SELECT c FROM ClientDetailsEntity c")
+})
+public class ClientDetailsEntity implements ClientDetails {
+
+	/**
+	 * Create a blank ClientDetailsEntity
+	 */
+	public ClientDetailsEntity() {
+		
+	}
+
+	private String clientId;	
+	private String clientSecret;
+	private Set<String> scope;
+	private Set<String> authorizedGrantTypes;
+	private Set<GrantedAuthority> authorities = Collections.emptySet();
+	private String clientName;
+	private String clientDescription;
+	private boolean allowRefresh = false; // do we allow refresh tokens for this client?
+	private Long accessTokenTimeout; // in seconds
+	private Long refreshTokenTimeout; // in seconds
+	private String owner; // userid of who registered it
+	private String registeredRedirectUri;
+	private Set<String> resourceIds;
+
+	// TODO:
+	/*
+	private boolean allowMultipleAccessTokens; // do we allow multiple access tokens, or not?
+	private boolean reuseRefreshToken; // do we let someone reuse a refresh token?
+	*/
+	
+	/**
+     * @return the clientId
+     */
+	@Id
+    public String getClientId() {
+    	return clientId;
+    }
+
+	/**
+     * @param clientId The OAuth2 client_id, must be unique to this client 
+     */
+    public void setClientId(String clientId) {
+    	this.clientId = clientId;
+    }
+
+	/**
+     * @return the clientSecret
+     */
+	@Basic
+	public String getClientSecret() {
+    	return clientSecret;
+    }
+
+	/**
+     * @param clientSecret the OAuth2 client_secret (optional)
+     */
+    public void setClientSecret(String clientSecret) {
+    	this.clientSecret = clientSecret;
+    }
+
+	/**
+     * @return the scope
+     */
+	@ElementCollection(fetch = FetchType.EAGER)
+	@CollectionTable(
+			name="scope",
+			joinColumns=@JoinColumn(name="owner_id")
+	)
+	public Set<String> getScope() {
+    	return scope;
+    }
+
+	/**
+     * @param scope the set of scopes allowed to be issued to this client
+     */
+    public void setScope(Set<String> scope) {
+    	this.scope = scope;
+    }
+
+	/**
+     * @return the authorizedGrantTypes
+     */
+	@ElementCollection(fetch = FetchType.EAGER)
+	@CollectionTable(
+			name="authorizedgranttypes",
+			joinColumns=@JoinColumn(name="owner_id")
+	)
+	public Set<String> getAuthorizedGrantTypes() {
+    	return authorizedGrantTypes;
+    }
+
+	/**
+     * @param authorizedGrantTypes the OAuth2 grant types that this client is allowed to use  
+     */
+    public void setAuthorizedGrantTypes(Set<String> authorizedGrantTypes) {
+    	this.authorizedGrantTypes = authorizedGrantTypes;
+    }
+
+	/**
+     * @return the authorities
+     */
+	@ElementCollection(fetch = FetchType.EAGER)
+	@CollectionTable(
+			name="authorities",
+			joinColumns=@JoinColumn(name="owner_id")
+	)
+    public Set<GrantedAuthority> getAuthorities() {
+    	return authorities;
+    }
+
+	/**
+     * @param authorities the Spring Security authorities this client is given
+     */
+    public void setAuthorities(Set<GrantedAuthority> authorities) {
+    	this.authorities = authorities;
+    }
+
+	/**
+	 * If the clientSecret is not null, then it is always required.
+     */
+    @Override
+    public boolean isSecretRequired() {
+	    return getClientSecret() != null;
+    }
+
+	/**
+	 * If the scope list is not null or empty, then this client has been scoped.
+     */
+    @Override
+    public boolean isScoped() {
+    	return getScope() != null && !getScope().isEmpty();
+    }
+
+	/**
+     * @return the clientName
+     */
+	@Basic
+    public String getClientName() {
+    	return clientName;
+    }
+
+	/**
+     * @param clientName Human-readable name of the client (optional)
+     */
+    public void setClientName(String clientName) {
+    	this.clientName = clientName;
+    }
+
+	/**
+     * @return the clientDescription
+     */
+	@Basic
+    public String getClientDescription() {
+    	return clientDescription;
+    }
+
+	/**
+     * @param clientDescription Human-readable long description of the client (optional)
+     */
+    public void setClientDescription(String clientDescription) {
+    	this.clientDescription = clientDescription;
+    }
+
+	/**
+     * @return the allowRefresh
+     */
+	@Basic
+    public boolean isAllowRefresh() {
+    	return allowRefresh;
+    }
+
+	/**
+     * @param allowRefresh Whether to allow for issuance of refresh tokens or not (defaults to false)
+     */
+    public void setAllowRefresh(boolean allowRefresh) {
+    	this.allowRefresh = allowRefresh;
+    }
+
+	/**
+     * @param accessTokenTimeout Lifetime of access tokens, in seconds (optional - leave null for no timeout)
+     */
+	@Basic
+    public Long getAccessTokenTimeout() {
+    	return accessTokenTimeout;
+    }
+
+	/**
+     * @param accessTokenTimeout the accessTokenTimeout to set
+     */
+    public void setAccessTokenTimeout(Long accessTokenTimeout) {
+    	this.accessTokenTimeout = accessTokenTimeout;
+    }
+
+	/**
+     * @return the refreshTokenTimeout
+     */
+	@Basic
+    public Long getRefreshTokenTimeout() {
+    	return refreshTokenTimeout;
+    }
+
+	/**
+     * @param refreshTokenTimeout Lifetime of refresh tokens, in seconds (optional - leave null for no timeout)
+     */
+    public void setRefreshTokenTimeout(Long refreshTokenTimeout) {
+    	this.refreshTokenTimeout = refreshTokenTimeout;
+    }
+
+	/**
+     * @return the owner
+     */
+	@Basic
+    public String getOwner() {
+    	return owner;
+    }
+
+	/**
+     * @param owner User ID of the person who registered this client (optional)
+     */
+    public void setOwner(String owner) {
+    	this.owner = owner;
+    }
+
+	/**
+     * @return the registeredRedirectUri
+     */
+	@Basic
+    public String getRegisteredRedirectUri() {
+    	return registeredRedirectUri;
+    }
+
+	/**
+     * @param registeredRedirectUri the registeredRedirectUri to set
+     */
+    public void setRegisteredRedirectUri(String registeredRedirectUri) {
+    	this.registeredRedirectUri = registeredRedirectUri;
+    }
+
+	/**
+     * @return the resourceIds
+     */
+    public Set<String> getResourceIds() {
+    	return resourceIds;
+    }
+
+	/**
+     * @param resourceIds the resourceIds to set
+     */
+	@ElementCollection(fetch = FetchType.EAGER)
+	@CollectionTable(
+			name="resource_ids",
+			joinColumns=@JoinColumn(name="owner_id")
+	)
+    public void setResourceIds(Set<String> resourceIds) {
+    	this.resourceIds = resourceIds;
+    }
+
+	/* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+	    return "ClientDetailsEntity [" + (clientId != null ? "clientId=" + clientId + ", " : "") + (scope != null ? "scope=" + scope + ", " : "") + (clientName != null ? "clientName=" + clientName + ", " : "") + (owner != null ? "owner=" + owner : "") + "]";
+    }
+
+	/* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+	    final int prime = 31;
+	    int result = 1;
+	    result = prime * result + ((clientId == null) ? 0 : clientId.hashCode());
+	    return result;
+    }
+
+	/* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+	    if (this == obj) {
+		    return true;
+	    }
+	    if (obj == null) {
+		    return false;
+	    }
+	    if (getClass() != obj.getClass()) {
+		    return false;
+	    }
+	    ClientDetailsEntity other = (ClientDetailsEntity) obj;
+	    if (clientId == null) {
+		    if (other.clientId != null) {
+			    return false;
+		    }
+	    } else if (!clientId.equals(other.clientId)) {
+		    return false;
+	    }
+	    return true;
+    }
+
+    public static ClientDetailsEntityBuilder makeBuilder() {
+    	return new ClientDetailsEntityBuilder();
+    }
+    
+	public static class ClientDetailsEntityBuilder {
+		private ClientDetailsEntity instance;
+		
+		private ClientDetailsEntityBuilder() {
+			instance = new ClientDetailsEntity();
+		}
+
+		/**
+         * @param clientId
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientId(java.lang.String)
+         */
+        public ClientDetailsEntityBuilder setClientId(String clientId) {
+	        instance.setClientId(clientId);
+			return this;
+        }
+
+		/**
+         * @param clientSecret
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientSecret(java.lang.String)
+         */
+        public ClientDetailsEntityBuilder setClientSecret(String clientSecret) {
+	        instance.setClientSecret(clientSecret);
+			return this;
+        }
+
+		/**
+         * @param scope
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setScope(java.util.List)
+         */
+        public ClientDetailsEntityBuilder setScope(Set<String> scope) {
+	        instance.setScope(scope);
+			return this;
+        }
+
+		/**
+         * @param authorizedGrantTypes
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setAuthorizedGrantTypes(java.util.List)
+         */
+        public ClientDetailsEntityBuilder setAuthorizedGrantTypes(Set<String> authorizedGrantTypes) {
+	        instance.setAuthorizedGrantTypes(authorizedGrantTypes);
+			return this;
+        }
+
+		/**
+         * @param authorities
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setAuthorities(java.util.List)
+         */
+        public ClientDetailsEntityBuilder setAuthorities(Set<GrantedAuthority> authorities) {
+	        instance.setAuthorities(authorities);
+			return this;
+        }
+
+		/**
+         * @param clientName
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientName(java.lang.String)
+         */
+        public ClientDetailsEntityBuilder setClientName(String clientName) {
+	        instance.setClientName(clientName);
+			return this;
+        }
+
+		/**
+         * @param clientDescription
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setClientDescription(java.lang.String)
+         */
+        public ClientDetailsEntityBuilder setClientDescription(String clientDescription) {
+	        instance.setClientDescription(clientDescription);
+			return this;
+        }
+
+		/**
+         * @param allowRefresh
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setAllowRefresh(boolean)
+         */
+        public ClientDetailsEntityBuilder setAllowRefresh(boolean allowRefresh) {
+	        instance.setAllowRefresh(allowRefresh);
+			return this;
+        }
+
+		/**
+         * @param accessTokenTimeout
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setAccessTokenTimeout(java.lang.Long)
+         */
+        public ClientDetailsEntityBuilder setAccessTokenTimeout(Long accessTokenTimeout) {
+	        instance.setAccessTokenTimeout(accessTokenTimeout);
+			return this;
+        }
+
+		/**
+         * @param refreshTokenTimeout
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setRefreshTokenTimeout(java.lang.Long)
+         */
+        public ClientDetailsEntityBuilder setRefreshTokenTimeout(Long refreshTokenTimeout) {
+	        instance.setRefreshTokenTimeout(refreshTokenTimeout);
+			return this;
+        }
+
+		/**
+         * @param owner
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setOwner(java.lang.String)
+         */
+        public ClientDetailsEntityBuilder setOwner(String owner) {
+	        instance.setOwner(owner);
+			return this;
+        }
+
+        
+        
+        /**
+         * Complete the builder
+         * @return
+         */
+		public ClientDetailsEntity finish() {
+			return instance;
+		}
+
+		/**
+         * @param registeredRedirectUri
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setRegisteredRedirectUri(java.lang.String)
+         */
+        public ClientDetailsEntityBuilder setRegisteredRedirectUri(String registeredRedirectUri) {
+	        instance.setRegisteredRedirectUri(registeredRedirectUri);
+	        return this;
+        }
+
+		/**
+         * @param resourceIds
+         * @see org.mitre.oauth2.model.ClientDetailsEntity#setResourceIds(java.util.List)
+         */
+        public ClientDetailsEntityBuilder setResourceIds(Set<String> resourceIds) {
+	        instance.setResourceIds(resourceIds);
+	        return this;
+        }
+		
+	}
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntityFactory.java b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntityFactory.java
new file mode 100644
index 000000000..5f9f68ccb
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntityFactory.java
@@ -0,0 +1,9 @@
+package org.mitre.oauth2.model;
+
+import org.mitre.oauth2.model.ClientDetailsEntity.ClientDetailsEntityBuilder;
+
+public interface ClientDetailsEntityFactory {
+	
+	public ClientDetailsEntity createClient(String clientId, String clientSecret);
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/ClientGeneratorFactory.java b/server/src/main/java/org/mitre/oauth2/model/ClientGeneratorFactory.java
new file mode 100644
index 000000000..ceaa22cf6
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/ClientGeneratorFactory.java
@@ -0,0 +1,32 @@
+package org.mitre.oauth2.model;
+
+import java.util.UUID;
+
+import org.apache.commons.codec.binary.Base64;
+import org.mitre.oauth2.model.ClientDetailsEntity.ClientDetailsEntityBuilder;
+import org.springframework.stereotype.Service;
+
+/**
+ * A factory for making OAuth2 clients with autogenerated IDs and secrets (as desired)
+ * @author jricher
+ *
+ */
+@Service
+public class ClientGeneratorFactory implements ClientDetailsEntityFactory {
+
+	@Override
+	public ClientDetailsEntity createClient(String clientId, String clientSecret) {
+		ClientDetailsEntityBuilder builder = ClientDetailsEntity.makeBuilder();
+		if (clientId == null) {
+			clientId = UUID.randomUUID().toString();
+		}
+		builder.setClientId(clientId);
+		if (clientSecret == null) {
+			clientSecret = Base64.encodeBase64((UUID.randomUUID().toString() + UUID.randomUUID().toString()).getBytes()).toString();
+		}
+		builder.setClientSecret(clientSecret);
+		
+		return builder.finish();
+	}
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java
new file mode 100644
index 000000000..11aebde73
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java
@@ -0,0 +1,210 @@
+/**
+ * 
+ */
+package org.mitre.oauth2.model;
+
+import java.util.Date;
+import java.util.Set;
+
+import javax.persistence.Basic;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.Transient;
+
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+
+/**
+ * @author jricher
+ *
+ */
+@Entity
+@Table(name="accesstoken")
+@NamedQueries({
+	@NamedQuery(name = "OAuth2AccessTokenEntity.getByRefreshToken", query = "select a from OAuth2AccessTokenEntity a where a.refreshToken = :refreshToken"),
+	@NamedQuery(name = "OAuth2AccessTokenEntity.getByClient", query = "select a from OAuth2AccessTokenEntity a where a.client = :client"),
+	@NamedQuery(name = "OAuth2AccessTokenEntity.getExpired", query = "select a from OAuth2AccessTokenEntity a where a.expiration is not null and a.expiration < current_timestamp")
+})
+public class OAuth2AccessTokenEntity extends OAuth2AccessToken {
+
+	private ClientDetailsEntity client;
+	
+	private OAuth2Authentication authentication; // the authentication that made this access
+	
+	/**
+	 * 
+	 */
+	public OAuth2AccessTokenEntity() {
+		super(null);
+	}
+	
+	
+	/**
+     * @return the authentication
+     */
+	@Lob
+	@Basic
+    public OAuth2Authentication getAuthentication() {
+    	return authentication;
+    }
+
+
+	/**
+     * @param authentication the authentication to set
+     */
+    public void setAuthentication(OAuth2Authentication authentication) {
+    	this.authentication = authentication;
+    }
+
+
+	/**
+     * @return the client
+     */
+	@ManyToOne
+	@JoinColumn(name = "client_id")
+    public ClientDetailsEntity getClient() {
+    	return client;
+    }
+
+
+	/**
+     * @param client the client to set
+     */
+    public void setClient(ClientDetailsEntity client) {
+    	this.client = client;
+    }
+	
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getValue()
+     */
+    @Override
+    @Id
+    @Column(name="id")
+    public String getValue() {
+	    // TODO Auto-generated method stub
+	    return super.getValue();
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setValue(java.lang.String)
+     */
+    @Override
+    public void setValue(String value) {
+	    // TODO Auto-generated method stub
+	    super.setValue(value);
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getExpiration()
+     */
+    @Override
+    @Basic
+    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
+    public Date getExpiration() {
+	    // TODO Auto-generated method stub
+	    return super.getExpiration();
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setExpiration(java.util.Date)
+     */
+    @Override
+    public void setExpiration(Date expiration) {
+	    // TODO Auto-generated method stub
+	    super.setExpiration(expiration);
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getTokenType()
+     */
+    @Override
+    @Basic
+    public String getTokenType() {
+	    // TODO Auto-generated method stub
+	    return super.getTokenType();
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setTokenType(java.lang.String)
+     */
+    @Override
+    public void setTokenType(String tokenType) {
+	    // TODO Auto-generated method stub
+	    super.setTokenType(tokenType);
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getRefreshToken()
+     */
+    @Override
+    @ManyToOne
+    @JoinColumn(name="refresh_token_id")
+    public OAuth2RefreshTokenEntity getRefreshToken() {
+	    // TODO Auto-generated method stub
+	    return (OAuth2RefreshTokenEntity) super.getRefreshToken();
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setRefreshToken(org.springframework.security.oauth2.common.OAuth2RefreshToken)
+     */
+    public void setRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+	    // TODO Auto-generated method stub
+	    super.setRefreshToken(refreshToken);
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setRefreshToken(org.springframework.security.oauth2.common.OAuth2RefreshToken)
+     */
+    @Override
+    public void setRefreshToken(OAuth2RefreshToken refreshToken) {
+    	if (!(refreshToken instanceof OAuth2RefreshTokenEntity)) {
+    		// TODO: make a copy constructor instead....
+    		throw new IllegalArgumentException("Not a storable refresh token entity!");
+    	}
+    	// force a pass through to the entity version
+    	setRefreshToken((OAuth2RefreshTokenEntity)refreshToken);
+    }
+	
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#getScope()
+     */
+    @Override
+    @ElementCollection(fetch=FetchType.EAGER)
+    @CollectionTable(
+    		joinColumns=@JoinColumn(name="owner_id"),
+    		name="scope"
+    )
+    public Set<String> getScope() {
+	    // TODO Auto-generated method stub
+	    return super.getScope();
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2AccessToken#setScope(java.util.Set)
+     */
+    @Override
+    public void setScope(Set<String> scope) {
+	    // TODO Auto-generated method stub
+	    super.setScope(scope);
+    }
+
+    @Transient
+	public boolean isExpired() {
+		return getExpiration() == null ? false : System.currentTimeMillis() > getExpiration().getTime();
+	}
+
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntityFactory.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntityFactory.java
new file mode 100644
index 000000000..b4bd1ba38
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntityFactory.java
@@ -0,0 +1,7 @@
+package org.mitre.oauth2.model;
+
+public interface OAuth2AccessTokenEntityFactory {
+
+	public OAuth2AccessTokenEntity createNewAccessToken();
+	
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java
new file mode 100644
index 000000000..72d73f48f
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java
@@ -0,0 +1,136 @@
+/**
+ * 
+ */
+package org.mitre.oauth2.model;
+
+import java.util.Date;
+import java.util.Set;
+
+import javax.persistence.Basic;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.Transient;
+
+import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
+
+/**
+ * @author jricher
+ *
+ */
+@Entity
+@Table(name="refreshtoken")
+@NamedQueries({
+	@NamedQuery(name = "OAuth2RefreshTokenEntity.getByClient", query = "select r from OAuth2RefreshTokenEntity r where r.client = :client"),
+	@NamedQuery(name = "OAuth2RefreshTokenEntity.getExpired", query = "select r from OAuth2RefreshTokenEntity r where r.expiration is not null and r.expiration < current_timestamp")
+})
+public class OAuth2RefreshTokenEntity extends ExpiringOAuth2RefreshToken {
+
+	private ClientDetailsEntity client;
+
+	private  Set<String> scope; // we save the scope issued to the refresh token so that we can reissue a new access token	
+	
+	/**
+	 * 
+	 */
+	public OAuth2RefreshTokenEntity() {
+		// TODO Auto-generated constructor stub
+	}
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2RefreshToken#getValue()
+     */
+    @Override
+    @Id
+    @Column(name="id")
+    public String getValue() {
+	    // TODO Auto-generated method stub
+	    return super.getValue();
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.OAuth2RefreshToken#setValue(java.lang.String)
+     */
+    @Override
+    public void setValue(String value) {
+	    // TODO Auto-generated method stub
+	    super.setValue(value);
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken#getExpiration()
+     */
+    @Override
+    @Basic
+    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
+    public Date getExpiration() {
+	    // TODO Auto-generated method stub
+	    return super.getExpiration();
+    }
+
+	/* (non-Javadoc)
+     * @see org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken#setExpiration(java.util.Date)
+     */
+    @Override
+    public void setExpiration(Date expiration) {
+	    // TODO Auto-generated method stub
+	    super.setExpiration(expiration);
+    }
+
+    /**
+     * Has this token expired?
+     * @return true if it has a timeout set and the timeout has passed
+     */
+    @Transient
+	public boolean isExpired() {
+		return getExpiration() == null ? false : System.currentTimeMillis() > getExpiration().getTime();
+	}
+	
+	/**
+     * @return the client
+     */
+	@ManyToOne(fetch = FetchType.EAGER)
+	@JoinColumn(name = "client_id")
+    public ClientDetailsEntity getClient() {
+    	return client;
+    }
+
+
+	/**
+     * @param client the client to set
+     */
+    public void setClient(ClientDetailsEntity client) {
+    	this.client = client;
+    }
+
+	/**
+     * @return the scope
+     */
+	@ElementCollection(fetch = FetchType.EAGER)
+	@CollectionTable(
+			joinColumns=@JoinColumn(name="owner_id"),
+			name="scope"
+	)
+    public Set<String> getScope() {
+    	return scope;
+    }
+
+	/**
+     * @param scope the scope to set
+     */
+    public void setScope(Set<String> scope) {
+    	this.scope = scope;
+    }
+    
+
+    
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntityFactory.java b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntityFactory.java
new file mode 100644
index 000000000..2a6ac4785
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntityFactory.java
@@ -0,0 +1,7 @@
+package org.mitre.oauth2.model;
+
+public interface OAuth2RefreshTokenEntityFactory {
+
+	public OAuth2RefreshTokenEntity createNewRefreshToken();
+	
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/UUIDTokenFactory.java b/server/src/main/java/org/mitre/oauth2/model/UUIDTokenFactory.java
new file mode 100644
index 000000000..91f93856a
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/UUIDTokenFactory.java
@@ -0,0 +1,39 @@
+package org.mitre.oauth2.model;
+
+import java.util.UUID;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class UUIDTokenFactory implements OAuth2AccessTokenEntityFactory, OAuth2RefreshTokenEntityFactory {
+
+	/**
+	 * Create a new access token and set its value to a random UUID
+	 */
+	@Override
+	public OAuth2AccessTokenEntity createNewAccessToken() {
+		// create our token container
+		OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
+		
+		// set a random value (TODO: support JWT)
+	    String tokenValue = UUID.randomUUID().toString();
+	    token.setValue(tokenValue);
+	    
+	    return token;
+	}
+
+	/**
+	 * Create a new refresh token and set its value to a random UUID
+	 */
+	@Override
+    public OAuth2RefreshTokenEntity createNewRefreshToken() {
+		OAuth2RefreshTokenEntity refreshToken = new OAuth2RefreshTokenEntity();
+		
+		// set a random value for the refresh
+		String refreshTokenValue = UUID.randomUUID().toString();
+		refreshToken.setValue(refreshTokenValue);
+
+		return refreshToken;
+    }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/serializer/JSONOAuthClientView.java b/server/src/main/java/org/mitre/oauth2/model/serializer/JSONOAuthClientView.java
new file mode 100644
index 000000000..843d7e3ac
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/serializer/JSONOAuthClientView.java
@@ -0,0 +1,66 @@
+package org.mitre.oauth2.model.serializer;
+
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.web.servlet.view.AbstractView;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+public class JSONOAuthClientView extends AbstractView {
+
+	@Override
+	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+		Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
+
+			@Override
+			public boolean shouldSkipField(FieldAttributes f) {
+				return false;
+			}
+
+			@Override
+			public boolean shouldSkipClass(Class<?> clazz) {
+				// skip the JPA binding wrapper
+				if (clazz.equals(BeanPropertyBindingResult.class)) {
+					return true;
+				} else {
+					return false;
+				}
+			}
+
+		})
+		.registerTypeAdapter(GrantedAuthority.class, new JsonSerializer<GrantedAuthority>() {
+			@Override
+			public JsonElement serialize(GrantedAuthority src, Type typeOfSrc, JsonSerializationContext context) {
+			    return new JsonPrimitive(src.getAuthority());
+			}
+		})
+		.create();
+
+		response.setContentType("application/json");
+
+		Writer out = response.getWriter();
+
+		Object obj = model.get("entity");
+		if (obj == null) {
+			obj = model;
+		}
+
+		gson.toJson(obj, out);
+
+	}
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/model/serializer/TokenIntrospection.java b/server/src/main/java/org/mitre/oauth2/model/serializer/TokenIntrospection.java
new file mode 100644
index 000000000..28af3d3f2
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/model/serializer/TokenIntrospection.java
@@ -0,0 +1,100 @@
+package org.mitre.oauth2.model.serializer;
+
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.web.servlet.view.AbstractView;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+public class TokenIntrospection extends AbstractView {
+
+	@Override
+    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+		Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
+
+			@Override
+			public boolean shouldSkipField(FieldAttributes f) {
+				/*
+				if (f.getDeclaringClass().isAssignableFrom(OAuth2AccessTokenEntity.class)) {
+					// we don't want to serialize the whole object, just the scope and timeout
+					if (f.getName().equals("scope")) {
+						return false;
+					} else if (f.getName().equals("expiration")) {
+						return false;
+					} else {
+						// skip everything else on this class
+						return true;
+					}
+				} else {
+					// serialize other classes without filter (lists and sets and things)
+					return false;
+				}
+				*/
+				return false;
+			}
+
+			@Override
+			public boolean shouldSkipClass(Class<?> clazz) {
+				// skip the JPA binding wrapper
+				if (clazz.equals(BeanPropertyBindingResult.class)) {
+					return true;
+				} else {
+					return false;
+				}
+			}
+
+		})
+		.registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer<OAuth2AccessTokenEntity>() {
+            public JsonElement serialize(OAuth2AccessTokenEntity src, Type typeOfSrc, JsonSerializationContext context) {
+            	JsonObject token = new JsonObject();
+            	
+            	token.addProperty("valid", true);
+            	
+            	JsonArray scopes = new JsonArray();
+            	for (String scope : src.getScope()) {
+	                scopes.add(new JsonPrimitive(scope));
+                }
+            	token.add("scope", scopes);
+            	
+            	token.add("expires", context.serialize(src.getExpiration()));
+            	
+            	return token;
+            }
+			
+		})
+		.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
+		.create();
+
+		response.setContentType("application/json");
+
+		Writer out = response.getWriter();
+
+		Object obj = model.get("entity");
+		if (obj == null) {
+			obj = model;
+		}
+
+		gson.toJson(obj, out);
+		
+    }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java b/server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java
new file mode 100644
index 000000000..cd9de162f
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java
@@ -0,0 +1,22 @@
+package org.mitre.oauth2.repository;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.stereotype.Repository;
+
+public interface OAuth2ClientRepository {
+
+	public ClientDetailsEntity getClientById(String clientId);
+
+	public ClientDetailsEntity saveClient(ClientDetailsEntity client);
+
+	public void deleteClient(ClientDetailsEntity client);
+
+	public ClientDetailsEntity updateClient(String clientId, ClientDetailsEntity client);
+
+	public Collection<ClientDetailsEntity> getAllClients();
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java b/server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java
new file mode 100644
index 000000000..475a5c0c3
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java
@@ -0,0 +1,35 @@
+package org.mitre.oauth2.repository;
+
+import java.util.List;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+
+public interface OAuth2TokenRepository {
+
+	public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity token);
+
+	public OAuth2RefreshTokenEntity getRefreshTokenByValue(String refreshTokenValue);
+
+	public void clearAccessTokensForRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+
+	public void removeRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+	
+	public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+
+	public OAuth2AccessTokenEntity getAccessTokenByValue(String accessTokenValue);
+
+	public void removeAccessToken(OAuth2AccessTokenEntity accessToken);
+
+	public void clearTokensForClient(ClientDetailsEntity client);
+
+	public List<OAuth2AccessTokenEntity> getAccessTokensForClient(ClientDetailsEntity client);
+
+	public List<OAuth2RefreshTokenEntity> getRefreshTokensForClient(ClientDetailsEntity client);
+
+	public List<OAuth2AccessTokenEntity> getExpiredAccessTokens();
+
+	public List<OAuth2RefreshTokenEntity> getExpiredRefreshTokens();
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java
new file mode 100644
index 000000000..7022270bb
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java
@@ -0,0 +1,74 @@
+package org.mitre.oauth2.repository.impl;
+
+import java.util.Collection;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.TypedQuery;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.repository.OAuth2ClientRepository;
+import org.mitre.util.jpa.JpaUtil;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author jricher
+ *
+ */
+@Repository
+@Transactional
+public class JpaOAuth2ClientRepository implements OAuth2ClientRepository {
+
+	@PersistenceContext
+	private EntityManager manager;
+	
+	public JpaOAuth2ClientRepository() {
+		
+	}
+	
+	public JpaOAuth2ClientRepository(EntityManager manager) {
+		this.manager = manager;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.mitre.oauth2.repository.OAuth2ClientRepository#getClientById(java.lang.String)
+	 */
+	@Override
+	public ClientDetailsEntity getClientById(String clientId) {
+		return manager.find(ClientDetailsEntity.class, clientId);
+	}
+
+	/* (non-Javadoc)
+	 * @see org.mitre.oauth2.repository.OAuth2ClientRepository#saveClient(org.mitre.oauth2.model.ClientDetailsEntity)
+	 */
+	@Override
+	public ClientDetailsEntity saveClient(ClientDetailsEntity client) {
+		return JpaUtil.saveOrUpdate(client.getClientId(), manager, client);
+	}
+
+	/* (non-Javadoc)
+	 * @see org.mitre.oauth2.repository.OAuth2ClientRepository#deleteClient(org.mitre.oauth2.model.ClientDetailsEntity)
+	 */
+	@Override
+	public void deleteClient(ClientDetailsEntity client) {
+		ClientDetailsEntity found = getClientById(client.getClientId());
+		if (found != null) {
+			manager.remove(found);
+		} else {
+			throw new IllegalArgumentException("Client not found: " + client);
+		}
+	}
+
+	@Override
+    public ClientDetailsEntity updateClient(String clientId, ClientDetailsEntity client) {
+	    return JpaUtil.saveOrUpdate(clientId, manager, client);
+    }
+
+	@Override
+    public Collection<ClientDetailsEntity> getAllClients() {
+		TypedQuery<ClientDetailsEntity> query = manager.createNamedQuery("ClientDetailsEntity.findAll", ClientDetailsEntity.class);
+		return query.getResultList();
+    }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java
new file mode 100644
index 000000000..85cd9041d
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java
@@ -0,0 +1,138 @@
+package org.mitre.oauth2.repository.impl;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.TypedQuery;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.repository.OAuth2TokenRepository;
+import org.mitre.util.jpa.JpaUtil;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+@Transactional
+public class JpaOAuth2TokenRepository implements OAuth2TokenRepository {
+
+	@PersistenceContext
+	private EntityManager manager;
+
+	@Override
+	public OAuth2AccessTokenEntity getAccessTokenByValue(String accessTokenValue) {
+		return manager.find(OAuth2AccessTokenEntity.class, accessTokenValue);
+	}
+	
+	@Override
+	@Transactional
+	public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity token) {
+		return JpaUtil.saveOrUpdate(token.getValue(), manager, token);
+	}
+	
+	@Override
+	@Transactional
+	public void removeAccessToken(OAuth2AccessTokenEntity accessToken) {
+		OAuth2AccessTokenEntity found = getAccessTokenByValue(accessToken.getValue());
+		if (found != null) {
+			manager.remove(found);
+		} else {
+			throw new IllegalArgumentException("Access token not found: " + accessToken);
+		}
+	}
+	
+	@Override
+	@Transactional
+    public void clearAccessTokensForRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+		TypedQuery<OAuth2AccessTokenEntity> query = manager.createNamedQuery("OAuth2AccessTokenEntity.getByRefreshToken", OAuth2AccessTokenEntity.class);
+		query.setParameter("refreshToken", refreshToken);
+	    List<OAuth2AccessTokenEntity> accessTokens = query.getResultList();
+	    for (OAuth2AccessTokenEntity accessToken : accessTokens) {
+	        removeAccessToken(accessToken);
+        }
+    }
+
+	@Override
+	public OAuth2RefreshTokenEntity getRefreshTokenByValue(String refreshTokenValue) {
+		return manager.find(OAuth2RefreshTokenEntity.class, refreshTokenValue);
+	}
+	
+	@Override
+	@Transactional
+	public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+		return JpaUtil.saveOrUpdate(refreshToken.getValue(), manager, refreshToken);
+	}
+	
+	@Override
+	@Transactional
+    public void removeRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+		OAuth2RefreshTokenEntity found = getRefreshTokenByValue(refreshToken.getValue());
+		if (found != null) {
+			manager.remove(found);
+		} else {
+			throw new IllegalArgumentException("Refresh token not found: " + refreshToken);
+		}	    
+    }
+
+	@Override
+	@Transactional
+    public void clearTokensForClient(ClientDetailsEntity client) {
+		TypedQuery<OAuth2AccessTokenEntity> queryA = manager.createNamedQuery("OAuth2AccessTokenEntity.getByClient", OAuth2AccessTokenEntity.class);
+		queryA.setParameter("client", client);
+	    List<OAuth2AccessTokenEntity> accessTokens = queryA.getResultList();
+	    for (OAuth2AccessTokenEntity accessToken : accessTokens) {
+	        removeAccessToken(accessToken);
+        }
+		TypedQuery<OAuth2RefreshTokenEntity> queryR = manager.createNamedQuery("OAuth2RefreshTokenEntity.getByClient", OAuth2RefreshTokenEntity.class);
+		queryR.setParameter("client", client);
+	    List<OAuth2RefreshTokenEntity> refreshTokens = queryR.getResultList();
+	    for (OAuth2RefreshTokenEntity refreshToken : refreshTokens) {
+	        removeRefreshToken(refreshToken);
+        }	    
+    }
+
+	/* (non-Javadoc)
+     * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getAccessTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+     */
+    @Override
+    public List<OAuth2AccessTokenEntity> getAccessTokensForClient(ClientDetailsEntity client) {
+		TypedQuery<OAuth2AccessTokenEntity> queryA = manager.createNamedQuery("OAuth2AccessTokenEntity.getByClient", OAuth2AccessTokenEntity.class);
+		queryA.setParameter("client", client);
+	    List<OAuth2AccessTokenEntity> accessTokens = queryA.getResultList();
+	    return accessTokens;
+    }
+
+	/* (non-Javadoc)
+     * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getRefreshTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+     */
+    @Override
+    public List<OAuth2RefreshTokenEntity> getRefreshTokensForClient(ClientDetailsEntity client) {
+		TypedQuery<OAuth2RefreshTokenEntity> queryR = manager.createNamedQuery("OAuth2RefreshTokenEntity.getByClient", OAuth2RefreshTokenEntity.class);
+		queryR.setParameter("client", client);
+	    List<OAuth2RefreshTokenEntity> refreshTokens = queryR.getResultList();
+	    return refreshTokens;
+    }
+
+	/* (non-Javadoc)
+     * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getExpiredAccessTokens()
+     */
+    @Override
+    public List<OAuth2AccessTokenEntity> getExpiredAccessTokens() {
+		TypedQuery<OAuth2AccessTokenEntity> queryA = manager.createNamedQuery("OAuth2AccessTokenEntity.getExpired", OAuth2AccessTokenEntity.class);
+	    List<OAuth2AccessTokenEntity> accessTokens = queryA.getResultList();
+	    return accessTokens;
+    }
+
+	/* (non-Javadoc)
+     * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getExpiredRefreshTokens()
+     */
+    @Override
+    public List<OAuth2RefreshTokenEntity> getExpiredRefreshTokens() {
+		TypedQuery<OAuth2RefreshTokenEntity> queryR = manager.createNamedQuery("OAuth2RefreshTokenEntity.getExpired", OAuth2RefreshTokenEntity.class);
+	    List<OAuth2RefreshTokenEntity> refreshTokens = queryR.getResultList();
+	    return refreshTokens;
+    }	
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java b/server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java
new file mode 100644
index 000000000..d1c9757ad
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java
@@ -0,0 +1,22 @@
+package org.mitre.oauth2.service;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.security.oauth2.provider.ClientDetailsService;
+
+public interface ClientDetailsEntityService extends ClientDetailsService {
+
+	public ClientDetailsEntity loadClientByClientId(String clientId) throws OAuth2Exception;
+
+	public ClientDetailsEntity createClient(String clientId, String clientSecret, Set<String> scope, Set<String> grantTypes, String redirectUri, Set<GrantedAuthority> authorities, Set<String> resourceIds, String name, String description, boolean allowRefresh, Long accessTokenTimeout, Long refreshTokenTimeout, String owner);
+	
+	public void deleteClient(ClientDetailsEntity client);
+	
+	public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDetailsEntity newClient);
+
+	public Collection<ClientDetailsEntity> getAllClients();
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java b/server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java
new file mode 100644
index 000000000..724a4cd8d
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java
@@ -0,0 +1,26 @@
+package org.mitre.oauth2.service;
+
+import java.util.List;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
+import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
+
+public interface OAuth2TokenEntityService extends AuthorizationServerTokenServices, ResourceServerTokenServices {
+
+	public OAuth2AccessTokenEntity getAccessToken(String accessTokenValue);
+	
+	public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue);
+
+	public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken);
+
+	public void revokeAccessToken(OAuth2AccessTokenEntity accessToken);
+	
+	public List<OAuth2AccessTokenEntity> getAccessTokensForClient(ClientDetailsEntity client);
+	
+	public List<OAuth2RefreshTokenEntity> getRefreshTokensForClient(ClientDetailsEntity client);
+
+	public void clearExpiredTokens();
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
new file mode 100644
index 000000000..d865ac4fa
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
@@ -0,0 +1,130 @@
+package org.mitre.oauth2.service.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.ClientDetailsEntityFactory;
+import org.mitre.oauth2.repository.OAuth2ClientRepository;
+import org.mitre.oauth2.repository.OAuth2TokenRepository;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.stereotype.Service;
+
+import com.google.common.base.Strings;
+
+@Service
+public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEntityService {
+
+	@Autowired
+	private OAuth2ClientRepository clientRepository;
+	
+	@Autowired
+	private OAuth2TokenRepository tokenRepository;
+	
+	@Autowired
+	private ClientDetailsEntityFactory clientFactory;
+	
+	public DefaultOAuth2ClientDetailsEntityService() {
+		
+	}
+	
+	public DefaultOAuth2ClientDetailsEntityService(OAuth2ClientRepository clientRepository, 
+			OAuth2TokenRepository tokenRepository, ClientDetailsEntityFactory clientFactory) {
+		this.clientRepository = clientRepository;
+		this.tokenRepository = tokenRepository;
+		this.clientFactory = clientFactory;
+	}
+	
+	/**
+	 * Get the client for the given ID
+	 */
+	@Override
+	public ClientDetailsEntity loadClientByClientId(String clientId) throws OAuth2Exception, InvalidClientException, IllegalArgumentException {
+		if (!Strings.isNullOrEmpty(clientId)) {
+			ClientDetailsEntity client = clientRepository.getClientById(clientId);
+			if (client == null) {
+				throw new InvalidClientException("Client with id " + clientId + " was not found");
+			}
+			else {
+				return client;
+			}
+		}
+		
+		throw new IllegalArgumentException("Client id must not be empty!");
+	}
+	
+	/**
+	 * Create a new client with the appropriate fields filled in
+	 */
+	@Override
+    public ClientDetailsEntity createClient(String clientId, String clientSecret, 
+    		Set<String> scope, Set<String> grantTypes, String redirectUri, Set<GrantedAuthority> authorities,
+    		Set<String> resourceIds,
+    		String name, String description, boolean allowRefresh, Long accessTokenTimeout, 
+    		Long refreshTokenTimeout, String owner) {
+		
+		// TODO: check "owner" locally?
+
+		ClientDetailsEntity client = clientFactory.createClient(clientId, clientSecret);
+		client.setScope(scope);
+		client.setAuthorizedGrantTypes(grantTypes);
+		client.setRegisteredRedirectUri(redirectUri);
+		client.setAuthorities(authorities);
+		client.setClientName(name);
+		client.setClientDescription(description);
+		client.setAllowRefresh(allowRefresh);
+		client.setAccessTokenTimeout(accessTokenTimeout);
+		client.setRefreshTokenTimeout(refreshTokenTimeout);
+		client.setResourceIds(resourceIds);
+		client.setOwner(owner);
+
+		clientRepository.saveClient(client);
+		
+		return client;
+		
+	}
+	
+	/**
+	 * Delete a client and all its associated tokens
+	 */
+	@Override
+    public void deleteClient(ClientDetailsEntity client) throws InvalidClientException {
+		
+		if (clientRepository.getClientById(client.getClientId()) == null) {
+			throw new InvalidClientException("Client with id " + client.getClientId() + " was not found");
+		}
+		
+		// clean out any tokens that this client had issued
+		tokenRepository.clearTokensForClient(client);
+		
+		// take care of the client itself
+		clientRepository.deleteClient(client);
+		
+	}
+
+	/**
+	 * Update the oldClient with information from the newClient. The 
+	 * id from oldClient is retained.
+	 */
+	@Override
+    public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDetailsEntity newClient) throws IllegalArgumentException {
+		if (oldClient != null && newClient != null) {
+			return clientRepository.updateClient(oldClient.getClientId(), newClient);
+		}
+		throw new IllegalArgumentException("Neither old client or new client can be null!");
+    }
+
+	/**
+	 * Get all clients in the system
+	 */
+	@Override
+    public Collection<ClientDetailsEntity> getAllClients() {
+		return clientRepository.getAllClients();
+    }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
new file mode 100644
index 000000000..3b71b7318
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
@@ -0,0 +1,319 @@
+/**
+ * 
+ */
+package org.mitre.oauth2.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntityFactory;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntityFactory;
+import org.mitre.oauth2.repository.OAuth2TokenRepository;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.security.oauth2.provider.client.ClientAuthenticationToken;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Sets;
+
+
+/**
+ * @author jricher
+ * 
+ */
+@Service
+public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityService {
+	
+	private static Logger logger = LoggerFactory.getLogger(DefaultOAuth2ProviderTokenService.class);
+
+	@Autowired
+	private OAuth2TokenRepository tokenRepository;
+	
+	@Autowired
+	private ClientDetailsEntityService clientDetailsService;
+	
+	@Autowired
+	private OAuth2AccessTokenEntityFactory accessTokenFactory;
+	
+	@Autowired
+	private OAuth2RefreshTokenEntityFactory refreshTokenFactory;
+	
+	@Override
+    public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
+		if (authentication != null && 
+				authentication.getAuthorizationRequest() != null) {
+			// look up our client
+			AuthorizationRequest clientAuth = authentication.getAuthorizationRequest();
+			
+			ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
+		
+			if (client == null) {
+				throw new InvalidClientException("Client not found: " + clientAuth.getClientId());
+			}
+			
+			OAuth2AccessTokenEntity token = accessTokenFactory.createNewAccessToken();
+		    
+		    // attach the client
+	    	token.setClient(client);
+	    	
+		    // inherit the scope from the auth
+	    	// this lets us match which scope is requested 
+		    if (client.isScoped()) {
+		    	
+		    	// restrict granted scopes to a valid subset of those 
+		    	Set<String> validScopes = Sets.newHashSet();
+		    	
+		    	for (String requested : clientAuth.getScope()) {
+	                if (client.getScope().contains(requested)) {
+	                	validScopes.add(requested);
+	                } else {
+	                	logger.warn("Client " + client.getClientId() + " requested out of permission scope: " + requested);
+	                }
+                }
+		    	
+		    	token.setScope(validScopes);
+		    }
+
+		    // make it expire if necessary
+	    	if (client.getAccessTokenTimeout() != null) {
+	    		Date expiration = new Date(System.currentTimeMillis() + (client.getAccessTokenTimeout() * 1000L));
+	    		token.setExpiration(expiration);
+	    	}
+		    
+	    	// attach the authorization so that we can look it up later
+	    	token.setAuthentication(authentication);
+	    	
+	    	// attach a refresh token, if this client is allowed to request them
+	    	if (client.isAllowRefresh()) {
+	    		OAuth2RefreshTokenEntity refreshToken = refreshTokenFactory.createNewRefreshToken();
+	    		
+	    		// make it expire if necessary
+	    		if (client.getRefreshTokenTimeout() != null) {
+		    		Date expiration = new Date(System.currentTimeMillis() + (client.getRefreshTokenTimeout() * 1000L));
+		    		refreshToken.setExpiration(expiration);
+	    		}
+	    		
+	    		// save our scopes so that we can reuse them later for more auth tokens
+	    		// TODO: save the auth instead of the just the scope?
+			    if (client.isScoped()) {
+			    	refreshToken.setScope(clientAuth.getScope());
+			    }
+			
+			    tokenRepository.saveRefreshToken(refreshToken);
+			    
+	    		token.setRefreshToken(refreshToken);
+	    	}
+	    	
+		    tokenRepository.saveAccessToken(token);		    
+		    
+		    return token;
+		}
+		
+	    throw new AuthenticationCredentialsNotFoundException("No authentication credentials found");
+    }
+
+	@Override
+    public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, Set<String> scope) throws AuthenticationException {
+		
+		OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue);
+		
+		if (refreshToken == null) {
+			throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue);
+		}
+		
+		ClientDetailsEntity client = refreshToken.getClient();
+		
+		//Make sure this client allows access token refreshing
+		if (!client.isAllowRefresh()) {
+			throw new InvalidClientException("Client does not allow refreshing access token!");
+		}
+		
+		// clear out any access tokens
+		// TODO: make this a configurable option
+		tokenRepository.clearAccessTokensForRefreshToken(refreshToken);
+		
+		if (refreshToken.isExpired()) {
+			tokenRepository.removeRefreshToken(refreshToken);
+			throw new InvalidTokenException("Expired refresh token: " + refreshTokenValue);			
+		}
+		
+		// TODO: have the option to recycle the refresh token here, too
+		// for now, we just reuse it as long as it's valid, which is the original intent
+
+		OAuth2AccessTokenEntity token = accessTokenFactory.createNewAccessToken();
+
+		
+		if (scope != null && !scope.isEmpty()) { 
+			// ensure a proper subset of scopes 
+			if (refreshToken.getScope() != null && refreshToken.getScope().containsAll(scope)) {
+				// set the scope of the new access token if requested
+				refreshToken.setScope(scope);
+			} else {
+				// up-scoping is not allowed
+				// (TODO: should this throw InvalidScopeException? For now just pass through)
+				token.setScope(refreshToken.getScope());
+			}
+		} else {
+			// otherwise inherit the scope of the refresh token (if it's there -- this can return a null scope set)
+			token.setScope(refreshToken.getScope());
+		}
+	    
+    	token.setClient(client);
+    	
+    	if (client.getAccessTokenTimeout() != null) {
+    		Date expiration = new Date(System.currentTimeMillis() + (client.getAccessTokenTimeout() * 1000L));
+    		token.setExpiration(expiration);
+    	}
+    	
+    	token.setRefreshToken(refreshToken);
+    	
+    	tokenRepository.saveAccessToken(token);
+    	
+    	return token;
+		
+    }
+
+	@Override
+    public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException {
+		
+		OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue);
+		
+		if (accessToken == null) {
+			throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
+		}
+		
+		if (accessToken.isExpired()) {
+			tokenRepository.removeAccessToken(accessToken);
+			throw new InvalidTokenException("Expired access token: " + accessTokenValue);
+		}
+		
+	    return accessToken.getAuthentication();
+    }
+
+
+	@Override
+    public OAuth2AccessTokenEntity getAccessToken(String accessTokenValue) throws AuthenticationException {
+		OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue);
+		if (accessToken == null) {
+			throw new InvalidTokenException("Access token for value " + accessTokenValue + " was not found");
+		}
+		else {
+			return accessToken;	
+		}
+    }
+
+	@Override
+    public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue) throws AuthenticationException {
+		OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue);
+		if (refreshToken == null) {
+			throw new InvalidTokenException("Refresh token for value " + refreshTokenValue + " was not found");
+		}
+		else {
+			return refreshToken;	
+		}
+    }
+	
+	@Override
+    public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
+	    tokenRepository.clearAccessTokensForRefreshToken(refreshToken);
+	    tokenRepository.removeRefreshToken(refreshToken);	    
+    }
+
+	@Override
+    public void revokeAccessToken(OAuth2AccessTokenEntity accessToken) {
+		tokenRepository.removeAccessToken(accessToken);	    
+    }
+	
+
+	/* (non-Javadoc)
+     * @see org.mitre.oauth2.service.OAuth2TokenEntityService#getAccessTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+     */
+    @Override
+    public List<OAuth2AccessTokenEntity> getAccessTokensForClient(ClientDetailsEntity client) {
+	    return tokenRepository.getAccessTokensForClient(client);
+    }
+
+	/* (non-Javadoc)
+     * @see org.mitre.oauth2.service.OAuth2TokenEntityService#getRefreshTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity)
+     */
+    @Override
+    public List<OAuth2RefreshTokenEntity> getRefreshTokensForClient(ClientDetailsEntity client) {
+    	return tokenRepository.getRefreshTokensForClient(client);
+    }
+
+    @Override
+    @Scheduled(fixedRate = 5 * 60 * 1000) // schedule this task every five minutes
+    public void clearExpiredTokens() {
+    	logger.info("Cleaning out all expired tokens");
+    	
+    	List<OAuth2AccessTokenEntity> accessTokens = tokenRepository.getExpiredAccessTokens();
+    	logger.info("Found " + accessTokens.size() + " expired access tokens");
+    	for (OAuth2AccessTokenEntity oAuth2AccessTokenEntity : accessTokens) {
+	        revokeAccessToken(oAuth2AccessTokenEntity);
+        }
+    	
+    	List<OAuth2RefreshTokenEntity> refreshTokens = tokenRepository.getExpiredRefreshTokens();
+    	logger.info("Found " + refreshTokens.size() + " expired refresh tokens");
+    	for (OAuth2RefreshTokenEntity oAuth2RefreshTokenEntity : refreshTokens) {
+	        revokeRefreshToken(oAuth2RefreshTokenEntity);
+        }
+    }
+    
+    /**
+	 * Get a builder object for this class (for tests)
+	 * @return
+	 */
+	public static DefaultOAuth2ProviderTokenServicesBuilder makeBuilder() {
+		return new DefaultOAuth2ProviderTokenServicesBuilder();
+	}
+	
+	/**
+	 * Builder class for test harnesses.
+	 */
+	public static class DefaultOAuth2ProviderTokenServicesBuilder {
+		private DefaultOAuth2ProviderTokenService instance;
+		
+		private DefaultOAuth2ProviderTokenServicesBuilder() {
+			instance = new DefaultOAuth2ProviderTokenService();
+		}
+		
+		public DefaultOAuth2ProviderTokenServicesBuilder setTokenRepository(OAuth2TokenRepository tokenRepository) {
+			instance.tokenRepository = tokenRepository;
+			return this;
+		}
+		
+		public DefaultOAuth2ProviderTokenServicesBuilder setClientDetailsService(ClientDetailsEntityService clientDetailsService) {
+			instance.clientDetailsService = clientDetailsService;
+			return this;
+		}
+		
+		public DefaultOAuth2ProviderTokenServicesBuilder setAccessTokenFactory(OAuth2AccessTokenEntityFactory accessTokenFactory) {
+			instance.accessTokenFactory = accessTokenFactory;
+			return this;
+		}
+		
+		public DefaultOAuth2ProviderTokenServicesBuilder setRefreshTokenFactory(OAuth2RefreshTokenEntityFactory refreshTokenFactory) {
+			instance.refreshTokenFactory = refreshTokenFactory;
+			return this;
+		}
+		
+		public OAuth2TokenEntityService finish() {
+			return instance;
+		}
+	}
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java b/server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java
new file mode 100644
index 000000000..e47490c39
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java
@@ -0,0 +1,43 @@
+package org.mitre.oauth2.web;
+
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+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 IntrospectionEndpoint {
+
+	@Autowired
+	OAuth2TokenEntityService tokenServices;
+	
+	public IntrospectionEndpoint() {
+		
+	}
+	
+	public IntrospectionEndpoint(OAuth2TokenEntityService tokenServices) {
+		this.tokenServices = tokenServices;
+	}
+	
+	// TODO
+	@RequestMapping("/oauth/verify")
+	public ModelAndView verify(@RequestParam("token") String tokenValue, 
+			ModelAndView modelAndView) {
+		OAuth2AccessTokenEntity token = tokenServices.getAccessToken(tokenValue);
+		
+		if (token == null) {
+			// if it's not a valid token, we'll print a 404
+			modelAndView.setViewName("tokenNotFound");
+		} else {
+			// if it's a valid token, we'll print out the scope and expiration
+			modelAndView.setViewName("tokenIntrospection");
+			modelAndView.addObject("entity", token);
+		}
+		
+		return modelAndView;
+	}
+	
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/OAuthClientAPI.java b/server/src/main/java/org/mitre/oauth2/web/OAuthClientAPI.java
new file mode 100644
index 000000000..8182df153
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/OAuthClientAPI.java
@@ -0,0 +1,201 @@
+package org.mitre.oauth2.web;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.exception.ClientNotFoundException;
+import org.mitre.oauth2.exception.DuplicateClientIdException;
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.GrantedAuthorityImpl;
+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;
+
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+@Controller
+@RequestMapping("/manager/oauth/clients/api")
+public class OAuthClientAPI {
+
+	@Autowired
+	private ClientDetailsEntityService clientService;
+	
+	private static final Logger logger = LoggerFactory.getLogger(OAuthClientAPI.class);
+	
+	public OAuthClientAPI() {
+		
+	}
+	
+	public OAuthClientAPI(ClientDetailsEntityService clientService) {
+		this.clientService = clientService;
+	}
+	
+	// TODO: i think this needs a fancier binding than just strings on the way in
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/add")
+    public ModelAndView apiAddClient(ModelAndView modelAndView,
+    		@RequestParam String clientId, @RequestParam String clientSecret, 
+    		@RequestParam String scope, // space delimited 
+    		@RequestParam String grantTypes, // space delimited
+    		@RequestParam(required=false) String redirectUri, 
+    		@RequestParam String authorities, // space delimited
+    		@RequestParam(required=false) String resourceIds, // space delimited
+    		@RequestParam(required=false) String name, 
+    		@RequestParam(required=false) String description, 
+    		@RequestParam(required=false, defaultValue="false") boolean allowRefresh,
+    		@RequestParam(required=false) Long accessTokenTimeout, 
+    		@RequestParam(required=false) Long refreshTokenTimeout, 
+    		@RequestParam(required=false) String owner
+    		) {
+    	logger.info("apiAddClient - start");
+    	ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);
+    	if (oldClient != null) {
+    		throw new DuplicateClientIdException(clientId);
+    	}
+    	
+    	Splitter spaceDelimited = Splitter.on(" ");
+    	// parse all of our space-delimited lists
+    	Set<String> scopeSet = Sets.newHashSet(spaceDelimited.split(scope));
+    	Set<String> grantTypesSet = Sets.newHashSet(spaceDelimited.split(grantTypes)); // TODO: make a stronger binding to GrantTypes
+    	logger.info("apiAddClient - before creating authorities list");
+    	Set<GrantedAuthority> authoritiesSet = Sets.newHashSet(
+    			Iterables.transform(spaceDelimited.split(authorities), new Function<String, GrantedAuthority>() {
+    				@Override
+    				public GrantedAuthority apply(String auth) {
+    					return new GrantedAuthorityImpl(auth);
+    				}
+    			}));
+    	logger.info("apiAddClient - printing client details");
+    	logger.info("Making call to create client with " + clientId + ", " + clientSecret 
+    			+ ", " + scopeSet + ", " + grantTypesSet + ", " + redirectUri + ", " 
+    			+ authoritiesSet + ", " + name + ", " + description + ", " + allowRefresh 
+    			+ ", " + accessTokenTimeout + ", " + refreshTokenTimeout + ", " + owner);
+    	
+    	Set<String> resourceIdSet = Sets.newHashSet(spaceDelimited.split(resourceIds));
+    	
+		ClientDetailsEntity client = clientService.createClient(clientId, clientSecret, 
+    			scopeSet, grantTypesSet, redirectUri, authoritiesSet, resourceIdSet, name, description, 
+    			allowRefresh, accessTokenTimeout, refreshTokenTimeout, owner);
+    	logger.info("apiAddClient - adding model objects");
+    	modelAndView.addObject("entity", client);
+    	modelAndView.setViewName("jsonOAuthClientView");
+    	logger.info("apiAddClient - end");
+    	return modelAndView;
+    }
+
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/delete")
+    public ModelAndView apiDeleteClient(ModelAndView modelAndView,
+    		@RequestParam String clientId) {
+    	
+    	ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+    	
+    	if (client == null) {
+    		throw new ClientNotFoundException("Client not found: " + clientId);
+    	}
+    	
+    	clientService.deleteClient(client);
+    	
+    	modelAndView.setViewName("management/successfullyRemoved");
+    	return modelAndView;
+    }
+
+	// TODO: the serializtion of this falls over, don't know why
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+    @RequestMapping("/getAll")
+    public ModelAndView apiGetAllClients(ModelAndView modelAndView) {
+    	
+    	Collection<ClientDetailsEntity> clients = clientService.getAllClients();
+    	modelAndView.addObject("entity", clients);
+    	modelAndView.setViewName("jsonOAuthClientView");
+    	
+    	return modelAndView;
+    }
+
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/update")
+    public ModelAndView apiUpdateClient(ModelAndView modelAndView,
+    		@RequestParam String clientId, @RequestParam String clientSecret, 
+    		@RequestParam String scope, // space delimited 
+    		@RequestParam String grantTypes, // space delimited
+    		@RequestParam(required=false) String redirectUri, 
+    		@RequestParam String authorities, // space delimited
+    		@RequestParam(required=false) String resourceIds, // space delimited
+    		@RequestParam(required=false) String name, 
+    		@RequestParam(required=false) String description, 
+    		@RequestParam(required=false, defaultValue="false") boolean allowRefresh,
+    		@RequestParam(required=false) Long accessTokenTimeout, 
+    		@RequestParam(required=false) Long refreshTokenTimeout, 
+    		@RequestParam(required=false) String owner			
+    ) {
+    	ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+    	
+    	if (client == null) {
+    		throw new ClientNotFoundException("Client not found: " + clientId);
+    	}
+    	
+    	Splitter spaceDelimited = Splitter.on(" ");
+    	// parse all of our space-delimited lists
+    	Set<String> scopeSet = Sets.newHashSet(spaceDelimited.split(scope));
+    	Set<String> grantTypesSet = Sets.newHashSet(spaceDelimited.split(grantTypes)); // TODO: make a stronger binding to GrantTypes
+    	Set<GrantedAuthority> authoritiesSet = Sets.newHashSet(
+    			Iterables.transform(spaceDelimited.split(authorities), new Function<String, GrantedAuthority>() {
+    				@Override
+    				public GrantedAuthority apply(String auth) {
+    					return new GrantedAuthorityImpl(auth);
+    				}
+    			}));
+    	Set<String> resourceIdSet = Sets.newHashSet(spaceDelimited.split(resourceIds));
+    	
+    	
+    	client.setClientSecret(clientSecret);
+    	client.setScope(scopeSet);
+    	client.setAuthorizedGrantTypes(grantTypesSet);
+    	client.setRegisteredRedirectUri(redirectUri);
+    	client.setAuthorities(authoritiesSet);
+    	client.setResourceIds(resourceIdSet);
+    	client.setClientName(name);
+    	client.setClientDescription(description);
+    	client.setAllowRefresh(allowRefresh);
+    	client.setAccessTokenTimeout(accessTokenTimeout);
+    	client.setRefreshTokenTimeout(refreshTokenTimeout);
+    	client.setOwner(owner);		
+    	
+    	clientService.updateClient(client, client);
+    	
+    	modelAndView.addObject("entity", client);
+    	modelAndView.setViewName("jsonOAuthClientView");
+    	
+    	return modelAndView;
+    }
+
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/getById")
+    public ModelAndView getClientById(ModelAndView modelAndView,
+    		@RequestParam String clientId) {
+    	
+    	ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+    	
+    	if (client == null) {
+    		throw new ClientNotFoundException("Client not found: " + clientId);
+    	}
+    	
+    	modelAndView.addObject("entity", client);
+    	modelAndView.setViewName("jsonOAuthClientView");
+    
+    	return modelAndView;
+    }
+
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/OAuthClientController.java b/server/src/main/java/org/mitre/oauth2/web/OAuthClientController.java
new file mode 100644
index 000000000..f96f67531
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/OAuthClientController.java
@@ -0,0 +1,166 @@
+/**
+ * 
+ */
+package org.mitre.oauth2.web;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.GrantedAuthorityImpl;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import com.google.common.collect.Sets;
+
+
+/**
+ * 
+ * Endpoint for managing OAuth2 clients
+ * 
+ * @author jricher
+ *
+ */
+@Controller
+@RequestMapping("/manager/oauth/clients")
+public class OAuthClientController {
+	
+	private final static Set<String> GRANT_TYPES = Sets.newHashSet("authorization_code", "client_credentials", "password", "implicit");
+
+	@Autowired
+	private ClientDetailsEntityService clientService;
+
+	@Autowired
+	private OAuth2TokenEntityService tokenService;
+	
+	private Logger logger;
+	
+	public OAuthClientController() {
+		logger = LoggerFactory.getLogger(this.getClass());
+	}
+	
+	public OAuthClientController(ClientDetailsEntityService clientService, OAuth2TokenEntityService tokenService) {
+		this.clientService = clientService;
+		this.tokenService = tokenService;
+		logger = LoggerFactory.getLogger(this.getClass());
+	}
+	
+	/**
+	 * Redirect to the "/" version of the root
+	 * @param modelAndView
+	 * @return
+	 */
+	@RequestMapping("")
+	public ModelAndView redirectRoot(ModelAndView modelAndView) {
+		modelAndView.setViewName("redirect:/manager/oauth/clients/");
+		return modelAndView;
+	}
+	
+	/**
+	 * View all clients
+	 * @param modelAndView
+	 * @return
+	 */
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/")
+	public ModelAndView viewAllClients(ModelAndView modelAndView) {
+		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+		//ClientAuthenticationToken clientAuth = (ClientAuthenticationToken) ((OAuth2Authentication) auth).getClientAuthentication();
+		AuthorizationRequest clientAuth = ((OAuth2Authentication) auth).getAuthorizationRequest();
+		
+		logger.info("Client auth = " + clientAuth);		
+		logger.info("Granted authorities = " + clientAuth.getAuthorities().toString());
+		
+		Collection<ClientDetailsEntity> clients = clientService.getAllClients();
+		modelAndView.addObject("clients", clients);
+		modelAndView.setViewName("/management/oauth/clientIndex");
+		
+		return modelAndView;
+	}
+	
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/add")
+	public ModelAndView redirectAdd(ModelAndView modelAndView) {
+		modelAndView.setViewName("redirect:/manager/oauth/clients/add/");
+		return modelAndView;
+	}
+	
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/add/")
+	public ModelAndView addClientPage(ModelAndView modelAndView) {
+		
+		Set<GrantedAuthority> auth = Sets.newHashSet();
+		auth.add(new GrantedAuthorityImpl("ROLE_CLIENT"));
+		
+		ClientDetailsEntity client = ClientDetailsEntity.makeBuilder()
+				.setScope(Sets.newHashSet("scope"))
+				.setAuthorities(auth) // why do we have to pull this into a separate list?
+				.setAuthorizedGrantTypes(Sets.newHashSet("authorization_code"))
+				.finish();
+		modelAndView.addObject("availableGrantTypes", GRANT_TYPES);
+		modelAndView.addObject("client", client);
+		
+		modelAndView.setViewName("/management/oauth/editClient");
+		return modelAndView;
+	}
+	
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/delete/{clientId}")
+	public ModelAndView deleteClientConfirmation(ModelAndView modelAndView,
+			@PathVariable String clientId) {
+
+		ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+		modelAndView.addObject("client", client);
+		modelAndView.setViewName("/management/oauth/deleteClientConfirm");
+		
+		return modelAndView;
+	}
+
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/edit/{clientId}")
+	public ModelAndView editClientPage(ModelAndView modelAndView,
+			@PathVariable String clientId) {
+		
+		ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+		
+		modelAndView.addObject("availableGrantTypes", GRANT_TYPES);
+		modelAndView.addObject("client", client);
+		modelAndView.setViewName("/management/oauth/editClient");
+		
+		return modelAndView;
+	}
+	
+	@PreAuthorize("hasRole('ROLE_ADMIN')")
+	@RequestMapping("/view/{clientId}")
+	public ModelAndView viewClientDetails(ModelAndView modelAndView,
+			@PathVariable String clientId) {
+		
+		ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+		
+		List<OAuth2AccessTokenEntity> accessTokens = tokenService.getAccessTokensForClient(client);
+		List<OAuth2RefreshTokenEntity> refreshTokens = tokenService.getRefreshTokensForClient(client);
+		
+		modelAndView.addObject("client", client);
+		modelAndView.addObject("accessTokens", accessTokens);
+		modelAndView.addObject("refreshTokens", refreshTokens);
+		
+		modelAndView.setViewName("/management/oauth/viewClient");
+		return modelAndView;
+	}
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java b/server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java
new file mode 100644
index 000000000..f11b509c8
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java
@@ -0,0 +1,70 @@
+/**
+ * 
+ */
+package org.mitre.oauth2.web;
+
+import org.mitre.oauth2.exception.ClientNotFoundException;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.SessionAttributes;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * @author jricher
+ *
+ */
+@Controller
+@SessionAttributes(types = AuthorizationRequest.class)
+public class OAuthConfirmationController {
+
+	private ClientDetailsEntityService clientService;
+	
+	public OAuthConfirmationController() {
+		
+	}
+	
+	public OAuthConfirmationController(ClientDetailsEntityService clientService) {
+		this.clientService = clientService;
+	}
+	
+	@PreAuthorize("hasRole('ROLE_USER')")
+	@RequestMapping("/oauth/user/approve")
+	public ModelAndView confimAccess(@ModelAttribute AuthorizationRequest clientAuth, 
+			ModelAndView modelAndView) {
+		
+		ClientDetails client = clientService.loadClientByClientId(clientAuth.getClientId());
+		
+		if (client == null) {
+			throw new ClientNotFoundException("Client not found: " + clientAuth.getClientId());
+		}
+		
+		modelAndView.addObject("auth_request", clientAuth);
+	    modelAndView.addObject("client", client);
+	    modelAndView.setViewName("oauth/approve");
+	    
+	    return modelAndView;
+	}
+
+	/**
+     * @return the clientService
+     */
+    public ClientDetailsEntityService getClientService() {
+    	return clientService;
+    }
+
+	/**
+     * @param clientService the clientService to set
+     */
+    @Autowired
+    public void setClientService(ClientDetailsEntityService clientService) {
+    	this.clientService = clientService;
+    }
+	
+	
+}
diff --git a/server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java b/server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java
new file mode 100644
index 000000000..324c94f0e
--- /dev/null
+++ b/server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java
@@ -0,0 +1,79 @@
+package org.mitre.oauth2.web;
+
+import org.mitre.oauth2.exception.PermissionDeniedException;
+import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
+import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+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 RevocationEndpoint {
+	@Autowired
+	OAuth2TokenEntityService tokenServices;
+	
+	public RevocationEndpoint() {
+		
+	}
+	
+	public RevocationEndpoint(OAuth2TokenEntityService tokenServices) {
+		this.tokenServices = tokenServices;
+	}
+	
+	// TODO
+	@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
+	@RequestMapping("/oauth/revoke")
+	public ModelAndView revoke(@RequestParam("token") String tokenValue, 
+			ModelAndView modelAndView) {
+
+		OAuth2RefreshTokenEntity refreshToken = tokenServices.getRefreshToken(tokenValue);
+		OAuth2AccessTokenEntity accessToken = tokenServices.getAccessToken(tokenValue);
+
+		if (refreshToken == null && accessToken == null) {
+			// TODO: this should throw a 400 with a JSON error code
+			throw new InvalidTokenException("Invalid OAuth token: " + tokenValue);
+		}
+		
+		// TODO: there should be a way to do this in SPEL, right?
+		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+		if (auth instanceof OAuth2Authentication) {
+			// we've got a client acting on its own behalf, not an admin
+			//ClientAuthentication clientAuth = (ClientAuthenticationToken) ((OAuth2Authentication) auth).getClientAuthentication();
+			AuthorizationRequest clientAuth = ((OAuth2Authentication) auth).getAuthorizationRequest();
+
+			if (refreshToken != null) {
+				if (!refreshToken.getClient().getClientId().equals(clientAuth.getClientId())) {
+					// trying to revoke a token we don't own, fail
+					// TODO: this should throw a 403 
+					throw new PermissionDeniedException("Client tried to revoke a token it doesn't own");
+				}
+			} else {
+				if (!accessToken.getClient().getClientId().equals(clientAuth.getClientId())) {
+					// trying to revoke a token we don't own, fail
+					// TODO: this should throw a 403 
+					throw new PermissionDeniedException("Client tried to revoke a token it doesn't own");
+				}
+			}
+		}
+		
+		// if we got this far, we're allowed to do this
+		if (refreshToken != null) {
+			tokenServices.revokeRefreshToken(refreshToken);
+		} else {
+			tokenServices.revokeAccessToken(accessToken);
+		}
+		
+		// TODO: throw a 200 back (no content?)
+		return modelAndView;
+	}
+
+}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/Address.java b/server/src/main/java/org/mitre/openid/connect/model/Address.java
index f74b968e6..9fefc09b8 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/Address.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/Address.java
@@ -1,10 +1,14 @@
 package org.mitre.openid.connect.model;
 
 import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
 
 @Entity
 public class Address {
 
+	private Long id;
 	private String formatted;
 	private String street_address;
 	private String locality;
@@ -12,6 +16,8 @@ public class Address {
 	private String postal_code;
 	private String country;
 	
+	
+	
 	/**
 	 * @return the formatted
 	 */
@@ -84,5 +90,21 @@ public class Address {
 	public void setCountry(String country) {
 		this.country = country;
 	}
+
+	/**
+     * @return the id
+     */
+	@Id
+	@GeneratedValue(strategy=GenerationType.IDENTITY)
+	public Long getId() {
+    	return id;
+    }
+
+	/**
+     * @param id the id to set
+     */
+    public void setId(Long id) {
+    	this.id = id;
+    }
 		
 }
diff --git a/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java b/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
index 3f16c4d03..4b6e4682a 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
@@ -8,6 +8,8 @@ import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
 import javax.persistence.Temporal;
 
 import org.springframework.security.oauth2.provider.ClientDetails;
@@ -48,7 +50,7 @@ public class ApprovedSite {
      * @return the id
      */
 	@Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
     public Long getId() {
     	return id;
     }
@@ -63,6 +65,7 @@ public class ApprovedSite {
 	/**
      * @return the userInfo
      */
+    @ManyToOne
     public UserInfo getUserInfo() {
     	return userInfo;
     }
@@ -123,6 +126,7 @@ public class ApprovedSite {
 	/**
      * @return the allowedScopes
      */
+    @OneToMany
     public Collection<String> getAllowedScopes() {
     	return allowedScopes;
     }
diff --git a/server/src/main/java/org/mitre/openid/connect/model/Event.java b/server/src/main/java/org/mitre/openid/connect/model/Event.java
index d287ad996..bc776c423 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/Event.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/Event.java
@@ -2,7 +2,12 @@ package org.mitre.openid.connect.model;
 
 import java.util.Date;
 
+import javax.persistence.Basic;
 import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Temporal;
 
 /**
  * Class to contain a logged event in the system.
@@ -19,5 +24,46 @@ public class Event {
 	private Long id;
 	private EventType type;
 	private Date timestamp;
+
+	/**
+     * @return the id
+     */
+	@Id
+	@GeneratedValue(strategy=GenerationType.IDENTITY)
+    public Long getId() {
+    	return id;
+    }
+	/**
+     * @param id the id to set
+     */
+    public void setId(Long id) {
+    	this.id = id;
+    }
+	/**
+     * @return the type
+     */
+    public EventType getType() {
+    	return type;
+    }
+	/**
+     * @param type the type to set
+     */
+    public void setType(EventType type) {
+    	this.type = type;
+    }
+	/**
+     * @return the timestamp
+     */
+    @Basic
+    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
+    public Date getTimestamp() {
+    	return timestamp;
+    }
+	/**
+     * @param timestamp the timestamp to set
+     */
+    public void setTimestamp(Date timestamp) {
+    	this.timestamp = timestamp;
+    }
 	
 }
diff --git a/server/src/main/java/org/mitre/openid/connect/model/IdToken.java b/server/src/main/java/org/mitre/openid/connect/model/IdToken.java
deleted file mode 100644
index 5a08edb4f..000000000
--- a/server/src/main/java/org/mitre/openid/connect/model/IdToken.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.mitre.openid.connect.model;
-
-import javax.persistence.Entity;
-
-import org.mitre.jwt.model.Jwt;
-
-/*
- * TODO: This class needs to be encoded as a JWT
- */
-@Entity
-public class IdToken extends Jwt {
-
-	private String iss;
-	private String user_id;
-	private String aud;
-	private String exp;
-	private String iso29115;
-	private String nonce;
-	private String auth_time;
-	/**
-	 * @return the iss
-	 */
-	public String getIss() {
-		return iss;
-	}
-	/**
-	 * @param iss the iss to set
-	 */
-	public void setIss(String iss) {
-		this.iss = iss;
-	}
-	/**
-	 * @return the user_id
-	 */
-	public String getUser_id() {
-		return user_id;
-	}
-	/**
-	 * @param user_id the user_id to set
-	 */
-	public void setUser_id(String user_id) {
-		this.user_id = user_id;
-	}
-	/**
-	 * @return the aud
-	 */
-	public String getAud() {
-		return aud;
-	}
-	/**
-	 * @param aud the aud to set
-	 */
-	public void setAud(String aud) {
-		this.aud = aud;
-	}
-	/**
-	 * @return the exp
-	 */
-	public String getExp() {
-		return exp;
-	}
-	/**
-	 * @param exp the exp to set
-	 */
-	public void setExp(String exp) {
-		this.exp = exp;
-	}
-	/**
-	 * @return the iso29115
-	 */
-	public String getIso29115() {
-		return iso29115;
-	}
-	/**
-	 * @param iso29115 the iso29115 to set
-	 */
-	public void setIso29115(String iso29115) {
-		this.iso29115 = iso29115;
-	}
-	/**
-	 * @return the nonce
-	 */
-	public String getNonce() {
-		return nonce;
-	}
-	/**
-	 * @param nonce the nonce to set
-	 */
-	public void setNonce(String nonce) {
-		this.nonce = nonce;
-	}
-	/**
-	 * @return the auth_time
-	 */
-	public String getAuth_time() {
-		return auth_time;
-	}
-	/**
-	 * @param auth_time the auth_time to set
-	 */
-	public void setAuth_time(String auth_time) {
-		this.auth_time = auth_time;
-	}
-	
-}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/IdTokenClaims.java b/server/src/main/java/org/mitre/openid/connect/model/IdTokenClaims.java
new file mode 100644
index 000000000..23c2f2e57
--- /dev/null
+++ b/server/src/main/java/org/mitre/openid/connect/model/IdTokenClaims.java
@@ -0,0 +1,77 @@
+package org.mitre.openid.connect.model;
+
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+import org.mitre.jwt.model.Jwt;
+import org.mitre.jwt.model.JwtClaims;
+
+/*
+ * TODO: This class needs to be encoded as a JWT
+ */
+@Entity
+public class IdTokenClaims extends JwtClaims {
+
+	public static final String USER_ID = "user_id";
+	public static final String AUTHENTICATION_CONTEXT_CLASS_REFERENCE = "acr";
+	public static final String NONCE = "nonce";
+	public static final String AUTH_TIME = "auth_time";
+
+	private Long id;
+	
+	/**
+     * @return the id
+     */
+	@Id
+	@GeneratedValue(strategy=GenerationType.IDENTITY)
+    public Long getId() {
+    	return id;
+    }
+	/**
+     * @param id the id to set
+     */
+    public void setId(Long id) {
+    	this.id = id;
+    }
+
+
+	public String getUserId() {
+		return getClaimAsString(USER_ID);
+	}
+	
+	public void setUserId(String user_id) {
+		setClaim(USER_ID, user_id);
+	}
+	
+
+	public String getAuthContext() {
+		return getClaimAsString(AUTHENTICATION_CONTEXT_CLASS_REFERENCE);		
+	}
+	
+	public void setAuthContext(String acr) {
+		setClaim(AUTHENTICATION_CONTEXT_CLASS_REFERENCE, acr);
+	}
+	
+	
+	public String getNonce() {
+		return getClaimAsString(NONCE);
+	}
+	
+	public void setNonce(String nonce) {
+		setClaim(NONCE, nonce);
+	}
+	
+	
+	public Date getAuthTime() {
+		return getClaimAsDate(AUTH_TIME);
+	}
+	
+	public void setAuthTime(Date authTime) {
+		setClaim(AUTH_TIME, authTime);
+	}
+	
+}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java b/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
index 67c8a2687..acbebd898 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
@@ -1,6 +1,7 @@
 package org.mitre.openid.connect.model;
 
 import javax.persistence.Entity;
+import javax.persistence.Id;
 
 @Entity
 public class UserInfo {
@@ -29,6 +30,7 @@ public class UserInfo {
 	/**
 	 * @return the user_id
 	 */
+	@Id
 	public String getUser_id() {
 		return user_id;
 	}
diff --git a/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java b/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
index ca5b4987d..019988888 100644
--- a/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
+++ b/server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
@@ -6,6 +6,7 @@ import java.util.Date;
 import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
+import javax.persistence.ManyToOne;
 
 import org.springframework.security.oauth2.provider.ClientDetails;
 
@@ -19,7 +20,7 @@ public class WhitelistedSite {
 
     // unique id
 	@Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
     
     // who added this site to the whitelist (should be an admin)
@@ -30,5 +31,6 @@ public class WhitelistedSite {
 	
 	// what scopes be allowed by default
 	// this should include all information for what data to access
+	@ManyToOne
 	private Collection<String> allowedScopes;
 }
diff --git a/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java b/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java
index 98676bb66..e67e7c597 100644
--- a/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java
+++ b/server/src/main/java/org/mitre/openid/connect/repository/IdTokenRepository.java
@@ -1,11 +1,11 @@
 package org.mitre.openid.connect.repository;
 
-import org.mitre.openid.connect.model.IdToken;
+import org.mitre.openid.connect.model.IdTokenClaims;
 
 public interface IdTokenRepository {
 
-	public IdToken getById(Long id);
+	public IdTokenClaims getById(Long id);
 	
-	public IdToken save(IdToken idToken);
+	public IdTokenClaims save(IdTokenClaims idToken);
 	
 }
diff --git a/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java b/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java
index 935509d85..1430acef9 100644
--- a/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java
+++ b/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java
@@ -1,6 +1,6 @@
 package org.mitre.openid.connect.web;
 
-import org.mitre.openid.connect.model.IdToken;
+import org.mitre.openid.connect.model.IdTokenClaims;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -13,7 +13,7 @@ public class CheckIDEndpoint {
 	@RequestMapping("/")
 	public ModelAndView checkID(@RequestParam("id_token") String idToken, ModelAndView mav) {
 		
-		IdToken token = new IdToken();
+		IdTokenClaims token = new IdTokenClaims();
 		
 		//TODO: Set claims
 		
diff --git a/server/src/main/java/org/mitre/pushee/openid/service/impl/OpenIDUserDetailsService.java b/server/src/main/java/org/mitre/pushee/openid/service/impl/OpenIDUserDetailsService.java
new file mode 100644
index 000000000..4d608930e
--- /dev/null
+++ b/server/src/main/java/org/mitre/pushee/openid/service/impl/OpenIDUserDetailsService.java
@@ -0,0 +1,98 @@
+package org.mitre.pushee.openid.service.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.GrantedAuthorityImpl;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+
+public class OpenIDUserDetailsService implements UserDetailsService {
+
+    private String openIdRoot;
+    private String admins;
+    private List<String> adminList = new ArrayList<String>();
+
+    private GrantedAuthority roleUser = new GrantedAuthorityImpl("ROLE_USER");
+	private GrantedAuthority roleAdmin = new GrantedAuthorityImpl("ROLE_ADMIN");
+
+    /**
+     * @return the roleUser
+     */
+    public GrantedAuthority getRoleUser() {
+    	return roleUser;
+    }
+
+	/**
+     * @param roleUser the roleUser to set
+     */
+    public void setRoleUser(GrantedAuthority roleUser) {
+    	this.roleUser = roleUser;
+    }
+
+	/**
+     * @return the roleAdmin
+     */
+    public GrantedAuthority getRoleAdmin() {
+    	return roleAdmin;
+    }
+
+	/**
+     * @param roleAdmin the roleAdmin to set
+     */
+    public void setRoleAdmin(GrantedAuthority roleAdmin) {
+    	this.roleAdmin = roleAdmin;
+    }
+
+    public String getOpenIdRoot() {
+        return openIdRoot;
+    }
+
+    public void setOpenIdRoot(String openIdRoot) {
+        this.openIdRoot = openIdRoot;
+    }
+
+    public String getAdmins() {
+        return admins;
+    }
+
+    public void setAdmins(String admins) {
+        this.admins = admins;
+        adminList.clear();
+        Iterables.addAll(adminList, Splitter.on(',').omitEmptyStrings().split(admins));
+    }
+
+    public UserDetails loadUserByUsername(String identifier) throws UsernameNotFoundException, DataAccessException {
+
+        if (identifier != null && identifier.startsWith(openIdRoot)) {
+            String username = identifier;
+            //String username = identifier.replace(openIdRoot, ""); // strip off the OpenID root
+            String password = "notused";
+            boolean enabled = true;
+            boolean accountNonExpired = true;
+            boolean credentialsNonExpired = true;
+            boolean accountNonLocked = true;
+            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
+            authorities.add(roleUser);
+
+            // calculate raw user id (SUI)
+            // TODO: make this more generic, right now only works with postfix names
+            String userid = identifier.replace(openIdRoot, "");
+
+            if (adminList.contains(userid)) {
+                authorities.add(roleAdmin);
+            }
+
+            return new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
+        } else {
+            throw new UsernameNotFoundException("Identifier " + identifier + " did not match OpenID root " + openIdRoot);
+        }
+    }
+}
diff --git a/server/src/main/java/org/mitre/util/jpa/JpaUtil.java b/server/src/main/java/org/mitre/util/jpa/JpaUtil.java
new file mode 100644
index 000000000..9ce03c1b9
--- /dev/null
+++ b/server/src/main/java/org/mitre/util/jpa/JpaUtil.java
@@ -0,0 +1,37 @@
+package org.mitre.util.jpa;
+
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import java.util.List;
+
+/**
+ * @author mfranklin
+ *         Date: 4/28/11
+ *         Time: 2:13 PM
+ */
+public class JpaUtil {
+    public static <T> T getSingleResult(List<T> list) {
+        switch(list.size()) {
+            case 0:
+                return null;
+            case 1:
+                return list.get(0);
+            default:
+                throw new IncorrectResultSizeDataAccessException(1);
+        }
+    }
+
+    public static <T, I> T saveOrUpdate(I id, EntityManager entityManager, T entity) {
+        if (id == null) {
+            entityManager.persist(entity);
+            entityManager.flush();
+            return entity;
+        } else {
+            T tmp = entityManager.merge(entity);
+            entityManager.flush();
+            return tmp;
+        }
+    }
+}
diff --git a/spring-security-oauth b/spring-security-oauth
index 7a55ca546..0aa98f094 160000
--- a/spring-security-oauth
+++ b/spring-security-oauth
@@ -1 +1 @@
-Subproject commit 7a55ca546a3567a8a1833340d0ff9e2b47cf4bdc
+Subproject commit 0aa98f09467f90b36ed06ba5cdbb82722f08de26
diff --git a/upstream-patch/README.txt b/upstream-patch/README.txt
new file mode 100644
index 000000000..cf7d5d9e5
--- /dev/null
+++ b/upstream-patch/README.txt
@@ -0,0 +1 @@
+This directory contains .patch files which must be applied to the upstream spring-security-oauth2 module in order for the project to build.
diff --git a/upstream-patch/secoauth_183.patch b/upstream-patch/secoauth_183.patch
new file mode 100644
index 000000000..43e148be1
--- /dev/null
+++ b/upstream-patch/secoauth_183.patch
@@ -0,0 +1,113 @@
+diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java
+index 20d2512..a773b29 100644
+--- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java
++++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java
+@@ -9,9 +9,17 @@ public class ExpiringOAuth2RefreshToken extends OAuth2RefreshToken {
+ 
+ 	private static final long serialVersionUID = 3449554332764129719L;
+ 
+-	private final Date expiration;
++	private Date expiration;
+ 
+ 	/**
++	 * Create an expiring refresh token with a null value and no expiration
++	 * @param expiration
++	 */
++	public ExpiringOAuth2RefreshToken() {
++		this(null, null);
++	}
++	
++	/**
+ 	 * @param value
+ 	 */
+ 	public ExpiringOAuth2RefreshToken(String value, Date expiration) {
+@@ -28,4 +36,11 @@ public class ExpiringOAuth2RefreshToken extends OAuth2RefreshToken {
+ 		return expiration;
+ 	}
+ 
++	/**
++	 * Set the expiration of this token
++	 */
++	public void setExpiration(Date expiration) {
++		this.expiration = expiration;
++	}
++	
+ }
+diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
+index 791780f..25edf77 100644
+--- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
++++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
+@@ -57,7 +57,7 @@ public class OAuth2AccessToken implements Serializable {
+ 	 */
+ 	public static String SCOPE = "scope";
+ 
+-	private final String value;
++	private String value;
+ 
+ 	private Date expiration;
+ 
+@@ -74,8 +74,10 @@ public class OAuth2AccessToken implements Serializable {
+ 		this.value = value;
+ 	}
+ 
+-	@SuppressWarnings("unused")
+-	private OAuth2AccessToken() {
++	/**
++	 * Create an access token with no value
++	 */
++	public OAuth2AccessToken() {
+ 		this(null);
+ 	}
+ 
+@@ -88,6 +90,14 @@ public class OAuth2AccessToken implements Serializable {
+ 		return value;
+ 	}
+ 
++	/**
++	 * Set the value of the token.
++	 * @param value the token value
++	 */
++	public void setValue(String value) {
++		this.value = value;
++	}
++	
+ 	public int getExpiresIn() {
+ 		return expiration != null ? Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
+ 				.intValue() : 0;
+diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java
+index 00b002c..96f3f1b 100644
+--- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java
++++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java
+@@ -15,9 +15,16 @@ public class OAuth2RefreshToken implements Serializable {
+ 
+ 	private static final long serialVersionUID = 8349970621900575838L;
+ 
+-	private final String value;
++	private String value;
+ 
+ 	/**
++	 * Create an empty token with no value
++	 */
++	public OAuth2RefreshToken() {
++		this(null);
++	}
++	
++	/**
+ 	 * Create a new refresh token.
+ 	 */
+ 	@JsonCreator
+@@ -35,6 +42,14 @@ public class OAuth2RefreshToken implements Serializable {
+ 		return value;
+ 	}
+ 
++	/**
++	 * Set the value of the token
++	 * @param value the value of the token
++	 */
++	public void setValue(String value) {
++		this.value = value;
++	}
++	
+ 	@Override
+ 	public String toString() {
+ 		return getValue();