org.eclipse.jetty.security.authentication.FormAuthenticator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of a-jetty-base Show documentation
Show all versions of a-jetty-base Show documentation
A-Jetty Base can run on Android Java as well as on standard Java 7+ and it can run precompiled JSP/JSTL.
//
// ========================================================================
// Copyright (c) 1995-2016 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.authentication;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.HttpChannel;
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.StringUtil;
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;
/**
* FORM Authenticator.
*
* This authenticator implements form authentication will use dispatchers to
* the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
* Otherwise it will redirect.
*
* The form authenticator redirects unauthenticated requests to a log page
* which should use a form to gather username/password from the user and send them
* to the /j_security_check URI within the context. FormAuthentication uses
* {@link SessionAuthentication} to wrap Authentication results so that they
* are associated with the session.
*
*
*/
public class FormAuthenticator extends LoginAuthenticator
{
private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
public final static String __J_SECURITY_CHECK = "/j_security_check";
public final static String __J_USERNAME = "j_username";
public final static String __J_PASSWORD = "j_password";
private String _formErrorPage;
private String _formErrorPath;
private String _formLoginPage;
private String _formLoginPath;
private boolean _dispatch;
private boolean _alwaysSaveUri;
public FormAuthenticator()
{
}
/* ------------------------------------------------------------ */
public FormAuthenticator(String login,String error,boolean dispatch)
{
this();
if (login!=null)
setLoginPage(login);
if (error!=null)
setErrorPage(error);
_dispatch=dispatch;
}
/* ------------------------------------------------------------ */
/**
* 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
*/
public void setAlwaysSaveUri (boolean alwaysSave)
{
_alwaysSaveUri = alwaysSave;
}
/* ------------------------------------------------------------ */
public boolean getAlwaysSaveUri ()
{
return _alwaysSaveUri;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
*/
@Override
public void setConfiguration(AuthConfiguration configuration)
{
super.setConfiguration(configuration);
String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
if (login!=null)
setLoginPage(login);
String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
if (error!=null)
setErrorPage(error);
String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
_dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
}
/* ------------------------------------------------------------ */
@Override
public String getAuthMethod()
{
return Constraint.__FORM_AUTH;
}
/* ------------------------------------------------------------ */
private void setLoginPage(String path)
{
if (!path.startsWith("/"))
{
LOG.warn("form-login-page must start with /");
path = "/" + path;
}
_formLoginPage = path;
_formLoginPath = path;
if (_formLoginPath.indexOf('?') > 0)
_formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
}
/* ------------------------------------------------------------ */
private void setErrorPage(String path)
{
if (path == null || path.trim().length() == 0)
{
_formErrorPath = null;
_formErrorPage = null;
}
else
{
if (!path.startsWith("/"))
{
LOG.warn("form-error-page must start with /");
path = "/" + path;
}
_formErrorPage = path;
_formErrorPath = path;
if (_formErrorPath.indexOf('?') > 0)
_formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
}
}
/* ------------------------------------------------------------ */
@Override
public UserIdentity login(String username, Object password, ServletRequest request)
{
UserIdentity user = super.login(username,password,request);
if (user!=null)
{
HttpSession session = ((HttpServletRequest)request).getSession(true);
Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
}
return user;
}
/* ------------------------------------------------------------ */
@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 base_request = HttpChannel.getCurrentHttpChannel().getRequest();
HttpMethod m = HttpMethod.fromString(method);
base_request.setMethod(m,m.asString());
}
/* ------------------------------------------------------------ */
@Override
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
{
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
String uri = request.getRequestURI();
if (uri==null)
uri=URIUtil.SLASH;
mandatory|=isJSecurityCheck(uri);
if (!mandatory)
return new DeferredAuthentication(this);
if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
return new DeferredAuthentication(this);
HttpSession session = request.getSession(true);
try
{
// Handle a request for authentication.
if (isJSecurityCheck(uri))
{
final String username = request.getParameter(__J_USERNAME);
final String password = request.getParameter(__J_PASSWORD);
UserIdentity user = login(username, password, request);
LOG.debug("jsecuritycheck {} {}",username,user);
session = request.getSession(true);
if (user!=null)
{
// Redirect to original request
String nuri;
FormAuthentication form_auth;
synchronized(session)
{
nuri = (String) session.getAttribute(__J_URI);
if (nuri == null || nuri.length() == 0)
{
nuri = request.getContextPath();
if (nuri.length() == 0)
nuri = URIUtil.SLASH;
}
form_auth = new FormAuthentication(getAuthMethod(),user);
}
LOG.debug("authenticated {}->{}",form_auth,nuri);
response.setContentLength(0);
Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
return form_auth;
}
// not authenticated
if (LOG.isDebugEnabled())
LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
if (_formErrorPage == null)
{
LOG.debug("auth failed {}->403",username);
if (response != null)
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
else if (_dispatch)
{
LOG.debug("auth failed {}=={}",username,_formErrorPage);
RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
dispatcher.forward(new FormRequest(request), new FormResponse(response));
}
else
{
LOG.debug("auth failed {}->{}",username,_formErrorPage);
Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
}
return Authentication.SEND_FAILURE;
}
// Look for cached authentication
Authentication authentication = (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 j_uri=(String)session.getAttribute(__J_URI);
if (j_uri!=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,j_uri);
StringBuffer buf = request.getRequestURL();
if (request.getQueryString() != null)
buf.append("?").append(request.getQueryString());
if (j_uri.equals(buf.toString()))
{
MultiMap j_post = (MultiMap)session.getAttribute(__J_POST);
if (j_post!=null)
{
LOG.debug("auth rePOST {}->{}",authentication,j_uri);
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
base_request.setContentParameters(j_post);
}
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.getId());
return Authentication.UNAUTHENTICATED;
}
// remember the current URI
synchronized (session)
{
// But only if it is not set already, or we save every uri that leads to a login form 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()))
{
Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
MultiMap formParameters = new MultiMap<>();
base_request.extractFormParameters(formParameters);
session.setAttribute(__J_POST, formParameters);
}
}
}
// send the the challenge
if (_dispatch)
{
LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
dispatcher.forward(new FormRequest(request), new FormResponse(response));
}
else
{
LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
}
return Authentication.SEND_CONTINUE;
}
catch (IOException | ServletException 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 isLoginOrErrorPage(String pathInContext)
{
return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
}
/* ------------------------------------------------------------ */
@Override
public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
{
return true;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
protected static class FormRequest extends HttpServletRequestWrapper
{
public FormRequest(HttpServletRequest request)
{
super(request);
}
@Override
public long getDateHeader(String name)
{
if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
return -1;
return super.getDateHeader(name);
}
@Override
public String getHeader(String name)
{
if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
return null;
return super.getHeader(name);
}
@Override
public Enumeration getHeaderNames()
{
return Collections.enumeration(Collections.list(super.getHeaderNames()));
}
@Override
public Enumeration getHeaders(String name)
{
if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
return Collections.enumeration(Collections.emptyList());
return super.getHeaders(name);
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
protected static class FormResponse extends HttpServletResponseWrapper
{
public FormResponse(HttpServletResponse response)
{
super(response);
}
@Override
public void addDateHeader(String name, long date)
{
if (notIgnored(name))
super.addDateHeader(name,date);
}
@Override
public void addHeader(String name, String value)
{
if (notIgnored(name))
super.addHeader(name,value);
}
@Override
public void setDateHeader(String name, long date)
{
if (notIgnored(name))
super.setDateHeader(name,date);
}
@Override
public void setHeader(String name, String value)
{
if (notIgnored(name))
super.setHeader(name,value);
}
private boolean notIgnored(String name)
{
if (HttpHeader.CACHE_CONTROL.is(name) ||
HttpHeader.PRAGMA.is(name) ||
HttpHeader.ETAG.is(name) ||
HttpHeader.EXPIRES.is(name) ||
HttpHeader.LAST_MODIFIED.is(name) ||
HttpHeader.AGE.is(name))
return false;
return true;
}
}
/* ------------------------------------------------------------ */
/** This Authentication represents a just completed Form authentication.
* Subsequent requests from the same user are authenticated by the presents
* of a {@link SessionAuthentication} instance in their session.
*/
public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
{
public FormAuthentication(String method, UserIdentity userIdentity)
{
super(method,userIdentity);
}
@Override
public String toString()
{
return "Form"+super.toString();
}
}
}