How to use separate realms for authentication and

2019-04-10 01:41发布

问题:

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:

  1. What is correct setup/configuration to achieve above mentioned scenario?
  2. What is the best practice to manage users and roles across different/seperate applications?

回答1:

You need only one realm that extends AuthorizingRealm. It will provide

  • authc: method doGetAuthenticationInfo (CAS server)
  • authz: method doGetAuthorizationInfo (JDBC)

Hope this helps



回答2:

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:

  1. Create a new class "AuthenticatingCasRealm" that extends org.apache.shiro.realm.AuthenticatingRealm and implements org.apache.shiro.util.Initializable.

  2. 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.)

  3. Strip out all methods that were implemented for org.apache.shiro.realm.AuthorizingRealm.

  4. 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"));
    }
}


回答3:

We had a similar case where we use a LDAP Realm for authentication and used the standard shiro.ini file for the authorization for a simple use case.

To complement the answer of 'justin.hughey', I give the blueprint (could be spring as well) configuration in order to make your use case working:

<!-- Bean for Authentication --> 
<bean id="rccadRealm" class="org.mydomain.myproject.security.shiro.ldap.realm.LdapRealm"
        init-method="init">
    <property name="searchBase" value="${realm.searchBase}" />
    <property name="singleUserFilter" value="${realm.singleUserFilter}" />
    <property name="timeout" value="30000" />
    <property name="url" value="${contextFactory.url}" />
    <property name="systemUsername" value="${contextFactory.systemUsername}" />
    <property name="systemPassword" value="${contextFactory.systemPassword}" />
</bean>

<!-- Bean for Authorization --> 
<bean id="iniRealm" class="org.mydomain.myproject.security.realm.AuthzOnlyIniRealm">
    <argument value="file:$[config.base]/etc/shiro.ini"/>
    <property name="authorizationCachingEnabled" value="true" />
</bean>

<bean id="myModularAuthenticator"
    class="org.mydomain.myproject.security.service.MyModularRealmAuthenticator">
    <property name="realms">
        <list>
            <ref component-id="ldapRealm" />
        </list>
    </property>
</bean>

<bean id="mySecurityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
    <property name="authenticator" ref="myModularAuthenticator" />
    <property name="authorizer" ref="iniRealm" />
    <property name="cacheManager" ref="cacheManager" />
</bean>

The key things is that we needed:

  • a modularRealmAuthenticator and let the default strategy (as there's only one realm) for the 'authenticator'
  • a special AuthzOnlyIniRealm which overrides the method supports returning false to prevent using it for authentication.

Our LdapRealm implementation is just an extension of the Shiro ActiveDirectoryRealm.