Java OpenID Connect example using MITREid and SPRING
This guide uses the MITREid Connect client, a certified OpenID Connect reference implementation in Java on the Spring framework.
It is assumed that the user has knowledge of developing applications using Java and in this case is using the Spring framework. This example is based on the document at: https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/wiki/Client-configuration.
Goal in this example
Authenticate a user and display all the received claims on a page. In the real world you would read the claims and feed them into your authorisation / user-session management process.
Instructions
Add the OIDC dependencies
In addition to any other dependencies you have related to your project, you will need to include openid-connect-client
in the dependencies section of your POM.
<dependency>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-client</artifactId>
<version>1.3.1</version>
</dependency>
E.g. in this example POM, it is inserted at lines 19 - 23:
Example POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ouk.org.eduserv.iam</groupId>
<artifactId>mitre-id</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java-version>1.8</java-version>
<spring.version>4.3.7.RELEASE</spring.version>
<slf4j.version>1.7.25</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-client</artifactId>
<version>1.3.1</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java-version}</source>
<target>${java-version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>install</id>
<phase>install</phase>
<goals>
<goal>sources</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.2.10.v20150310</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webApp>
<webInfIncludeJarPattern>.*/spring-[^/]*\.jar$</webInfIncludeJarPattern>
</webApp>
</configuration>
</plugin>
</plugins>
</build>
<name>mitre-id</name>
</project>
Configure the root-context.xml
servlet
Typically /WEB-INF/spring/root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<!-- SPEL processors -->
<security:global-method-security pre-post-annotations="enabled" proxy-target-class="true" authentication-manager-ref="authenticationManager">
<!--you could also wire in the expression handler up at the layer of the http filters. See https://jira.springsource.org/browse/SEC-1452 -->
</security:global-method-security>
<bean id="webexpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />
</beans>
Configure the servlet-context.xml
servlet
Typically /WEB-INF/spring/appServlet/servlet-context.xml
This is where the parts come together.
You define the filter under the bean openIdConnectAuthenticationFilter
.
This example configures a dynamic server configuration as dynamicServerConfigurationService
which will dynamically fetch the server configuration from https://connect.openathens.net/.well-known/openid-configuration, and cache the keys for 30minutes
The staticClientConfigurationService
is used to configure the client, which will be registered in the OpenAthens publisher dashboard. The name, secret and redirectUris provided in the dashboard will need to be configured here as a property.
The plainAuthRequestUrlBuilder
constructs the authentication request. OpenAthens supports plain text querystring authentication requests only and they look like:
https://connect.openathens.net/oidc/auth?response_type=code&client_id=athens.oidc-app-v1.7624fu71-904b-42cb-af49-dc14a1337ab7&scope=openid&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fopenid_connect_login&nonce=2da5f123b6cb1&state=12345678837e9
The MITREid Connect client supports JWT authentication requests (signed and encrypted), but these are not supported by OpenAthens. They look like:
https://connect.openathens.net/oidc/auth?request=eyJhbGciOiJSUzI1NiJ9.eyJzY29wZSI6Im9wZW5pZCIsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL2xvZ2luIiwic3RhdGUiOiIxMmNjMzVmMTE1NmYxIiwibm9uY2UiOiIyYzYzMDUyOTQ3MmQ2IiwiY2xpZW50X2lkIjoiYXRoZW5zLm9pZGMtYXBwLXYxLjc2OTRkZTcxLTkwNGEtNDRjYi1hZjQ5LWRjMTRhNzNiNTBiNyJ9.ch0zGefRy9aFcj4L8z_e6Tvx8B3RlSzJixLfu-7SqaifvAk6SIdaQVsZiBtWmitH9NgZcabfnXdJRTnQPAltdfF8bMMqo1UwCOnD6aDE6D_ntOhVZs8Z9KyR-m1Z_W8wKKvkQ8FCEsbLARI8uGpVUGjiKk1v2DvHFqWpalh4xJw
You set the connection to OpenAthens in a servlet-context.xml file - see lines 117 - 142 in the example:
Example servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<mvc:annotation-driven />
<mvc:interceptors>
<!-- Inject the UserInfo into the current context -->
<bean id="userInfoInterceptor" class="org.mitre.openid.connect.web.UserInfoInterceptor" />
</mvc:interceptors>
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<mvc:resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<context:component-scan base-package="org.mitre.web" />
<security:global-method-security pre-post-annotations="enabled" proxy-target-class="true" authentication-manager-ref="authenticationManager"/>
<security:http auto-config="false" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint"
pattern="/**">
<security:custom-filter before="PRE_AUTH_FILTER" ref="openIdConnectAuthenticationFilter" />
<security:logout />
</security:http>
<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<constructor-arg name="loginFormUrl" value="/openid_connect_login" />
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="openIdConnectAuthenticationProvider" />
</security:authentication-manager>
<bean id="openIdConnectAuthenticationProvider" class="org.mitre.openid.connect.client.OIDCAuthenticationProvider" />
<!--
-
- The authentication filter
-
-->
<bean id="openIdConnectAuthenticationFilter" class="org.mitre.openid.connect.client.OIDCAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="issuerService" ref="hybridIssuerService" />
<property name="serverConfigurationService" ref="dynamicServerConfigurationService" />
<property name="clientConfigurationService" ref="staticClientConfigurationService" />
<property name="authRequestOptionsService" ref="staticAuthRequestOptionsService" />
<property name="authRequestUrlBuilder" ref="plainAuthRequestUrlBuilder" />
</bean>
<!--
-
- Issuer Services: Determine which identity provider issuer is used.
-
-->
<!--
Hybrid issuer service. If an issuer is passed in directly with the "iss" parameter, it will use that. If not, it will
look for an "identifier" parameter to do Webfinger discovery on that. Failing that, it will redirect to the login
page URL.
-->
<bean class="org.mitre.openid.connect.client.service.impl.HybridIssuerService" id="hybridIssuerService">
<property name="loginPageUrl" value="login" />
<property name="forceHttps" value="false" /> <!-- this default property forces the webfinger issuer URL to be HTTPS, turn off for development work -->
</bean>
<!--
-
- Server configuration: determines the parameters and URLs of the server to talk to.
-
-->
<!--
Dynamic server configuration, fetches the server's information using OIDC Discovery.
-->
<bean class="org.mitre.openid.connect.client.service.impl.DynamicServerConfigurationService" id="dynamicServerConfigurationService" />
<!--
-
- Client Configuration: Determine which client identifier and credentials are used.
-
-->
<!--
Static Client Configuration. Configures a client statically by storing configuration on a per-issuer basis.
-->
<bean
class="org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService"
id="staticClientConfigurationService">
<property name="clients">
<map>
<entry key="https://connect.openathens.net">
<bean class="org.mitre.oauth2.model.RegisteredClient">
<property name="clientId"
value="<insert_your_clientId_here>"/>
<property name="clientSecret" value="<insert_your_clientSecret_here>"/>
<property name="scope">
<set value-type="java.lang.String">
<value>openid</value>
</set>
</property>
<property name="tokenEndpointAuthMethod" value="SECRET_BASIC" />
<property name="redirectUris">
<set>
<value>http://localhost:8080/openid_connect_login</value>
</set>
</property>
</bean>
</entry>
</map>
</property>
</bean>
<!--
-
- Auth request options service: returns the optional components of the request
-
-->
<bean class="org.mitre.openid.connect.client.service.impl.StaticAuthRequestOptionsService" id="staticAuthRequestOptionsService" />
<!--
-
- Authorization URL Builders: create the URL to redirect the user to for authorization.
-
-->
<!--
Plain authorization request builder, puts all options as query parameters on the GET request
-->
<bean class="org.mitre.openid.connect.client.service.impl.PlainAuthRequestUrlBuilder" id="plainAuthRequestUrlBuilder" />
<!--
-
- Utility beans for the above classes
-
-->
<!--
This service fetches and caches JWK sets from URLs.
-->
<bean id="validatorCache" class="org.mitre.jwt.signer.service.impl.JWKSetCacheService" />
</beans>
Configure the web.xml
Typically /WEB-INF/web.xml
Example web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.appServlet</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Configuring a basic JSP page to show the released attributes
E.g: /WEB-INF/views/home.jsp
Example home.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>OIDC</title>
</head>
<body>
<style type="text/css">
.tg {border-collapse:collapse;border-spacing:0;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg th{text-align: left;font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg .tg-yw4l{vertical-align:top}
th.tg-sort-header::-moz-selection { background:transparent; }th.tg-sort-header::selection { background:transparent; }th.tg-sort-header { cursor:pointer; }table th.tg-sort-header:after { content:''; float:right; margin-top:7px; border-width:0 4px 4px; border-style:solid; border-color:#404040 transparent; visibility:hidden; }table th.tg-sort-header:hover:after { visibility:visible; }table th.tg-sort-desc:after,table th.tg-sort-asc:after,table th.tg-sort-asc:hover:after { visibility:visible; opacity:0.4; }table th.tg-sort-desc:after { border-bottom:none; border-width:4px 4px 0; }@media screen and (max-width: 767px) {.tg {width: auto !important;}.tg col {width: auto !important;}.tg-wrap {overflow-x: auto;-webkit-overflow-scrolling: touch;}}</style>
<div class="tg-wrap">
<c:choose>
<c:when test="${empty userInfo}">
<h2>Click <a href="http://localhost:8080/openid_connect_login?identifier=https://connect.openathens.net">here</a> to login.</h2>
</c:when>
<c:otherwise>
<h2>You have successfully logged in. Attributes available are...</h2>
<table id="tg-miYfA" class="tg">
<c:forEach items="${userInfo}" var="entry">
<tr>
<th class="tg-yw4l">${entry.key}</th>
<th class="tg-yw4l">${entry.value}</th>
</tr>
</c:forEach>
</table>
</c:otherwise>
</c:choose>
</div>
<script type="text/javascript" charset="utf-8">var TgTableSort=window.TgTableSort||function(n,t){"use strict";function r(n,t){for(var e=[],o=n.childNodes,i=0;i<o.length;++i){var u=o[i];if("."==t.substring(0,1)){var a=t.substring(1);f(u,a)&&e.push(u)}else u.nodeName.toLowerCase()==t&&e.push(u);var c=r(u,t);e=e.concat(c)}return e}function e(n,t){var e=[],o=r(n,"tr");return o.forEach(function(n){var o=r(n,"td");t>=0&&t<o.length&&e.push(o[t])}),e}function o(n){return n.textContent||n.innerText||""}function i(n){return n.innerHTML||""}function u(n,t){var r=e(n,t);return r.map(o)}function a(n,t){var r=e(n,t);return r.map(i)}function c(n){var t=n.className||"";return t.match(/\S+/g)||[]}function f(n,t){return-1!=c(n).indexOf(t)}function s(n,t){f(n,t)||(n.className+=" "+t)}function d(n,t){if(f(n,t)){var r=c(n),e=r.indexOf(t);r.splice(e,1),n.className=r.join(" ")}}function v(n){d(n,L),d(n,E)}function l(n,t,e){r(n,"."+E).map(v),r(n,"."+L).map(v),e==T?s(t,E):s(t,L)}function g(n){return function(t,r){var e=n*t.str.localeCompare(r.str);return 0==e&&(e=t.index-r.index),e}}function h(n){return function(t,r){var e=+t.str,o=+r.str;return e==o?t.index-r.index:n*(e-o)}}function m(n,t,r){var e=u(n,t),o=e.map(function(n,t){return{str:n,index:t}}),i=e&&-1==e.map(isNaN).indexOf(!0),a=i?h(r):g(r);return o.sort(a),o.map(function(n){return n.index})}function p(n,t,r,o){for(var i=f(o,E)?N:T,u=m(n,r,i),c=0;t>c;++c){var s=e(n,c),d=a(n,c);s.forEach(function(n,t){n.innerHTML=d[u[t]]})}l(n,o,i)}function x(n,t){var r=t.length;t.forEach(function(t,e){t.addEventListener("click",function(){p(n,r,e,t)}),s(t,"tg-sort-header")})}var T=1,N=-1,E="tg-sort-asc",L="tg-sort-desc";return function(t){var e=n.getElementById(t),o=r(e,"tr"),i=o.length>0?r(o[0],"td"):[];0==i.length&&(i=r(o[0],"th"));for(var u=1;u<o.length;++u){var a=r(o[u],"td");if(a.length!=i.length)return}x(e,i)}}(document);document.addEventListener("DOMContentLoaded",function(n){TgTableSort("tg-miYfA")});</script>
<body>
</html>
Configure the Java class to process attributes and make them available to the JSP page.
Example class
package org.mitre.web;
import java.io.IOException;
import java.security.Principal;
import java.util.HashMap;
import java.util.Locale;
import org.mitre.openid.connect.client.OIDCAuthenticationFilter;
import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.mitre.openid.connect.model.UserInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
private static final Logger LOG = LoggerFactory.getLogger(HomeController.class);
// filter reference so we can get class names and things like that.
@Autowired
private OIDCAuthenticationFilter filter;
private OIDCAuthenticationToken token;
private UserInfo userInfo;
@SuppressWarnings("unchecked")
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model, Principal p) {
if (p != null) {
token = ( OIDCAuthenticationToken ) p;
userInfo = token.getUserInfo();
HashMap<String, Object> atts = new HashMap<>();
try {
atts = new ObjectMapper().readValue(userInfo.toJson().toString(), HashMap.class);
if (userInfo != null) {
LOG.debug("Attributes available :"+atts.toString());
model.addAttribute("userInfo", atts);
}
} catch ( IOException e) {
LOG.debug("Unable to map userInfo to HashMap {}",e);
}
}
model.addAttribute("issuerServiceClass", filter.getIssuerService().getClass().getSimpleName());
model.addAttribute("serverConfigurationServiceClass",
filter.getServerConfigurationService().getClass().getSimpleName());
model.addAttribute("clientConfigurationServiceClass",
filter.getClientConfigurationService().getClass().getSimpleName());
model.addAttribute("authRequestOptionsServiceClass",
filter.getAuthRequestOptionsService().getClass().getSimpleName());
model.addAttribute("authRequestUriBuilderClass", filter.getAuthRequestUrlBuilder().getClass().getSimpleName());
return "home";
}
}
Test
This should all now be invokable from http://localhost:8080/openid_connect_login?identifier=https://connect.openathens.net