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 "hit" 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 "{@code myRealm}", 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 "{@code myRealm}", 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}("jsmith", "myRealm");
* 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);
}