View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.spring.remoting;
20  
21  import org.aopalliance.intercept.MethodInvocation;
22  import org.apache.shiro.SecurityUtils;
23  import org.apache.shiro.session.Session;
24  import org.apache.shiro.session.mgt.NativeSessionManager;
25  import org.apache.shiro.session.mgt.SessionKey;
26  import org.apache.shiro.session.mgt.SessionManager;
27  import org.apache.shiro.subject.Subject;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
31  import org.springframework.remoting.support.RemoteInvocation;
32  import org.springframework.remoting.support.RemoteInvocationFactory;
33  
34  import java.io.Serializable;
35  
36  /**
37   * A {@link RemoteInvocationFactory} that passes the session ID to the server via a
38   * {@link RemoteInvocation} {@link RemoteInvocation#getAttribute(String) attribute}.
39   * This factory is the client-side part of
40   * the Shiro Spring remoting invocation.  A {@link SecureRemoteInvocationExecutor} should
41   * be used to export the server-side remote services to ensure that the appropriate
42   * Subject and Session are bound to the remote thread during execution.
43   *
44   * @since 0.1
45   */
46  public class SecureRemoteInvocationFactory extends DefaultRemoteInvocationFactory {
47  
48      private static final Logger log = LoggerFactory.getLogger(SecureRemoteInvocationFactory.class);
49  
50      public static final String SESSION_ID_KEY = SecureRemoteInvocationFactory.class.getName() + ".SESSION_ID_KEY";
51      public static final String HOST_KEY = SecureRemoteInvocationFactory.class.getName() + ".HOST_KEY";
52  
53      private static final String SESSION_ID_SYSTEM_PROPERTY_NAME = "shiro.session.id";
54  
55      private String sessionId;
56  
57      public SecureRemoteInvocationFactory() {
58      }
59  
60      public SecureRemoteInvocationFactory(String sessionId) {
61          this();
62          this.sessionId = sessionId;
63      }
64  
65      /**
66       * Creates a {@link RemoteInvocation} with the current session ID as an
67       * {@link RemoteInvocation#getAttribute(String) attribute}.
68       *
69       * @param mi the method invocation that the remote invocation should be based on.
70       * @return a remote invocation object containing the current session ID as an attribute.
71       */
72      public RemoteInvocation createRemoteInvocation(MethodInvocation mi) {
73  
74          Serializable sessionId = null;
75          String host = null;
76          boolean sessionManagerMethodInvocation = false;
77  
78          //If the calling MI is for a remoting SessionManager delegate, we need to acquire the session ID from the method
79          //argument and NOT interact with SecurityUtils/subject.getSession to avoid a stack overflow
80          Class miDeclaringClass = mi.getMethod().getDeclaringClass();
81          if (SessionManager.class.equals(miDeclaringClass) || NativeSessionManager.class.equals(miDeclaringClass)) {
82              sessionManagerMethodInvocation = true;
83              //for SessionManager calls, all method calls except the 'start' methods require a SessionKey
84              // as the first argument, so just get it from there:
85              if (!mi.getMethod().getName().equals("start")) {
86                  SessionKey./../../../../org/apache/shiro/session/mgt/SessionKey.html#SessionKey">SessionKey key = (SessionKey) mi.getArguments()[0];
87                  sessionId = key.getSessionId();
88              }
89          }
90  
91          //tried the delegate. Use the injected session id if given
92          if (sessionId == null) sessionId = this.sessionId;
93  
94          // If sessionId is null, only then try the Subject:
95          if (sessionId == null) {
96              try {
97                  // HACK Check if can get the securityManager - this'll cause an exception if it's not set 
98                  SecurityUtils.getSecurityManager();
99                  if (!sessionManagerMethodInvocation) {
100                     Subject subject = SecurityUtils.getSubject();
101                     Session session = subject.getSession(false);
102                     if (session != null) {
103                         sessionId = session.getId();
104                         host = session.getHost();
105                     }
106                 }
107             }
108             catch (Exception e) {
109                 log.trace("No security manager set. Trying next to get session id from system property");
110             }
111         }
112         //No call to the sessionManager, and the Subject doesn't have a session.  Try a system property
113         //as a last result:
114         if (sessionId == null) {
115             if (log.isTraceEnabled()) {
116                 log.trace("No Session found for the currently executing subject via subject.getSession(false).  " +
117                         "Attempting to revert back to the 'shiro.session.id' system property...");
118             }
119             sessionId = System.getProperty(SESSION_ID_SYSTEM_PROPERTY_NAME);
120             if (sessionId == null && log.isTraceEnabled()) {
121                 log.trace("No 'shiro.session.id' system property found.  Heuristics have been exhausted; " +
122                         "RemoteInvocation will not contain a sessionId.");
123             }
124         }
125 
126         RemoteInvocation ri = new RemoteInvocation(mi);
127         if (sessionId != null) {
128             ri.addAttribute(SESSION_ID_KEY, sessionId);
129         }
130         if (host != null) {
131             ri.addAttribute(HOST_KEY, host);
132         }
133 
134         return ri;
135     }
136 }