cas 與 shiro 集合

1.CAS與客戶端直接整合

參考資料:Configuring the Jasig CAS Client for Java in the web.xmlhtml

相關配置直接寫在web.xml文件中java

內容以下:web

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>cas oss info</servlet-name>
        <servlet-class>com.gqshao.cas.servlet.InfoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cas oss info</servlet-name>
        <url-pattern>/info</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>cas oss logout</servlet-name>
        <servlet-class>com.gqshao.cas.servlet.LogoutServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cas oss logout</servlet-name>
        <url-pattern>/logout</url-pattern>
    </servlet-mapping>

    <!-- 用於單點退出,該過濾器用於實現單點登出功能,可選配置 The SingleSignOutFilter can affect character encoding. -->
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>

    <!-- Filter 定義 -->
    <!-- Character Encoding filter -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out -->
    <!-- 該過濾器用於實現單點登出功能,可選配置。 -->
    <filter>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- https://wiki.jasig.org/display/CASC/Configuring+the+Jasig+CAS+Client+for+Java+in+the+web.xml -->
    <!-- AuthenticationFilter是檢測是否須要經過身份驗證的用戶。若是一個用戶須要身份驗證,它將用戶重定向到CAS服務器。 -->
    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>https://sso.gqshao.com:8443/cas/login</param-value>
        </init-param>
        <init-param>
            <!--這裏的server是服務端的IP -->
            <param-name>serverName</param-name>
            <param-value>http://sso.gqshao.com</param-value>
        </init-param>
        <init-param>
            <param-name>renew</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- 使用的是Cas20ProxyReceivingTicketValidationFilter 驗證使用CAS2.0協議的門票 -->
    <!-- 根據CAS文檔描述:If you are using proxy validation, you should map the validation filter before the authentication filter. -->
    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>https://sso.gqshao.com:8443/cas</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://sso.gqshao.com</param-value>
        </init-param>
        <init-param>
            <param-name>useSession</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- 該過濾器負責實現HttpServletRequest請求的包裹 -->
    <!-- 好比容許開發者經過HttpServletRequest的getRemoteUser()方法得到SSO登陸用戶的登陸名,可選配置。 -->
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>
            org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 該過濾器使得開發者能夠經過org.jasig.cas.client.util.AssertionHolder來獲取用戶的登陸名。 -->
    <!-- 好比AssertionHolder.getAssertion().getPrincipal().getName()。 -->
    <filter>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- ======================== 單點登陸結束 ======================== -->
    <welcome-file-list>
        <welcome-file>WEB-INF/views/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

這裏面用到一個JSP和兩個Servlet在展現項目中存在spring

 

 

2.CAS經過Spring整合到項目中

參考資料 Configuring the JA-SIG CAS Client for Java using Springapache

與上面一種差很少,只不過在web.xml中filter經過spring的DelegatingFilterProxy進行代理,另外須要注意的是bean ticketValidationFilter的屬性p:redirectAfterValidation="true"是單點登出的關鍵spring-mvc

web.xml緩存

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:/applicationContext-cas.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out -->
    <!-- 用於單點退出,該過濾器用於實現單點登出功能,可選配置 The SingleSignOutFilter can affect character encoding. -->
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>cas oss info</servlet-name>
        <servlet-class>com.gqshao.cas.servlet.InfoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cas oss info</servlet-name>
        <url-pattern>/info</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>cas oss logout</servlet-name>
        <servlet-class>com.gqshao.cas.servlet.LogoutServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cas oss logout</servlet-name>
        <url-pattern>/logout</url-pattern>
    </servlet-mapping>

    <!-- Filter 定義 -->
    <!-- Character Encoding filter -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- CAS -->
    <filter>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>singleSignOutFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>authenticationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>ticketValidationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>httpServletRequestWrapperFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>assertionThreadLocalFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

