How can I delegate JAAS authorization checks to Shiro?

JavaAuthorizationAuthenticationJaasShiro

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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:

  1. The effective SecurityManager, specifically its checkXXX group of methods.
  2. The ProtectionDomain class, particularly its implies(Permission) method.
  3. The key implies(ProtectionDomain, Permission) method of the effective Policy.
  4. Secondarily, the 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:

  1. Authorization is AccessControlContext-independent. Shiro-native authorization attributes (SNAAs), whatever they are, apply to the entire thread. Code origin is irrelevant.
  2. Code origin matters, mandating use of the sandbox. SNAAs are still AccessControlContext-independent.
  3. Code origin and SNAAs are both relevant and AccessControlContext-dependent.

1. Authorization based solely on SNAAs
  1. 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.

  2. Subclass SecurityManager, overriding at least the two checkPermission methods such that they

    1. translate, if necessary, the Permission argument into something Shiro's PDP (SPDP) understands, prior to
    2. delegating to the SPDP with the thread-local SNAAs and permission (and throwing a SecurityException 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.


2. Hybrid authorization, combining code origin with context-insensitive SNAAs
  1. Manage authentication however you see fit; once again associate the Shiro-specific Subject with the thread itself.
  2. Subclass 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.
  3. If the SPDP is to additionally handle evaluation of permissions granted to code originating from a certain location and/or set of code signers, you will also have to subclass 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.

3. Hybrid authorization, combining code origin with SNAAs, both context-sensitive
  1. Manage authentication however you see fit. Unfortunately the subject handling part is not as trivial as creating a ThreadLocal in this case.

  2. Subclass, instantiate, and install a Policy that performs the combined duties of SecurityManager::checkPermission and Policy::implies, as individually described in the second case.

  3. Instantiate and install a standard SecurityManager.

  4. Create a ProtectionDomain subclass, capable of storing and exposing the SNAAs.

  5. Author1 a DomainCombiner that

    1. is constructed with the SNAAs;

    2. implements combine(ProtectionDomain[], ProtectionDomain[]) such that

      1. it replaces the first (the "current" context) array argument's domains with equivalent instances of the custom implementation;
      2. then appends the second (the "assigned" or "inherited" context) argument's ones, if any, to the former as-is; and lastly
      3. returns the concatenation.

    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.

  6. 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 Principals 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDeniz AcayView Question on Stackoverflow
Solution 1 - JavaUuxView Answer on Stackoverflow