/* * Copyright 2010-2016 the original author or authors. * * Licensed 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.springframework.security.web.jaasapi; import java.io.IOException; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.security.authentication.jaas.JaasAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.GenericFilterBean; /** * <p> * A <code>Filter</code> which attempts to obtain a JAAS <code>Subject</code> and continue * the <code>FilterChain</code> running as that <code>Subject</code>. * </p> * <p> * By using this <code>Filter</code> in conjunction with Spring's * <code>JaasAuthenticationProvider</code> both Spring's <code>SecurityContext</code> and * a JAAS <code>Subject</code> can be populated simultaneously. This is useful when * integrating with code that requires a JAAS <code>Subject</code> to be populated. * </p> * * @author Rob Winch * @see #doFilter(ServletRequest, ServletResponse, FilterChain) * @see #obtainSubject(ServletRequest) */ public class JaasApiIntegrationFilter extends GenericFilterBean { // ~ Instance fields // ================================================================================================ private boolean createEmptySubject; // ~ Methods // ======================================================================================================== /** * <p> * Attempts to obtain and run as a JAAS <code>Subject</code> using * {@link #obtainSubject(ServletRequest)}. * </p> * * <p> * If the <code>Subject</code> is <code>null</code> and <tt>createEmptySubject</tt> is * <code>true</code>, an empty, writeable <code>Subject</code> is used. This allows * for the <code>Subject</code> to be populated at the time of login. If the * <code>Subject</code> is <code>null</code>, the <code>FilterChain</code> continues * with no additional processing. If the <code>Subject</code> is not <code>null</code> * , the <code>FilterChain</code> is ran with * {@link Subject#doAs(Subject, PrivilegedExceptionAction)} in conjunction with the * <code>Subject</code> obtained. * </p> */ public final void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws ServletException, IOException { Subject subject = obtainSubject(request); if (subject == null && createEmptySubject) { if (logger.isDebugEnabled()) { logger.debug("Subject returned was null and createEmtpySubject is true; creating new empty subject to run as."); } subject = new Subject(); } if (subject == null) { if (logger.isDebugEnabled()) { logger.debug("Subject is null continue running with no Subject."); } chain.doFilter(request, response); return; } final PrivilegedExceptionAction<Object> continueChain = new PrivilegedExceptionAction<Object>() { public Object run() throws IOException, ServletException { chain.doFilter(request, response); return null; } }; if (logger.isDebugEnabled()) { logger.debug("Running as Subject " + subject); } try { Subject.doAs(subject, continueChain); } catch (PrivilegedActionException e) { throw new ServletException(e.getMessage(), e); } } /** * <p> * Obtains the <code>Subject</code> to run as or <code>null</code> if no * <code>Subject</code> is available. * </p> * <p> * The default implementation attempts to obtain the <code>Subject</code> from the * <code>SecurityContext</code>'s <code>Authentication</code>. If it is of type * <code>JaasAuthenticationToken</code> and is authenticated, the <code>Subject</code> * is returned from it. Otherwise, <code>null</code> is returned. * </p> * * @param request the current <code>ServletRequest</code> * @return the Subject to run as or <code>null</code> if no <code>Subject</code> is * available. */ protected Subject obtainSubject(ServletRequest request) { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); if (logger.isDebugEnabled()) { logger.debug("Attempting to obtainSubject using authentication : " + authentication); } if (authentication == null) { return null; } if (!authentication.isAuthenticated()) { return null; } if (!(authentication instanceof JaasAuthenticationToken)) { return null; } JaasAuthenticationToken token = (JaasAuthenticationToken) authentication; LoginContext loginContext = token.getLoginContext(); if (loginContext == null) { return null; } return loginContext.getSubject(); } /** * Sets <code>createEmptySubject</code>. If the value is <code>true</code>, and * {@link #obtainSubject(ServletRequest)} returns <code>null</code>, an empty, * writeable <code>Subject</code> is created instead. Otherwise no * <code>Subject</code> is used. The default is <code>false</code>. * * @param createEmptySubject the new value */ public final void setCreateEmptySubject(boolean createEmptySubject) { this.createEmptySubject = createEmptySubject; } }