All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.security.authentication.FormAuthenticator Maven / Gradle / Ivy

There is a newer version: 2.0.31
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.security.authentication;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.security.AuthenticationState;
import org.eclipse.jetty.security.AuthenticationState.Succeeded;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.Constraint;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserIdentity;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Session;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 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 = LoggerFactory.getLogger(FormAuthenticator.class); public static final String __FORM_LOGIN_PAGE = "org.eclipse.jetty.security.form_login_page"; public static final String __FORM_ERROR_PAGE = "org.eclipse.jetty.security.form_error_page"; public static final String __FORM_DISPATCH = "org.eclipse.jetty.security.dispatch"; public static final String __J_URI = "org.eclipse.jetty.security.form_URI"; public static final String __J_POST = "org.eclipse.jetty.security.form_POST"; public static final String __J_METHOD = "org.eclipse.jetty.security.form_METHOD"; public static final String __J_SECURITY_CHECK = "/j_security_check"; public static final String __J_USERNAME = "j_username"; public static final 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 true to always save the uri */ public void setAlwaysSaveUri(boolean alwaysSave) { _alwaysSaveUri = alwaysSave; } public boolean getAlwaysSaveUri() { return _alwaysSaveUri; } @Override public void setConfiguration(Configuration configuration) { super.setConfiguration(configuration); String login = configuration.getParameter(FormAuthenticator.__FORM_LOGIN_PAGE); if (login != null) setLoginPage(login); String error = configuration.getParameter(FormAuthenticator.__FORM_ERROR_PAGE); if (error != null) setErrorPage(error); String dispatch = configuration.getParameter(FormAuthenticator.__FORM_DISPATCH); _dispatch = dispatch == null ? _dispatch : Boolean.parseBoolean(dispatch); } @Override public String getAuthenticationType() { return Authenticator.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, Request request, Response response) { UserIdentity user = super.login(username, password, request, response); if (user != null) { Session session = request.getSession(true); AuthenticationState cached = new SessionAuthentication(getAuthenticationType(), user, password); session.setAttribute(SessionAuthentication.AUTHENTICATED_ATTRIBUTE, cached); } return user; } @Override public void logout(Request request, Response response) { super.logout(request, response); Session session = request.getSession(false); if (session == null) return; //clean up session session.removeAttribute(SessionAuthentication.AUTHENTICATED_ATTRIBUTE); } @Override public Request prepareRequest(Request request, AuthenticationState authenticationState) { // 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). if (authenticationState instanceof Succeeded) { Session session = request.getSession(false); HttpURI juri = (HttpURI)session.getAttribute(__J_URI); HttpURI uri = request.getHttpURI(); if ((uri.equals(juri))) { session.removeAttribute(__J_URI); Object post = session.removeAttribute(__J_POST); if (post instanceof CompletableFuture futureFields) FormFields.set(request, (CompletableFuture)futureFields); String method = (String)session.removeAttribute(__J_METHOD); if (method != null && request.getMethod().equals(method)) { return new Request.Wrapper(request) { @Override public String getMethod() { return method; } }; } } } return request; } protected Fields getParameters(Request request) { try { Fields queryFields = Request.extractQueryParameters(request); Fields formFields = FormFields.from(request).get(); return Fields.combine(queryFields, formFields); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } protected String encodeURL(String url, Request request) { Session session = request.getSession(false); if (session == null) return url; return session.encodeURI(request, url, request.getHeaders().contains(HttpHeader.COOKIE)); } @Override public Constraint.Authorization getConstraintAuthentication(String pathInContext, Constraint.Authorization existing, Function getSession) { if (isJSecurityCheck(pathInContext)) return Constraint.Authorization.ANY_USER; if (isLoginOrErrorPage(pathInContext)) return Constraint.Authorization.ALLOWED; return existing; } @Override public AuthenticationState validateRequest(Request request, Response response, Callback callback) throws ServerAuthException { String pathInContext = Request.getPathInContext(request); boolean jSecurityCheck = isJSecurityCheck(pathInContext); // Handle a request for authentication. if (jSecurityCheck) { Fields parameters = getParameters(request); final String username = parameters.getValue(__J_USERNAME); final String password = parameters.getValue(__J_PASSWORD); UserIdentity user = login(username, password, request, response); if (LOG.isDebugEnabled()) LOG.debug("jsecuritycheck {} {}", username, user); if (user != null) { // Redirect to original request Session session = request.getSession(false); HttpURI savedURI = (HttpURI)session.getAttribute(__J_URI); String originalURI = savedURI != null ? savedURI.getPathQuery() : Request.getContextPath(request); if (originalURI == null) originalURI = "/"; UserAuthenticationSent formAuth = new UserAuthenticationSent(getAuthenticationType(), user); Response.sendRedirect(request, response, callback, encodeURL(originalURI, request), true); return formAuth; } // not authenticated if (LOG.isDebugEnabled()) LOG.debug("auth failed {}=={}", username, _formErrorPage); return sendError(request, response, callback); } // Look for cached authentication Session session = request.getSession(false); AuthenticationState authenticationState = session == null ? null : (AuthenticationState)session.getAttribute(SessionAuthentication.AUTHENTICATED_ATTRIBUTE); if (LOG.isDebugEnabled()) LOG.debug("auth {}", authenticationState); // Has authentication been revoked? if (authenticationState instanceof Succeeded succeeded && _loginService != null && !_loginService.validate(succeeded.getUserIdentity())) { if (LOG.isDebugEnabled()) LOG.debug("auth revoked {}", authenticationState); session.removeAttribute(SessionAuthentication.AUTHENTICATED_ATTRIBUTE); authenticationState = null; } if (authenticationState != null) return authenticationState; // if we can't send challenge if (response.isCommitted()) { if (LOG.isDebugEnabled()) LOG.debug("auth deferred {}", session == null ? null : session.getId()); return null; } // 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 form redirect if (session.getAttribute(__J_URI) == null || _alwaysSaveUri) { HttpURI juri = request.getHttpURI(); session.setAttribute(__J_URI, juri.asImmutable()); if (!HttpMethod.GET.is(request.getMethod())) session.setAttribute(__J_METHOD, request.getMethod()); if (HttpMethod.POST.is(request.getMethod())) { try { CompletableFuture futureFields = FormFields.from(request); futureFields.get(); session.setAttribute(__J_POST, futureFields); } catch (ExecutionException e) { throw new ServerAuthException(e.getCause()); } catch (InterruptedException e) { throw new ServerAuthException(e); } } } } // send the challenge if (LOG.isDebugEnabled()) LOG.debug("challenge {}->{}", session.getId(), _formLoginPage); return sendChallenge(request, response, callback); } private AuthenticationState dispatch(String path, Request request, Response response, Callback callback) { try { String newPath = URIUtil.addPaths(request.getContext().getContextPath(), path); HttpURI.Mutable newUri = HttpURI.build(request.getHttpURI()).pathQuery(newPath); return new AuthenticationState.ServeAs(newUri); } catch (Throwable t) { Response.writeError(request, response, callback, t); return AuthenticationState.SEND_FAILURE; } } private AuthenticationState sendError(Request request, Response response, Callback callback) { if (_formErrorPage == null) Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403); else if (_dispatch) return dispatch(_formErrorPage, request, response, callback); else Response.sendRedirect(request, response, callback, encodeURL(URIUtil.addPaths(request.getContext().getContextPath(), _formErrorPage), request), true); return AuthenticationState.SEND_FAILURE; } private AuthenticationState sendChallenge(Request request, Response response, Callback callback) { if (_dispatch) { return dispatch(_formLoginPage, request, response, callback); } else { Response.sendRedirect(request, response, callback, encodeURL(URIUtil.addPaths(request.getContext().getContextPath(), _formLoginPage), request), true); return AuthenticationState.SEND_FAILURE; } } 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)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy