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

Posted

技术标签:

【中文标题】Shiro 和 CAS 如何使用不同的域进行身份验证和授权?【英文标题】:How to use separate realms for authentication and authorization with Shiro and CAS? 【发布时间】:2013-05-16 14:48:05 【问题描述】:

我正在开发一个 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 注册领域的方式应该是正确的。我真的找不到很好的设置示例。

我有两个问题:

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

【问题讨论】:

你检查角色了吗?我们使用与 casRealm 非常相似的配置进行身份验证,使用 textRealm 和/或 activeDirectoryRealm 进行授权。对于权限,我们使用 RolePermissionResolver 的自定义实现。 在 casRealm 中我们省略了 defaultRoles 属性。 【参考方案1】:

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

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

CasRealm 是另一回事,没有办法(据我所知)轻易告诉 Shiro 在检查权限时不要使用实现 Authorizer 的领域。我个人觉得令人沮丧的是,大多数协议的默认实现都假设需要授权和身份验证。我希望每个都被分成两个实现(例如 AuthenticatingCasRealm、AuthorizingCasRealm)。

使用多个领域时检查权限背后的逻辑是documented here。引用此行为的具体文本是:

第 4 步:检查每个配置的 Realm 是否实现 相同的授权者接口。如果是,Realm 各自的 hasRole*, 调用了 checkRole*、isPermitted* 或 checkPermission* 方法。

基于此,理论上您可以覆盖每个命名方法及其所有重载实现以始终返回“false”。

我对这个问题的解决方案是基于我之前关于将每个领域分为两个组件的评论,一个用于身份验证,一个用于授权。这样你最终会得到更多重复的代码,但它清楚地表明了你期望从你的实现中得到什么行为。

具体方法如下:

    创建一个新类“AuthenticatingCasRealm”,扩展 org.apache.shiro.realm.AuthenticatingRealm 并实现 org.apache.shiro.util.Initializable。

    复制现有CasRealm source 的内容并将其粘贴到新的“AuthenticatingCasRealm”类中。 (我知道对现有代码采取复制和粘贴的方式通常是不受欢迎的,但是在所描述的情况下,我知道没有其他解决问题的方法。)

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

    更新您的 Shrio 配置以引用您的新 AuthenticatingCasRealm 实现。

基于这些更改,您现在应该在 Shrio 配置中有两个自定义实现; JdbcRealm 之一覆盖了“支持”方法,而 CasRealm 之一删除了授权 API 方法。

有一个additional method 基于通过 Shiro 的配置显式声明一个 Authorizer 可能更适合您的情况。

这是通过自定义 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"));
    

【讨论】:

【参考方案2】:

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

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

希望对你有帮助

【讨论】:

如果使用上述方法,您只需要配置现有领域,不需要任何自定义实现。【参考方案3】:

我们有一个类似的案例,我们使用 LDAP 领域进行身份验证,并使用标准 shiro.ini 文件进行简单用例的授权。

为了补充“justin.hughey”的答案,我给出了蓝图(也可以是 spring)配置,以使您的用例能够正常工作:

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

关键是我们需要:

modularRealmAuthenticator 并让“身份验证者”的默认策略(因为只有一个领域) 一个特殊的 AuthzOnlyIniRealm 覆盖方法 supports 返回 false 以防止将其用于身份验证。

我们的 LdapRealm 实现只是 Shiro ActiveDirectoryRealm 的扩展。

【讨论】:

以上是关于Shiro 和 CAS 如何使用不同的域进行身份验证和授权?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Apache Shiro 和 CAS 在 SSO 上重定向循环,用于 Web 应用

shiro多realm验证之——shiro实现不同身份使用不同Realm进行验证(转)

如何配置 .net 核心以将身份验证 cookie 发送到不同的域

cas4.2.7与shiro进行整合

使用 CAS 进行身份验证和 LDAP 进行授权的 Spring 项目

在与 api 不同的域上对单页应用进行 Auth0 身份验证