注意: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。