diff --git a/account-chooser/README.md b/account-chooser/README.md index 79211e92c..fe6413749 100644 --- a/account-chooser/README.md +++ b/account-chooser/README.md @@ -2,53 +2,60 @@ ## 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 -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: - - + + - + - - - - - - - - - - - + + - + The keys must match those found in the OpenIdConnectAuthenticationFilter's configuration like so: - + + + + + - - - - - - + + + + + + + + - … + . . . + + +## 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" \ No newline at end of file diff --git a/account-chooser/docs/cancel.png b/account-chooser/docs/cancel.png new file mode 100644 index 000000000..98446ba51 Binary files /dev/null and b/account-chooser/docs/cancel.png differ diff --git a/account-chooser/docs/not_supported.png b/account-chooser/docs/not_supported.png new file mode 100644 index 000000000..c7e70ad1e Binary files /dev/null and b/account-chooser/docs/not_supported.png differ diff --git a/account-chooser/docs/protocol.md b/account-chooser/docs/protocol.md new file mode 100644 index 000000000..872ef085d --- /dev/null +++ b/account-chooser/docs/protocol.md @@ -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" \ No newline at end of file diff --git a/account-chooser/docs/protocol.pdf b/account-chooser/docs/protocol.pdf new file mode 100644 index 000000000..388a7d3a7 Binary files /dev/null and b/account-chooser/docs/protocol.pdf differ diff --git a/account-chooser/docs/success.png b/account-chooser/docs/success.png new file mode 100644 index 000000000..fb4c1a0c0 Binary files /dev/null and b/account-chooser/docs/success.png differ diff --git a/account-chooser/docs/webSequenceDiagram.txt b/account-chooser/docs/webSequenceDiagram.txt new file mode 100644 index 000000000..478561ccf --- /dev/null +++ b/account-chooser/docs/webSequenceDiagram.txt @@ -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 diff --git a/account-chooser/src/main/java/org/mitre/account_chooser/AccountChooserConfig.java b/account-chooser/src/main/java/org/mitre/account_chooser/AccountChooserConfig.java new file mode 100644 index 000000000..e4943aad1 --- /dev/null +++ b/account-chooser/src/main/java/org/mitre/account_chooser/AccountChooserConfig.java @@ -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 issuers = new HashMap(); + + public Map getIssuers() { + return issuers; + } + + public String[] getValidClientIds() { + return validClientIds; + } + + public void setIssuers(Map issuers) { + this.issuers = issuers; + } + + public void setValidClientIds(String[] validClientIds) { + + this.validClientIds = validClientIds; + } +} diff --git a/account-chooser/src/main/java/org/mitre/account_chooser/AccountChooserController.java b/account-chooser/src/main/java/org/mitre/account_chooser/AccountChooserController.java index ee2ebb513..1bab2c8e2 100644 --- a/account-chooser/src/main/java/org/mitre/account_chooser/AccountChooserController.java +++ b/account-chooser/src/main/java/org/mitre/account_chooser/AccountChooserController.java @@ -16,6 +16,11 @@ package org.mitre.account_chooser; 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; @@ -33,14 +38,49 @@ import org.springframework.web.servlet.ModelAndView; * * @author nemonik * - * See README.md for configuration. + * See README.md for configuration. * */ @Controller 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 queryStringFields) { + + StringBuilder URLBuilder = new StringBuilder(baseURI); + + char appendChar = '?'; + + for (Map.Entry 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 - OIDCServers servers; + AccountChooserConfig accountChooserConfig; private static Log logger = LogFactory .getLog(AccountChooserController.class); @@ -51,18 +91,51 @@ public class AccountChooserController { * @param redirectUri * A redirection URI where the response will be sent * @return + * @throws IOException */ @RequestMapping(value = "/", method = { RequestMethod.GET, RequestMethod.POST }) 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.addObject("servers", servers); - modelAndView.addObject("redirect_uri", redirectUri); - modelAndView.setViewName("chooser"); + ModelAndView modelAndView = null; + + if (Arrays.asList(accountChooserConfig.getValidClientIds()).contains( + 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 urlVariables = new HashMap(); + + 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; + } /** @@ -81,9 +154,50 @@ public class AccountChooserController { */ @RequestMapping(value = "/selected") 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 { - response.sendRedirect(redirectUri + "?oidc_alias=" + alias); + // Handle Submit + + Map urlVariables = new HashMap(); + 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 urlVariables = new HashMap(); + urlVariables.put("error", "end_user_cancelled"); + urlVariables.put("error_description", + "The end-user refused to select an Account."); + + response.sendRedirect(AccountChooserController.buildURL(redirectUri, + urlVariables)); + } } diff --git a/account-chooser/src/main/java/org/mitre/account_chooser/OIDCServer.java b/account-chooser/src/main/java/org/mitre/account_chooser/OIDCServer.java index cd5c3581a..f37849fb4 100644 --- a/account-chooser/src/main/java/org/mitre/account_chooser/OIDCServer.java +++ b/account-chooser/src/main/java/org/mitre/account_chooser/OIDCServer.java @@ -18,6 +18,8 @@ package org.mitre.account_chooser; /** * @author nemonik * + * Holdes properties of the Issuer, e.g. name, icon URL, description, et cetera + * */ public class OIDCServer { diff --git a/account-chooser/src/main/java/org/mitre/account_chooser/OIDCServers.java b/account-chooser/src/main/java/org/mitre/account_chooser/OIDCServers.java deleted file mode 100644 index cb21f9a68..000000000 --- a/account-chooser/src/main/java/org/mitre/account_chooser/OIDCServers.java +++ /dev/null @@ -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 servers = new HashMap(); - - 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 getServers() { - return servers; - } - - /** - * Set the OIDCServers associated with this - * - * @param signers - * List of JwtSigners to associate with this service - */ - public void setServers(Map servers) { - this.servers = servers; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "OIDCServers [servers=" + servers - + "]"; - } - -} diff --git a/account-chooser/src/main/webapp/WEB-INF/spring-servlet.xml b/account-chooser/src/main/webapp/WEB-INF/spring-servlet.xml index 317d55109..66a11ca0b 100644 --- a/account-chooser/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/account-chooser/src/main/webapp/WEB-INF/spring-servlet.xml @@ -35,26 +35,17 @@ - - + + - + - - - - - - - - - - - + + \ No newline at end of file diff --git a/account-chooser/src/main/webapp/WEB-INF/views/chooser.jsp b/account-chooser/src/main/webapp/WEB-INF/views/chooser.jsp index c67a5c69d..9ca4e9708 100644 --- a/account-chooser/src/main/webapp/WEB-INF/views/chooser.jsp +++ b/account-chooser/src/main/webapp/WEB-INF/views/chooser.jsp @@ -3,9 +3,8 @@ <%@page import="org.mitre.account_chooser.OIDCServer"%> <%@page import="java.util.Map"%> <%@page import="java.util.Iterator"%> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - @@ -36,22 +35,21 @@ body {
-
+
- <% - Map map = servers.getServers(); - Iterator entries = map.entrySet().iterator(); + Iterator entries = issuers.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); - String alias = (String) entry.getKey(); - OIDCServer server = (OIDCServer) entry.getValue(); + String id = (String) entry.getKey(); + OIDCServer issuer = (OIDCServer) entry.getValue(); %> - + <% } %> @@ -61,12 +59,13 @@ body {
- "> + +
- - + Submit + Cancel
@@ -94,7 +93,10 @@ body { diff --git a/account-chooser/src/main/webapp/WEB-INF/views/error.jsp b/account-chooser/src/main/webapp/WEB-INF/views/error.jsp new file mode 100644 index 000000000..7362e73be --- /dev/null +++ b/account-chooser/src/main/webapp/WEB-INF/views/error.jsp @@ -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"%> + + + + + +Account Chooser + + + + + + + + + + + + +
+
+
+

+ ${error}
+ ${error_description} +

+ + Return to client +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/openid-connect-client/README.md b/openid-connect-client/README.md index 972d3d152..64b972466 100644 --- a/openid-connect-client/README.md +++ b/openid-connect-client/README.md @@ -4,9 +4,9 @@ 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: @@ -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*. -## 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 - -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: +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: + class="org.mitre.openid.connect.client.OIDCAuthenticationUsingChooserFilter"> - + - + @@ -180,17 +91,14 @@ The configuration of the filter would change by adding a OIDCServers property to - - ... + 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 = '&'; } @@ -188,8 +196,11 @@ public class OpenIdConnectAuthenticationFilter extends .replace("=", ""); } catch (GeneralSecurityException generalSecurityException) { + // generalSecurityException.printStackTrace(); + throw new IllegalStateException(generalSecurityException); + } return signature; @@ -216,39 +227,37 @@ public class OpenIdConnectAuthenticationFilter extends return signer.verify(sigBytes); } catch (GeneralSecurityException generalSecurityException) { + // generalSecurityException.printStackTrace(); + throw new IllegalStateException(generalSecurityException); + } catch (UnsupportedEncodingException unsupportedEncodingException) { + // unsupportedEncodingException.printStackTrace(); + 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 oidcServerConfigs = new HashMap(); + protected PublicKey publicKey; - private String scope; + protected PrivateKey privateKey; - private int httpSocketTimeout = HTTP_SOCKET_TIMEOUT; - - private PublicKey publicKey; - - private PrivateKey privateKey; - - private Signature signer; + protected Signature signer; /** * OpenIdConnectAuthenticationFilter constructor */ - protected OpenIdConnectAuthenticationFilter() { + protected AbstractOIDCAuthenticationFilter() { super(FILTER_PROCESSES_URL); - - oidcServerConfig = new OIDCServerConfiguration(); } @Override @@ -258,40 +267,6 @@ public class OpenIdConnectAuthenticationFilter extends Assert.notNull(errorRedirectURI, "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; try { @@ -321,76 +296,13 @@ public class OpenIdConnectAuthenticationFilter extends * javax.servlet.http.HttpServletResponse) */ @Override - public Authentication attemptAuthentication(HttpServletRequest request, - HttpServletResponse response) throws AuthenticationException, + public Authentication attemptAuthentication(HttpServletRequest arg0, + HttpServletResponse arg1) throws AuthenticationException, 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 urlVariables = new HashMap(); - - 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; } - public String getAccountChooserURI() { - return accountChooserURI; - } - - public Map getOidcServerConfigs() { - return oidcServerConfigs; - } - /** * Handles the authorization grant response * @@ -400,20 +312,11 @@ public class OpenIdConnectAuthenticationFilter extends * @return The authenticated user token, or null if authentication is * incomplete. */ - private Authentication handleAuthorizationGrantResponse( - HttpServletRequest request) { + protected Authentication handleAuthorizationGrantResponse( + HttpServletRequest request, OIDCServerConfiguration serverConfig) { 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"); // Handle Token Endpoint interaction @@ -440,7 +343,7 @@ public class OpenIdConnectAuthenticationFilter extends MultiValueMap form = new LinkedMultiValueMap(); form.add("grant_type", "authorization_code"); form.add("code", authorizationGrant); - form.add("redirect_uri", OpenIdConnectAuthenticationFilter + form.add("redirect_uri", AbstractOIDCAuthenticationFilter .buildRedirectURI(request, new String[] { "code" })); // pass clientId and clientSecret in post of request @@ -448,15 +351,16 @@ public class OpenIdConnectAuthenticationFilter extends form.add("client_secret", serverConfig.getClientSecret()); if (debug) { - logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointURI()); + logger.debug("tokenEndpointURI = " + + serverConfig.getTokenEndpointURI()); logger.debug("form = " + form); } String jsonString = null; try { - jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointURI(), form, - String.class); + jsonString = restTemplate.postForObject( + serverConfig.getTokenEndpointURI(), form, String.class); } catch (HttpClientErrorException httpClientErrorException) { // Handle error @@ -554,8 +458,9 @@ public class OpenIdConnectAuthenticationFilter extends jsonString = null; try { - jsonString = restTemplate.postForObject(serverConfig.getCheckIDEndpointURI(), - form, String.class); + jsonString = restTemplate.postForObject( + serverConfig.getCheckIDEndpointURI(), form, + String.class); } catch (HttpClientErrorException httpClientErrorException) { // Handle error @@ -651,7 +556,7 @@ public class OpenIdConnectAuthenticationFilter extends * @throws IOException * If an input or output exception occurs */ - private void handleAuthorizationRequest(HttpServletRequest request, + protected void handleAuthorizationRequest(HttpServletRequest request, HttpServletResponse response, OIDCServerConfiguration serverConfiguration) throws IOException { @@ -662,7 +567,7 @@ public class OpenIdConnectAuthenticationFilter extends urlVariables.put("response_type", "code"); urlVariables.put("client_id", serverConfiguration.getClientId()); urlVariables.put("scope", scope); - urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter + urlVariables.put("redirect_uri", AbstractOIDCAuthenticationFilter .buildRedirectURI(request, null)); // 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 - response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL( + response.sendRedirect(AbstractOIDCAuthenticationFilter.buildURL( serverConfiguration.getAuthorizationEndpointURI(), urlVariables)); } @@ -699,7 +604,7 @@ public class OpenIdConnectAuthenticationFilter extends * @throws IOException * If an input or output exception occurs */ - private void handleError(HttpServletRequest request, + protected void handleError(HttpServletRequest request, HttpServletResponse response) throws IOException { String error = request.getParameter("error"); @@ -718,44 +623,15 @@ public class OpenIdConnectAuthenticationFilter extends requestParams.put("error_uri", errorURI); } - response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL( + response.sendRedirect(AbstractOIDCAuthenticationFilter.buildURL( 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) { this.errorRedirectURI = errorRedirectURI; } - public void setOidcServerConfigs( - Map oidcServerConfigs) { - this.oidcServerConfigs = oidcServerConfigs; - } - public void setScope(String scope) { this.scope = scope; } - - public void setTokenEndpointURI(String tokenEndpointURI) { - oidcServerConfig.setTokenEndpointURI(tokenEndpointURI); - } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java new file mode 100644 index 000000000..4f9f0d877 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java @@ -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); + } +} diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationUsingChooserFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationUsingChooserFilter.java new file mode 100644 index 000000000..119779128 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationUsingChooserFilter.java @@ -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 oidcServerConfigs = new HashMap(); + + /** + * 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 urlVariables = new HashMap(); + + 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 oidcServerConfigs) { + this.oidcServerConfigs = oidcServerConfigs; + } +} diff --git a/openid-connect-server/.settings/org.eclipse.wst.common.component b/openid-connect-server/.settings/org.eclipse.wst.common.component index 5b4c881f0..f1cfea574 100644 --- a/openid-connect-server/.settings/org.eclipse.wst.common.component +++ b/openid-connect-server/.settings/org.eclipse.wst.common.component @@ -5,10 +5,7 @@ - - uses - - + uses