I'm developing a server-side application that needs authentication and authorization based on objects. I like Shiro's simplicity, but for being compatible with JAAS, I wrote a LoginModule that uses Apache Shiro as the underlying mechanism.
But my problem is that I couldn't find a way to delegate JAAS authorization checks to Shiro. How can I achieve this?
Note: The answer addresses the general case where an external authorization system is to be integrated with the JVM, by means of the standard security framework. It is not Shiro- or JMX-specific, as I am familiar with neither.
Conceptually, it appears that you are after the policy decision point (PDP) -- the facility where authorization queries ("is entity X allowed to do Y?") are evaluated, that is. The JDK offers several of these:
SecurityManager
, specifically its checkXXX
group of methods.ProtectionDomain
class, particularly its implies(Permission)
method.implies(ProtectionDomain, Permission)
method of the effective Policy
.implies
methods of CodeSource
, PermissionCollection
, Permission
, and Principal
.Any of the aforementioned methods may be overridden in order to customize, at ascending granularity, the functionality of the conceptual PDP. It should be noted that JAAS did (contrary to what its name suggests) not really bring its own PDP along; rather, it provided the means for the domain and policy to support principal-based queries, in addition to the original trust factor of code origin. Hence, to my eye, your requirement of remaining "JAAS-compatible" basically translates to wanting to use the (original-plus-JAAS) Java SE authorization model, a.k.a. the sandbox, which I doubt to be what you desire. Frameworks such as Shiro tend to be employed when the standard model is deemed either too low-level and/or performance-intensive; in other words, when authorization logic need not evaluate every single stack frame for a given set of trust factors, due to those factors being more frequently context-insensitive than not. Depending on the validity of my assumption, three main cases arise for examination:
AccessControlContext
-independent. Shiro-native authorization attributes (SNAAs), whatever they are, apply to the entire thread. Code origin is irrelevant.AccessControlContext
-independent.AccessControlContext
-dependent.Manage authentication however you see fit. If you wish to continue using JAAS' javax.security.auth
SPI for authentication, forget about establishing a standard Subject
as the authentication outcome, instead directly tying the Shiro-specific one to thread-local storage. This way you get more convenient access to the SNAAs, and avoid having to use AccessControlContext
(and suffer the potential performance penalty), for their retrieval.
Subclass SecurityManager
, overriding at least the two checkPermission
methods such that they
Permission
argument into something Shiro's PDP (SPDP) understands, prior toSecurityException
should the SPDP signal access denial).The security context-receiving overload may simply disregard the corresponding argument. At application initialization time, instantiate and install (System::setSecurityManager
) your implementation.
Subject
with the thread itself.SecurityManager
, overriding at least the two checkPermission
methods, this time around such that they delegate to both the SPDP and/or the overridden implementation (which in turn calls checkPermission
on, accordingly, the current or supplied access control context). Which one(s) and in what order should be consulted for any given permission is of course implementation-dependent. When both are to be invoked, the SPDP should be queried first, since it will likely respond faster than the access control context.Policy
, implementing implies(ProtectionDomain, Permission)
such that, like SecurityManager::checkPermission
above, it passes some intelligible representation of the domain (typically only its CodeSource
) and permission arguments -- but logically not the SNAAs -- to the SPDP. The implementation should be efficient to the extent possible, since it will be invoked once per domain per access control context at checkPermission
time. Instantiate and install (Policy::setPolicy
) your implementation.Manage authentication however you see fit. Unfortunately the subject handling part is not as trivial as creating a ThreadLocal
in this case.
Subclass, instantiate, and install a Policy
that performs the combined duties of SecurityManager::checkPermission
and Policy::implies
, as individually described in the second case.
Instantiate and install a standard SecurityManager
.
Create a ProtectionDomain
subclass, capable of storing and exposing the SNAAs.
Author1 a DomainCombiner
that
is constructed with the SNAAs;
implements combine(ProtectionDomain[], ProtectionDomain[])
such that
Like Policy::implies
, the implementation should be efficient (e.g. by eliminating duplicates), as it will be invoked every time the getContext
and checkPermission
AccessController
methods are.
Upon successful authentication, create a new AccessControlContext
that wraps the current one, along with an instance of the custom DomainCombiner
, in turn wrapping the SNAAs. Wrap code to be executed beyond that point "within" an AccessController::doPrivilegedWithCombiner
invocation, also passing along the replacement access control context.
1 Instead of using custom domains and your own combiner implementation, there is also the seemingly simpler alternative of translating the SNAAs into Principal
s and, using the standard SubjectDomainCombiner
, binding them to the current AccessControlContext
's domains (as above, or simply via Subject::doAs
). Whether this approach reduces the policy's efficiency depends primarily on the call stack's depth (how many distinct domains the access control context comprises). Eventually the caching optimizations you thought you could avoid implementing as part of the domain combiner will hit you back when authoring the policy, so this is essentially a design decision you will have to make at that point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With