add OpenId module to support OpenId Connect authentication
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
2b72f08f1b
commit
d33b96f411
|
@ -299,6 +299,11 @@
|
|||
<artifactId>jetty-security</artifactId>
|
||||
<version>9.4.21-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-openid</artifactId>
|
||||
<version>9.4.21-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
|
|
|
@ -711,6 +711,11 @@
|
|||
<artifactId>jetty-alpn-openjdk8-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-openid</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-conscrypt-server</artifactId>
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<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">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<version>9.4.21-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jetty-openid</artifactId>
|
||||
<name>Jetty :: OpenID</name>
|
||||
<description>Jetty OpenID Connect infrastructure</description>
|
||||
<url>http://www.eclipse.org/jetty</url>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.openid</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<onlyAnalyze>org.eclipse.jetty.security.openid.*</onlyAnalyze>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util-ajax</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.tests</groupId>
|
||||
<artifactId>jetty-http-tools</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
<New id="OpenIdConfiguration" class="org.eclipse.jetty.security.openid.OpenIdConfiguration">
|
||||
<Arg><Property name="jetty.openid.identityProvider"/></Arg>
|
||||
<Arg><Property name="jetty.openid.clientId"/></Arg>
|
||||
<Arg><Property name="jetty.openid.clientSecret"/></Arg>
|
||||
<Call name="addScopes">
|
||||
<Arg>
|
||||
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
|
||||
<Arg><Property name="jetty.openid.scopes"/></Arg>
|
||||
</Call>
|
||||
</Arg>
|
||||
</Call>
|
||||
</New>
|
||||
<Call name="addBean">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.security.openid.OpenIdLoginService">
|
||||
<Arg><Ref refid="OpenIdConfiguration"/></Arg>
|
||||
<Arg><Ref refid="BaseLoginService"/></Arg>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Configure>
|
|
@ -0,0 +1,31 @@
|
|||
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[description]
|
||||
Adds OpenId Connect authentication.
|
||||
|
||||
[depend]
|
||||
security
|
||||
|
||||
[lib]
|
||||
lib/jetty-openid-${jetty.version}.jar
|
||||
lib/jetty-util-ajax-${jetty.version}.jar
|
||||
|
||||
[files]
|
||||
basehome:modules/openid/openid-baseloginservice.xml|etc/openid-baseloginservice.xml
|
||||
|
||||
[xml]
|
||||
etc/openid-baseloginservice.xml
|
||||
etc/jetty-openid.xml
|
||||
|
||||
[ini-template]
|
||||
## Identity Provider
|
||||
# jetty.openid.identityProvider=https://accounts.google.com/
|
||||
|
||||
## Client ID
|
||||
# jetty.openid.clientId=1051168419525-5nl60mkugb77p9j194mrh287p1e0ahfi.apps.googleusercontent.com
|
||||
|
||||
## Client Secret
|
||||
# jetty.openid.clientSecret=XT_MIsSv_aUCGollauCaJY8S
|
||||
|
||||
## Scopes
|
||||
# jetty.openid.scopes=email,profile
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
<Configure id="BaseLoginService">
|
||||
<!-- Optional code to configure the base LoginService used by the OpenIdLoginService
|
||||
<New id="BaseLoginService" class="org.eclipse.jetty.security.HashLoginService">
|
||||
<Set name="config">/Users/Lachlan/webtide/jetty-base/etc/realm.properties</Set>
|
||||
<Set name="hotReload">true</Set>
|
||||
</New>
|
||||
-->
|
||||
</Configure>
|
|
@ -0,0 +1,481 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.security.ServerAuthException;
|
||||
import org.eclipse.jetty.security.UserAuthentication;
|
||||
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
|
||||
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.SessionAuthentication;
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.server.Authentication.User;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
||||
/**
|
||||
* OpenId Connect Authenticator.
|
||||
*
|
||||
* <p>This authenticator implements authentication using OpenId Connect on top of OAuth 2.0.
|
||||
*
|
||||
* <p>The authenticator redirects unauthenticated requests to the identity providers authorization endpoint
|
||||
* which will eventually redirect back to the redirectUri with an authorization code which will be exchanged with
|
||||
* the token_endpoint for an id_token. The request is then restored back to the original uri requested.
|
||||
* {@link SessionAuthentication} is then used to wrap Authentication results so that they are associated with the session.</p>
|
||||
*/
|
||||
public class OpenIdAuthenticator extends LoginAuthenticator
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(OpenIdAuthenticator.class);
|
||||
|
||||
public static final String __USER_CLAIMS = "org.eclipse.jetty.security.openid.user_claims";
|
||||
public static final String __RESPONSE_JSON = "org.eclipse.jetty.security.openid.response";
|
||||
public static final String __ERROR_PAGE = "org.eclipse.jetty.security.openid.error_page";
|
||||
public static final String __J_URI = "org.eclipse.jetty.security.openid.URI";
|
||||
public static final String __J_POST = "org.eclipse.jetty.security.openid.POST";
|
||||
public static final String __J_METHOD = "org.eclipse.jetty.security.openid.METHOD";
|
||||
public static final String __CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token";
|
||||
public static final String __J_SECURITY_CHECK = "/j_security_check";
|
||||
|
||||
private OpenIdConfiguration _configuration;
|
||||
private String _errorPage;
|
||||
private String _errorPath;
|
||||
private boolean _alwaysSaveUri;
|
||||
|
||||
public OpenIdAuthenticator()
|
||||
{
|
||||
}
|
||||
|
||||
public OpenIdAuthenticator(OpenIdConfiguration configuration, String errorPage)
|
||||
{
|
||||
this._configuration = configuration;
|
||||
if (errorPage != null)
|
||||
setErrorPage(errorPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(AuthConfiguration configuration)
|
||||
{
|
||||
super.setConfiguration(configuration);
|
||||
|
||||
String error = configuration.getInitParameter(__ERROR_PAGE);
|
||||
if (error != null)
|
||||
setErrorPage(error);
|
||||
|
||||
if (_configuration != null)
|
||||
return;
|
||||
|
||||
LoginService loginService = configuration.getLoginService();
|
||||
if (!(loginService instanceof OpenIdLoginService))
|
||||
throw new IllegalArgumentException("invalid LoginService");
|
||||
this._configuration = ((OpenIdLoginService)loginService).getConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod()
|
||||
{
|
||||
return Constraint.__OPENID_AUTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, uris that cause a redirect to a login page will always
|
||||
* be remembered. If false, only the first uri that leads to a login
|
||||
* page redirect is remembered.
|
||||
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
|
||||
*
|
||||
* @param alwaysSave true to always save the uri
|
||||
*/
|
||||
public void setAlwaysSaveUri(boolean alwaysSave)
|
||||
{
|
||||
_alwaysSaveUri = alwaysSave;
|
||||
}
|
||||
|
||||
public boolean getAlwaysSaveUri()
|
||||
{
|
||||
return _alwaysSaveUri;
|
||||
}
|
||||
|
||||
private void setErrorPage(String path)
|
||||
{
|
||||
if (path == null || path.trim().length() == 0)
|
||||
{
|
||||
_errorPath = null;
|
||||
_errorPage = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!path.startsWith("/"))
|
||||
{
|
||||
LOG.warn("error-page must start with /");
|
||||
path = "/" + path;
|
||||
}
|
||||
_errorPage = path;
|
||||
_errorPath = path;
|
||||
|
||||
if (_errorPath.indexOf('?') > 0)
|
||||
_errorPath = _errorPath.substring(0, _errorPath.indexOf('?'));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserIdentity login(String username, Object credentials, ServletRequest request)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("login {} {} {}", username, credentials, request);
|
||||
|
||||
UserIdentity user = super.login(username, credentials, request);
|
||||
if (user != null)
|
||||
{
|
||||
HttpSession session = ((HttpServletRequest)request).getSession();
|
||||
Authentication cached = new SessionAuthentication(getAuthMethod(), user, credentials);
|
||||
session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
|
||||
session.setAttribute(__USER_CLAIMS, ((OpenIdCredentials)credentials).getClaims());
|
||||
session.setAttribute(__RESPONSE_JSON, ((OpenIdCredentials)credentials).getResponse());
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(ServletRequest request)
|
||||
{
|
||||
super.logout(request);
|
||||
HttpServletRequest httpRequest = (HttpServletRequest)request;
|
||||
HttpSession session = httpRequest.getSession(false);
|
||||
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
//clean up session
|
||||
session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
|
||||
session.removeAttribute(__USER_CLAIMS);
|
||||
session.removeAttribute(__RESPONSE_JSON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareRequest(ServletRequest request)
|
||||
{
|
||||
//if this is a request resulting from a redirect after auth is complete
|
||||
//(ie its from a redirect to the original request uri) then due to
|
||||
//browser handling of 302 redirects, the method may not be the same as
|
||||
//that of the original request. Replace the method and original post
|
||||
//params (if it was a post).
|
||||
//
|
||||
//See Servlet Spec 3.1 sec 13.6.3
|
||||
HttpServletRequest httpRequest = (HttpServletRequest)request;
|
||||
HttpSession session = httpRequest.getSession(false);
|
||||
if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
|
||||
return; //not authenticated yet
|
||||
|
||||
String juri = (String)session.getAttribute(__J_URI);
|
||||
if (juri == null || juri.length() == 0)
|
||||
return; //no original uri saved
|
||||
|
||||
String method = (String)session.getAttribute(__J_METHOD);
|
||||
if (method == null || method.length() == 0)
|
||||
return; //didn't save original request method
|
||||
|
||||
StringBuffer buf = httpRequest.getRequestURL();
|
||||
if (httpRequest.getQueryString() != null)
|
||||
buf.append("?").append(httpRequest.getQueryString());
|
||||
|
||||
if (!juri.equals(buf.toString()))
|
||||
return; //this request is not for the same url as the original
|
||||
|
||||
//restore the original request's method on this request
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Restoring original method {} for {} with method {}", method, juri, httpRequest.getMethod());
|
||||
Request baseRequest = Request.getBaseRequest(request);
|
||||
baseRequest.setMethod(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
|
||||
{
|
||||
final HttpServletRequest request = (HttpServletRequest)req;
|
||||
final HttpServletResponse response = (HttpServletResponse)res;
|
||||
final Request baseRequest = Request.getBaseRequest(request);
|
||||
final Response baseResponse = baseRequest.getResponse();
|
||||
|
||||
String uri = request.getRequestURI();
|
||||
if (uri == null)
|
||||
uri = URIUtil.SLASH;
|
||||
|
||||
mandatory |= isJSecurityCheck(uri);
|
||||
if (!mandatory)
|
||||
return new DeferredAuthentication(this);
|
||||
|
||||
if (isErrorPage(URIUtil.addPaths(request.getServletPath(), request.getPathInfo())) && !DeferredAuthentication.isDeferred(response))
|
||||
return new DeferredAuthentication(this);
|
||||
|
||||
try
|
||||
{
|
||||
// Handle a request for authentication.
|
||||
if (isJSecurityCheck(uri))
|
||||
{
|
||||
String authCode = request.getParameter("code");
|
||||
if (authCode != null)
|
||||
{
|
||||
// Verify anti-forgery state token
|
||||
String state = request.getParameter("state");
|
||||
String antiForgeryToken = (String)request.getSession().getAttribute(__CSRF_TOKEN);
|
||||
if (antiForgeryToken == null || !antiForgeryToken.equals(state))
|
||||
{
|
||||
LOG.warn("auth failed 403: invalid state parameter");
|
||||
if (response != null)
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return Authentication.SEND_FAILURE;
|
||||
}
|
||||
|
||||
// Attempt to login with the provided authCode
|
||||
OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration);
|
||||
UserIdentity user = login(null, credentials, request);
|
||||
HttpSession session = request.getSession(false);
|
||||
if (user != null)
|
||||
{
|
||||
// Redirect to original request
|
||||
String nuri;
|
||||
synchronized (session)
|
||||
{
|
||||
nuri = (String)session.getAttribute(__J_URI);
|
||||
|
||||
if (nuri == null || nuri.length() == 0)
|
||||
{
|
||||
nuri = request.getContextPath();
|
||||
if (nuri.length() == 0)
|
||||
nuri = URIUtil.SLASH;
|
||||
}
|
||||
}
|
||||
OpenIdAuthentication openIdAuth = new OpenIdAuthentication(getAuthMethod(), user);
|
||||
LOG.debug("authenticated {}->{}", openIdAuth, nuri);
|
||||
|
||||
response.setContentLength(0);
|
||||
int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
|
||||
baseResponse.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
|
||||
return openIdAuth;
|
||||
}
|
||||
}
|
||||
|
||||
// not authenticated
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("OpenId authentication FAILED");
|
||||
if (_errorPage == null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("auth failed 403");
|
||||
if (response != null)
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("auth failed {}", _errorPage);
|
||||
int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
|
||||
baseResponse.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _errorPage)));
|
||||
}
|
||||
|
||||
return Authentication.SEND_FAILURE;
|
||||
}
|
||||
|
||||
// Look for cached authentication
|
||||
HttpSession session = request.getSession(false);
|
||||
Authentication authentication = session == null ? null : (Authentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
|
||||
if (authentication != null)
|
||||
{
|
||||
// Has authentication been revoked?
|
||||
if (authentication instanceof Authentication.User &&
|
||||
_loginService != null &&
|
||||
!_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
|
||||
{
|
||||
LOG.debug("auth revoked {}", authentication);
|
||||
session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
|
||||
}
|
||||
else
|
||||
{
|
||||
synchronized (session)
|
||||
{
|
||||
String jUri = (String)session.getAttribute(__J_URI);
|
||||
if (jUri != null)
|
||||
{
|
||||
//check if the request is for the same url as the original and restore
|
||||
//params if it was a post
|
||||
LOG.debug("auth retry {}->{}", authentication, jUri);
|
||||
StringBuffer buf = request.getRequestURL();
|
||||
if (request.getQueryString() != null)
|
||||
buf.append("?").append(request.getQueryString());
|
||||
|
||||
if (jUri.equals(buf.toString()))
|
||||
{
|
||||
MultiMap<String> jPost = (MultiMap<String>)session.getAttribute(__J_POST);
|
||||
if (jPost != null)
|
||||
{
|
||||
LOG.debug("auth rePOST {}->{}", authentication, jUri);
|
||||
baseRequest.setContentParameters(jPost);
|
||||
}
|
||||
session.removeAttribute(__J_URI);
|
||||
session.removeAttribute(__J_METHOD);
|
||||
session.removeAttribute(__J_POST);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.debug("auth {}", authentication);
|
||||
return authentication;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we can't send challenge
|
||||
if (DeferredAuthentication.isDeferred(response))
|
||||
{
|
||||
LOG.debug("auth deferred {}", session == null ? null : session.getId());
|
||||
return Authentication.UNAUTHENTICATED;
|
||||
}
|
||||
|
||||
// remember the current URI
|
||||
session = (session != null ? session : request.getSession(true));
|
||||
synchronized (session)
|
||||
{
|
||||
// But only if it is not set already, or we save every uri that leads to a login redirect
|
||||
if (session.getAttribute(__J_URI) == null || _alwaysSaveUri)
|
||||
{
|
||||
StringBuffer buf = request.getRequestURL();
|
||||
if (request.getQueryString() != null)
|
||||
buf.append("?").append(request.getQueryString());
|
||||
session.setAttribute(__J_URI, buf.toString());
|
||||
session.setAttribute(__J_METHOD, request.getMethod());
|
||||
|
||||
if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
|
||||
{
|
||||
MultiMap<String> formParameters = new MultiMap<>();
|
||||
baseRequest.extractFormParameters(formParameters);
|
||||
session.setAttribute(__J_POST, formParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send the the challenge
|
||||
String challengeUri = getChallengeUri(request);
|
||||
LOG.debug("challenge {}->{}", session.getId(), challengeUri);
|
||||
int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
|
||||
baseResponse.sendRedirect(redirectCode, response.encodeRedirectURL(challengeUri));
|
||||
|
||||
return Authentication.SEND_CONTINUE;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new ServerAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isJSecurityCheck(String uri)
|
||||
{
|
||||
int jsc = uri.indexOf(__J_SECURITY_CHECK);
|
||||
|
||||
if (jsc < 0)
|
||||
return false;
|
||||
int e = jsc + __J_SECURITY_CHECK.length();
|
||||
if (e == uri.length())
|
||||
return true;
|
||||
char c = uri.charAt(e);
|
||||
return c == ';' || c == '#' || c == '/' || c == '?';
|
||||
}
|
||||
|
||||
public boolean isErrorPage(String pathInContext)
|
||||
{
|
||||
return pathInContext != null && (pathInContext.equals(_errorPath));
|
||||
}
|
||||
|
||||
private String getRedirectUri(HttpServletRequest request)
|
||||
{
|
||||
final StringBuffer redirectUri = new StringBuffer(128);
|
||||
URIUtil.appendSchemeHostPort(redirectUri, request.getScheme(),
|
||||
request.getServerName(), request.getServerPort());
|
||||
redirectUri.append(request.getContextPath());
|
||||
redirectUri.append(__J_SECURITY_CHECK);
|
||||
return redirectUri.toString();
|
||||
}
|
||||
|
||||
protected String getChallengeUri(HttpServletRequest request)
|
||||
{
|
||||
HttpSession session = request.getSession();
|
||||
String antiForgeryToken;
|
||||
synchronized (session)
|
||||
{
|
||||
antiForgeryToken = (session.getAttribute(__CSRF_TOKEN) == null)
|
||||
? new BigInteger(130, new SecureRandom()).toString(32)
|
||||
: (String)session.getAttribute(__CSRF_TOKEN);
|
||||
session.setAttribute(__CSRF_TOKEN, antiForgeryToken);
|
||||
}
|
||||
|
||||
// any custom scopes requested from configuration
|
||||
StringBuilder scopes = new StringBuilder();
|
||||
for (String s : _configuration.getScopes())
|
||||
{
|
||||
scopes.append("%20" + s);
|
||||
}
|
||||
|
||||
return _configuration.getAuthEndpoint() +
|
||||
"?client_id=" + _configuration.getClientId() +
|
||||
"&redirect_uri=" + getRedirectUri(request) +
|
||||
"&scope=openid" + scopes +
|
||||
"&state=" + antiForgeryToken +
|
||||
"&response_type=code";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Authentication represents a just completed OpenId Connect authentication.
|
||||
* Subsequent requests from the same user are authenticated by the presents
|
||||
* of a {@link SessionAuthentication} instance in their session.
|
||||
*/
|
||||
public static class OpenIdAuthentication extends UserAuthentication implements Authentication.ResponseSent
|
||||
{
|
||||
public OpenIdAuthentication(String method, UserIdentity userIdentity)
|
||||
{
|
||||
super(method, userIdentity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "OpenId" + super.toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import org.eclipse.jetty.security.Authenticator;
|
||||
import org.eclipse.jetty.security.DefaultAuthenticatorFactory;
|
||||
import org.eclipse.jetty.security.IdentityService;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
||||
public class OpenIdAuthenticatorFactory extends DefaultAuthenticatorFactory
|
||||
{
|
||||
@Override
|
||||
public Authenticator getAuthenticator(Server server, ServletContext context, Authenticator.AuthConfiguration configuration, IdentityService identityService, LoginService loginService)
|
||||
{
|
||||
String auth = configuration.getAuthMethod();
|
||||
if (Constraint.__OPENID_AUTH.equalsIgnoreCase(auth))
|
||||
return new OpenIdAuthenticator();
|
||||
return super.getAuthenticator(server, context, configuration, identityService, loginService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.ajax.JSON;
|
||||
|
||||
public class OpenIdConfiguration
|
||||
{
|
||||
private static String CONFIG_PATH = "/.well-known/openid-configuration";
|
||||
|
||||
private final String identityProvider;
|
||||
private final String authEndpoint;
|
||||
private final String tokenEndpoint;
|
||||
private final String clientId;
|
||||
private final String clientSecret;
|
||||
private final Map<String, Object> discoveryDocument;
|
||||
|
||||
private List<String> scopes = new ArrayList<>();
|
||||
|
||||
public OpenIdConfiguration(String provider, String clientId, String clientSecret)
|
||||
{
|
||||
this.identityProvider = provider;
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
|
||||
try
|
||||
{
|
||||
if (provider.endsWith("/"))
|
||||
provider = provider.substring(0, provider.length() - 1);
|
||||
|
||||
URI providerUri = URI.create(provider + CONFIG_PATH);
|
||||
InputStream inputStream = providerUri.toURL().openConnection().getInputStream();
|
||||
String content = IO.toString(inputStream);
|
||||
discoveryDocument = (Map)JSON.parse(content);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new IllegalArgumentException("invalid identity provider", e);
|
||||
}
|
||||
|
||||
if (discoveryDocument.get("issuer") == null)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
authEndpoint = (String)discoveryDocument.get("authorization_endpoint");
|
||||
if (authEndpoint == null)
|
||||
throw new IllegalArgumentException("authorization_endpoint");
|
||||
|
||||
tokenEndpoint = (String)discoveryDocument.get("token_endpoint");
|
||||
if (tokenEndpoint == null)
|
||||
throw new IllegalArgumentException("token_endpoint");
|
||||
}
|
||||
|
||||
public Map<String, Object> getDiscoveryDocument()
|
||||
{
|
||||
return discoveryDocument;
|
||||
}
|
||||
|
||||
public String getAuthEndpoint()
|
||||
{
|
||||
return authEndpoint;
|
||||
}
|
||||
|
||||
public String getClientId()
|
||||
{
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret()
|
||||
{
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
public String getIdentityProvider()
|
||||
{
|
||||
return identityProvider;
|
||||
}
|
||||
|
||||
public String getTokenEndpoint()
|
||||
{
|
||||
return tokenEndpoint;
|
||||
}
|
||||
|
||||
public void addScopes(String... scopes)
|
||||
{
|
||||
for (String scope : scopes)
|
||||
{
|
||||
this.scopes.add(scope);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getScopes()
|
||||
{
|
||||
return scopes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.ajax.JSON;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class OpenIdCredentials
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(OpenIdCredentials.class);
|
||||
|
||||
private final String redirectUri;
|
||||
private final OpenIdConfiguration configuration;
|
||||
private String authCode;
|
||||
private Map<String, Object> response;
|
||||
private Map<String, Object> claims;
|
||||
|
||||
public OpenIdCredentials(String authCode, String redirectUri, OpenIdConfiguration configuration)
|
||||
{
|
||||
this.authCode = authCode;
|
||||
this.redirectUri = redirectUri;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public String getUserId()
|
||||
{
|
||||
return (String)claims.get("sub");
|
||||
}
|
||||
|
||||
public Map<String, Object> getClaims()
|
||||
{
|
||||
return claims;
|
||||
}
|
||||
|
||||
public Map<String, Object> getResponse()
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
public void redeemAuthCode() throws IOException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("redeemAuthCode() {}", this);
|
||||
|
||||
if (authCode != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
String jwt = getJWT();
|
||||
decodeJWT(jwt);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("userInfo {}", claims);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// reset authCode as it can only be used once
|
||||
authCode = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validate()
|
||||
{
|
||||
if (authCode != null)
|
||||
return false;
|
||||
|
||||
// Check audience should be clientId
|
||||
String audience = (String)claims.get("aud");
|
||||
if (!configuration.getIdentityProvider().equals(audience))
|
||||
{
|
||||
LOG.warn("Audience claim MUST contain the value of the Issuer Identifier for the OP", this);
|
||||
//return false;
|
||||
}
|
||||
|
||||
String issuer = (String)claims.get("iss");
|
||||
if (!configuration.getClientId().equals(issuer))
|
||||
{
|
||||
LOG.warn("Issuer claim MUST be the client_id of the OAuth Client {}", this);
|
||||
//return false;
|
||||
}
|
||||
|
||||
// Check expiry
|
||||
long expiry = (Long)claims.get("exp");
|
||||
long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F);
|
||||
if (currentTimeSeconds > expiry)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("OpenId Credentials expired {}", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void decodeJWT(String jwt) throws IOException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("decodeJWT {}", jwt);
|
||||
|
||||
String[] sections = jwt.split("\\.");
|
||||
if (sections.length != 3)
|
||||
throw new IllegalArgumentException("JWT does not contain 3 sections");
|
||||
|
||||
String jwtHeaderString = new String(Base64.getDecoder().decode(sections[0]), StandardCharsets.UTF_8);
|
||||
String jwtClaimString = new String(Base64.getDecoder().decode(sections[1]), StandardCharsets.UTF_8);
|
||||
String jwtSignature = sections[2];
|
||||
|
||||
Map<String, Object> jwtHeader = (Map)JSON.parse(jwtHeaderString);
|
||||
LOG.debug("JWT Header: {}", jwtHeader);
|
||||
|
||||
// validate signature
|
||||
LOG.warn("Signature NOT validated {}", jwtSignature);
|
||||
|
||||
// response should be a set of name/value pairs
|
||||
claims = (Map)JSON.parse(jwtClaimString);
|
||||
}
|
||||
|
||||
private String getJWT() throws IOException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("getJWT {}", authCode);
|
||||
|
||||
// Use the auth code to get the id_token from the OpenID Provider
|
||||
String urlParameters = "code=" + authCode +
|
||||
"&client_id=" + configuration.getClientId() +
|
||||
"&client_secret=" + configuration.getClientSecret() +
|
||||
"&redirect_uri=" + redirectUri +
|
||||
"&grant_type=authorization_code";
|
||||
|
||||
byte[] payload = urlParameters.getBytes(StandardCharsets.UTF_8);
|
||||
URL url = new URL(configuration.getTokenEndpoint());
|
||||
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Host", configuration.getIdentityProvider());
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
connection.setRequestProperty("charset", "utf-8");
|
||||
|
||||
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream()))
|
||||
{
|
||||
wr.write(payload);
|
||||
}
|
||||
|
||||
// get response and extract id_token jwt
|
||||
InputStream content = (InputStream)connection.getContent();
|
||||
response = (Map)JSON.parse(IO.toString(content));
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("responseMap: {}", response);
|
||||
|
||||
return (String)response.get("id_token");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import javax.security.auth.Subject;
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
import org.eclipse.jetty.security.IdentityService;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class OpenIdLoginService extends ContainerLifeCycle implements LoginService
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(OpenIdLoginService.class);
|
||||
|
||||
private final OpenIdConfiguration _configuration;
|
||||
private final LoginService loginService;
|
||||
private IdentityService identityService;
|
||||
|
||||
public OpenIdLoginService(OpenIdConfiguration configuration)
|
||||
{
|
||||
this(configuration, null);
|
||||
}
|
||||
|
||||
public OpenIdLoginService(OpenIdConfiguration configuration, LoginService loginService)
|
||||
{
|
||||
_configuration = configuration;
|
||||
this.loginService = loginService;
|
||||
addBean(this.loginService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _configuration.getIdentityProvider();
|
||||
}
|
||||
|
||||
public OpenIdConfiguration getConfiguration()
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserIdentity login(String identifier, Object credentials, ServletRequest req)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("login({}, {}, {})", identifier, credentials, req);
|
||||
|
||||
OpenIdCredentials openIdCredentials = (OpenIdCredentials)credentials;
|
||||
try
|
||||
{
|
||||
openIdCredentials.redeemAuthCode();
|
||||
if (!openIdCredentials.validate())
|
||||
return null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
OpenIdUserPrincipal userPrincipal = new OpenIdUserPrincipal(openIdCredentials);
|
||||
Subject subject = new Subject();
|
||||
subject.getPrincipals().add(userPrincipal);
|
||||
subject.getPrivateCredentials().add(credentials);
|
||||
subject.setReadOnly();
|
||||
|
||||
if (loginService != null)
|
||||
{
|
||||
UserIdentity userIdentity = loginService.login(openIdCredentials.getUserId(), "", req);
|
||||
if (userIdentity == null)
|
||||
return null;
|
||||
|
||||
return new OpenIdUserIdentity(subject, userPrincipal, userIdentity);
|
||||
}
|
||||
|
||||
return identityService.newUserIdentity(subject, userPrincipal, new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserIdentity user)
|
||||
{
|
||||
Principal userPrincipal = user.getUserPrincipal();
|
||||
if (!(userPrincipal instanceof OpenIdUserPrincipal))
|
||||
return false;
|
||||
|
||||
OpenIdCredentials credentials = ((OpenIdUserPrincipal)userPrincipal).getCredentials();
|
||||
return credentials.validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityService getIdentityService()
|
||||
{
|
||||
return loginService == null ? identityService : loginService.getIdentityService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentityService(IdentityService service)
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Running");
|
||||
|
||||
if (loginService != null)
|
||||
loginService.setIdentityService(service);
|
||||
else
|
||||
identityService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(UserIdentity user)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.security.Principal;
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
|
||||
public class OpenIdUserIdentity implements UserIdentity
|
||||
{
|
||||
private final Subject subject;
|
||||
private final Principal userPrincipal;
|
||||
private final UserIdentity userIdentity;
|
||||
|
||||
public OpenIdUserIdentity(Subject subject, Principal userPrincipal)
|
||||
{
|
||||
this(subject, userPrincipal, null);
|
||||
}
|
||||
|
||||
public OpenIdUserIdentity(Subject subject, Principal userPrincipal, UserIdentity userIdentity)
|
||||
{
|
||||
this.subject = subject;
|
||||
this.userPrincipal = userPrincipal;
|
||||
this.userIdentity = userIdentity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subject getSubject()
|
||||
{
|
||||
return subject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal()
|
||||
{
|
||||
return userPrincipal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role, Scope scope)
|
||||
{
|
||||
return userIdentity == null ? false : userIdentity.isUserInRole(role, scope);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
|
||||
public class OpenIdUserPrincipal implements Principal, Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -6226920753748399662L;
|
||||
private final OpenIdCredentials _credentials;
|
||||
|
||||
public OpenIdUserPrincipal(OpenIdCredentials credentials)
|
||||
{
|
||||
_credentials = credentials;
|
||||
}
|
||||
|
||||
public OpenIdCredentials getCredentials()
|
||||
{
|
||||
return _credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _credentials.getUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return _credentials.getUserId();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.security.Authenticator;
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
||||
public class OpenIdAuthenticationDemo
|
||||
{
|
||||
public static class AdminPage extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.getWriter().println("<p>this is the admin page "+request.getUserPrincipal()+": <a href=\"/\">Home</a></p>");
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginPage extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.getWriter().println("<p>you logged in <a href=\"/\">Home</a></p>");
|
||||
}
|
||||
}
|
||||
|
||||
public static class LogoutPage extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
request.getSession().invalidate();
|
||||
response.sendRedirect("/");
|
||||
}
|
||||
}
|
||||
|
||||
public static class HomePage extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
|
||||
response.getWriter().println("<h1>Home Page</h1>");
|
||||
|
||||
Principal userPrincipal = request.getUserPrincipal();
|
||||
if (userPrincipal != null)
|
||||
{
|
||||
Map<String, Object> userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.__USER_CLAIMS);
|
||||
response.getWriter().println("<p>Welcome: " + userInfo.get("name") + "</p>");
|
||||
response.getWriter().println("<a href=\"/profile\">Profile</a><br>");
|
||||
response.getWriter().println("<a href=\"/admin\">Admin</a><br>");
|
||||
response.getWriter().println("<a href=\"/logout\">Logout</a><br>");
|
||||
}
|
||||
else
|
||||
{
|
||||
response.getWriter().println("<p>Please Login <a href=\"/login\">Login</a></p>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProfilePage extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
|
||||
Map<String, Object> userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.__USER_CLAIMS);
|
||||
|
||||
response.getWriter().println("<!-- Add icon library -->\n" +
|
||||
"<div class=\"card\">\n" +
|
||||
" <img src=\""+userInfo.get("picture")+"\" style=\"width:30%\">\n" +
|
||||
" <h1>"+ userInfo.get("name") +"</h1>\n" +
|
||||
" <p class=\"title\">"+userInfo.get("email")+"</p>\n" +
|
||||
" <p>UserId: " + userInfo.get("sub") +"</p>\n" +
|
||||
"</div>");
|
||||
|
||||
response.getWriter().println("<a href=\"/\">Home</a><br>");
|
||||
response.getWriter().println("<a href=\"/logout\">Logout</a><br>");
|
||||
}
|
||||
}
|
||||
|
||||
public static class ErrorPage extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
|
||||
response.getWriter().println("<h1>error: not authorized</h1>");
|
||||
response.getWriter().println("<p>" + request.getUserPrincipal() + "</p>");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Server server = new Server(8080);
|
||||
ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
|
||||
|
||||
// Add servlets
|
||||
context.addServlet(ProfilePage.class, "/profile");
|
||||
context.addServlet(LoginPage.class, "/login");
|
||||
context.addServlet(AdminPage.class, "/admin");
|
||||
context.addServlet(LogoutPage.class, "/logout");
|
||||
context.addServlet(HomePage.class, "/*");
|
||||
context.addServlet(ErrorPage.class, "/error");
|
||||
|
||||
// configure security constraints
|
||||
Constraint constraint = new Constraint();
|
||||
constraint.setName(Constraint.__OPENID_AUTH);
|
||||
constraint.setRoles(new String[]{"**"});
|
||||
constraint.setAuthenticate(true);
|
||||
|
||||
Constraint adminConstraint = new Constraint();
|
||||
adminConstraint.setName(Constraint.__OPENID_AUTH);
|
||||
adminConstraint.setRoles(new String[]{"admin"});
|
||||
adminConstraint.setAuthenticate(true);
|
||||
|
||||
// constraint mappings
|
||||
ConstraintMapping profileMapping = new ConstraintMapping();
|
||||
profileMapping.setConstraint(constraint);
|
||||
profileMapping.setPathSpec("/profile");
|
||||
ConstraintMapping loginMapping = new ConstraintMapping();
|
||||
loginMapping.setConstraint(constraint);
|
||||
loginMapping.setPathSpec("/login");
|
||||
ConstraintMapping adminMapping = new ConstraintMapping();
|
||||
adminMapping.setConstraint(adminConstraint);
|
||||
adminMapping.setPathSpec("/admin");
|
||||
|
||||
// security handler
|
||||
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||
securityHandler.setRealmName("OpenID Connect Authentication");
|
||||
securityHandler.addConstraintMapping(profileMapping);
|
||||
securityHandler.addConstraintMapping(loginMapping);
|
||||
securityHandler.addConstraintMapping(adminMapping);
|
||||
|
||||
|
||||
// Google Authentication
|
||||
OpenIdConfiguration configuration = new OpenIdConfiguration(
|
||||
"https://accounts.google.com/",
|
||||
"1051168419525-5nl60mkugb77p9j194mrh287p1e0ahfi.apps.googleusercontent.com",
|
||||
"XT_MIsSv_aUCGollauCaJY8S");
|
||||
configuration.addScopes("email", "profile");
|
||||
|
||||
/*
|
||||
// Microsoft Authentication
|
||||
OpenIdConfiguration configuration = new OpenIdConfiguration(
|
||||
"https://login.microsoftonline.com/common/v2.0",
|
||||
"5f05dea8-2bd9-45de-b30f-cf5c102b8784",
|
||||
"IfhQJKi-5[vxhh_=ldqt0y4PkV3z_1ca");
|
||||
*/
|
||||
|
||||
/*
|
||||
// Yahoo Authentication
|
||||
OpenIdConfiguration configuration = new OpenIdConfiguration(
|
||||
"https://login.yahoo.com",
|
||||
"dj0yJmk9ME5Id05yTkdGNDdPJmQ9WVdrOU9VcHVZWEp4TkdrbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmc3Y9MCZ4PTE2",
|
||||
"1e7f0eeb0ba0af9d9198f9be760f66ae3ea9e3b5");
|
||||
configuration.addScopes("sdps-r");
|
||||
*/
|
||||
|
||||
/*
|
||||
// Create a realm.properties file to associate roles with users
|
||||
Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir"));
|
||||
Path tmpPath = Files.createTempFile(tmpDir, "realm", ".properties");
|
||||
tmpPath.toFile().deleteOnExit();
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(tmpPath, StandardCharsets.UTF_8, StandardOpenOption.WRITE))
|
||||
{
|
||||
// <userId>:[,<rolename> ...]
|
||||
writer.write("114260987481616800581:,admin");
|
||||
}
|
||||
|
||||
// This must be added to the OpenIdLoginService in constructor below
|
||||
HashLoginService hashLoginService = new HashLoginService();
|
||||
hashLoginService.setConfig(tmpPath.toAbsolutePath().toString());
|
||||
hashLoginService.setHotReload(true);
|
||||
*/
|
||||
|
||||
// Configure OpenIdLoginService optionally providing a base LoginService to provide user roles
|
||||
OpenIdLoginService loginService = new OpenIdLoginService(configuration);//, hashLoginService);
|
||||
securityHandler.setLoginService(loginService);
|
||||
|
||||
Authenticator authenticator = new OpenIdAuthenticator(configuration, "/error");
|
||||
securityHandler.setAuthenticator(authenticator);
|
||||
context.setSecurityHandler(securityHandler);
|
||||
|
||||
server.start();
|
||||
server.join();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# Setup default logging implementation for during testing
|
||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
# org.eclipse.jetty.security.openid.LEVEL=DEBUG
|
|
@ -37,6 +37,8 @@ public class Constraint implements Cloneable, Serializable
|
|||
public static final String __SPNEGO_AUTH = "SPNEGO";
|
||||
public static final String __NEGOTIATE_AUTH = "NEGOTIATE";
|
||||
|
||||
public static final String __OPENID_AUTH = "OPENID";
|
||||
|
||||
public static boolean validateMethod(String method)
|
||||
{
|
||||
if (method == null)
|
||||
|
@ -48,7 +50,8 @@ public class Constraint implements Cloneable, Serializable
|
|||
method.equals(__CERT_AUTH) ||
|
||||
method.equals(__CERT_AUTH2) ||
|
||||
method.equals(__SPNEGO_AUTH) ||
|
||||
method.equals(__NEGOTIATE_AUTH));
|
||||
method.equals(__NEGOTIATE_AUTH) ||
|
||||
method.equals(__OPENID_AUTH));
|
||||
}
|
||||
|
||||
public static final int DC_UNSET = -1;
|
||||
|
|
Loading…
Reference in New Issue