如何使用Shiro和CAS使用单独的域进行身份验证和授权?

时间:2013-05-21 18:12:27

标签: spring security cas shiro

我正在开发一个Web应用程序,其中多个应用程序通过CAS SSO服务器进行身份验证。然而,每个应用程序应保持各自的角色,并且这些角色存储在特定于应用程序的数据库中。所以,我需要有2个领域,一个用于CAS(用于authc),另一个用于DB(用于authz)。

这是我目前的shiro配置。我正在重定向到CAS工作正常,但登录用户(主题)似乎没有加载角色/权限(例如,SecurityUtil.isPermitted()没有按预期工作)

<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>

我使用securityManager注册域的方式应该是正确的。我无法找到一个很好的设置示例。

我在这里有两个问题:

  1. 实现上述方案的正确设置/配置是什么?
  2. 跨不同/单独的应用程序管理用户和角色的最佳做法是什么?

2 个答案:

答案 0 :(得分:3)

您只需要一个扩展 AuthorizingRealm 的领域。它将提供

  • authc:方法doGetAuthenticationInfo(CAS服务器)
  • authz:方法doGetAuthorizationInfo(JDBC)

希望这有帮助

答案 1 :(得分:3)

您遇到的问题与CasRealm和JdbcRealm都扩展AuthorizingRealm(Authorizer)和AuthenticatingRealm这一事实有关。我要采取的第一步是使用JdbcRealm。 JdbcRealm实现继承了AuthenticatingRealm#supports(AuthenticationToken token)方法实现。如果您扩展JdbcRealm并覆盖&#34;支持&#34;返回的方法&#34; false&#34;对于所有令牌类型,JdbcRealm将不再用于身份验证。

@Override
public boolean supports (AuthenticationToken token) {
    return false;
}

CasRealm是一个不同的故事,没有办法(我知道)在检查权限时很容易告诉Shiro不使用实现Authorizer的领域。我个人觉得大多数协议的默认实现都假定需要授权和身份验证,这令人沮丧。我希望每个都分为两个实现(例如AuthenticatingCasRealm,AuthorizingCasRealm)。

在使用多个域时检查权限的逻辑是documented here。引用此行为的特定文本是:

  

步骤4:检查每个配置的Realm以查看它是否实现了   相同的Authorizer接口。如果是这样,Realm各自拥有hasRole *,   checkRole *,isPermitted *,或调用checkPermission *方法。

基于此,理论上你可以覆盖每个命名方法及其所有重载实现,以便始终返回&#34; false&#34;。

我对此问题的解决方案基于我先前关于将每个领域分为两个组件的评论,一个用于身份验证,另一个用于授权。您最终会以这种方式获得更多重复代码,但它明确表示您希望从实施中获得哪些行为。

以下是如何解决这个问题:

  1. 创建一个新类&#34; AuthenticatingCasRealm&#34;扩展org.apache.shiro.realm.AuthenticatingRealm并实现org.apache.shiro.util.Initializable。

  2. 将现有CasRealm source的内容复制并粘贴到新的&#34; AuthenticatingCasRealm&#34;类。 (我知道采用现有代码的复制粘贴路线经常不赞成,但在所描述的情况下,我知道没有其他方法可以解决问题。)

  3. 删除为org.apache.shiro.realm.AuthorizingRealm实现的所有方法。

  4. 更新您的Shrio配置以引用您的新AuthenticatingCasRealm实施。

  5. 根据这些更改,您现在应该在Shrio配置中有两个自定义实现;其中一个JdbcRealm覆盖&#34;支持&#34;方法和CasRealm之一删除授权API方法。

    有一个additional method基于通过Shiro的配置明确声明授权人,可能更适合您的情况。

    以下是通过自定义ShiroFilter扩展名明确声明Authorizer和Authenticator。两者都在启动时实现并注册到提供的JNDI名称。

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