001    /*
002     * Copyright (c) 2002-2005 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.security.remoting;
017    
018    import java.io.IOException;
019    import java.net.HttpURLConnection;
020    
021    import org.apache.commons.codec.binary.Base64;
022    import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor;
023    import org.springframework.richclient.security.AuthenticationAware;
024    import org.springframework.security.Authentication;
025    
026    /**
027     * Adds BASIC authentication support to <code>SimpleHttpInvokerRequestExecutor</code>.
028     * This class assumes that a single authentication credential will be used for the whole
029     * application (as is typical with a rich client). So, regardless of the thread involved,
030     * it will use the Authentication object obtained from the last authentication token
031     * received.
032     * <p>
033     * In comparison, see
034     * {@link org.springframework.security.context.httpinvoker.AuthenticationSimpleHttpInvokerRequestExecutor}
035     * for a class that manages the Authentication per-thread. If you need to have threads
036     * with different authentication credentials, then you should use the Spring Security class instead.
037     * <p>
038     * Typically, instances of this class will be automatically created by using
039     * {@link BasicAuthHttpInvokerProxyFactoryBean}, which will take care of keeping this
040     * instance updated with the latest authentication token.  In case you want to use this
041     * executor with a different invoker factory, this class implements
042     * {@link AuthenticationAware} so that it will automatically receive notification of
043     * changes in the authentication token.
044     * <p>
045     * Note that this configuration could lead to instances of this class receiving two
046     * notifications of an authentication token change.  However, that would only happen
047     * if both the {@link BasicAuthHttpInvokerProxyFactoryBean} is used and this class
048     * is created as a bean in the application context.  This seemed unlikely enough
049     * that I erred on the side of "ease of use".  Also, the current implementation
050     * does nothing more than store a reference to the new token, so receiving two
051     * notifications isn't a problem.
052     * 
053     * @author Larry Streepy
054     * @see org.springframework.richclient.security.remoting.BasicAuthHttpInvokerProxyFactoryBean
055     * 
056     */
057    public class BasicAuthHttpInvokerRequestExecutor extends SimpleHttpInvokerRequestExecutor implements
058            AuthenticationAware {
059    
060        private Authentication authentication;
061    
062        /**
063         * Constructor.
064         */
065        public BasicAuthHttpInvokerRequestExecutor() {
066        }
067    
068        /*
069         * (non-Javadoc)
070         * @see org.springframework.richclient.security.AuthenticationAware#setAuthenticationToken(org.springframework.security.Authentication)
071         */
072        public void setAuthenticationToken(Authentication authentication) {
073            this.authentication = authentication;
074        }
075    
076        /**
077         * Get the Authentication object for the current user, if any.
078         */
079        public Authentication getAuthenticationToken() {
080            return authentication;
081        }
082    
083        //
084        // === SimpleHttpInvokerRequestExecutor methods ===
085        //
086    
087        /**
088         * Provided so subclasses can perform additional configuration if required (eg set
089         * additional request headers for non-security related information etc).
090         * 
091         * @param con the HTTP connection to prepare
092         * @param contentLength the length of the content to send
093         * 
094         * @throws IOException if thrown by HttpURLConnection methods
095         */
096        protected void doPrepareConnection(HttpURLConnection con, int contentLength) throws IOException {
097        }
098    
099        /**
100         * Called every time a HTTP invocation is made.
101         * <p>
102         * Simply allows the parent to setup the connection, and then adds an
103         * <code>Authorization</code> HTTP header property that will be used for BASIC
104         * authentication. Following that a call to {@link #doPrepareConnection} is made to
105         * allow subclasses to apply any additional configuration desired to the connection
106         * prior to invoking the request.
107         * <p>
108         * The previously saved authentication token is used to obtain the principal and
109         * credentials. If the saved token is null, then the "Authorization" header will not
110         * be added to the request.
111         * 
112         * @param con the HTTP connection to prepare
113         * @param contentLength the length of the content to send
114         * 
115         * @throws IOException if thrown by HttpURLConnection methods
116         */
117        protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException {
118    
119            super.prepareConnection( con, contentLength );
120    
121            Authentication auth = getAuthenticationToken();
122    
123            if( (auth != null) && (auth.getName() != null) && (auth.getCredentials() != null) ) {
124                String base64 = auth.getName() + ":" + auth.getCredentials().toString();
125                con.setRequestProperty( "Authorization", "Basic " + new String( Base64.encodeBase64( base64.getBytes() ) ) );
126    
127                if( logger.isDebugEnabled() ) {
128                    logger.debug( "HttpInvocation now presenting via BASIC authentication with token:: " + auth );
129                }
130            } else {
131                if( logger.isDebugEnabled() ) {
132                    logger.debug( "Unable to set BASIC authentication header as Authentication token is invalid: " + auth );
133                }
134            }
135    
136            doPrepareConnection( con, contentLength );
137        }
138    
139    }