You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2009/09/15 20:21:48 UTC

svn commit: r815437 - in /incubator/shiro/trunk: core/src/main/java/org/apache/shiro/subject/ support/spring/src/main/java/org/apache/shiro/spring/remoting/

Author: lhazlewood
Date: Tue Sep 15 18:21:48 2009
New Revision: 815437

URL: http://svn.apache.org/viewvc?rev=815437&view=rev
Log:
SHIRO-25: added execute(Callable) and execute(Runnable) methods to associate single-call-only 'runAs' support.  Also refactored the Spring SecureRemoteInvocationExecutor to use this more convenient approach, rather than having to manage a ThreadState object manually.

Added:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/ExecutionException.java
Modified:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/Subject.java
    incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java
    incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java?rev=815437&r1=815436&r2=815437&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java Tue Sep 15 18:21:48 2009
@@ -42,24 +42,24 @@
 import java.util.concurrent.Callable;
 
 /**
- * Implementation of the <tt>Subject</tt> interface that delegates
+ * Implementation of the {@code Subject} interface that delegates
  * method calls to an underlying {@link org.apache.shiro.mgt.SecurityManager SecurityManager} instance for security checks.
- * It is essentially a <tt>SecurityManager</tt> proxy.
+ * It is essentially a {@code SecurityManager} proxy.
  * <p/>
- * This implementation does not maintain state such as roles and permissions (only <code>Subject</code>
+ * This implementation does not maintain state such as roles and permissions (only {@code Subject}
  * {@link #getPrincipals() principals}, such as usernames or user primary keys) for better performance in a stateless
- * architecture.  It instead asks the underlying <tt>SecurityManager</tt> every time to perform
+ * architecture.  It instead asks the underlying {@code SecurityManager} every time to perform
  * the authorization check.
  * <p/>
  * A common misconception in using this implementation is that an EIS resource (RDBMS, etc) would
  * be &quot;hit&quot; every time a method is called.  This is not necessarily the case and is
- * up to the implementation of the underlying <tt>SecurityManager</tt> instance.  If caching of authorization
+ * up to the implementation of the underlying {@code SecurityManager} instance.  If caching of authorization
  * data is desired (to eliminate EIS round trips and therefore improve database performance), it is considered
- * much more elegant to let the underlying <tt>SecurityManager</tt> implementation or its delegate components
- * manage caching, not this class.  A <tt>SecurityManager</tt> is considered a business-tier component,
+ * much more elegant to let the underlying {@code SecurityManager} implementation or its delegate components
+ * manage caching, not this class.  A {@code SecurityManager} is considered a business-tier component,
  * where caching strategies are better suited.
  * <p/>
- * Applications from large and clustered to simple and vm local all benefit from
+ * Applications from large and clustered to simple and JVM-local all benefit from
  * stateless architectures.  This implementation plays a part in the stateless programming
  * paradigm and should be used whenever possible.
  *
@@ -121,8 +121,8 @@
     }
 
     protected boolean hasPrincipals() {
-        PrincipalCollection pc = getPrincipals();
-        return pc != null && !pc.isEmpty();
+        PrincipalCollection principals = getPrincipals();
+        return principals != null && !principals.isEmpty();
     }
 
     /**
@@ -183,7 +183,7 @@
 
     protected void assertAuthzCheckPossible() throws AuthorizationException {
         if (!hasPrincipals()) {
-            String msg = "This subject is anonymous - it does not have any identifying principals associated, and " +
+            String msg = "This subject is anonymous - it does not have any identifying principals and " +
                     "authorization operations require an identity to check against.  A Subject instance will " +
                     "acquire these identifying principals automatically after a successful login is performed " +
                     "be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " +
@@ -260,9 +260,9 @@
         }
         this.authenticated = true;
         if (token instanceof InetAuthenticationToken) {
-            InetAddress addy = ((InetAuthenticationToken) token).getInetAddress();
-            if (addy != null) {
-                this.inetAddress = addy;
+            InetAddress inetAddress = ((InetAuthenticationToken) token).getInetAddress();
+            if (inetAddress != null) {
+                this.inetAddress = inetAddress;
             }
         }
         ThreadContext.bind(this);
@@ -302,14 +302,12 @@
             this.session = null;
             this.principals = null;
             this.authenticated = false;
+            this.inetAddress = null;
             //Don't set securityManager to null here - the Subject can still be
             //used, it is just considered anonymous at this point.  The SecurityManager instance is
             //necessary if the subject would log in again or acquire a new session.  This is in response to
             //https://issues.apache.org/jira/browse/JSEC-22
             //this.securityManager = null;
-
-            //also keep the inetAddress to retain their location:
-            //this.inetAddress = null;
         }
     }
 
@@ -332,6 +330,20 @@
         }
     }
 
+    public <V> V execute(Callable<V> callable) throws ExecutionException {
+        Callable<V> associated = associateWith(callable);
+        try {
+            return associated.call();
+        } catch (Throwable t) {
+            throw new ExecutionException(t);
+        }
+    }
+
+    public void execute(Runnable runnable) {
+        Runnable associated = associateWith(runnable);
+        associated.run();
+    }
+
     public <V> Callable<V> associateWith(Callable<V> callable) {
         return new SubjectCallable<V>(this, callable);
     }

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/ExecutionException.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/ExecutionException.java?rev=815437&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/ExecutionException.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/ExecutionException.java Tue Sep 15 18:21:48 2009
@@ -0,0 +1,41 @@
+/*
+ * 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.shiro.subject;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Exception wrapping any potential checked exception thrown when a {@code Subject} executes a
+ * {@link java.util.concurrent.Callable}.  This is a nicer alternative than forcing calling code to catch
+ * a normal checked {@code Exception} when it may not be necessary.
+ * <p/>
+ * If thrown, the causing exception will always be accessible via the {@link #getCause() getCause()} method.
+ *
+ * @since 1.0
+ */
+public class ExecutionException extends ShiroException {
+
+    public ExecutionException(Throwable cause) {
+        super(cause);
+    }
+
+    public ExecutionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/Subject.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/Subject.java?rev=815437&r1=815436&r2=815437&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/Subject.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/Subject.java Tue Sep 15 18:21:48 2009
@@ -352,21 +352,23 @@
      * <em>NOT</em> considered authenticated.  A check against {@link #isAuthenticated() isAuthenticated()} is a more
      * strict check than that reflected by this method.  For example, a check to see if a subject can access financial
      * information should probably depend on {@link #isAuthenticated() isAuthenticated()} to <em>guarantee</em> a
-     * proven identity, and not this method.
+     * verified identity, and not this method.
      * <p/>
-     * Once the subject is authenticated, they are no longer considered remembered because identity would have been
-     * proven during the current session.
+     * Once the subject is authenticated, they are no longer considered remembered because their identity would have
+     * been verified during the current session.
      * <h4>Remembered vs Authenticated</h4>
      * Authentication is the process of <em>proving</em> you are who you say you are.  When a user is only remembered,
-     * the remenbered identity gives the system an idea who that user probably is, but in reality, has no way of
+     * the remembered identity gives the system an idea who that user probably is, but in reality, has no way of
      * absolutely <em>guaranteeing</em> if the remembered {@code Subject} represents the user currently
      * using the application.
      * <p/>
      * So although many parts of the application can still perform user-specific logic based on the remembered
      * {@link #getPrincipals() principals}, such as customized views, it should never perform highly-sensitive
-     * operations until the user has legitimately proven their identity by executing a successful authentication attempt.
+     * operations until the user has legitimately verified their identity by executing a successful authentication
+     * attempt.
      * <p/>
-     * We see this paradigm all over the web, and we'll use <a href="http://www.amazon.com">Amazon.com</a> as an example:
+     * We see this paradigm all over the web, and we will use <a href="http://www.amazon.com">Amazon.com</a> as an
+     * example:
      * <p/>
      * When you visit Amazon.com and perform a login and ask it to 'remember me', it will set a cookie with your
      * identity.  If you don't log out and your session expires, and you come back, say the next day, Amazon still knows
@@ -422,6 +424,33 @@
     void logout();
 
     /**
+     * Associates the specified {@code Runnable} with this {@code Subject} instance and then executes it on the
+     * currently running thread.  If you want to execute the {@code Callable} on a different thread, it is better to
+     * use the {@link #associateWith(Callable)} method instead.
+     *
+     * @param callable the Callable to associate with this subject and then execute.
+     * @param <V>      the type of return value the {@code Callable} will return
+     * @return the resulting object returned by the {@code Callable}'s execution.
+     * @throws ExecutionException if the {@code Callable}'s {@link Callable#call call} method throws an exception.
+     * @since 1.0
+     */
+    <V> V execute(Callable<V> callable) throws ExecutionException;
+
+    /**
+     * Associates the specified {@code Runnable} with this {@code Subject} instance and then executes it on the
+     * currently running thread.  If you want to execute the {@code Runnable} on a different thread, it is better to
+     * use the {@link #associateWith(Runnable)} method instead.
+     * <p/>
+     * <b>Note</b>: This method is primarily provided to execute existing/legacy Runnable implementations.  It is better
+     * for new code to use {@link #execute(Callable)} since that supports the ability to return values and catch
+     * exceptions.
+     *
+     * @param runnable the {@code Runnable} to associate with this {@code Subject} and then execute.
+     * @since 1.0
+     */
+    void execute(Runnable runnable);
+
+    /**
      * Returns a {@code Callable} instance matching the given argument while additionally ensuring that it will
      * retain and execute under this Subject's identity.  The returned object can be used with an
      * {@link java.util.concurrent.ExecutorService ExecutorService} to execute as this Subject.
@@ -553,7 +582,7 @@
          * <p/>
          * The ability to reference a {@code Subject} and their server-side session
          * <em>across clients of different mediums</em> such as web applications, Java applets,
-         * standalone C# clients over XMLRPC and/or SOAP, and many others. This is a <em>huge</em>
+         * standalone C# clients over XML-RPC and/or SOAP, and many others. This is a <em>huge</em>
          * benefit in heterogeneous enterprise applications.
          * <p/>
          * To maintain session integrity across client mediums, the {@code sessionId} <b>must</b> be transmitted
@@ -603,8 +632,9 @@
          * <p/>
          * For example, if your application's unique identifier for users is a {@code String} username, and you wanted
          * to create a {@code Subject} instance that reflected a user whose username is
-         * 'jsmith', and you knew the Realm that could acquire {@code jsmith}'s principals based on the username was
-         * named &quot;{@code myRealm}&quot;, you might create the '{@code jsmith} {@code Subject} instance this way:
+         * '{@code jsmith}', and you knew the Realm that could acquire {@code jsmith}'s principals based on the username
+         * was named &quot;{@code myRealm}&quot;, you might create the '{@code jsmith} {@code Subject} instance this
+         * way:
          * <pre>
          * PrincipalCollection identity = new {@link org.apache.shiro.subject.SimplePrincipalCollection#SimplePrincipalCollection(Object, String) SimplePrincipalCollection}(&quot;jsmith&quot;, &quot;myRealm&quot;);
          * Subject jsmith = new Subject.Builder().principals(identity).buildSubject();</pre>
@@ -631,8 +661,8 @@
         /**
          * Ensures the {@code Subject} being built will be considered
          * {@link org.apache.shiro.subject.Subject#isAuthenticated() authenticated}.  Per the
-         * {@link org.apache.shiro.subject.Subject#isAuthenticated() isAuthenticated()} JavaDoc, be extremely wary
-         * when specifying {@code true} - you should know what you're doing and have a good reason for ignoring Shiro's
+         * {@link org.apache.shiro.subject.Subject#isAuthenticated() isAuthenticated()} JavaDoc, be careful
+         * when specifying {@code true} - you should know what you are doing and have a good reason for ignoring Shiro's
          * default authentication state mechanisms.
          *
          * @param authenticated whether or not the built {@code Subject} will be considered authenticated.

Modified: incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java?rev=815437&r1=815436&r2=815437&view=diff
==============================================================================
--- incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java (original)
+++ incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java Tue Sep 15 18:21:48 2009
@@ -18,10 +18,10 @@
  */
 package org.apache.shiro.spring.remoting;
 
+import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.mgt.SecurityManager;
-import org.apache.shiro.mgt.SubjectFactory;
+import org.apache.shiro.subject.ExecutionException;
 import org.apache.shiro.subject.Subject;
-import org.apache.shiro.subject.support.SubjectThreadState;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
@@ -30,8 +30,7 @@
 import java.io.Serializable;
 import java.lang.reflect.InvocationTargetException;
 import java.net.InetAddress;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.concurrent.Callable;
 
 
 /**
@@ -79,20 +78,23 @@
     |               M E T H O D S               |
     ============================================*/
     @SuppressWarnings({"unchecked"})
-    public Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
-
-        SubjectThreadState subjectThreadState = null;
+    public Object invoke(final RemoteInvocation invocation, final Object targetObject)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
 
         try {
-            Map context = new HashMap();
+            SecurityManager securityManager =
+                    this.securityManager != null ? this.securityManager : SecurityUtils.getSecurityManager();
+
+            Subject.Builder builder = new Subject.Builder(securityManager);
+
             InetAddress inet = (InetAddress) invocation.getAttribute(SecureRemoteInvocationFactory.INET_ADDRESS_KEY);
             if (inet != null) {
-                context.put(SubjectFactory.INET_ADDRESS, inet);
+                builder.inetAddress(inet);
             }
 
             Serializable sessionId = invocation.getAttribute(SecureRemoteInvocationFactory.SESSION_ID_KEY);
             if (sessionId != null) {
-                context.put(SubjectFactory.SESSION_ID, sessionId);
+                builder.sessionId(sessionId);
             } else {
                 if (log.isTraceEnabled()) {
                     log.trace("RemoteInvocation did not contain a Shiro Session id attribute under " +
@@ -101,23 +103,25 @@
                 }
             }
 
-            Subject subject = securityManager.createSubject(context);
-            subjectThreadState = new SubjectThreadState(subject);
-            subjectThreadState.bind();
-
-            return super.invoke(invocation, targetObject);
-        } catch (NoSuchMethodException nsme) {
-            throw nsme;
-        } catch (IllegalAccessException iae) {
-            throw iae;
-        } catch (InvocationTargetException ite) {
-            throw ite;
+            Subject subject = builder.buildSubject();
+            return subject.execute(new Callable() {
+                public Object call() throws Exception {
+                    return SecureRemoteInvocationExecutor.super.invoke(invocation, targetObject);
+                }
+            });
+        } catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof NoSuchMethodException) {
+                throw (NoSuchMethodException) cause;
+            } else if (cause instanceof IllegalAccessException) {
+                throw (IllegalAccessException) cause;
+            } else if (cause instanceof InvocationTargetException) {
+                throw (InvocationTargetException) cause;
+            } else {
+                throw new InvocationTargetException(cause);
+            }
         } catch (Throwable t) {
             throw new InvocationTargetException(t);
-        } finally {
-            if (subjectThreadState != null) {
-                subjectThreadState.clear();
-            }
         }
     }
 }

Modified: incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java?rev=815437&r1=815436&r2=815437&view=diff
==============================================================================
--- incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java (original)
+++ incubator/shiro/trunk/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java Tue Sep 15 18:21:48 2009
@@ -18,22 +18,21 @@
  */
 package org.apache.shiro.spring.remoting;
 
-import java.io.Serializable;
-import java.net.InetAddress;
-
 import org.aopalliance.intercept.MethodInvocation;
-import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
-import org.springframework.remoting.support.RemoteInvocation;
-import org.springframework.remoting.support.RemoteInvocationFactory;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.util.ThreadContext;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
+import org.springframework.remoting.support.RemoteInvocation;
+import org.springframework.remoting.support.RemoteInvocationFactory;
+
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.util.Map;
 
 
 /**
@@ -75,9 +74,9 @@
         if (SessionManager.class.equals(mi.getMethod().getDeclaringClass())) {
             sessionManagerMethodInvocation = true;
             //for SessionManager calls, all method calls require the session id as the first argument, with
-            //the exception of 'start' that takes in an InetAddress.  So, ignore that one case:
+            //the exception of 'start' that takes in an InetAddress or a Map.  So, ignore those two cases:
             Object firstArg = mi.getArguments()[0];
-            if (!(firstArg instanceof InetAddress)) {
+            if (!(firstArg instanceof InetAddress || firstArg instanceof Map)) {
                 sessionId = (Serializable) firstArg;
             }
         }
@@ -87,7 +86,7 @@
             Subject subject = SecurityUtils.getSubject();
             Session session = subject.getSession(false);
             if (session != null) {
-                inet = session.getHostAddress();                
+                inet = session.getHostAddress();
                 sessionId = session.getId();
             }
         }
@@ -97,16 +96,16 @@
         if (sessionId == null) {
             if (log.isTraceEnabled()) {
                 log.trace("No Session found for the currently executing subject via subject.getSession(false).  " +
-                    "Attempting to revert back to the 'shiro.session.id' system property...");
+                        "Attempting to revert back to the 'shiro.session.id' system property...");
             }
             sessionId = System.getProperty(SESSION_ID_SYSTEM_PROPERTY_NAME);
             if (sessionId == null && log.isTraceEnabled()) {
                 log.trace("No 'shiro.session.id' system property found.  Heuristics have been exhausted; " +
-                    "RemoteInvocation will not contain a sessionId.");
+                        "RemoteInvocation will not contain a sessionId.");
             }
         }
 
-        if ( inet == null ) {
+        if (inet == null) {
             //try thread context:
             inet = ThreadContext.getInetAddress();
         }
@@ -115,7 +114,7 @@
         if (sessionId != null) {
             ri.addAttribute(SESSION_ID_KEY, sessionId);
         }
-        if ( inet != null ) {
+        if (inet != null) {
             ri.addAttribute(INET_ADDRESS_KEY, inet);
         }