/src/main/resources/applicationContext-cas.xml服務器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:sec="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
    <!-- 讀取配置文件 -->
    <context:property-placeholder location="classpath*:cas.properties" ignore-unresolvable="true" />

    <bean name="singleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />


    <bean name="authenticationFilter" class="org.jasig.cas.client.authentication.AuthenticationFilter" p:renew="false"
        p:gateway="false" p:casServerLoginUrl="${cas.server.login.url}" p:serverName="${server.name}" />

    <bean name="ticketValidationFilter" class="org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter"
        p:redirectAfterValidation="true" p:serverName="${server.name}">
        <property name="ticketValidator">
            <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
                <constructor-arg index="0" value="${cas.server.url}" />
            </bean>
        </property>
    </bean>

    <bean name="httpServletRequestWrapperFilter" class="org.jasig.cas.client.util.HttpServletRequestWrapperFilter" />

    <bean name="assertionThreadLocalFilter" class="org.jasig.cas.client.util.AssertionThreadLocalFilter" />

</beans>

配置文件cas.propertiessession

cas.server.url=https://sso.gqshao.com:8443/cas
cas.server.login.url=https://sso.gqshao.com:8443/cas/login
#Client Address
server.name=http://sso.gqshao.com:8090

二.CAS與Shiro進行整合

Shiro的使用,請參考我博客中《簡單的Spring整合Shiromvc

 

注意,這裏沒有采用shiro提供的shiro-cas依賴,一樣也沒有使用到org.apache.shiro.cas.CasFilter,但本身實現的CustomFormAuthenticationFilter參考了CasFilter

 

1.web.xml

首先web.xml中分別進行cas和shiro的filter的配置,須要注意的是filter的位置關係。

另外要注意的是cas登錄認證以後返回的地址

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:/applicationContext-cas-shiro.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out -->
    <!-- 用於單點退出,該過濾器用於實現單點登出功能,可選配置 The SingleSignOutFilter can affect character encoding. -->
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>cas oss info</servlet-name>
        <servlet-class>com.gqshao.cas.servlet.InfoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cas oss info</servlet-name>
        <url-pattern>/info</url-pattern>
    </servlet-mapping>

    <!-- Filter 定義 -->
    <!-- Character Encoding filter -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- CAS -->
    <filter>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>singleSignOutFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>ticketValidationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>authenticationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>httpServletRequestWrapperFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>assertionThreadLocalFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Shiro Security filter -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
    <session-config>
        <session-timeout>60</session-timeout>
    </session-config>
</web-app>

