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

org.apache.cxf.fediz.jetty9.FederationAuthenticator Maven / Gradle / Ivy

There is a newer version: 1.7.0
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.cxf.fediz.jetty9;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.bind.JAXBException;

import org.apache.cxf.fediz.core.FederationConstants;
import org.apache.cxf.fediz.core.SAMLSSOConstants;
import org.apache.cxf.fediz.core.config.FederationProtocol;
import org.apache.cxf.fediz.core.config.FedizConfigurator;
import org.apache.cxf.fediz.core.config.FedizContext;
import org.apache.cxf.fediz.core.config.SAMLProtocol;
import org.apache.cxf.fediz.core.exception.ProcessingException;
import org.apache.cxf.fediz.core.metadata.MetadataDocumentHandler;
import org.apache.cxf.fediz.core.processor.FedizProcessor;
import org.apache.cxf.fediz.core.processor.FedizProcessorFactory;
import org.apache.cxf.fediz.core.processor.FedizRequest;
import org.apache.cxf.fediz.core.processor.FedizResponse;
import org.apache.cxf.fediz.core.processor.RedirectionResponse;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
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.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;

/**
 * Federation Authenticator.
 * 

* This authenticator implements form authentication will redirect to the Identity Provider * by sending a WS-Federation SignIn request. *

*

* The federation authenticator redirects unauthenticated requests to an Identity Provider which use any kind of * mechanism to authenticate the user. * FederationAuthentication uses {@link SessionAuthentication} to wrap Authentication results so that they are * associated with the session. *

*/ public class FederationAuthenticator extends LoginAuthenticator { 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_CONTEXT = "org.eclipse.jetty.security.form_CONTEXT"; private static final Logger LOG = Log.getLogger(FederationAuthenticator.class); private static final String SECURITY_TOKEN_ATTR = "org.apache.fediz.SECURITY_TOKEN"; private String configFile; private FedizConfigurator configurator; private String encoding = "UTF-8"; public FederationAuthenticator() { } /** * */ @Override public void setConfiguration(AuthConfiguration configuration) { super.setConfiguration(configuration); // is called after the bean setting -> do initialization here LOG.debug(configuration.getInitParameterNames().toString()); try { File f = new File(getConfigFile()); if (!f.exists()) { String jettyHome = System.getProperty("jetty.home"); if (jettyHome != null && jettyHome.length() > 0) { f = new File(jettyHome.concat(File.separator + getConfigFile())); } } configurator = new FedizConfigurator(); configurator.loadConfig(f); LOG.debug("Fediz configuration read from " + f.getAbsolutePath()); } catch (JAXBException | IOException e) { //[TODO] use other exception throw new RuntimeException("Failed to load Fediz configuration", e); //throw new ServerAuthException("Failed to load Fediz configuration", // e); } } /* ------------------------------------------------------------ */ public String getAuthMethod() { return "WSFED"; } public String getConfigFile() { return configFile; } public void setConfigFile(String configFile) { this.configFile = configFile; } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { this.encoding = encoding; } /* ------------------------------------------------------------ */ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; HttpSession session = request.getSession(true); String contextName = request.getSession().getServletContext().getContextPath(); if (contextName == null || contextName.isEmpty()) { contextName = "/"; } FedizContext fedConfig = getContextConfiguration(contextName); // Check to see if it is a metadata request MetadataDocumentHandler mdHandler = new MetadataDocumentHandler(fedConfig); if (mdHandler.canHandleRequest(request)) { Authentication authentication = Authentication.SEND_FAILURE; if (mdHandler.handleRequest(request, response)) { authentication = Authentication.SEND_CONTINUE; } return authentication; } if (!mandatory) { return new DeferredAuthentication(this); } try { req.setCharacterEncoding(this.encoding); } catch (UnsupportedEncodingException ex) { LOG.warn("Unsupported encoding '" + this.encoding + "'", ex); } try { String action = request.getParameter(FederationConstants.PARAM_ACTION); Authentication authentication = null; // Handle a request for authentication. if (isSignInRequest(request, fedConfig)) { authentication = handleSignInRequest(request, response, session, fedConfig); } else if (FederationConstants.ACTION_SIGNOUT_CLEANUP.equals(action)) { authentication = handleSignOutCleanup(response, session); } else if (!FederationConstants.ACTION_SIGNOUT.equals(action) && action != null) { LOG.warn("Not supported action found in parameter wa: " + action); response.sendError(HttpServletResponse.SC_BAD_REQUEST); authentication = Authentication.UNAUTHENTICATED; } if (authentication != null) { return authentication; } // Look for cached authentication authentication = handleCachedAuthentication(request, response, session, fedConfig); if (authentication != null) { 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()); if (MimeTypes.Type.FORM_ENCODED.asString().equals(req.getContentType()) && HttpMethod.POST.asString().equals(request.getMethod())) { Request baseRequest = (Request)req; //(req instanceof Request)?(Request)req:HttpConnection.getCurrentConnection().getRequest(); baseRequest.extractParameters(); session.setAttribute(J_POST, new MultiMap(baseRequest.getQueryParameters())); } } } FedizProcessor wfProc = FedizProcessorFactory.newFedizProcessor(fedConfig.getProtocol()); signInRedirectToIssuer(request, response, wfProc, session); return Authentication.SEND_CONTINUE; } catch (IOException e) { throw new ServerAuthException(e); } /* * catch (ServletException e) { throw new ServerAuthException(e); } */ } private Authentication handleSignInRequest(HttpServletRequest request, HttpServletResponse response, HttpSession session, FedizContext fedConfig) throws IOException { FedizResponse wfRes = null; if (LOG.isDebugEnabled()) { LOG.debug("SignIn request found"); } String action = request.getParameter(FederationConstants.PARAM_ACTION); String responseToken = getResponseToken(request, fedConfig); if (responseToken == null) { if (LOG.isDebugEnabled()) { LOG.debug("SignIn request must contain a response token from the IdP"); } response.sendError(HttpServletResponse.SC_BAD_REQUEST); return Authentication.SEND_FAILURE; } else { FedizRequest wfReq = new FedizRequest(); wfReq.setAction(action); wfReq.setResponseToken(responseToken); wfReq.setState(request.getParameter("RelayState")); wfReq.setRequest(request); X509Certificate[] certs = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate"); wfReq.setCerts(certs); FederationLoginService fedLoginService = (FederationLoginService)this._loginService; UserIdentity user = fedLoginService.login(null, wfReq, fedConfig); if (user != null) { session = renewSession(request, response); // Redirect to original request String nuri; synchronized (session) { // Check the context String savedContext = (String) session.getAttribute(J_CONTEXT); String receivedContext = request.getParameter(FederationConstants.PARAM_CONTEXT); if (savedContext == null || !savedContext.equals(receivedContext)) { LOG.warn("The received wctx parameter does not match the saved value"); response.sendError(HttpServletResponse.SC_FORBIDDEN); return Authentication.UNAUTHENTICATED; } nuri = (String) session.getAttribute(J_URI); if (nuri == null || nuri.length() == 0) { nuri = request.getContextPath(); if (nuri.length() == 0) { nuri = URIUtil.SLASH; } } Authentication cached = new SessionAuthentication(getAuthMethod(), user, wfRes); session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached); } FederationUserIdentity fui = (FederationUserIdentity)user; session.setAttribute(SECURITY_TOKEN_ATTR, fui.getToken()); response.setContentLength(0); response.sendRedirect(response.encodeRedirectURL(nuri)); return new FederationAuthentication(getAuthMethod(), user); } // not authenticated if (LOG.isDebugEnabled()) { LOG.debug("WSFED authentication FAILED"); } if (response != null) { response.sendError(HttpServletResponse.SC_FORBIDDEN); } return Authentication.UNAUTHENTICATED; } } private Authentication handleSignOutCleanup(HttpServletResponse response, HttpSession session) throws IOException { if (LOG.isDebugEnabled()) { LOG.debug("SignOutCleanup request found"); LOG.debug("SignOutCleanup action..."); } session.invalidate(); final ServletOutputStream responseOutputStream = response.getOutputStream(); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("logout.jpg"); if (inputStream == null) { LOG.warn("Could not write logout.jpg"); return Authentication.SEND_FAILURE; } int read = 0; byte[] buf = new byte[1024]; while ((read = inputStream.read(buf)) != -1) { responseOutputStream.write(buf, 0, read); } inputStream.close(); responseOutputStream.flush(); return Authentication.SEND_SUCCESS; } private Authentication handleCachedAuthentication(HttpServletRequest request, HttpServletResponse response, HttpSession session, FedizContext fedConfig) throws IOException { Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED); if (authentication != null) { // Has authentication been revoked? if (authentication instanceof Authentication.User && isTokenExpired(fedConfig, ((Authentication.User)authentication).getUserIdentity())) { session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED); } else { //logout String action = request.getParameter(FederationConstants.PARAM_ACTION); boolean logout = FederationConstants.ACTION_SIGNOUT.equals(action); String logoutUrl = fedConfig.getLogoutURL(); String uri = request.getRequestURI(); if (uri == null) { uri = URIUtil.SLASH; } String contextName = request.getSession().getServletContext().getContextPath(); if (contextName == null || contextName.isEmpty()) { contextName = "/"; } if (logout || logoutUrl != null && !logoutUrl.isEmpty() && uri.equals(contextName + logoutUrl)) { session.invalidate(); FedizProcessor wfProc = FedizProcessorFactory.newFedizProcessor(fedConfig.getProtocol()); signOutRedirectToIssuer(request, response, wfProc); return Authentication.SEND_CONTINUE; } String jUri = (String)session.getAttribute(J_URI); @SuppressWarnings("unchecked") MultiMap jPost = (MultiMap)session.getAttribute(J_POST); if (jUri != null && jPost != null) { StringBuffer buf = request.getRequestURL(); if (request.getQueryString() != null) { buf.append("?").append(request.getQueryString()); } if (jUri.equals(buf.toString())) { // This is a retry of an original POST request // so restore method and parameters session.removeAttribute(J_POST); Request baseRequest = (Request)request; // (req instanceof Request)?(Request) // req:HttpConnection.getCurrentConnection().getRequest(); baseRequest.setMethod(HttpMethod.POST.asString()); baseRequest.setQueryParameters(jPost); } } else if (jUri != null) { session.removeAttribute(J_URI); } return authentication; } } return null; } private boolean isTokenExpired(FedizContext fedConfig, UserIdentity userIdentity) { if (fedConfig.isDetectExpiredTokens()) { try { FederationUserIdentity fui = (FederationUserIdentity)userIdentity; Date tokenExpires = fui.getExpiryDate(); if (tokenExpires == null) { LOG.debug("Token doesn't expire"); return false; } Date currentTime = new Date(); if (!currentTime.after(tokenExpires)) { return false; } else { LOG.warn("Token already expired. Clean up and redirect"); return true; } } catch (ClassCastException ex) { LOG.warn("UserIdentity must be instance of FederationUserIdentity"); throw new IllegalStateException("UserIdentity must be instance of FederationUserIdentity"); } } return false; } private boolean isSignInRequest(ServletRequest request, FedizContext fedConfig) { if (fedConfig.getProtocol() instanceof FederationProtocol && FederationConstants.ACTION_SIGNIN.equals( request.getParameter(FederationConstants.PARAM_ACTION))) { return true; } else if (fedConfig.getProtocol() instanceof SAMLProtocol && request.getParameter(SAMLSSOConstants.RELAY_STATE) != null) { return true; } return false; } private String getResponseToken(ServletRequest request, FedizContext fedConfig) { if (fedConfig.getProtocol() instanceof FederationProtocol) { return request.getParameter(FederationConstants.PARAM_RESULT); } else if (fedConfig.getProtocol() instanceof SAMLProtocol) { return request.getParameter(SAMLSSOConstants.SAML_RESPONSE); } return null; } /* ------------------------------------------------------------ */ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException { return true; } /** * Called to redirect sign-in to the IDP/Issuer * * @param request * Request we are processing * @param response * Response we are populating * @param processor * FederationProcessor * @param session The HTTPSession * @throws IOException * If the forward to the login page fails and the call to * {@link HttpServletResponse#sendError(int, String)} throws an * {@link IOException} */ protected void signInRedirectToIssuer(HttpServletRequest request, HttpServletResponse response, FedizProcessor processor, HttpSession session) throws IOException { //Not supported in jetty 7.6 //String contextName = request.getServletContext().getContextPath(); String contextName = request.getSession().getServletContext().getContextPath(); if (contextName == null || contextName.isEmpty()) { contextName = "/"; } FedizContext fedCtx = this.configurator.getFedizContext(contextName); try { RedirectionResponse redirectionResponse = processor.createSignInRequest(request, fedCtx); String redirectURL = redirectionResponse.getRedirectionURL(); if (redirectURL != null) { Map headers = redirectionResponse.getHeaders(); if (!headers.isEmpty()) { for (Entry entry : headers.entrySet()) { response.addHeader(entry.getKey(), entry.getValue()); } } synchronized (session) { session.setAttribute(J_CONTEXT, redirectionResponse.getRequestState().getState()); } response.sendRedirect(redirectURL); } else { LOG.warn("Failed to create SignInRequest."); response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create SignInRequest."); } } catch (ProcessingException ex) { LOG.warn("Failed to create SignInRequest: " + ex.getMessage()); response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create SignInRequest."); } } protected void signOutRedirectToIssuer(HttpServletRequest request, HttpServletResponse response, FedizProcessor processor) throws IOException { //Not supported in jetty 7.6 //String contextName = request.getServletContext().getContextPath(); String contextName = request.getSession().getServletContext().getContextPath(); if (contextName == null || contextName.isEmpty()) { contextName = "/"; } FedizContext fedCtx = this.configurator.getFedizContext(contextName); try { RedirectionResponse redirectionResponse = processor.createSignOutRequest(request, null, fedCtx); //TODO String redirectURL = redirectionResponse.getRedirectionURL(); if (redirectURL != null) { Map headers = redirectionResponse.getHeaders(); if (!headers.isEmpty()) { for (Entry entry : headers.entrySet()) { response.addHeader(entry.getKey(), entry.getValue()); } } response.sendRedirect(redirectURL); } else { LOG.warn("Failed to create SignOutRequest."); response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create SignOutRequest."); } } catch (ProcessingException ex) { LOG.warn("Failed to create SignOutRequest: " + ex.getMessage()); response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create SignOutRequest."); } } private FedizContext getContextConfiguration(String contextName) { if (configurator == null) { throw new IllegalStateException("No Fediz configuration available"); } FedizContext config = configurator.getFedizContext(contextName); if (config == null) { throw new IllegalStateException("No Fediz configuration for context :" + contextName); } String jettyHome = System.getProperty("jetty.home"); if (jettyHome != null && jettyHome.length() > 0) { config.setRelativePath(jettyHome); } return config; } /* ------------------------------------------------------------ */ /** * This Authentication represents a just completed Federation authentication. Subsequent requests from the same * user are authenticated by the presents of a {@link SessionAuthentication} instance in their session. */ public static class FederationAuthentication extends UserAuthentication implements Authentication.ResponseSent { public FederationAuthentication(String method, UserIdentity userIdentity) { super(method, userIdentity); } @Override public String toString() { return "WSFED" + super.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy