SEC-1431: Added openid-selector to openid sample, plus AX configuration for myopenid.com.

This commit is contained in:
Luke Taylor 2010-04-21 17:16:03 +01:00
parent 2f025fba6c
commit def5f88c8c
26 changed files with 707 additions and 25 deletions

View File

@ -99,6 +99,26 @@ Success! Your web filters appear to be properly configured!
the same application context file. This means there are actually two identical
authentication providers configured in this application. </para>
</section>
<section xml:id="openid-sample">
<title>OpenID Sample</title>
<para>
The OpenID sample demonstrates how to use the namespace to configure OpenID and how to set up
<link xlink:href="http://openid.net/specs/openid-attribute-exchange-1_0.html">attribute exchange</link>
configurations for Google, Yahoo and MyOpenID identity providers (you can experiment with adding others
if you wish). It uses the JQuery-based <link xlink:href="http://code.google.com/p/openid-selector/">openid-selector</link>
project to provide a user-friendly login page which allows the user to easily select a provider, rather than
typing in the full OpenID identifier.
</para>
<para>
The application differs from normal authentication scenarios in that it allows any user to access the site (provided
their OpenID authentication is successful). The first time you login, you will get a <quote>Welcome [your name]"</quote>
message. If you logout and log back in (with the same OpenID identity) then this should change to <quote>Welcome Back</quote>.
This is achieved by using a custom <interfacename>UserDetailsService</interfacename> which assigns a standard role
to any user and stores the identities internally in a map. Obviously a real application would use a database instead.
Have a look at the source form more information. This class also takes into account the fact that different attributes may be returned
from different providers and builds the name with which it addresses the user accordingly.
</para>
</section>
<section xml:id="cas-sample">
<title>CAS Sample</title>
<para> The CAS sample requires that you run both a CAS server and CAS client. It isn't

View File

@ -69,6 +69,10 @@ public class CustomUserDetailsService implements UserDetailsService, Authenticat
firstName = attribute.getValues().get(0);
}
if (attribute.getName().equals("lastname")) {
lastName = attribute.getValues().get(0);
}
if (attribute.getName().equals("fullname")) {
fullName = attribute.getValues().get(0);
}

View File

@ -11,8 +11,11 @@
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http>
<intercept-url pattern="/**" access="ROLE_USER"/>
<intercept-url pattern="/openidlogin.jsp*" filters="none"/>
<intercept-url pattern="/images/*" filters="none"/>
<intercept-url pattern="/css/*" filters="none"/>
<intercept-url pattern="/js/*" filters="none"/>
<intercept-url pattern="/**" access="ROLE_USER"/>
<logout/>
<openid-login login-page="/openidlogin.jsp" user-service-ref="registeringUserService"
authentication-failure-url="/openidlogin.jsp?login_error=true">
@ -25,6 +28,10 @@
<openid-attribute name="email" type="http://axschema.org/contact/email" required="true"/>
<openid-attribute name="fullname" type="http://axschema.org/namePerson" required="true" />
</attribute-exchange>
<attribute-exchange identifier-match=".*myopenid.com.*">
<openid-attribute name="email" type="http://schema.openid.net/contact/email" required="true"/>
<openid-attribute name="fullname" type="http://schema.openid.net/namePerson" required="true" />
</attribute-exchange>
</openid-login>
<remember-me token-repository-ref="tokenRepo"/>
</http>

View File

