1
0
mirror of synced 2026-05-22 21:33:16 +00:00

SEC-935: Support for OpenID attribute exchange and changes to namespace syntax to allow simple configuration of attributes to request.

This commit is contained in:
Luke Taylor
2009-08-13 23:55:25 +00:00
parent 5e4743d8f2
commit 48988bde84
16 changed files with 341 additions and 123 deletions
@@ -14,51 +14,63 @@
*/
package org.springframework.security.openid;
import org.openid4java.association.AssociationException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.MessageException;
import org.openid4java.message.ParameterList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.association.AssociationException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
/**
*
* @author Ray Krueger
* @version $Id$
*/
public class OpenID4JavaConsumer implements OpenIDConsumer {
private static final String DISCOVERY_INFO_KEY = DiscoveryInformation.class.getName();
//~ Instance fields ================================================================================================
protected final Log logger = LogFactory.getLog(getClass());
private final ConsumerManager consumerManager;
private List<OpenIDAttribute> attributesToFetch = Collections.emptyList();
//~ Constructors ===================================================================================================
public OpenID4JavaConsumer(ConsumerManager consumerManager) {
this.consumerManager = consumerManager;
public OpenID4JavaConsumer() throws ConsumerException {
this.consumerManager = new ConsumerManager();
}
public OpenID4JavaConsumer() throws ConsumerException {
this(new ConsumerManager());
public OpenID4JavaConsumer(List<OpenIDAttribute> attributes) throws ConsumerException {
this(new ConsumerManager(), attributes);
}
public OpenID4JavaConsumer(ConsumerManager consumerManager, List<OpenIDAttribute> attributes)
throws ConsumerException {
this.consumerManager = consumerManager;
this.attributesToFetch = Collections.unmodifiableList(attributes);
}
//~ Methods ========================================================================================================
public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl) throws OpenIDConsumerException {
return beginConsumption(req, identityUrl, returnToUrl, returnToUrl);
}
@SuppressWarnings("unchecked")
public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl, String realm)
throws OpenIDConsumerException {
@@ -71,31 +83,37 @@ public class OpenID4JavaConsumer implements OpenIDConsumer {
}
DiscoveryInformation information = consumerManager.associate(discoveries);
HttpSession session = req.getSession(true);
session.setAttribute(DiscoveryInformation.class.getName(), information);
req.getSession().setAttribute(DISCOVERY_INFO_KEY, information);
AuthRequest authReq;
try {
authReq = consumerManager.authenticate(information, returnToUrl, realm);
if (!attributesToFetch.isEmpty()) {
FetchRequest fetchRequest = FetchRequest.createFetchRequest();
for (OpenIDAttribute attr : attributesToFetch) {
fetchRequest.addAttribute(attr.getName(), attr.getType(), attr.isRequired(), attr.getCount());
}
authReq.addExtension(fetchRequest);
}
} catch (MessageException e) {
throw new OpenIDConsumerException("Error processing ConumerManager authentication", e);
throw new OpenIDConsumerException("Error processing ConsumerManager authentication", e);
} catch (ConsumerException e) {
throw new OpenIDConsumerException("Error processing ConumerManager authentication", e);
throw new OpenIDConsumerException("Error processing ConsumerManager authentication", e);
}
return authReq.getDestinationUrl(true);
}
public OpenIDAuthenticationToken endConsumption(HttpServletRequest request)
throws OpenIDConsumerException {
@SuppressWarnings("unchecked")
public OpenIDAuthenticationToken endConsumption(HttpServletRequest request) throws OpenIDConsumerException {
final boolean debug = logger.isDebugEnabled();
// extract the parameters from the authentication response
// (which comes in as a HTTP request from the OpenID provider)
ParameterList openidResp = new ParameterList(request.getParameterMap());
// retrieve the previously stored discovery information
DiscoveryInformation discovered = (DiscoveryInformation) request.getSession()
.getAttribute(DiscoveryInformation.class.getName());
DiscoveryInformation discovered = (DiscoveryInformation) request.getSession().getAttribute(DISCOVERY_INFO_KEY);
// extract the receiving URL from the HTTP request
StringBuffer receivingURL = request.getRequestURL();
@@ -118,16 +136,46 @@ public class OpenID4JavaConsumer implements OpenIDConsumer {
throw new OpenIDConsumerException("Error verifying openid response", e);
}
// fetch the attributesToFetch of the response
Message authSuccess = verification.getAuthResponse();
List<OpenIDAttribute> attributes = new ArrayList<OpenIDAttribute>(this.attributesToFetch.size());
if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
if (debug) {
logger.debug("Extracting attributes retrieved by attribute exchange");
}
try {
MessageExtension ext = authSuccess.getExtension(AxMessage.OPENID_NS_AX);
if (ext instanceof FetchResponse) {
FetchResponse fetchResp = (FetchResponse) ext;
for (OpenIDAttribute attr : attributesToFetch) {
List<String> values = fetchResp.getAttributeValues(attr.getName());
if (!values.isEmpty()) {
OpenIDAttribute fetched = new OpenIDAttribute(attr.getName(), attr.getType(), values);
fetched.setRequired(attr.isRequired());
attributes.add(fetched);
}
}
}
} catch (MessageException e) {
attributes.clear();
throw new OpenIDConsumerException("Attribute retrievel failed", e);
}
if (debug) {
logger.debug("Retrieved attributes" + attributes);
}
}
// examine the verification result and extract the verified identifier
Identifier verified = verification.getVerifiedId();
if (verified != null) {
return new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, verified.getIdentifier(),
"some message");
} else {
if (verified == null) {
return new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.FAILURE,
discovered.getClaimedIdentifier().getIdentifier(),
"Verification status message: [" + verification.getStatusMsg() + "]");
discovered.getClaimedIdentifier().getIdentifier(),
"Verification status message: [" + verification.getStatusMsg() + "]", attributes);
}
return new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, verified.getIdentifier(),
"some message", attributes);
}
}
@@ -0,0 +1,93 @@
package org.springframework.security.openid;
import java.util.List;
import org.springframework.util.Assert;
/**
* Represents an OpenID subject identity attribute.
* <p>
* Can be used for configuring the <tt>OpenID4JavaConsumer</tt> with the attributes which should be requested during a
* fetch request, or to hold values for an attribute which are returned during the authentication process.
*
* @author Luke Taylor
* @version $Id$
* @since 3.0
*/
public class OpenIDAttribute {
private final String name;
private final String typeIdentifier;
private boolean required = false;
private int count = 1;
private final List<String> values;
public OpenIDAttribute(String name, String type) {
this.name = name;
this.typeIdentifier = type;
this.values = null;
}
public OpenIDAttribute(String name, String type, List<String> values) {
Assert.notEmpty(values);
this.name = name;
this.typeIdentifier = type;
this.values = values;
}
/**
* The attribute name
*/
public String getName() {
return name;
}
/**
* The attribute type Identifier (a URI).
*/
public String getType() {
return typeIdentifier;
}
/**
* The "required" flag for the attribute when used with an authentication request. Defaults to "false".
*/
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
/**
* The requested count for the attribute when it is used as part of an authentication
* request. Defaults to 1.
*/
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
/**
* The values obtained from an attribute exchange.
*/
public List<String> getValues() {
Assert.notNull(values, "Cannot read values from an authentication request attribute");
return values;
}
public String toString() {
StringBuilder result = new StringBuilder("[");
result.append(name);
if (values != null) {
result.append(":");
result.append(values.toString());
}
result.append("]");
return result.toString();
}
}
@@ -63,14 +63,12 @@ public class OpenIDAuthenticationProvider implements AuthenticationProvider, Ini
OpenIDAuthenticationToken response = (OpenIDAuthenticationToken) authentication;
OpenIDAuthenticationStatus status = response.getStatus();
// handle the various possibilites
// handle the various possibilities
if (status == OpenIDAuthenticationStatus.SUCCESS) {
// Lookup user details
UserDetails userDetails = userDetailsService.loadUserByUsername(response.getIdentityUrl());
return new OpenIDAuthenticationToken(userDetails.getAuthorities(), response.getStatus(),
response.getIdentityUrl());
return createSuccessfulAuthentication(userDetails, response);
} else if (status == OpenIDAuthenticationStatus.CANCELLED) {
throw new AuthenticationCancelledException("Log in cancelled");
@@ -89,6 +87,21 @@ public class OpenIDAuthenticationProvider implements AuthenticationProvider, Ini
return null;
}
/**
* Handles the creation of the final <tt>Authentication</tt> object which will be returned by the provider.
* <p>
* The default implementation just creates a new OpenIDAuthenticationToken from the original, but with the
* UserDetails as the principal and including the authorities loaded by the UserDetailsService.
*
* @param userDetails the loaded UserDetails object
* @param auth the token passed to the authenticate method, containing
* @return the token which will represent the authenticated user.
*/
protected Authentication createSuccessfulAuthentication(UserDetails userDetails, OpenIDAuthenticationToken auth) {
return new OpenIDAuthenticationToken(userDetails, userDetails.getAuthorities(),
auth.getIdentityUrl(), auth.getAttributes());
}
/**
* Used to load the authorities for the authenticated OpenID user.
*/
@@ -30,29 +30,39 @@ public class OpenIDAuthenticationToken extends AbstractAuthenticationToken {
//~ Instance fields ================================================================================================
private final OpenIDAuthenticationStatus status;
private final Object principal;
private final String identityUrl;
private final String message;
private final List<OpenIDAttribute> attributes;
//~ Constructors ===================================================================================================
public OpenIDAuthenticationToken(OpenIDAuthenticationStatus status, String identityUrl, String message) {
public OpenIDAuthenticationToken(OpenIDAuthenticationStatus status, String identityUrl,
String message, List<OpenIDAttribute> attributes) {
super(new ArrayList<GrantedAuthority>(0));
this.principal = identityUrl;
this.status = status;
this.identityUrl = identityUrl;
this.message = message;
this.attributes = attributes;
setAuthenticated(false);
}
/**
* Created by the OpenIDAuthenticationProvider on successful authentication.
* <b>Do not use directly</b>
* Created by the <tt>OpenIDAuthenticationProvider</tt> on successful authentication.
*
* @param principal usually the <tt>UserDetails</tt> returned by the the configured <tt>UserDetailsService</tt>
* used by the <tt>OpenIDAuthenticationProvider</tt>.
*
*/
public OpenIDAuthenticationToken(List<GrantedAuthority> authorities, OpenIDAuthenticationStatus status, String identityUrl) {
public OpenIDAuthenticationToken(Object principal, List<GrantedAuthority> authorities,
String identityUrl, List<OpenIDAttribute> attributes) {
super(authorities);
this.status = status;
this.principal = principal;
this.status = OpenIDAuthenticationStatus.SUCCESS;
this.identityUrl = identityUrl;
this.message = null;
this.attributes = attributes;
setAuthenticated(true);
}
@@ -76,14 +86,24 @@ public class OpenIDAuthenticationToken extends AbstractAuthenticationToken {
}
/**
* Returns the <tt>identityUrl</tt> value.
* Returns the <tt>principal</tt> value.
*
* @see org.springframework.security.core.Authentication#getPrincipal()
*/
public Object getPrincipal() {
return identityUrl;
return principal;
}
public OpenIDAuthenticationStatus getStatus() {
return status;
}
public List<OpenIDAttribute> getAttributes() {
return attributes;
}
@Override
public String toString() {
return "[" + super.toString() + ", attributes : " + attributes +"]";
}
}
@@ -26,12 +26,6 @@ import javax.servlet.http.HttpServletRequest;
*/
public interface OpenIDConsumer {
/**
* @deprecated Use {@link #beginConsumption(javax.servlet.http.HttpServletRequest, String, String, String)}
*/
public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl)
throws OpenIDConsumerException;
/**
* Given the request, the claimedIdentity, the return to url, and a realm, lookup the openId authentication
* page the user should be redirected to.
@@ -22,8 +22,6 @@ import javax.servlet.http.HttpServletRequest;
/**
* DOCUMENT ME!
*
* @author Robin Bramley, Opsera Ltd
*/
public class MockOpenIDConsumer implements OpenIDConsumer {
@@ -54,17 +52,6 @@ public class MockOpenIDConsumer implements OpenIDConsumer {
return redirectUrl;
}
/* (non-Javadoc)
* @see org.springframework.security.ui.openid.OpenIDConsumer#beginConsumption(javax.servlet.http.HttpServletRequest, java.lang.String)
*/
public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl)
throws OpenIDConsumerException {
throw new UnsupportedOperationException("This method is deprecated, stop using it");
}
/* (non-Javadoc)
* @see org.springframework.security.ui.openid.OpenIDConsumer#endConsumption(javax.servlet.http.HttpServletRequest)
*/
public OpenIDAuthenticationToken endConsumption(HttpServletRequest req)
throws OpenIDConsumerException {
return token;
@@ -79,9 +66,6 @@ public class MockOpenIDConsumer implements OpenIDConsumer {
this.redirectUrl = redirectUrl;
}
/* (non-Javadoc)
* @see org.springframework.security.ui.openid.OpenIDConsumer#setReturnToUrl(java.lang.String)
*/
public void setReturnToUrl(String returnToUrl) {
// TODO Auto-generated method stub
}
@@ -46,7 +46,7 @@ public class OpenIDAuthenticationProviderTests extends TestCase {
OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
provider.setUserDetailsService(new MockUserDetailsService());
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.CANCELLED, USERNAME, "");
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.CANCELLED, USERNAME, "" ,null);
assertFalse(preAuth.isAuthenticated());
@@ -65,7 +65,7 @@ public class OpenIDAuthenticationProviderTests extends TestCase {
OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
provider.setUserDetailsService(new MockUserDetailsService());
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.ERROR, USERNAME, "");
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.ERROR, USERNAME, "", null);
assertFalse(preAuth.isAuthenticated());
@@ -84,7 +84,7 @@ public class OpenIDAuthenticationProviderTests extends TestCase {
OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
provider.setUserDetailsService(new MockUserDetailsService());
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.FAILURE, USERNAME, "");
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.FAILURE, USERNAME, "", null);
assertFalse(preAuth.isAuthenticated());
@@ -103,7 +103,7 @@ public class OpenIDAuthenticationProviderTests extends TestCase {
OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
provider.setUserDetailsService(new MockUserDetailsService());
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SETUP_NEEDED, USERNAME, "");
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SETUP_NEEDED, USERNAME, "", null);
assertFalse(preAuth.isAuthenticated());
@@ -122,7 +122,7 @@ public class OpenIDAuthenticationProviderTests extends TestCase {
OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
provider.setUserDetailsService(new MockUserDetailsService());
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, USERNAME, "");
Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, USERNAME, "", null);
assertFalse(preAuth.isAuthenticated());
@@ -132,7 +132,7 @@ public class OpenIDAuthenticationProviderTests extends TestCase {
assertTrue(postAuth instanceof OpenIDAuthenticationToken);
assertTrue(postAuth.isAuthenticated());
assertNotNull(postAuth.getPrincipal());
assertEquals(preAuth.getPrincipal(), postAuth.getPrincipal());
assertTrue(postAuth.getPrincipal() instanceof UserDetails);
assertNotNull(postAuth.getAuthorities());
assertTrue(postAuth.getAuthorities().size() > 0);
assertTrue(((OpenIDAuthenticationToken) postAuth).getStatus() == OpenIDAuthenticationStatus.SUCCESS);
@@ -1,38 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.openid;
import org.springframework.security.openid.OpenIDAuthenticationStatus;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import junit.framework.TestCase;
/**
* @author Ray Krueger
*/
public class OpenIDAuthenticationTokenTests extends TestCase {
public void test() throws Exception {
OpenIDAuthenticationToken token = newToken();
assertEquals(token, newToken());
}
private OpenIDAuthenticationToken newToken() {
return new OpenIDAuthenticationToken(
OpenIDAuthenticationStatus.SUCCESS,
"http://raykrueger.blogspot.com/",
"what is this for anyway?");
}
}