1. 程式人生 > >基於Spirng的Shiro安全框架與CAS SSO的整合

基於Spirng的Shiro安全框架與CAS SSO的整合

首先需要在maven的pom檔案中新增依賴

< dependency>
     <groupId >org.apache.shiro </groupId >
     <artifactId > shiro-cas </artifactId >
     <version >${shiro.version} </version >
     <exclusions >
          <exclusion >
            <artifactId > servlet-api </artifactId
>
<groupId >javax.servlet </groupId > </exclusion > <exclusion > <artifactId >commons-logging </artifactId > <groupId >commons-logging </groupId > </exclusion > </exclusions >
</dependency > <dependency > <groupId >org.apache.shiro </groupId > <artifactId > shiro-spring</ artifactId> <version >${shiro.version} </version > </dependency > <dependency > <groupId >org.apache.shiro </groupId
>
<artifactId > shiro-ehcache </artifactId > <version >${shiro.version} </version > </dependency >

在web.xml中新增相應的filter和listener

<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter>
        <filter-name>singleSignOutFilter</filter-name>
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
    </filter>
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>

    <filter-mapping>
        <filter-name>singleSignOutFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

先來看一下shiro的property檔案

shiro.session.timeout=1800000
shiro.session.validate.timespan=1800000
shiro.login.url=http://login.51idc.cn/user/login?service=http://localhost:8086/tickets/shiro-cas
shiro.logout.url=http://login.51idc.cn/user/logout?service=http://localhost:8086/tickets/ticket/create
shiro.login.success.url=http://localhost:8086/home
shiro.casServer.url=http://login.51idc.cn/user
shiro.client.cas=http://localhost:8086/tickets/shiro-cas
shiro.failureUrl=/error.jsp

下面進行applicationContext-shiro.xml的配置,記得在sping的主配置檔案中引用此配置

<!-- Shiro Filter
    anon:匿名過濾器,不用登入也可以訪問
    authc:如果繼續操作,需要做對應的表單驗證否則不能通過
    authcBasic:基本http驗證過濾,如果不通過,跳轉登入頁面
    logout:登入退出過濾器
    noSessionCreation:沒有session建立過濾器
    perms:許可權過濾器
    port:埠過濾器,可以設定是否是指定埠如果不是跳轉到登入頁面
    rest:http方法過濾器,可以指定如post不能進行訪問等
    roles:    角色過濾器,判斷當前使用者是否指定角色
    ssl:請求需要通過ssl,如果不是跳轉回登入頁
    user:如果訪問一個已知使用者,比如記住我功能,走這個過濾器
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!--沒有單點登入下的配置:沒有許可權或者失敗後跳轉的頁面 -->
        <!-- <property name="loginUrl" value="/login/toLoginAction"/> -->
        <!--有單點登入的配置:登入 CAS 服務端地址,引數 service 為服務端的返回地址 -->
        <property name="loginUrl" value="${shiro.login.url}" />
        <property name="successUrl" value="${shiro.login.success.url}" />
        <property name="filters">
            <map>
                <entry key="casFilter" value-ref="casFilter" />
                <entry key="anon" value-ref="anonymousFilter" />
                <entry key="logout" value-ref="logoutFilter" />
                <entry key="loginSuccessFilter" value-ref="loginSuccessFilter" />   
            </map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /shiro-cas = casFilter
                /**/*.js = anon
                /**/*.css = anon
                /static/** = anon
                /api/** = anon
                /logout = logout
                /admin/** = loginSuccessFilter,roles[admin]
                /** =loginSuccessFilter,roles[employee]
            </value>
        </property>
    </bean>
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
    <!--配置驗證錯誤時的失敗頁面(Ticket 校驗不通過時展示的錯誤頁面) -->
    <property name="failureUrl" value="${shiro.failureUrl}" />
</bean>

<bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
    <property name="redirectUrl" value="${shiro.logout.url}" />
</bean>

<bean id="anonymousFilter" class="org.apache.shiro.web.filter.authc.AnonymousFilter" />

<!-- 自定義的filter-->
<bean id="loginSuccessFilter" class="com.anchnet.tickets.web.filters.LoginSuccessFilter" />


<!-- Shiro's main business-tier object for web-enabled applications -->
        <property name="sessionManager" ref="defaultWebSessionManager"/>
        <property name="realms">
            <list>
                <ref bean="casRealm" />
            </list>
        </property>
        <property name="subjectFactory" ref="casSubjectFactory" />

        <property name="cacheManager" ref="shiroEhcacheManager" />
    </bean>

<bean id="casRealm" class="com.anchnet.tickets.service.account.CustomCASRealm">
        <property name="defaultRoles" value="ROLE_USER" />
        <property name="casServerUrlPrefix" value="${shiro.casServer.url}" />
        <!--客戶端的回撥地址設定,必須和上面的shiro-cas過濾器攔截的地址一致 -->
        <property name="casService" value="${shiro.client.cas}" />
</bean>

<!--Define the realm you want to use to connect to your back-end security 
    datasource: -->
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />

<!-- default web session manager -->
<bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <property name="globalSessionTimeout" value="${shiro.session.timeout}"/>
    <property name="sessionIdCookie" ref="simpleCookie"/>
    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <property name="deleteInvalidSessions" value="true"/>
</bean>

<bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg index="0" value="JSESSIONID_COOKIE"/>
    <property name="httpOnly" value="true"/>
</bean>

<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
    <property name="sessionManager" ref="defaultWebSessionManager"/>
    <property name="interval" value="${shiro.session.validate.timespan}"/>
</bean>

<!-- 使用者授權資訊Cache, 採用EhCache -->
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:cache/ehcache-shiro.xml" />
    </bean>

<!-- 保證實現了Shiro內部lifecycle函式的bean執行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

<!--AOP式方法級許可權檢查 -->
<!--Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!--the lifecycleBeanProcessor has run: -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
    <property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
</bean>

上面的使用者授權的cache採用ehcache,以下是ehcache-shiro.xml的配置

<ehcache updateCheck="false" name="shiroCache">
    <!-- http://ehcache.org/ehcache.xml -->
   <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />
</ehcache>

下面詳細介紹 CasRealm bean的實現

import java.io.Serializable;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;

public class CustomCASRealm extends CasRealm {

    private static Logger logger = LoggerFactory
            .getLogger(CustomCASRealm.class);

    // @Autowired
    protected AccountService accountService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String loginName = (String) principals.getPrimaryPrincipal();
        if (loginName == null) {
            throw new UnauthorizedException("無效的憑證");
        }
        try {
            Map rolesMaps = (Map) principals.asList().get(1);
            String roles = (String) rolesMaps.get("role");
            if (roles.length() > 0) {
                roles = roles.substring(1);
                roles = roles.substring(0, roles.length() - 1);
                logger.debug("!!!!! get roles:{}", roles);
                authorizationInfo.addRoles(ImmutableList.copyOf(StringUtils.split(roles, ", ")));
            } else {
                logger.debug("user {} has no roles!!!", loginName);
            }

        } catch (Exception e) {
            e.printStackTrace();
            throw new UnauthorizedException("無效的憑證");
        }
        return authorizationInfo;
    }