@ -0,0 +1,45 @@
#openid_form {
width: 580px;
}
#openid_form legend {
font-weight: bold;
}
#openid_choice {
display: none;
}
#openid_input_area {
clear: both;
padding: 10px;
}
#openid_btns, #openid_btns br {
clear: both;
}
#openid_highlight {
padding: 3px;
background-color: #FFFCC9;
float: left;
}
.openid_large_btn {
width: 100px;
height: 60px;
border: 1px solid #DDD;
margin: 3px;
float: left;
}
.openid_small_btn {
width: 24px;
height: 24px;
border: 1px solid #DDD;
margin: 3px;
float: left;
}
a.openid_large_btn:focus {
outline: none;
}
a.openid_large_btn:focus
{
-moz-outline-style: none;
}
.openid_selected {
border: 4px solid #DDD;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,220 @@
/**
* jQuery.query - Query String Modification and Creation for jQuery
* Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
* Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
* Date: 2009/02/08
*
* @author Blair Mitchelmore
* @version 2.1.3
*
**/
new function(settings) {
// Various Settings
var $separator = settings.separator || '&';
var $spaces = settings.spaces === false ? false : true;
var $suffix = settings.suffix === false ? '' : '[]';
var $prefix = settings.prefix === false ? false : true;
var $hash = $prefix ? settings.hash === true ? "#" : "?" : "";
var $numbers = settings.numbers === false ? false : true;
jQuery.query = new function() {
var is = function(o, t) {
return o != undefined && o !== null && (!!t ? o.constructor == t : true);
};
var parse = function(path) {
var m, rx = /\[([^[]*)\]/g, match = /^(\S+?)(\[\S*\])?$/.exec(path), base = match[1], tokens = [];
while (m = rx.exec(match[2])) tokens.push(m[1]);
return [base, tokens];
};
var set = function(target, tokens, value) {
var o, token = tokens.shift();
if (typeof target != 'object') target = null;
if (token === "") {
if (!target) target = [];
if (is(target, Array)) {
target.push(tokens.length == 0 ? value : set(null, tokens.slice(0), value));
} else if (is(target, Object)) {
var i = 0;
while (target[i++] != null);
target[--i] = tokens.length == 0 ? value : set(target[i], tokens.slice(0), value);
} else {
target = [];
target.push(tokens.length == 0 ? value : set(null, tokens.slice(0), value));
}
} else if (token && token.match(/^\s*[0-9]+\s*$/)) {
var index = parseInt(token, 10);
if (!target) target = [];
target[index] = tokens.length == 0 ? value : set(target[index], tokens.slice(0), value);
} else if (token) {
var index = token.replace(/^\s*|\s*$/g, "");
if (!target) target = {};
if (is(target, Array)) {
var temp = {};
for (var i = 0; i < target.length; ++i) {
temp[i] = target[i];
}
target = temp;
}
target[index] = tokens.length == 0 ? value : set(target[index], tokens.slice(0), value);
} else {
return value;
}
return target;
};
var queryObject = function(a) {
var self = this;
self.keys = {};
if (a.queryObject) {
jQuery.each(a.get(), function(key, val) {
self.SET(key, val);
});
} else {
jQuery.each(arguments, function() {
var q = "" + this;
q = decodeURIComponent(q);
q = q.replace(/^[?#]/,''); // remove any leading ? || #
q = q.replace(/[;&]$/,''); // remove any trailing & || ;
if ($spaces) q = q.replace(/[+]/g,' '); // replace +'s with spaces
jQuery.each(q.split(/[&;]/), function(){
var key = this.split('=')[0];
var val = this.split('=')[1];
if (!key) return;
if ($numbers) {
if (/^[+-]?[0-9]+\.[0-9]*$/.test(val)) // simple float regex
val = parseFloat(val);
else if (/^[+-]?[0-9]+$/.test(val)) // simple int regex
val = parseInt(val, 10);
}
val = (!val && val !== 0) ? true : val;
if (val !== false && val !== true && typeof val != 'number')
val = val;
self.SET(key, val);
});
});
}
return self;
};
queryObject.prototype = {
queryObject: true,
has: function(key, type) {
var value = this.get(key);
return is(value, type);
},
GET: function(key) {
if (!is(key)) return this.keys;
var parsed = parse(key), base = parsed[0], tokens = parsed[1];
var target = this.keys[base];
while (target != null && tokens.length != 0) {
target = target[tokens.shift()];
}
return typeof target == 'number' ? target : target || "";
},
get: function(key) {
var target = this.GET(key);
if (is(target, Object))
return jQuery.extend(true, {}, target);
else if (is(target, Array))
return target.slice(0);
return target;
},
SET: function(key, val) {
var value = !is(val) ? null : val;
var parsed = parse(key), base = parsed[0], tokens = parsed[1];
var target = this.keys[base];
this.keys[base] = set(target, tokens.slice(0), value);
return this;
},
set: function(key, val) {
return this.copy().SET(key, val);
},
REMOVE: function(key) {
return this.SET(key, null).COMPACT();
},
remove: function(key) {
return this.copy().REMOVE(key);
},
EMPTY: function() {
var self = this;
jQuery.each(self.keys, function(key, value) {
delete self.keys[key];
});
return self;
},
load: function(url) {
var hash = url.replace(/^.*?[#](.+?)(?:\?.+)?$/, "$1");
var search = url.replace(/^.*?[?](.+?)(?:#.+)?$/, "$1");
return new queryObject(url.length == search.length ? '' : search, url.length == hash.length ? '' : hash);
},
empty: function() {
return this.copy().EMPTY();
},
copy: function() {
return new queryObject(this);
},
COMPACT: function() {
function build(orig) {
var obj = typeof orig == "object" ? is(orig, Array) ? [] : {} : orig;
if (typeof orig == 'object') {
function add(o, key, value) {
if (is(o, Array))
o.push(value);
else
o[key] = value;
}
jQuery.each(orig, function(key, value) {
if (!is(value)) return true;
add(obj, key, build(value));
});
}
return obj;
}
this.keys = build(this.keys);
return this;
},
compact: function() {
return this.copy().COMPACT();
},
toString: function() {
var i = 0, queryString = [], chunks = [], self = this;
var addFields = function(arr, key, value) {
if (!is(value) || value === false) return;
var o = [encodeURIComponent(key)];
if (value !== true) {
o.push("=");
o.push(encodeURIComponent(value));
}
arr.push(o.join(""));
};
var build = function(obj, base) {
var newKey = function(key) {
return !base || base == "" ? [key].join("") : [base, "[", key, "]"].join("");
};
jQuery.each(obj, function(key, value) {
if (typeof value == 'object')
build(value, newKey(key));
else
addFields(chunks, newKey(key), value);
});
};
build(this.keys);
if (chunks.length > 0) queryString.push($hash);
queryString.push(chunks.join($separator));
return queryString.join("");
}
};
return new queryObject(location.search, location.hash);
};
}(jQuery.query || {}); // Pass in jQuery.query as settings object

View File

@ -0,0 +1,20 @@
/*
Defines the base of where the OpenID Provider redirects its response to.
*/
var server_root = "http://openid-selector.googlecode.com/svn/trunk/"
/*
On the server-side you'd accept an OpenID URL and perform discovery
on it to find out the actual OpenID endpoint to send the authentication
request to. On the client side it isn't possible to lookup the endpoint
from the target server due to XSS restrictions. The endpoint for each
provider is therefore cached in this static file. If an endpoint isn't
specified for a provider then authentication on the client side cannot
proceed.
*/
var providers_endpoint = {
google: 'https://www.google.com/accounts/o8/ud',
yahoo: 'https://open.login.yahooapis.com/openid/op/auth',
aol: 'https://api.screenname.aol.com/auth/openidServer',
verisign: 'http://pip.verisignlabs.com/server'
}

View File

@ -0,0 +1,63 @@
var claimedID;
var providerID;
var openIDPopup;
function OpenID_iframe_then_popup_handler(provider, claimed) {
providerID = provider;
claimedID = claimed;
var immediateiframe = document.getElementById("openid_immediate_iframe");
var iframeurl = getBaseOpenIDProviderURL(providerID, claimedID, true);
immediateiframe.innerHTML = "<iframe frameborder='1' src='" + iframeurl + "'></iframe>";
}
function processOpenIDImmediateResponse(responseURL) {
var immediateiframe = document.getElementById("openid_immediate_iframe");
immediateiframe.innerHTML = responseURL;
var failure = new RegExp("openid.mode=setup_needed");
if(failure.test(responseURL)) {
var popupurl = getBaseOpenIDProviderURL(providerID, claimedID, false);
openIDPopup = window.open(popupurl, "OpenIDPopup");
} else {
alert("Success without popup!");
}
}
function processOpenIDSetupResponse(responseURL) {
openIDPopup.close();
var results = "";
var responseQuery = $.query.load(responseURL);
$.each(responseQuery.get(), function(key, value) {
results += "<br/>" + key + "=" + value;
});
document.getElementById("openid_status").innerHTML = "<br/>Result of authentication is: " + results;
}
function getBaseOpenIDProviderURL(provider, claimed, immediate) {
var providerEndpoint = providers_endpoint[provider];
var providerURL = providerEndpoint; //From previous discovery
providerURL += "?";
providerURL += "openid.ns=" + encodeURIComponent("http://specs.openid.net/auth/2.0");
if(providers[provider].label) {
providerURL += "&openid.claimed_id=" + encodeURIComponent(claimed);
providerURL += "&openid.identity=" + encodeURIComponent(claimed);
}
else {
providerURL += "&openid.claimed_id=" + encodeURIComponent("http://specs.openid.net/auth/2.0/identifier_select");
providerURL += "&openid.identity=" + encodeURIComponent("http://specs.openid.net/auth/2.0/identifier_select");
}
if(immediate) {
providerURL += "&openid.return_to=" + encodeURIComponent(server_root + "openid-client/checkid_immediate_response.html");
providerURL += "&openid.realm=" + encodeURIComponent(server_root + "openid-client/checkid_immediate_response.html");
providerURL += "&openid.mode=checkid_immediate";
} else {
providerURL += "&openid.return_to=" + encodeURIComponent(server_root + "openid-client/checkid_setup_response.html");
providerURL += "&openid.realm=" + encodeURIComponent(server_root + "openid-client/checkid_setup_response.html");
providerURL += "&openid.mode=checkid_setup";
}
return providerURL;
}

View File

@ -0,0 +1,240 @@
/*
Simple OpenID Plugin
http://code.google.com/p/openid-selector/
This code is licenced under the New BSD License.
*/
var providers_large = {
google: {
name: 'Google',
url: 'https://www.google.com/accounts/o8/id'
},
yahoo: {
name: 'Yahoo',
url: 'http://yahoo.com/'
},
aol: {
name: 'AOL',
label: 'Enter your AOL screenname.',
url: 'http://openid.aol.com/{username}'
},
verisign: {
name: 'Verisign',
label: 'Your Verisign username',
url: 'http://{username}.pip.verisignlabs.com/'
},
openid: {
name: 'OpenID',
label: 'Enter your OpenID.',
url: null
}
};
var providers_small = {
myopenid: {
name: 'MyOpenID',
label: 'Enter your MyOpenID username.',
url: 'http://{username}.myopenid.com/'
},
livejournal: {
name: 'LiveJournal',
label: 'Enter your Livejournal username.',
url: 'http://{username}.livejournal.com/'
},
flickr: {
name: 'Flickr',
label: 'Enter your Flickr username.',
url: 'http://flickr.com/{username}/'
},
technorati: {
name: 'Technorati',
label: 'Enter your Technorati username.',
url: 'http://technorati.com/people/technorati/{username}/'
},
wordpress: {
name: 'Wordpress',
label: 'Enter your Wordpress.com username.',
url: 'http://{username}.wordpress.com/'
},
blogger: {
name: 'Blogger',
label: 'Your Blogger account',
url: 'http://{username}.blogspot.com/'
},
vidoop: {
name: 'Vidoop',
label: 'Your Vidoop username',
url: 'http://{username}.myvidoop.com/'
},
claimid: {
name: 'ClaimID',
label: 'Your ClaimID username',
url: 'http://claimid.com/{username}'
}
};
var providers = $.extend({}, providers_large, providers_small);
var openid = {
demo: false,
ajaxHandler: null,
cookie_expires: 6*30, // 6 months.
cookie_name: 'openid_provider',
cookie_path: '/',
img_path: 'images/',
input_id: null,
provider_url: null,
provider_id: null,
init: function(input_id) {
var openid_btns = $('#openid_btns');
this.input_id = input_id;
$('#openid_choice').show();
$('#openid_input_area').empty();
// add box for each provider
for (id in providers_large) {
openid_btns.append(this.getBoxHTML(providers_large[id], 'large', '.gif'));
}
if (providers_small) {
openid_btns.append('<br/>');
for (id in providers_small) {
openid_btns.append(this.getBoxHTML(providers_small[id], 'small', '.ico'));
}
}
$('#openid_form').submit(this.submit);
var box_id = this.readCookie();
if (box_id) {
this.signin(box_id, true);
}
},
getBoxHTML: function(provider, box_size, image_ext) {
var box_id = provider["name"].toLowerCase();
return '<a title="'+provider["name"]+'" href="javascript: openid.signin(\''+ box_id +'\');"' +
' style="background: #FFF url(' + this.img_path + box_id + image_ext+') no-repeat center center" ' +
'class="' + box_id + ' openid_' + box_size + '_btn"></a>';
},
/* Provider image click */
signin: function(box_id, onload) {
var provider = providers[box_id];
if (! provider) {
return;
}
this.highlight(box_id);
this.setCookie(box_id);
this.provider_id = box_id;
this.provider_url = provider['url'];
// prompt user for input?
if (provider['label']) {
this.useInputBox(provider);
} else {
$('#openid_input_area').empty();
if (! onload) {
$('#openid_form').submit();
}
}
},
/* Sign-in button click */
submit: function() {
var url = openid.provider_url;
if (url) {
url = url.replace('{username}', $('#openid_username').val());
openid.setOpenIdUrl(url);
}
if(openid.ajaxHandler) {
openid.ajaxHandler(openid.provider_id, document.getElementById(openid.input_id).value);
return false;
}
if(openid.demo) {
alert("In client demo mode. Normally would have submitted OpenID:\r\n" + document.getElementById(openid.input_id).value);
return false;
}
return true;
},
setOpenIdUrl: function (url) {
var hidden = document.getElementById(this.input_id);
if (hidden != null) {
hidden.value = url;
} else {
$('#openid_form').append('<input type="hidden" id="' + this.input_id + '" name="' + this.input_id + '" value="'+url+'"/>');
}
},
highlight: function (box_id) {
// remove previous highlight.
var highlight = $('#openid_highlight');
if (highlight) {
highlight.replaceWith($('#openid_highlight a')[0]);
}
// add new highlight.
$('.'+box_id).wrap('<div id="openid_highlight"></div>');
},
setCookie: function (value) {
var date = new Date();
date.setTime(date.getTime()+(this.cookie_expires*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
document.cookie = this.cookie_name+"="+value+expires+"; path=" + this.cookie_path;
},
readCookie: function () {
var nameEQ = this.cookie_name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
},
useInputBox: function (provider) {
var input_area = $('#openid_input_area');
var html = '';
var id = 'openid_username';
var value = '';
var label = provider['label'];
var style = '';
if (label) {
html = '<p>' + label + '</p>';
}
if (provider['name'] == 'OpenID') {
id = this.input_id;
value = 'http://';
style = 'background:#FFF url('+this.img_path+'openid-inputicon.gif) no-repeat scroll 0 50%; padding-left:18px;';
}
html += '<input id="'+id+'" type="text" style="'+style+'" name="'+id+'" value="'+value+'" />' +
'<input id="openid_submit" type="submit" value="Sign-In"/>';
input_area.empty();
input_area.append(html);
$('#'+id).focus();
},
setDemoMode: function (demoMode) {
this.demo = demoMode;
},
setAjaxHandler: function (ajaxFunction) {
this.ajaxHandler = ajaxFunction;
}
};

View File

@ -1,33 +1,64 @@
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt' %>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>OpenID Login</title>
<html>
<head>
<title>Open ID Login</title>
</head>
<!-- Simple OpenID Selector -->
<link rel="stylesheet" href="<c:url value='/css/openid.css'/>" />
<script type="text/javascript" src="<c:url value='/js/jquery-1.2.6.min.js'/>"></script>
<script type="text/javascript" src="<c:url value='/js/openid-jquery.js'/>"></script>
<body onload="document.f.j_username.focus();">
<h3>Please Enter Your OpenID Identity</h3>
<script type="text/javascript">
$(document).ready(function() {
openid.init('openid_identifier');
// openid.setDemoMode(true); Stops form submission for client javascript-only test purposes
});
</script>
<!-- /Simple OpenID Selector -->
<%-- this form-login-page form is also used as the
form-error-page to ask for a login again.
--%>
<c:if test="${not empty param.login_error}">
<font color="red">
Your login attempt was not successful, try again.<br/><br/>
Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.
</font>
</c:if>
<style type="text/css">
/* Basic page formatting. */
body {
font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
}
</style>
</head>
<body>
<form name="f" action="<c:url value='j_spring_openid_security_check'/>" method="POST">
<table>
<tr><td>OpenID Identity:</td><td><input type='text' name='openid_identifier' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>
<tr><td><input type="checkbox" name="_spring_security_remember_me"></td><td>Remember me on this computer.</td></tr>
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
</table>
<c:if test="${not empty param.login_error}">
<font color="red">
Your login attempt was not successful, try again.<br/><br/>
Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.
</font>
</c:if>
</form>
<!-- Simple OpenID Selector -->
<form action="<c:url value='j_spring_openid_security_check'/>" method="post" id="openid_form">
<input type="hidden" name="action" value="verify" />
</body>
<fieldset>
<legend>Sign-in or Create New Account</legend>
<div id="openid_choice">
<p>Please click your account provider:</p>
<div id="openid_btns"></div>
</div>
<div id="openid_input_area">
<input id="openid_identifier" name="openid_identifier" type="text" value="http://" />
<input id="openid_submit" type="submit" value="Sign-In"/>
</div>
<noscript>
<p>OpenID is a service that allows you to log-on to many different websites using a single indentity.
Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
</noscript>
</fieldset>
</form>
<!-- /Simple OpenID Selector -->
</body>
</html>