yale-cas 与 shiro进行整合

注意:cas-client.version 3.2.1版本。3.3.0版本单点登出存在问题,还在研究。

一.普通的CAS客户端整合

1.CAS与客户端直接整合

参考资料:Configuring the Jasig CAS Client for Java in the web.xml

相关配置直接写在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>

    <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在展示项目中存在

 

 

2.CAS通过Spring整合到项目中

参考资料 Configuring the JA-SIG CAS Client for Java using Spring

与上面一种差不多,只不过在web.xml中filter通过spring的DelegatingFilterProxy进行代理,另外需要注意的是bean ticketValidationFilter的属性p:redirectAfterValidation="true"是单点登出的关键

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

 

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整合Shiro

 

注意,这里没有采用shiro提供的shiro-cas依赖,同样也没有使用到org.apache.shiro.cas.CasFilter,但自己实现的CustomFormAuthenticationFilter参考了CasFilter

 

在基于cas-server-webapp-support 4.0.0(服务器端)cas-client-core(客户端) 4.0.0构建的环境中可以正常使用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。

CCF大数据与计算智能大赛-面向电信行业存量用户的智能套餐个性化匹配模型联通赛-复赛第二名-【多分类,embedding】.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值