Merge remote branch 'origin/master'

pull/105/merge
Michael Jett 2012-05-16 13:28:24 -04:00
commit abf3f0ec33
19 changed files with 852 additions and 425 deletions

View File

@ -2,53 +2,60 @@
## Overview ## Overview
This is Web application created in response to [Issue #39] to permit the Client AuthenticationFilter to speak to multiple OpenID Connect servers. This is Web application created in response to [Issue #39] to permit the Client AuthenticationFilter to speak to multiple OpenID Connect Servers. The protocol between the Clinent and the Account Chooser UI application is documented the README.md of the openid-connect-client submodule.
## Configuration ## Configuration
Configure a bean configuration to the spring-servlet.xml like so: Configure AccountChooserController via configuring a AccountChooserConfig bean in the spring-servlet.xml like so:
<bean class="org.mitre.account_chooser.OIDCServers"> <bean name="AccountChooserConfig" class="org.mitre.account_chooser.AccountChooserConfig">
<property name="servers"> <property name="issuers">
<map> <map>
<entry key="1"> <entry key="http://sever.example.com:8080/openid-connect-server">
<bean class="org.mitre.account_chooser.OIDCServer"> <bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 1" /> <property name="name" value="Example Server" />
</bean>
</entry>
<entry key="2">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 2" />
</bean>
</entry>
<entry key="3">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 3" />
</bean> </bean>
</entry> </entry>
</map> </map>
</property> </property>
<property name="validClientIds" value="FGWEUIASJK, IUYTTYEV, GFHDSFYD" />
</bean> </bean>
The keys must match those found in the OpenIdConnectAuthenticationFilter's configuration like so: The keys must match those found in the OpenIdConnectAuthenticationFilter's configuration like so:
<bean id="openIdConnectAuthenticationFilter" <bean id="openIdConnectAuthenticationFilter"
class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter"> class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter">
<property name="OIDCServers"> <property name="errorRedirectURI" value="/login.jsp?authfail=openid" />
<property name="authenticationManager" ref="authenticationManager" />
<property name="accountChooserURI"
value="http://sever.example.com:8080/account-chooser" />
<property name="accountChooserClientID" value="FGWEUIASJK" />
<property name="oidcServerConfigs">
<map> <map>
<entry key="1"> <entry key="http://sever.example.com:8080/Fopenid-connect-server">
<property name="authorizationEndpointURI" <bean class="org.mitre.openid.connect.client.OIDCServerConfiguration">
value="http://sever.example.com:8080/openid-connect-server/oauth/authorize" /> <property name="authorizationEndpointURI"
<property name="tokenEndpointURI" value="http://sever.example.com:8080/openid-connect-server/oauth/authorize" />
value="http://sever.example.com:8080/openid-connect-server/oauth/token" /> <property name="tokenEndpointURI"
<property name="checkIDEndpointURI" value="http://sever.example.com:8080/openid-connect-server/oauth/token" />
value="http://sever.example.com:8080/openid-connect-server/checkid" /> <property name="checkIDEndpointURI"
<property name="clientId" value="http://sever.example.com:8080/openid-connect-server/checkid" />
value="someClientId" /> <property name="clientId"
<property name="clientSecret" value="someClientSecret" /> value="someClientId" />
<property name="clientSecret" value="someClientSecret" />
</bean>
</entry> </entry>
. . .
## Test the Default Configuration
To test the default config, deploy to a servlet container, and request:
http://localhost:8080/account-chooser/?redirect_uri=http://www.google.com&client_id=FGWEUIASJK
Click **Submit** or **Cancel**, and you will be Google will open. Study the URL parameters of each.
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client" [Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,127 @@
# OpenID Connect Client - Account Chooser UI Application Protocol
## Overview
This Client - Account Chooser Protocol proposed in response to [Issue #39].
#### Authorization when using Account Chooser Code Flow
The Authorization when using Account Chooser Code Flow goes through the following steps:
1. Client prepares an Account Chooser Request containing the desired request parameters.
2. Client sends a request to the Account Chooser.
3. Account Chooser presents a selection of OpenID Connect (OIDC) Servers from which the End-User must select from.
4. End-User selects an OIDC.
5. Account Chooser Sends the End-User back to the Client with key value of the OIDC End-User selected.
6. The Client begins the Authorization flow described in [Authorization Code Flow][OpenID Connect Standard] of the [OpenID Connect Standard].
#### Account Chooser Request
When the End-User wishes to access a Protected Resource and the End-User Authorization has not yet been obtained, the Client will redirect the End-User to Account Chooser.
Account Chooser MUST support the use of the HTTP "GET" and "POST" methods defined in RFC 2616 [RFC2616].
Clients MAY use the HTTP "GET" or "POST" method to send the Account Chooser Request to the Account Chooser. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
#### Client Prepares an Account Chooser Request
The Client prepares an Account Chooser Request to the Account Chooser with the request parameters using the HTTP "GET" or "POST" method.
The required Account Chooser Request parameters are as follows:
* redirect_uri - REQUIRED. A redirection URI where the response will be sent.
* client_id - REQUIRED. An identifier used to identify the Client to the Account Chooser UI application.
There is one method to construct and send the request to the Account Chooser:
* Simple Request Method
#### Simple Request Method
The Client prepares an Account Chooser Request to the Account Chooser using the appropriate parameters. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
The following is a non-normative example of an Account Chooser Request URL. Line wraps are for display purposes only.
http://server.example.com/chooser?
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
&client_id=FGWEUIASJK
#### Client sends a request to the Account Chooser
Having constructed the Account Chooser Request, the Client sends it to the Account Chooser. This MAY happen via redirect, hyperlinking, or any other means of directing the User-Agent to the Account Chooser URL.
Following is a non-normative example using HTTP redirect. Line wraps are for display purposes only.
HTTP/1.1 302 Found
Location: https://server.example.com/chooser?
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
&client_id=FGWEUIASJK
#### Account Chooser Sends the End-User back to the Client
After the End-User has select an OpenID Connect Server, the Client issues an Account Chooser Response and delivers it to the Client by adding the response parameters to redirect_uri specified in the Account Choose Request using the "application/x-www-form-urlencoded" format.
The following response parameters are included:
* issuer - REQUIRED. The [Issuer Identifier] describing the selected account.
The following is non-normative example of a responses. Line wraps are for display purposes only.
HTTP/1.1 302 Found
Location: https://client.example.com/openid_connect_login?
issuer=http%3A%2F%2Fsever.example.com%3A8080%2Fopenid-connect-server
A sequence diagram of a successful interaction would be:
![Success](./success.png)
#### The Client ID is not Supported by the Account Chooser Application
If the Client sends a Client identifier that is not supported by the Account Chooser UI application, the Account Chooser MUST return an error response back to the End-User by issuing a HTTP Response with a message concerning the error. The Account Chooser MUST NOT directly return to the Client via the redirection URI specified in the Account Chooser Request automatically.
The error message the Account Chooser presents to the End-User MAY give the End-User the means to return the indication of an error to the Client, e.g., via the End-User clicking on a HTML link built from adding the error parameters to the query component of the redirection URI. No other parameters SHOULD be returned.
The error response parameters are the following:
* error - REQUIRED. The error code.
* error_description - OPTIONAL. A human-readable UTF-8 encoded text description of the error.
The response parameters are added to the query component of the redirection URI specified in the Account Chooser Request.
The following is a non-normative example. Line wraps after the second line are for the display purposes only.
https://client.example.com/openid_connect_login?
error=not_supported
&error_description=The%20client_id%20is%20not%20supported%20by%20the%20AccountChooser%20UI%20application.
A sequence diagram of the interaction where the Account Chooser was not configured to support the Client would be:
![Not supported](./not_supported.png)
#### End-User refuses to select an Account
If the End-User refuses to select an Account, the Account Chooser MUST return an error response. The Account Chooser returns the Client via the redirection URI specified in the Account Chooser Request with the appropriate error parameters. No other parameters SHOULD be returned.
The error response parameters are the following:
* error - REQUIRED. The error code.
* error_description - OPTIONAL. A human-readable UTF-8 encoded text description of the error.
The response parameters are added to the query component of the redirection URI specified in the Account Chooser Request.
The following is a non-normative example. Line wraps after the second line are for the display purposes only.
HTTP/1.1 302 Found
Location: https://client.example.com/openid_connect_login?
error=end_user_cancelled
&error_description=The%20end%2Duser%20refused%20to%20select%20an%20Account.
A sequence diagram of an interaction where the End-User refused to select an Account would be:
![Cancel](./cancel.png)
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html "OpenID Connect Standard 1.0"
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html#code_flow "Authorization Code Flow, OpenID Connect Standard"
[Issuer Identifier]: http://openid.net/specs/openid-connect-messages-1_0.html#issuer_identifier "Issuer Identifier"
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,50 @@
Text used to create sequence diagrams @ http://www.websequencediagrams.com/
A Successful Client and Account Chooser Interaction
Client->End-User Browser: HTTP-Redirect\n to Account\n Chooser URL\nwith redirect_uri,\n and client_id\n parameters
activate End-User Browser
End-User Browser-->Account Chooser:HTTP Get/Put\n Request containing\n redirect_uri\n and client_id
note right of "Account Chooser": Client Id\n is verified as\n supported, and the\n End-User selects\n an Account
deactivate End-User Browser
activate Account Chooser
Account Chooser-->End-User Browser:HTTP-Redirect to \nAccount Chooser URL\n with issuer parameters
activate End-User Browser
deactivate Account Chooser
End-User Browser->Client: HTTP Get\n Request containing the\n issuer parameter
activate Client
deactivate End-User Browser
The Client ID is not Supported by the Account Chooser Application
Client->End-User Browser: HTTP-Redirect to\n Account Chooser URL\n w/ redirect_uri,\n and client_id parameters
activate End-User Browser
End-User Browser-->Account Chooser:HTTP Get/Put\n Request containing\n redirect_uri and client_id
note right of "Account Chooser": Client Id is\nnot supported.\nSo, an error must\nbe returned.
activate Account Chooser
deactivate End-User Browser
Account Chooser-->End-User Browser:Returns an\nHTTP Response\ncontaining an\nerror message.
deactivate Account Chooser
note right of "End-User Browser":The End-User is presented\nan error message\nthus ending the interaction.\n\nOptionally, the message\nMAY contain a HTML link\nto return the End-User\nback to the Client with\nan error and error description\nadded to the query component\nof the redirect_uri.
activate End-User Browser
End-User Browser->Client: If the End-User\nis presented and clicks\non a HTML link,\na HTTP "GET"\n Request containing the\nerror, and error_description\nparameter
activate Client
deactivate Account Chooser
End-User Cancels Account Selection
Client->End-User Browser: HTTP-Redirect to\n Account Chooser URL\nwith redirect_uri,\nand client_id parameters
activate End-User Browser
End-User Browser-->Account Chooser:HTTP Get/Put\n Request containing\nredirect_uri and client_id
note right of "Account Chooser": End-User refuses\nto select an\naccount via\ncancelling.
deactivate End-User Browser
activate Account Chooser
Account Chooser-->End-User Browser:HTTP-Redirect\n to Account Chooser\n URL with error,\nand error_description\nparameters
activate End-User Browser
deactivate Account Chooser
End-User Browser->Client: HTTP Get Request\ncontaining the error,\nand error_description\nparameter
activate Client
deactivate End-User Browser

View File

@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* 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.mitre.account_chooser;
import java.util.HashMap;
import java.util.Map;
/**
* Used to the configure AccountChooserController
*
* @author nemonik
*
*/
public class AccountChooserConfig {
private String[] validClientIds;
private Map<String, ? extends OIDCServer> issuers = new HashMap<String, OIDCServer>();
public Map<String, ? extends OIDCServer> getIssuers() {
return issuers;
}
public String[] getValidClientIds() {
return validClientIds;
}
public void setIssuers(Map<String, ? extends OIDCServer> issuers) {
this.issuers = issuers;
}
public void setValidClientIds(String[] validClientIds) {
this.validClientIds = validClientIds;
}
}

View File

@ -16,6 +16,11 @@
package org.mitre.account_chooser; package org.mitre.account_chooser;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -33,14 +38,49 @@ import org.springframework.web.servlet.ModelAndView;
* *
* @author nemonik * @author nemonik
* *
* See README.md for configuration. * See README.md for configuration.
* *
*/ */
@Controller @Controller
public class AccountChooserController { public class AccountChooserController {
/**
* Return the URL w/ GET parameters
*
* @param baseURI
* A String containing the protocol, server address, path, and
* program as per "http://server/path/program"
* @param queryStringFields
* A map where each key is the field name and the associated
* key's value is the field value used to populate the URL's
* query string
* @return A String representing the URL in form of
* http://server/path/program?query_string from the messaged
* parameters.
*/
public static String buildURL(String baseURI,
Map<String, String> queryStringFields) {
StringBuilder URLBuilder = new StringBuilder(baseURI);
char appendChar = '?';
for (Map.Entry<String, String> param : queryStringFields.entrySet()) {
try {
URLBuilder.append(appendChar).append(param.getKey())
.append('=')
.append(URLEncoder.encode(param.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException uee) {
throw new IllegalStateException(uee);
}
appendChar = '&';
}
return URLBuilder.toString();
}
@Autowired @Autowired
OIDCServers servers; AccountChooserConfig accountChooserConfig;
private static Log logger = LogFactory private static Log logger = LogFactory
.getLog(AccountChooserController.class); .getLog(AccountChooserController.class);
@ -51,18 +91,51 @@ public class AccountChooserController {
* @param redirectUri * @param redirectUri
* A redirection URI where the response will be sent * A redirection URI where the response will be sent
* @return * @return
* @throws IOException
*/ */
@RequestMapping(value = "/", method = { RequestMethod.GET, @RequestMapping(value = "/", method = { RequestMethod.GET,
RequestMethod.POST }) RequestMethod.POST })
public ModelAndView handleChooserRequest( public ModelAndView handleChooserRequest(
@RequestParam("redirect_uri") String redirectUri) { @RequestParam("redirect_uri") String redirectUri,
@RequestParam("client_id") String clientId,
HttpServletResponse response) throws IOException {
ModelAndView modelAndView = new ModelAndView("form"); ModelAndView modelAndView = null;
modelAndView.addObject("servers", servers);
modelAndView.addObject("redirect_uri", redirectUri); if (Arrays.asList(accountChooserConfig.getValidClientIds()).contains(
modelAndView.setViewName("chooser"); clientId)) {
// client_id supported
modelAndView = new ModelAndView("chooser");
modelAndView
.addObject("issuers", accountChooserConfig.getIssuers());
modelAndView.addObject("redirect_uri", redirectUri);
modelAndView.addObject("client_id", clientId);
} else {
// client_id not supported
Map<String, String> urlVariables = new HashMap<String, String>();
urlVariables.put("error", "not_supported");
urlVariables
.put("error_description",
"The client_id is not supported by the Account Chooser UI application.");
modelAndView = new ModelAndView("error");
modelAndView.addObject("error", urlVariables.get("error"));
modelAndView.addObject("error_description",
urlVariables.get("error_description"));
modelAndView.addObject("client_uri", AccountChooserController
.buildURL(redirectUri, urlVariables));
}
return modelAndView; return modelAndView;
} }
/** /**
@ -81,9 +154,50 @@ public class AccountChooserController {
*/ */
@RequestMapping(value = "/selected") @RequestMapping(value = "/selected")
public void processSubmit(@RequestParam("redirect_uri") String redirectUri, public void processSubmit(@RequestParam("redirect_uri") String redirectUri,
@RequestParam("alias") String alias, HttpServletResponse response) @RequestParam("issuer") String issuer,
@RequestParam("client_id") String clientId,
HttpServletResponse response)
throws IOException { throws IOException {
response.sendRedirect(redirectUri + "?oidc_alias=" + alias); // Handle Submit
Map<String, String> urlVariables = new HashMap<String, String>();
urlVariables.put("issuer", issuer);
response.sendRedirect(AccountChooserController.buildURL(redirectUri,
urlVariables));
}
/**
* Handles form submits
*
* @param redirectUri
* A redirection URI where the response will be sent.
* @param alias
* The OIDC alias selected.
* @param response
* Provide the HTTP-specific functionality for sending a
* response. In this case a redirect to redirect the End-User
* back to the OpenID Connect Client.
* @throws IOException
* If an output exception occurs in sending the redirect.
*/
@RequestMapping(value = "/cancel")
public void processCancel(@RequestParam("redirect_uri") String redirectUri,
@RequestParam("issuer") String issuer,
@RequestParam("client_id") String clientId,
HttpServletResponse response)
throws IOException {
// Handle Cancel
Map<String, String> urlVariables = new HashMap<String, String>();
urlVariables.put("error", "end_user_cancelled");
urlVariables.put("error_description",
"The end-user refused to select an Account.");
response.sendRedirect(AccountChooserController.buildURL(redirectUri,
urlVariables));
} }
} }

View File

@ -18,6 +18,8 @@ package org.mitre.account_chooser;
/** /**
* @author nemonik * @author nemonik
* *
* Holdes properties of the Issuer, e.g. name, icon URL, description, et cetera
*
*/ */
public class OIDCServer { public class OIDCServer {

View File

@ -1,77 +0,0 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* 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.mitre.account_chooser;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* @author nemonik
*
*/
public class OIDCServers implements InitializingBean {
private Map<String, ? extends OIDCServer> servers = new HashMap<String, OIDCServer>();
private static Log logger = LogFactory.getLog(OIDCServers.class);
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
// used for debugging...
if (!servers.isEmpty()) {
logger.info(this.toString());
}
}
/**
* Return the OIDCServers associated with this
*
* @return
*/
public Map<String, ? extends OIDCServer> getServers() {
return servers;
}
/**
* Set the OIDCServers associated with this
*
* @param signers
* List of JwtSigners to associate with this service
*/
public void setServers(Map<String, ? extends OIDCServer> servers) {
this.servers = servers;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "OIDCServers [servers=" + servers
+ "]";
}
}

View File

@ -35,26 +35,17 @@
<!-- End view configuration --> <!-- End view configuration -->
<bean class="org.mitre.account_chooser.OIDCServers"> <bean name="AccountChooserConfig" class="org.mitre.account_chooser.AccountChooserConfig">
<property name="servers"> <property name="issuers">
<map> <map>
<entry key="1"> <entry key="http://sever.example.com:8080/openid-connect-server">
<bean class="org.mitre.account_chooser.OIDCServer"> <bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 1" /> <property name="name" value="Example Server" />
</bean>
</entry>
<entry key="2">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 2" />
</bean>
</entry>
<entry key="3">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 3" />
</bean> </bean>
</entry> </entry>
</map> </map>
</property> </property>
<property name="validClientIds" value="FGWEUIASJK, IUYTTYEV, GFHDSFYD" />
</bean> </bean>
</beans> </beans>

View File

@ -3,9 +3,8 @@
<%@page import="org.mitre.account_chooser.OIDCServer"%> <%@page import="org.mitre.account_chooser.OIDCServer"%>
<%@page import="java.util.Map"%> <%@page import="java.util.Map"%>
<%@page import="java.util.Iterator"%> <%@page import="java.util.Iterator"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="servers" type="org.mitre.account_chooser.OIDCServers" <jsp:useBean id="issuers" type="java.util.Map"
scope="request" /> scope="request" />
<!DOCTYPE html> <!DOCTYPE html>
@ -36,22 +35,21 @@ body {
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="span12"> <div class="span12">
<form class="form-horizontal" action="selected" method="get"> <form name="chooser" class="form-horizontal" action="selected" method="get">
<div class="control-group"> <div class="control-group">
<label class="control-label" for="select01">Account:</label> <label class="control-label" for="select01">Account:</label>
<div class="controls"> <div class="controls">
<select name="alias"> <select name="issuer">
<% <%
Map map = servers.getServers(); Iterator entries = issuers.entrySet().iterator();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) { while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next(); Map.Entry entry = (Map.Entry) entries.next();
String alias = (String) entry.getKey(); String id = (String) entry.getKey();
OIDCServer server = (OIDCServer) entry.getValue(); OIDCServer issuer = (OIDCServer) entry.getValue();
%> %>
<option value="<%= alias %>"><%= server.getName() %></option> <option value="<%= id %>"><%= issuer.getName() %></option>
<% <%
} }
%> %>
@ -61,12 +59,13 @@ body {
</div> </div>
<div class="control-group"> <div class="control-group">
<div class="controls"> <div class="controls">
<input name="redirect_uri" type="hidden" value="<c:out value="${redirect_uri}"/>"> <input name="redirect_uri" type="hidden" value="${redirect_uri}">
<input name="client_id" type="hidden" value="${client_id}">
</div> </div>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button class="btn btn-primary" type="submit">Submit</button> <a class="btn btn-primary" href="javascript: submitForm('selected')">Submit</a>
<button class="btn">Cancel</button> <a class="btn" href="javascript: submitForm('cancel')">Cancel</a>
</div> </div>
</form> </form>
</div> </div>
@ -94,7 +93,10 @@ body {
<script type="text/javascript" language="JavaScript"> <script type="text/javascript" language="JavaScript">
// <![CDATA [ // <![CDATA [
// javascript secific to the page would go here, if I had any... function submitForm(action){
$('.form-horizontal').attr( 'action' , action );
$('.form-horizontal').submit();
}
// ]]> // ]]>
</script> </script>

View File

@ -0,0 +1,70 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@page import="org.mitre.account_chooser.OIDCServer"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.Iterator"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Account Chooser</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Account Chooser GUI">
<meta name="author" content="nemonik">
<link href="resources/bootstrap/css/bootstrap.css" rel="stylesheet">
<link href="resources/bootstrap/css/bootstrap-responsive.css"
rel="stylesheet">
<link href="resources/bootstrap/css/docs.css" rel="stylesheet">
<style>
body {
padding-top: 60px;
/* 60px to make the container go all the way to the bottom of the topbar */
}
</style>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="row">
<div class="span12">
<h1>
${error}<br>
<small>${error_description}</small>
</h1>
<a href="${client_uri}">Return to client</a>
</div>
</div>
</div>
<!--/container-->
<!-- Placed at the end of the document so the pages load faster -->
<script src="resources/bootstrap/js/jquery.js"></script>
<script src="resources/bootstrap/js/bootstrap-transition.js"></script>
<script src="resources/bootstrap/js/bootstrap-alert.js"></script>
<script src="resources/bootstrap/js/bootstrap-modal.js"></script>
<script src="resources/bootstrap/js/bootstrap-dropdown.js"></script>
<script src="resources/bootstrap/js/bootstrap-scrollspy.js"></script>
<script src="resources/bootstrap/js/bootstrap-tab.js"></script>
<script src="resources/bootstrap/js/bootstrap-tooltip.js"></script>
<script src="resources/bootstrap/js/bootstrap-popover.js"></script>
<script src="resources/bootstrap/js/bootstrap-button.js"></script>
<script src="resources/bootstrap/js/bootstrap-collapse.js"></script>
<script src="resources/bootstrap/js/bootstrap-carousel.js"></script>
<script src="resources/bootstrap/js/bootstrap-typeahead.js"></script>
<script type="text/javascript" language="JavaScript">
// <![CDATA [
// ]]>
</script>
</body>
</html>

View File

@ -4,9 +4,9 @@
This is the Client, a Spring Security AuthenticationFilter, to OpenID Connect Java Spring Server described by [OpenID Connect Standard]. This is the Client, a Spring Security AuthenticationFilter, to OpenID Connect Java Spring Server described by [OpenID Connect Standard].
## Configure ## Configuration of OIDCAuthenticationFilter
Configure the OpenIDConnectAuthenticationFilter by adding the XML to your application context security like so: Configure the OIDCAuthenticationFilter by adding the XML to your application context security like so:
<security:http auto-config="false" <security:http auto-config="false"
use-expressions="true" use-expressions="true"
@ -40,7 +40,7 @@ Configure the OpenIDConnectAuthenticationFilter by adding the XML to your applic
<security:authentication-manager alias="authenticationManager" /> <security:authentication-manager alias="authenticationManager" />
<bean id="openIdConnectAuthenticationProvider" <bean id="openIdConnectAuthenticationProvider"
class='org.mitre.openid.connect.client.OpenIdConnectAuthenticationProvider"> class='org.mitre.openid.connect.client.OIDCAuthenticationProvider">
<property name="userDetaulsService" ref="myUserDetailsService"/> <property name="userDetaulsService" ref="myUserDetailsService"/>
</bean> </bean>
@ -63,111 +63,22 @@ Configure the OpenIDConnectAuthenticationFilter by adding the XML to your applic
You will need to implement your own UserDetailsService and configure as the above does with the reference to *myUserDetailsService*. You will need to implement your own UserDetailsService and configure as the above does with the reference to *myUserDetailsService*.
## Proposed Account Chooser UI Application Extension ## Configuration of OIDCAuthenticationUsingChooserFilter
The following proposed extension is in response to [Issue #39]. The OIDCAuthenticationUsingChooserFilter was written in response to [Issue #39].
### Account Chooser Protocol Th Authentication Filter use the *oidcServerConfigs* property, a map of OIDC servers, an *accountChooserURI* property to denote the URI of the Account Chooser, and an *accountChooserClient* property to identify the Client to the Account Chooser UI application like so:
The following describes the protocol between the Client and Account Chooser UI application introduced in [Issue #39].
#### Authorization when using Account Chooser Code Flow
The Authorization when using Account Chooser Code Flow goes through the following steps.
1. Client prepares an Account Chooser Request containing the desired request parameters.
2. Client sends a request to the Account Chooser.
3. Account Chooser presents a selection of OpenID Connect (OIDC) Servers from which the End-User must select from.
4. End-User selects an OIDC.
5. Account Chooser Sends the End-User back to the Client with key value of the OIDC End-User selected.
6. The Client begins the Authorization flow desrcribed in [Authorization Code Flow][OpenID Connect Standard] of the [OpenID Connect Standard].
#### Account Chooser Request
When the End-User wishes to access a Protected Resource, and the End-User Authorization has not yet been obtained, the Client will redirect the End-User to Account Chooser.
Account Chooser MUST support the use of the HTTP "GET" and "POST" methods defined in RFC 2616 [RFC2616].
Clients MAY use the HTTP "GET" or "POST" method to send the Account Chooser Request to the Account Chooser. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
#### Client Prepares an Account Chooser Request
The Client prepares an Account Chooser Request to the Account Chooser with the request parameters using the HTTP "GET" or "POST" method.
The required Account Chooser Request parameters are as follows:
* redirect_uri - A redirection URI where the response will be sent.
There is one method to construct and send the request to the Account Chooser:
a. Simple Request Method
#### Simple Request Method
The Client prepares an Account Chooser Request to the Account Chooser using the appropriate parameters. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
The following is a non-normative example of an Account Chooser Request URL. Line wraps are for display purposes only.
http://server.example.com/chooser?
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
#### Client sends a request to the Account Chooser
Having constructed the Account Chooser Request, the Client sends it to the Account Chooser. This MAY happen via redirect, hyperlinking, or any other means of directing the User-Agent to the Account Chooser URL.
Following is a non-normative example using HTTP redirect. Line wraps are for display purposes only.
HTTP/1.1 302 Found
Location: https://server.example.com/chooser?
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
#### Account Chooser Sends the End-User back to the Client
After the End-User has select an OpenID Connect Server, it issues an Account Chooser Response and delivers it to the Client by adding the response parameters to redirect_uri specified in the Account Choose Request using the "application/x-www-form-urlencoded" format.
The following response parameters are included:
* oidc_alias - REQUIRED. The key used to configure the Client for its request of the selected OIDC server.
The following is non-normative example of a responses. Line wraps are for display purposes only.
HTTP/1.1 302 Found
Location: https://client.example.com/openid_connect_login?
oidc_alias=OIDC%20Server%201
#### End-User refuses to select an OIDC Server
If the End-User refuses to select an OIDC server, the Account Chooser MUST return an error response. The Account Chooser returns the Client via the redirection URI specified in the Account Chooser Request with the appropriate error parameters. No other parameters SHOULD be returned.
The error response parameters are the following:
* error - REQUIRED. The error code.
* error_description - OPTIONAL. A human-readable UTF-8 encoded text description of the error.
The response parameters are added to the query component of the redirection URI.
The following is a non-normative example. Line wraps after the second line are for the display purposes only.
HTTP/1.1 302 Found
Location: https://client.example.com/openid_connect_login?
error=end_user_cancelled
&error_description=The%20end%20user%20refused%20to%20select%20an%20OIDC%20server
### Modification to existing Client
#### Modifications to Client Configuration
The configuration of the filter would change by adding a OIDCServers property to the Client containing a map of OIDC servers, and a AccountChooserURI to denote the URI of the Account Chooser like so:
<bean id="openIdConnectAuthenticationFilter" <bean id="openIdConnectAuthenticationFilter"
class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter"> class="org.mitre.openid.connect.client.OIDCAuthenticationUsingChooserFilter">
<property name="errorRedirectURI" value="/login.jsp?authfail=openid" /> <property name="errorRedirectURI" value="/login.jsp?authfail=openid" />
<property name="authenticationManager" ref="authenticationManager" /> <property name="authenticationManager" ref="authenticationManager" />
<property name="AccountChooserURI" <property name="accountChooserURI"
value="http://sever.example.com:8080/account-chooser" /> value="http://sever.example.com:8080/account-chooser" />
<property name="accountChooserClientID" value="FGWEUIASJK" />
<property name="oidcServerConfigs"> <property name="oidcServerConfigs">
<map> <map>
<entry key="OIDC Server 1"> <entry key="http://sever.example.com:8080/Fopenid-connect-server">
<bean class="org.mitre.openid.connect.client.OIDCServerConfiguration"> <bean class="org.mitre.openid.connect.client.OIDCServerConfiguration">
<property name="authorizationEndpointURI" <property name="authorizationEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/oauth/authorize" /> value="http://sever.example.com:8080/openid-connect-server/oauth/authorize" />
@ -180,17 +91,14 @@ The configuration of the filter would change by adding a OIDCServers property to
<property name="clientSecret" value="someClientSecret" /> <property name="clientSecret" value="someClientSecret" />
</bean> </bean>
</entry> </entry>
<entry key="OIDC Server 2"> <entry key=". . .
...
</map> </map>
</property> </property>
</bean> </bean>
Again, you will need to implement your own UserDetailsService and configure as the above does with the reference to *myUserDetailsService*.
In cases where the Account Chooser will not be used, the Client will be configured with authorizationEndpointURI, tokenEndpointURI, checkIDEndpointURI, clientId, and clientSecret as the Client is presently.
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html "OpenID Connect Standard 1.0" [OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html "OpenID Connect Standard 1.0"
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html#code_flow "Authorization Code Flow, OpenID Connect Standard" [OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html#code_flow "Authorization Code Flow, OpenID Connect Standard"
[Issuer Identifier]: http://openid.net/specs/openid-connect-messages-1_0.html#issuer_identifier "Issuer Identifier"
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client" [Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"

View File

@ -67,16 +67,16 @@ import com.google.gson.JsonParser;
* @author nemonik * @author nemonik
* *
*/ */
public class OpenIdConnectAuthenticationFilter extends public class AbstractOIDCAuthenticationFilter extends
AbstractAuthenticationProcessingFilter { AbstractAuthenticationProcessingFilter {
private final static int HTTP_SOCKET_TIMEOUT = 30000; protected final static int HTTP_SOCKET_TIMEOUT = 30000;
private final static String SCOPE = "openid"; protected final static String SCOPE = "openid";
private final static int KEY_SIZE = 1024; protected final static int KEY_SIZE = 1024;
private final static String SIGNING_ALGORITHM = "SHA256withRSA"; protected final static String SIGNING_ALGORITHM = "SHA256withRSA";
private final static String NONCE_SIGNATURE_COOKIE_NAME = "nonce"; protected final static String NONCE_SIGNATURE_COOKIE_NAME = "nonce";
private final static String OIDC_ALIAS_COOKIE_NAME = "oidc_alias";
private final static String FILTER_PROCESSES_URL = "/openid_connect_login"; protected final static String FILTER_PROCESSES_URL = "/openid_connect_login";
/** /**
* Builds the redirect_uri that will be sent to the Authorization Endpoint. * Builds the redirect_uri that will be sent to the Authorization Endpoint.
@ -105,7 +105,9 @@ public class OpenIdConnectAuthenticationFilter extends
String name = (String) e.nextElement(); String name = (String) e.nextElement();
if ((ignore == null) || (!ignore.contains(name))) { if ((ignore == null) || (!ignore.contains(name))) {
// Assume for simplicity that there is only one value // Assume for simplicity that there is only one value
String value = request.getParameter(name); String value = request.getParameter(name);
if (value == null) { if (value == null) {
@ -150,13 +152,19 @@ public class OpenIdConnectAuthenticationFilter extends
char appendChar = '?'; char appendChar = '?';
for (Map.Entry<String, String> param : queryStringFields.entrySet()) { for (Map.Entry<String, String> param : queryStringFields.entrySet()) {
try { try {
URLBuilder.append(appendChar).append(param.getKey()) URLBuilder.append(appendChar).append(param.getKey())
.append('=') .append('=')
.append(URLEncoder.encode(param.getValue(), "UTF-8")); .append(URLEncoder.encode(param.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException uee) { } catch (UnsupportedEncodingException uee) {
throw new IllegalStateException(uee); throw new IllegalStateException(uee);
} }
appendChar = '&'; appendChar = '&';
} }
@ -188,8 +196,11 @@ public class OpenIdConnectAuthenticationFilter extends
.replace("=", ""); .replace("=", "");
} catch (GeneralSecurityException generalSecurityException) { } catch (GeneralSecurityException generalSecurityException) {
// generalSecurityException.printStackTrace(); // generalSecurityException.printStackTrace();
throw new IllegalStateException(generalSecurityException); throw new IllegalStateException(generalSecurityException);
} }
return signature; return signature;
@ -216,39 +227,37 @@ public class OpenIdConnectAuthenticationFilter extends
return signer.verify(sigBytes); return signer.verify(sigBytes);
} catch (GeneralSecurityException generalSecurityException) { } catch (GeneralSecurityException generalSecurityException) {
// generalSecurityException.printStackTrace(); // generalSecurityException.printStackTrace();
throw new IllegalStateException(generalSecurityException); throw new IllegalStateException(generalSecurityException);
} catch (UnsupportedEncodingException unsupportedEncodingException) { } catch (UnsupportedEncodingException unsupportedEncodingException) {
// unsupportedEncodingException.printStackTrace(); // unsupportedEncodingException.printStackTrace();
throw new IllegalStateException(unsupportedEncodingException); throw new IllegalStateException(unsupportedEncodingException);
} }
} }
private String errorRedirectURI; protected String errorRedirectURI;
private OIDCServerConfiguration oidcServerConfig; protected String scope;
private String accountChooserURI; protected int httpSocketTimeout = HTTP_SOCKET_TIMEOUT;
private Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs = new HashMap<String, OIDCServerConfiguration>(); protected PublicKey publicKey;
private String scope; protected PrivateKey privateKey;
private int httpSocketTimeout = HTTP_SOCKET_TIMEOUT; protected Signature signer;
private PublicKey publicKey;
private PrivateKey privateKey;
private Signature signer;
/** /**
* OpenIdConnectAuthenticationFilter constructor * OpenIdConnectAuthenticationFilter constructor
*/ */
protected OpenIdConnectAuthenticationFilter() { protected AbstractOIDCAuthenticationFilter() {
super(FILTER_PROCESSES_URL); super(FILTER_PROCESSES_URL);
oidcServerConfig = new OIDCServerConfiguration();
} }
@Override @Override
@ -258,40 +267,6 @@ public class OpenIdConnectAuthenticationFilter extends
Assert.notNull(errorRedirectURI, Assert.notNull(errorRedirectURI,
"An Error Redirect URI must be supplied"); "An Error Redirect URI must be supplied");
try {
// Validating configuration w/ Account Chooser UI Application
// settings
Assert.notNull(
oidcServerConfigs,
"Server Configurations must be supplied if the Account Chooser UI Application is to be used.");
Assert.notNull(
accountChooserURI,
"Account Chooser URI must be supplied if the Account Chooser UI Application is to be used.");
} catch (Exception e) {
// Failing over to validating configuration w/o Account Chooser UI
// Application settings
Assert.notNull(oidcServerConfig.getAuthorizationEndpointURI(),
"An Authorization Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getTokenEndpointURI(),
"A Token ID Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getCheckIDEndpointURI(),
"A Check ID Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getClientId(),
"A Client ID must be supplied");
Assert.notNull(oidcServerConfig.getClientSecret(),
"A Client Secret must be supplied");
}
KeyPairGenerator keyPairGenerator; KeyPairGenerator keyPairGenerator;
try { try {
@ -321,76 +296,13 @@ public class OpenIdConnectAuthenticationFilter extends
* javax.servlet.http.HttpServletResponse) * javax.servlet.http.HttpServletResponse)
*/ */
@Override @Override
public Authentication attemptAuthentication(HttpServletRequest request, public Authentication attemptAuthentication(HttpServletRequest arg0,
HttpServletResponse response) throws AuthenticationException, HttpServletResponse arg1) throws AuthenticationException,
IOException, ServletException { IOException, ServletException {
// Enter AuthenticationFilter here...
if (StringUtils.isNotBlank(request.getParameter("error"))) {
handleError(request, response);
} else if (request.getParameter("code") != null) {
return handleAuthorizationGrantResponse(request);
} else if (StringUtils.isNotBlank(accountChooserURI)) {
String oidcAlias = request.getParameter("oidc_alias");
if (StringUtils.isNotBlank(oidcAlias)) {
// Account Chooser UI selects the appropriate OIDC Server configuration
OIDCServerConfiguration oidcServerConfig = oidcServerConfigs
.get(oidcAlias);
if (oidcServerConfig != null) {
Cookie oidcAliasCookie = new Cookie(OIDC_ALIAS_COOKIE_NAME, oidcAlias);
response.addCookie(oidcAliasCookie);
handleAuthorizationRequest(request, response, oidcServerConfig);
} else {
throw new AuthenticationServiceException(
"Security Filter not configured for OIDC Alias: "
+ oidcAlias);
}
} else {
// redirect the End-User to the configured Account Chooser UI
Map<String, String> urlVariables = new HashMap<String, String>();
urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter
.buildRedirectURI(request, null));
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
accountChooserURI, urlVariables));
}
} else {
// Use configuration that doesn't involve the Account Chooser UI.
handleAuthorizationRequest(request, response, this.oidcServerConfig);
}
return null; return null;
} }
public String getAccountChooserURI() {
return accountChooserURI;
}
public Map<String, ? extends OIDCServerConfiguration> getOidcServerConfigs() {
return oidcServerConfigs;
}
/** /**
* Handles the authorization grant response * Handles the authorization grant response
* *
@ -400,20 +312,11 @@ public class OpenIdConnectAuthenticationFilter extends
* @return The authenticated user token, or null if authentication is * @return The authenticated user token, or null if authentication is
* incomplete. * incomplete.
*/ */
private Authentication handleAuthorizationGrantResponse( protected Authentication handleAuthorizationGrantResponse(
HttpServletRequest request) { HttpServletRequest request, OIDCServerConfiguration serverConfig) {
final boolean debug = logger.isDebugEnabled(); final boolean debug = logger.isDebugEnabled();
OIDCServerConfiguration serverConfig = this.oidcServerConfig;
// Which OIDC configuration?
Cookie oidcAliasCookie = WebUtils.getCookie(request, OIDC_ALIAS_COOKIE_NAME);
if (oidcAliasCookie != null) {
serverConfig = oidcServerConfigs.get(oidcAliasCookie.getValue());
}
String authorizationGrant = request.getParameter("code"); String authorizationGrant = request.getParameter("code");
// Handle Token Endpoint interaction // Handle Token Endpoint interaction
@ -440,7 +343,7 @@ public class OpenIdConnectAuthenticationFilter extends
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.add("grant_type", "authorization_code"); form.add("grant_type", "authorization_code");
form.add("code", authorizationGrant); form.add("code", authorizationGrant);
form.add("redirect_uri", OpenIdConnectAuthenticationFilter form.add("redirect_uri", AbstractOIDCAuthenticationFilter
.buildRedirectURI(request, new String[] { "code" })); .buildRedirectURI(request, new String[] { "code" }));
// pass clientId and clientSecret in post of request // pass clientId and clientSecret in post of request
@ -448,15 +351,16 @@ public class OpenIdConnectAuthenticationFilter extends
form.add("client_secret", serverConfig.getClientSecret()); form.add("client_secret", serverConfig.getClientSecret());
if (debug) { if (debug) {
logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointURI()); logger.debug("tokenEndpointURI = "
+ serverConfig.getTokenEndpointURI());
logger.debug("form = " + form); logger.debug("form = " + form);
} }
String jsonString = null; String jsonString = null;
try { try {
jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointURI(), form, jsonString = restTemplate.postForObject(
String.class); serverConfig.getTokenEndpointURI(), form, String.class);
} catch (HttpClientErrorException httpClientErrorException) { } catch (HttpClientErrorException httpClientErrorException) {
// Handle error // Handle error
@ -554,8 +458,9 @@ public class OpenIdConnectAuthenticationFilter extends
jsonString = null; jsonString = null;
try { try {
jsonString = restTemplate.postForObject(serverConfig.getCheckIDEndpointURI(), jsonString = restTemplate.postForObject(
form, String.class); serverConfig.getCheckIDEndpointURI(), form,
String.class);
} catch (HttpClientErrorException httpClientErrorException) { } catch (HttpClientErrorException httpClientErrorException) {
// Handle error // Handle error
@ -651,7 +556,7 @@ public class OpenIdConnectAuthenticationFilter extends
* @throws IOException * @throws IOException
* If an input or output exception occurs * If an input or output exception occurs
*/ */
private void handleAuthorizationRequest(HttpServletRequest request, protected void handleAuthorizationRequest(HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
OIDCServerConfiguration serverConfiguration) throws IOException { OIDCServerConfiguration serverConfiguration) throws IOException {
@ -662,7 +567,7 @@ public class OpenIdConnectAuthenticationFilter extends
urlVariables.put("response_type", "code"); urlVariables.put("response_type", "code");
urlVariables.put("client_id", serverConfiguration.getClientId()); urlVariables.put("client_id", serverConfiguration.getClientId());
urlVariables.put("scope", scope); urlVariables.put("scope", scope);
urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter urlVariables.put("redirect_uri", AbstractOIDCAuthenticationFilter
.buildRedirectURI(request, null)); .buildRedirectURI(request, null));
// Create a string value used to associate a user agent session // Create a string value used to associate a user agent session
@ -684,7 +589,7 @@ public class OpenIdConnectAuthenticationFilter extends
// TODO: display, prompt, request, request_uri // TODO: display, prompt, request, request_uri
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL( response.sendRedirect(AbstractOIDCAuthenticationFilter.buildURL(
serverConfiguration.getAuthorizationEndpointURI(), urlVariables)); serverConfiguration.getAuthorizationEndpointURI(), urlVariables));
} }
@ -699,7 +604,7 @@ public class OpenIdConnectAuthenticationFilter extends
* @throws IOException * @throws IOException
* If an input or output exception occurs * If an input or output exception occurs
*/ */
private void handleError(HttpServletRequest request, protected void handleError(HttpServletRequest request,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
String error = request.getParameter("error"); String error = request.getParameter("error");
@ -718,44 +623,15 @@ public class OpenIdConnectAuthenticationFilter extends
requestParams.put("error_uri", errorURI); requestParams.put("error_uri", errorURI);
} }
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL( response.sendRedirect(AbstractOIDCAuthenticationFilter.buildURL(
errorRedirectURI, requestParams)); errorRedirectURI, requestParams));
} }
public void setAccountChooserURI(String accountChooserURI) {
this.accountChooserURI = accountChooserURI;
}
public void setAuthorizationEndpointURI(String authorizationEndpointURI) {
oidcServerConfig.setAuthorizationEndpointURI(authorizationEndpointURI);
}
public void setCheckIDEndpointURI(String checkIDEndpointURI) {
oidcServerConfig.setCheckIDEndpointURI(checkIDEndpointURI);
}
public void setClientId(String clientId) {
oidcServerConfig.setClientId(clientId);
}
public void setClientSecret(String clientSecret) {
oidcServerConfig.setClientSecret(clientSecret);
}
public void setErrorRedirectURI(String errorRedirectURI) { public void setErrorRedirectURI(String errorRedirectURI) {
this.errorRedirectURI = errorRedirectURI; this.errorRedirectURI = errorRedirectURI;
} }
public void setOidcServerConfigs(
Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs) {
this.oidcServerConfigs = oidcServerConfigs;
}
public void setScope(String scope) { public void setScope(String scope) {
this.scope = scope; this.scope = scope;
} }
public void setTokenEndpointURI(String tokenEndpointURI) {
oidcServerConfig.setTokenEndpointURI(tokenEndpointURI);
}
} }

View File

@ -0,0 +1,133 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* 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.mitre.openid.connect.client;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.Assert;
/**
* The OpenID Connect Authentication Filter
*
* See README.md to to configure
*
* @author nemonik
*
*/
public class OIDCAuthenticationFilter extends AbstractOIDCAuthenticationFilter {
protected OIDCServerConfiguration oidcServerConfig;
/**
* OpenIdConnectAuthenticationFilter constructor
*/
protected OIDCAuthenticationFilter() {
super();
oidcServerConfig = new OIDCServerConfiguration();
}
/*
* (non-Javadoc)
*
* @see org.mitre.openid.connect.client.AbstractOIDCAuthenticationFilter#
* afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
// Validating configuration
Assert.notNull(oidcServerConfig.getAuthorizationEndpointURI(),
"An Authorization Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getTokenEndpointURI(),
"A Token ID Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getCheckIDEndpointURI(),
"A Check ID Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getClientId(),
"A Client ID must be supplied");
Assert.notNull(oidcServerConfig.getClientSecret(),
"A Client Secret must be supplied");
}
/*
* (non-Javadoc)
*
* @see org.mitre.openid.connect.client.AbstractOIDCAuthenticationFilter#
* attemptAuthentication(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
// Enter AuthenticationFilter here...
super.attemptAuthentication(request, response);
if (StringUtils.isNotBlank(request.getParameter("error"))) {
handleError(request, response);
} else if (request.getParameter("code") != null) {
return handleAuthorizationGrantResponse(request, oidcServerConfig);
} else {
handleAuthorizationRequest(request, response, oidcServerConfig);
}
return null;
}
public void setAuthorizationEndpointURI(String authorizationEndpointURI) {
oidcServerConfig.setAuthorizationEndpointURI(authorizationEndpointURI);
}
public void setCheckIDEndpointURI(String checkIDEndpointURI) {
oidcServerConfig.setCheckIDEndpointURI(checkIDEndpointURI);
}
public void setClientId(String clientId) {
oidcServerConfig.setClientId(clientId);
}
public void setClientSecret(String clientSecret) {
oidcServerConfig.setClientSecret(clientSecret);
}
public void setErrorRedirectURI(String errorRedirectURI) {
this.errorRedirectURI = errorRedirectURI;
}
public void setTokenEndpointURI(String tokenEndpointURI) {
oidcServerConfig.setTokenEndpointURI(tokenEndpointURI);
}
}

View File

@ -0,0 +1,178 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* 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.mitre.openid.connect.client;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.Assert;
import org.springframework.web.util.WebUtils;
/**
* The OpenID Connect Authentication Filter using Acount Chooser UI Application
*
* See README.md to to configure
*
* @author nemonik
*
*/
public class OIDCAuthenticationUsingChooserFilter extends
AbstractOIDCAuthenticationFilter {
protected final static String ISSUER_COOKIE_NAME = "issuer";
protected String accountChooserURI;
protected String accountChooserClientID;
protected Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs = new HashMap<String, OIDCServerConfiguration>();
/**
* OpenIdConnectAuthenticationFilter constructor
*/
protected OIDCAuthenticationUsingChooserFilter() {
super();
}
/*
* (non-Javadoc)
*
* @see org.mitre.openid.connect.client.AbstractOIDCAuthenticationFilter#
* afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
// Validating configuration
Assert.notNull(
oidcServerConfigs,
"Server Configurations must be supplied if the Account Chooser UI Application is to be used.");
Assert.notNull(
accountChooserURI,
"Account Chooser URI must be supplied if the Account Chooser UI Application is to be used.");
Assert.notNull(
accountChooserClientID,
"Account Chooser Client ID must be supplied if the Account Chooser UI Application is to be used.");
}
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.authentication.
* AbstractAuthenticationProcessingFilter
* #attemptAuthentication(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws IOException,
AuthenticationException, ServletException {
// Enter AuthenticationFilter here...
super.attemptAuthentication(request, response);
if (StringUtils.isNotBlank(request.getParameter("error"))) {
handleError(request, response);
} else if (request.getParameter("code") != null) {
// Which OIDC configuration?
Cookie oidcAliasCookie = WebUtils.getCookie(request,
ISSUER_COOKIE_NAME);
return handleAuthorizationGrantResponse(request,
oidcServerConfigs.get(oidcAliasCookie.getValue()));
} else {
String issuerId = request.getParameter("issuer");
if (StringUtils.isNotBlank(issuerId)) {
// Account Chooser UI provided and Issuer Identifier
OIDCServerConfiguration oidcServerConfig = oidcServerConfigs
.get(issuerId);
if (oidcServerConfig != null) {
// The Client is configured to support this Issuer
// Identifier
Cookie issuerCookie = new Cookie(ISSUER_COOKIE_NAME,
issuerId);
response.addCookie(issuerCookie);
handleAuthorizationRequest(request, response,
oidcServerConfig);
} else {
// The Client is NOT configured to support this Issuer
// Identifier
throw new AuthenticationServiceException(
"Security Filter not configured for OIDC Alias: "
+ issuerId);
}
} else {
// Redirect the End-User to the configured Account Chooser UI
// application
Map<String, String> urlVariables = new HashMap<String, String>();
urlVariables.put("redirect_uri",
OIDCAuthenticationUsingChooserFilter.buildRedirectURI(
request, null));
response.sendRedirect(OIDCAuthenticationUsingChooserFilter
.buildURL(accountChooserURI, urlVariables));
}
}
return null;
}
public void setAccountChooserClientID(String accountChooserClientID) {
this.accountChooserClientID = accountChooserClientID;
}
public void setAccountChooserURI(String accountChooserURI) {
this.accountChooserURI = accountChooserURI;
}
public void setOidcServerConfigs(
Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs) {
this.oidcServerConfigs = oidcServerConfigs;
}
}

View File

@ -5,10 +5,7 @@
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/> <wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/> <wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<dependent-module archiveName="spring-security-oauth2-1.0.0.BUILD-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/spring-security-oauth2-MITRE/spring-security-oauth2-MITRE"> <dependent-module archiveName="openid-connect-common-0.1-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/openid-connect-common/openid-connect-common">
<dependency-type>uses</dependency-type>
</dependent-module>
<dependent-module archiveName="openid-connect-common-0.1-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/openid-connect-common-MITRE/openid-connect-common-MITRE">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>
<property name="java-output-path" value="/openid/target/classes"/> <property name="java-output-path" value="/openid/target/classes"/>