I'm working on a web application where multiple applications authenticates through a CAS SSO Server. Howerver, each application should maintain their respective roles and these roles are stored in a database specific to the application. So, I need to have 2 realms, one for CAS (for authc) and another for DB (for authz).
This is my current shiro config. I'm getting the redirection to the CAS working properly, but the logged in user (Subject) doesn't seems to have the roles/permission loaded in it (e.g. SecurityUtil.isPermitted() not working as expected)
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="name" value="jdbcRealm" />
<property name="dataSource" ref="dataSource" />
<property name="authenticationQuery"
value="SELECT password FROM system_user_accounts WHERE username=? and status=10" />
<property name="userRolesQuery"
value="SELECT role_code FROM system_roles r, system_user_accounts u, system_user_roles ur WHERE u.user_id=ur.user_id AND r.role_id=ur.role_id AND u.username=?" />
<property name="permissionsQuery"
value="SELECT code FROM system_roles r, system_permissions p, system_role_permission rp WHERE r.role_id=rp.role_id AND p.permission_id=rp.permission_id AND r.role_code=?" />
<property name="permissionsLookupEnabled" value="true"></property>
<property name="cachingEnabled" value="true" />
<property name="credentialsMatcher" ref="passwordMatcher" />
</bean>
<!-- For CAS -->
<bean id="casRealm" class="org.apache.shiro.cas.CasRealm">
<property name="defaultRoles" value="ROLE_USER" />
<property name="casServerUrlPrefix" value="http://localhost:7080/auth" />
<property name="casService" value="http://localhost:8080/hawk-hck-web/shiro-cas" />
<property name="validationProtocol" value="SAML" />
<property name="cachingEnabled" value="true"></property>
</bean>
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />
<!-- Security Manager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<ref bean="casRealm" />
<ref bean="jdbcRealm" />
</list>
</property>
<property name="cacheManager" ref="cacheManager"/>
<property name="subjectFactory" ref="casSubjectFactory" />
</bean>
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<property name="failureUrl" value="/error"></property>
</bean>
<!-- Shiro filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="http://localhost:7080/auth/login?service=http://localhost:8080/hawk-hck-web/shiro-cas" />
<property name="successUrl" value="/home/index" />
<property name="unauthorizedUrl" value="/error" />
<property name="filters">
<util:map>
<entry key="casFilter" value-ref="casFilter" />
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
<!-- !!! Order matters !!! -->
/shiro-cas = casFilter
/login = anon
/logout = logout
/error = anon
/static/** = anon
/** = authc
</value>
</property>
</bean>
The way I register the realms with the securityManager should be in correct. I can't really find a good example of the setup.
I have 2 questions here:
The problem you are running into has to do with the fact that both CasRealm and JdbcRealm extends both AuthorizingRealm (Authorizer) and AuthenticatingRealm. First step I would take is with the JdbcRealm. The JdbcRealm implementation inherits the AuthenticatingRealm#supports(AuthenticationToken token) method implementation. If you extend JdbcRealm and override the "supports" method to return "false" for all token types the JdbcRealm will no longer be used for authentication purposes.
@Override
public boolean supports (AuthenticationToken token) {
return false;
}
The CasRealm is a different story, there is no way (that I know of) to easily tell Shiro to not use a realm that implements Authorizer when checking permissions. I personally find it frustrating that the default implementation for most protocols assumes that both authorization and authentication are needed. I would prefer each to be split into two implementations (eg AuthenticatingCasRealm, AuthorizingCasRealm).
The logic behind checking permissions when multiple realms are in use is documented here. The specific text that references this behavior is:
Step 4: Each configured Realm is checked to see if it implements the same Authorizer interface. If so, the Realm's own respective hasRole*, checkRole*, isPermitted*, or checkPermission* method is called.
Based on this, you theoretically could override each of the named methods and all of their overloaded implementations to always return "false".
My solution to this problem is based on my prior comment about splitting each realm into two components, one for authentication and one for authorization. You end up with more duplicate code this way but it is explicit in what behaviors you are expecting from your implementation.
Here's how to go about it:
Create a new class "AuthenticatingCasRealm" that extends org.apache.shiro.realm.AuthenticatingRealm and implements org.apache.shiro.util.Initializable.
Copy and paste the contents of the existing CasRealm source into your new "AuthenticatingCasRealm" class. (I am aware that taking a copy-and-paste route of existing code is often frowned upon however in the described circumstsance I know of no other way of solving the problem.)
Strip out all methods that were implemented for org.apache.shiro.realm.AuthorizingRealm.
Update your Shrio configuration to reference your new AuthenticatingCasRealm implementation.
Based on these changes you should now have two custom implementations in your Shrio config; one of JdbcRealm overriding the "supports" method and one of CasRealm removing the authorization API methods.
There is one additional method based on explicitly declaring an Authorizer via Shiro's configuration that may be better suited to your situation.
Here is an explicit declaration of an Authorizer and Authenticator via a custom ShiroFilter extension. Both were implemented and registered to the provided JNDI names at startup.
public class CustomShiroFilter extends ShiroFilter {
@Override
public void init () throws Exception {
super.init();
DefaultWebSecurityManager dwsm = (DefaultWebSecurityManager) getSecurityManager();
dwsm.setAuthorizer((Authorizer)JndiUtil.get("realms/authorizerRealm"));
dwsm.setAuthenticator((Authenticator)JndiUtil.get("realms/authenticatorRealm"));
}
}
You need only one realm that extends AuthorizingRealm. It will provide
doGetAuthenticationInfo
(CAS server)doGetAuthorizationInfo
(JDBC)Hope this helps
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