2 配置文件applicationContext-cas-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:sec="http://www.springframework.org/schema/security" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
    <!-- 讀取配置文件 -->
    <context:property-placeholder location="classpath*:cas.properties" ignore-unresolvable="true" />

    <bean name="singleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />

    <bean name="ticketValidationFilter" class="org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter"
        p:redirectAfterValidation="true" p:serverName="${server.name}" >
        <property name="ticketValidator">
            <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator" p:encoding="UTF-8">
                <constructor-arg index="0" value="${cas.server.url}" />
            </bean>
        </property>
    </bean>
    
    <bean name="authenticationFilter" class="org.jasig.cas.client.authentication.AuthenticationFilter" p:renew="false"
        p:gateway="false" p:casServerLoginUrl="${cas.server.login.url}" p:serverName="${server.name}" />



    <bean name="httpServletRequestWrapperFilter" class="org.jasig.cas.client.util.HttpServletRequestWrapperFilter" />

    <bean name="assertionThreadLocalFilter" class="org.jasig.cas.client.util.AssertionThreadLocalFilter" />

    <!-- Shiro -->
    <!-- Shiro's main business-tier object for web-enabled applications -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="shiroDbRealm" />
        <property name="cacheManager" ref="shiroEhcacheManager" />
    </bean>

    <!-- 項目自定義的Realm -->
    <bean id="shiroDbRealm" class="com.gqshao.cas.authentication.ShiroDbRealm" />

    <!-- Shiro Filter -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- 用於調用Controller -->
        <property name="loginUrl" value="/login" />
        <property name="successUrl" value="/" />
        <!-- 本身實現的formAuthcFilter,加入key type -->
        <property name="filters">
            <util:map>
                <entry key="authc">
                    <bean class="com.gqshao.cas.authentication.CustomFormAuthenticationFilter" />
                </entry>
            </util:map>
        </property>

        <property name="filterChainDefinitions">
            <value>
                /login = authc
                /logout = logout
                /static/** = anon
                /** = user
            </value>
        </property>
    </bean>

    <!-- 用戶受權信息Cache, 採用EhCache -->
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:security/ehcache-shiro.xml" />
    </bean>

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

    <!-- AOP式方法級權限檢查 -->
    <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>
</beans>

3.實現類

(1)一個繼承org.apache.shiro.web.filter.authc.AuthenticatingFilter的實現類

package com.gqshao.cas.authentication;

import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AssertionHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomFormAuthenticationFilter extends AuthenticatingFilter {

    private static final Logger log = LoggerFactory.getLogger(CustomFormAuthenticationFilter.class);

    public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure";

    public static final String DEFAULT_LOGINNAME_PARAM = "loginName";
    public static final String DEFAULT_PASSWORD_PARAM = "password";
    public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";
    // 自定義的輸入字段
    public static final String DEFAULT_CUSTOM_PARAM = "custom";

    private String loginNameParam = DEFAULT_LOGINNAME_PARAM;
    private String passwordParam = DEFAULT_PASSWORD_PARAM;
    private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;
    private String customParam = DEFAULT_CUSTOM_PARAM;

    private String failureKeyAttribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;

    public CustomFormAuthenticationFilter() {
        setLoginUrl(DEFAULT_LOGIN_URL);
    }

    @Override
    public void setLoginUrl(String loginUrl) {
        String previous = getLoginUrl();

        if (previous != null) {
            this.appliedPaths.remove(previous);
        }
        super.setLoginUrl(loginUrl);
        if (log.isTraceEnabled()) {
            log.trace("Adding login url to applied paths.");
        }
        this.appliedPaths.put(getLoginUrl(), null);
    }

    /**
     * 在訪問被拒絕後執行
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return executeLogin(request, response);
    }

    /**
     * 建立自定義的令牌
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {

        if (request instanceof HttpServletRequest) {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            AttributePrincipal principal = (AttributePrincipal) httpRequest.getUserPrincipal();
            if (principal == null) {
                return null;
            }
            CustomToken token = new CustomToken();
            Map<String, Object> attrs = principal.getAttributes();
            token.setLoginName(attrs.get("loginname").toString());
            token.setPassword(attrs.get("password").toString());
            token.setSalt(attrs.get("salt").toString());
            token.setCustom(attrs.get("custom").toString());
            token.setHost(getHost(request));
            return token;
        }
        return null;
    }

    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return (request instanceof HttpServletRequest)
                && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
    }

    protected boolean isRememberMe(ServletRequest request) {
        return false;
    }

    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
            ServletResponse response) throws Exception {
        issueSuccessRedirect(request, response);
        return false;
    }

    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
            ServletRequest request, ServletResponse response) {
        setFailureAttribute(request, e);
        return true;
    }

    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        String className = ae.getClass().getName();
        request.setAttribute(getFailureKeyAttribute(), className);
    }

    public String getFailureKeyAttribute() {
        return failureKeyAttribute;
    }

    public void setFailureKeyAttribute(String failureKeyAttribute) {
        this.failureKeyAttribute = failureKeyAttribute;
    }
}

(2)Token

package com.gqshao.cas.authentication;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.HostAuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;

public class CustomToken implements HostAuthenticationToken, RememberMeAuthenticationToken {

    private String loginName;
    private String password;
    private String host;
    private boolean rememberMe = false;
    private String custom;
    private String salt;

    public CustomToken() {
    }

    public CustomToken(String loginName, String password, String salt, boolean rememberMe, String host,
            String custom) {
        this.loginName = loginName;
        this.password = password;
        this.setSalt(salt);
        this.rememberMe = rememberMe;
        this.host = host;
        this.custom = custom;

    }

    public Object getPrincipal() {
        return getLoginName();
    }

    public Object getCredentials() {
        return getPassword();
    }

    public String getHost() {
        return host;
    }

    public boolean isRememberMe() {
        return rememberMe;
    }

    public void clear() {
        this.loginName = null;
        this.host = null;
        this.password = null;
        this.rememberMe = false;
        this.custom = null;
        this.setSalt(null);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName());
        sb.append(" - ");
        sb.append(loginName);
        sb.append(", rememberMe=").append(rememberMe);
        if (StringUtils.isNotBlank(host)) {
            sb.append(" (").append(host).append(")");
        }
        return sb.toString();
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getCustom() {
        return custom;
    }

    public void setCustom(String custom) {
        this.custom = custom;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public void setRememberMe(boolean rememberMe) {
        this.rememberMe = rememberMe;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

(3)AuthorizingRealm的實現類,這裏實際上是爲了封裝principal(ShiroUser),經過Shiro保存到Session中,後續能夠經過SecurityUtils.getSubject().getPrincipal()隨時調用,真正的登錄認證經過CAS已經完成。

package com.gqshao.cas.authentication;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.gqshao.cas.domain.ShiroUser;

public class ShiroDbRealm extends AuthorizingRealm {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    public ShiroDbRealm() {
        super();
        setAuthenticationTokenClass(CustomToken.class);
    }

    /**
     * 認證回調函數,登陸時調用.
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        CustomToken token = (CustomToken) authcToken;

        ShiroUser root = new ShiroUser();
        // TODO: 經過Token與本系統RBAC關聯起來
        root.setId("本身實現");
        root.setLoginName(token.getLoginName());
        root.setPassword(token.getPassword());
        root.setSalt(token.getSalt());
        root.setCustom(token.getCustom());
        logger.info("用戶[{}]登錄系統, IP:[{}]", token.getLoginName(),token.getHost());
        return new SimpleAuthenticationInfo(root, token.getPassword(), getName());
    }

    /**
     * 受權查詢回調函數, 進行鑑權但緩存中無用戶的受權信息時調用.
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // TODO: 實現鑑權
        return null;
    }

}

4.流程說明 和 注意事項

(1)登錄系統的時候,首先由CAS攔截,而後再CAS服務器端登錄認證;

(2)由於Shiro也須要登錄認證,因此CAS認證經過後,請求會被CustomFormAuthenticationFilter攔截,並調用方法onAccessDenied,此時開始走Shiro認證;

(3)首先會在CustomFormAuthenticationFilter的createToken中組裝Token。這時能夠經過調用httpRequest.getUserPrincipal()或AssertionHolder.getAssertion().getPrincipal();拿到CAS返回信息封裝的principal。經過解析principal,組裝Token,由於Token能夠實現定製,因此這裏按需求實現;

(4)Token組裝後,會調用ShiroDbRealm的doGetAuthenticationInfo方法進行登陸認證,由於實際的登錄認證已經在CAS服務器端實現,因此這裏主要是爲了shiro的principal,返回SimpleAuthenticationInfo,而且不要寫initCredentialsMatcher方法。

 

注意事項

1.CAS的principal(principal解釋)能夠經過配置相應Filter後,經過httpRequest.getUserPrincipal(), 或AssertionHolder.getAssertion().getPrincipal()獲得;

2.Shiro的能夠經過SecurityUtils.getSubject().getPrincipal()獲得;

3.當Shiro登錄認證以後,經過httpRequest.getUserPrincipal()獲得CAS Principal的方法不能夠在用;緣由是此時經過httpRequest.getUserPrincipal()調用返回的是org.apache.shiro.web.servlet.ShiroHttpServletRequest.ObjectPrincipal,而且這是一個私有類,而且實現了java.security.Principal。

轉載請註明 : http://sgq0085.iteye.com/blog/2003783

4.爲了傳遞中文參數,須要注意兩個地方,一個是服務器端的/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp頁面,須要設置爲<%@ page session="false" language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>;另外一個地方是客戶端的配置文件中Cas20ServiceTicketValidator的encoding屬性也要設置爲UTF-8。

相關文章
相關標籤/搜索