2019独角兽企业重金招聘Python工程师标准>>>

最近公司在搞单点登录,之前也做过,用的是58同城的wf框架,基于cas 的原理用拦截器自己写的一套。目前用cas+shiro+springmvc的框架,在网上参照张开涛的跟我学shiro

http://jinnianshilongnian.iteye.com/blog/2036730  、http://jinnianshilongnian.iteye.com/blog/2047168

cas 原理流程图:如下

当用户第一次访问应用系统的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;用户再访问别的应用的时候,就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。

下面将一些具体的配置:

cas 的

deployerConfigContext.xml

配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Licensed to Jasig under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. Jasig licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at the following location: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -->
<!-- | deployerConfigContext.xml centralizes into one file some of the declarative configuration that | all CAS deployers will need to modify. | | This file declares some of the Spring-managed JavaBeans that make up a CAS deployment. | The beans declared in this file are instantiated at context initialization time by the Spring | ContextLoaderListener declared in web.xml. It finds this file because this | file is among those declared in the context parameter "contextConfigLocation". | | By far the most common change you will need to make in this file is to change the last bean | declaration to replace the default authentication handler with | one implementing your approach for authenticating usernames and passwords. + --><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:c="http://www.springframework.org/schema/c" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:util="http://www.springframework.org/schema/util" 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.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsdhttp://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><!-- | The authentication manager defines security policy for authentication by specifying at a minimum | the authentication handlers that will be used to authenticate credential. While the AuthenticationManager | interface supports plugging in another implementation, the default PolicyBasedAuthenticationManager should | be sufficient in most cases. + --><bean id="authenticationManager"class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager"><constructor-arg><map><!-- | IMPORTANT | Every handler requires a unique name. | If more than one instance of the same handler class is configured, you must explicitly | set its name to something other than its default name (typically the simple class name). --><entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" /><!-- <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" /> --><entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver" /></map></constructor-arg><!-- Uncomment the metadata populator to allow clearpass to capture and cache the password This switch effectively will turn on clearpass. <property name="authenticationMetaDataPopulators"> <util:list> <bean class="org.jasig.cas.extension.clearpass.CacheCredentialsMetaDataPopulator" c:credentialCache-ref="encryptedMap" /> </util:list> </property> --><!-- | Defines the security policy around authentication. Some alternative policies that ship with CAS: | | * NotPreventedAuthenticationPolicy - all credential must either pass or fail authentication | * AllAuthenticationPolicy - all presented credential must be authenticated successfully | * RequiredHandlerAuthenticationPolicy - specifies a handler that must authenticate its credential to pass --><property name="authenticationPolicy"><bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" /></property></bean><!-- Required for proxy ticket mechanism. --><bean id="proxyAuthenticationHandler"class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"p:httpClient-ref="httpClient" p:requireSecure="false" /><!-- | TODO: Replace this component with one suitable for your enviroment. | | This component provides authentication for the kind of credential used in your environment. In most cases | credential is a username/password pair that lives in a system of record like an LDAP directory. | The most common authentication handler beans: | | * org.jasig.cas.authentication.LdapAuthenticationHandler | * org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler | * org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler | * org.jasig.cas.support.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler --><!-- <bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler"> <property name="users"> <map> <entry key="casuser" value="Mellon"/> </map> </property> </bean> --><!-- 这里配置验证密码的数据源 --><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"p:driverClass="com.mysql.jdbc.Driver"p:jdbcUrl="jdbc:mysql://192.168.1.230:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull"p:user="root" p:password="root" /><!-- 密码加密方式 SHA1/MD5 --><bean id="passwordEncoder"class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"c:encodingAlgorithm="MD5" p:characterEncoding="UTF-8" /><!-- <bean id="dbAuthHandler"class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"p:dataSource-ref="dataSource"p:sql="SELECT passport.password as password FROM passport  where passport.mobile=? and passport.is_del=0"p:passwordEncoder-ref="passwordEncoder" /> --><!-- 暂时不使用密码加密 --><!-- 到数据库查询用户密码 还有特定的加密字符串 --><bean id="dbAuthHandler" class="org.jasig.cas.adaptors.jdbc.MultiCriteriaQueryDatabaseAuthenticationHandler" p:dataSource-ref="dataSource"p:getPwdSql="select a.`password` as pwd from ss_user a where a.login_name = ? "p:getUsernameSql="select a.login_name as loginName from ss_user a where a.login_name = ? "p:getSaltSql="select a.salt as salt from ss_user a where a.login_name = ? "p:passwordEncoder-ref="passwordEncoder" /><!-- Required for proxy ticket mechanism --><bean id="proxyPrincipalResolver"class="org.jasig.cas.authentication.principal.BasicPrincipalResolver" /><!-- | Resolves a principal from a credential using an attribute repository that is configured to resolve | against a deployer-specific store (e.g. LDAP). --><bean id="primaryPrincipalResolver"class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver"><property name="attributeRepository" ref="attributeRepository" /></bean><!-- Bean that defines the attributes that a service may return. This example uses the Stub/Mock version. A real implementation may go against a database or LDAP server. The id should remain "attributeRepository" though. + --><bean id="attributeRepository"class="org.jasig.services.persondir.support.StubPersonAttributeDao"p:backingMap-ref="attrRepoBackingMap" /><util:map id="attrRepoBackingMap"><entry key="uid" value="uid" /><entry key="eduPersonAffiliation" value="eduPersonAffiliation" /><entry key="groupMembership" value="groupMembership" /></util:map><!-- Sample, in-memory data store for the ServiceRegistry. A real implementation would probably want to replace this with the JPA-backed ServiceRegistry DAO The name of this bean should remain "serviceRegistryDao". + --><bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"p:registeredServices-ref="registeredServicesList" /><util:list id="registeredServicesList"><bean class="org.jasig.cas.services.RegexRegisteredService" p:id="0"p:name="HTTP and IMAP" p:description="Allows HTTP(S) and IMAP(S) protocols"p:serviceId="^(https?|imaps?)://.*" p:evaluationOrder="10000001" /><!-- Use the following definition instead of the above to further restrict access to services within your domain (including sub domains). Note that example.com must be replaced with the domain you wish to permit. This example also demonstrates the configuration of an attribute filter that only allows for attributes whose length is 3. --><!-- <bean class="org.jasig.cas.services.RegexRegisteredService"> <property name="id" value="1" /> <property name="name" value="HTTP and IMAP on example.com" /> <property name="description" value="Allows HTTP(S) and IMAP(S) protocols on example.com" /> <property name="serviceId" value="^(https?|imaps?)://([A-Za-z0-9_-]+\.)*example\.com/.*" /> <property name="evaluationOrder" value="0" /> <property name="attributeFilter"> <bean class="org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter" c:regex="^\w{3}$" /> </property> </bean> --></util:list><bean id="auditTrailManager"class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" /><bean id="healthCheckMonitor" class="org.jasig.cas.monitor.HealthCheckMonitor"p:monitors-ref="monitorsList" /><util:list id="monitorsList"><bean class="org.jasig.cas.monitor.MemoryMonitor"p:freeMemoryWarnThreshold="10" /><!-- NOTE The following ticket registries support SessionMonitor: * DefaultTicketRegistry * JpaTicketRegistry Remove this monitor if you use an unsupported registry. --><bean class="org.jasig.cas.monitor.SessionMonitor"p:ticketRegistry-ref="ticketRegistry"p:serviceTicketCountWarnThreshold="5000" p:sessionCountWarnThreshold="100000" /></util:list>
</beans>

对应的代码如下:

/*** @文件名: MultiCriteriaQueryDatabaseAuthenticationHandler.java* @包 org.jasig.cas.adaptors.jdbc* @描述: 扩展基于数据库的身份验证* @作者:qpxboy@163.com* @创建时间 2016年4月1日 上午9:56:19* @版本 V1.0*/
package org.jasig.cas.adaptors.jdbc;import java.security.GeneralSecurityException;
import java.security.MessageDigest;import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.validation.constraints.NotNull;import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.jasig.cas.authentication.HandlerResult;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.UsernamePasswordCredential;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;public class MultiCriteriaQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {@NotNullprivate String getPwdSql;@NotNullprivate String getUsernameSql;@NotNullprivate String getSaltSql;@Overrideprotected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credentials) throws GeneralSecurityException, PreventedException {final String id = getPrincipalNameTransformer().transform(credentials.getUsername());final String password = credentials.getPassword();try {String dbPassword = getJdbcTemplate().queryForObject(this.getPwdSql, String.class, new Object[] { id});String username = getJdbcTemplate().queryForObject(this.getUsernameSql, String.class, new Object[] { id});String salt = getJdbcTemplate().queryForObject(this.getSaltSql, String.class, new Object[] { id});credentials.setUsername(username);// credentials.setPassword(dbPassword);if (!dbPassword.equals(entryptPassword(password, salt))) {throw new FailedLoginException("Password does not match value on record.");}//            return dbPassword.equals(encryptedPassword);//        } catch (final IncorrectResultSizeDataAccessException e) {//            // this means the username was not found.//            return false;//        }} catch (final IncorrectResultSizeDataAccessException e) {if (e.getActualSize() == 0) {throw new AccountNotFoundException(id + " not found with SQL query");} else {throw new FailedLoginException("Multiple records found for " + id);}} catch (final DataAccessException e) {throw new PreventedException("SQL exception while executing query for " + id, e);}return createHandlerResult(credentials, new SimplePrincipal(id), null);}public void setGetPwdSql(final String getPwdSql) {this.getPwdSql = getPwdSql;}public void setGetUsernameSql(final String getUsernameSql) {this.getUsernameSql = getUsernameSql;}public void setGetSaltSql(String getSaltSql) {this.getSaltSql = getSaltSql;}/*** 密码加密,使用salt经过1024次 sha-1 hash* * @param password* @param salt* @return*/private static String entryptPassword(final String password, final byte[] salt) {byte[] hashPassword = sha1(password.getBytes(), salt, 1024);return encodeHex(hashPassword);}private static byte[] sha1(byte[] input, byte[] salt, int iterations) {return digest(input, "SHA-1", salt, iterations);}/*** 对字符串进行散列, 支持md5与sha1算法.*/private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) {try {MessageDigest digest = MessageDigest.getInstance(algorithm);if (salt != null) {digest.update(salt);}byte[] result = digest.digest(input);for (int i = 1; i < iterations; i++) {digest.reset();result = digest.digest(result);}return result;} catch (GeneralSecurityException e) {e.printStackTrace();return null;}}/*** 密码加密,使用salt经过1024次 sha-1 hash* * @param password* @param salt* @return*/private  static String entryptPassword(final String password, final String salt) {byte[] salts = decodeHex(salt);return entryptPassword(password, salts);}/*** Hex编码.*/private static String encodeHex(byte[] input) {return Hex.encodeHexString(input);}/*** Hex解码.*/private static byte[] decodeHex(String input) {try {return Hex.decodeHex(input.toCharArray());} catch (DecoderException e) {e.printStackTrace();return null;}}
}

另外还有一点需要注意的是

ticketExpirationPolicies.xml 这个文件中

<util:constant id="SECONDS" static-field="java.util.concurrent.TimeUnit.SECONDS"/>

<bean id="serviceTicketExpirationPolicy" class="org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy"

c:numberOfUses="1" c:timeToKill="${st.timeToKillInSeconds:1800}" c:timeUnit-ref="SECONDS"/>

红色表明的部分要改大一点默认是2秒,这个是ticket的过期时间。

在 deployerConfigContext.xml 中:

<util:list id="monitorsList">
<bean class="org.jasig.cas.monitor.MemoryMonitor"
p:freeMemoryWarnThreshold="10" />
<!-- NOTE The following ticket registries support SessionMonitor: * DefaultTicketRegistry
* JpaTicketRegistry Remove this monitor if you use an unsupported registry. -->
<bean class="org.jasig.cas.monitor.SessionMonitor"
p:ticketRegistry-ref="ticketRegistry"
p:serviceTicketCountWarnThreshold="5000" p:sessionCountWarnThreshold="100000" />
</util:list>
freeMemoryWarnThreshold="10"

是session 的实效时间,之前查询数据库一直报错,原因是设置的太小了,导致数据库没有查询出来结果。session就关闭了。客户端的shiro.xml 配置如下:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
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.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-3.0.xsd"
default-lazy-init="true">
<!-- 缓存管理器 ehcache 的配置 -->
<!-- <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
</bean> -->
<!-- Realm实现 --><bean id="statelessRealm" class="com.techstar.shiro.realm.StatelessRealm"><property name="cachingEnabled" value="false"/></bean><bean id="statelessAuthcFilter" class="com.techstar.shiro.filter.StatelessAuthcFilter"/>
<!--  <bean id="statelessAuthcFilter" class="org.apache.shiro.web.filter.AccessControlFilter"/> -->
<bean id="roleAuthorizationFilter" class="com.techstar.shiro.RoleAuthorizationFilter"/>
<bean id="casRealm" class="com.techstar.security.service.ShiroDbRealm">
<property name="cachingEnabled" value="true" />
<property name="authenticationCachingEnabled" value="true" />
<property name="authenticationCacheName" value="authenticationCache" />
<property name="authorizationCachingEnabled" value="true" />
<property name="authorizationCacheName" value="authorizationCache" />
<!--该地址为cas server地址-->
<property name="casServerUrlPrefix" value="${casServerUrl}:${casServerPort}" />
<!-- 该地址为demo 的访问地址 + 下面配置的cas  filter -->
<property name="casService" value="${casClientUrl}:${casClientPort}/oauth" />
</bean>
<bean id="logoutFilter" class="com.techstar.shiro.filter.LogoutFilter">
<!--该地址为cas server地址-->
<property name="casServerLogoutUrl" value="${casServerUrl}:${casServerPort}/logout" /><!-- 返回登录地址,类似http://192.168.1.191:8090/boss/yw-base-consumer/logout.shtml --><property name="redirectUrl" value="${casServerUrl}:${casServerPort}/login?service=${casClientUrl}:${casClientPort}/oauth" /></bean><bean id="kickoutSessionControlFilter" class="com.techstar.shiro.filter.KickoutSessionControlFilter"><property name="cacheManager" ref="cacheManager"/><property name="kickoutAfter" value="false"/><property name="maxSession" value="2"/><!--该地址为cas server地址-->
<property name="casServerLogoutUrl" value="${casServerUrl}:${casServerPort}/logout" /><property name="kickoutUrl" value=""/></bean>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!--<property name="loginUrl" value="http://192.168.1.248:8080/offen-cas/login?service=http://192.168.1.191:8090/oauth"/>--> <!--访问demo 时,如果未通过cas认证将会跳转到认证中心,通过得跳转到下面配置的successUrl 里的地址 -->
<property name="loginUrl" value="${casServerUrl}:${casServerPort}/login?service=${casClientUrl}:${casClientPort}/oauth"/><!--访问demo 时,如果未通过cas认证将会跳转到认证中心,通过得跳转到下面配置的successUrl 里的地址 -->
<property name="successUrl" value="${authorityUrl}:${authorityPort}/security/system/getMenuList" />
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter" />
<entry key="cas" value-ref="casFilter" />
<entry key="role" value-ref="roleAuthorizationFilter"/><entry key="kickout" value-ref="kickoutSessionControlFilter"/>
<entry key="logout" value-ref="logoutFilter"/>
<entry key="user" value-ref="userFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/casFailure.jsp = anon
/api* = anon
/oauth* = cas
/images/** = anon
/css/** = anon
/js/** = anon
/editor/** = anon
/static/** = anon
/logout** = logout
/favicon.ico = anon
/favicon* = anon
/** = user
<!-- /** = authc,kickout -->
</value>
</property>
</bean>
<!-- 会话ID生成器 -->
<bean id="sessionIdGenerator"
class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" />
<!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid" />
<property name="httpOnly" value="true" />
<property name="maxAge" value="-1" />
</bean>
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe" />
<property name="httpOnly" value="true" />
<property name="maxAge" value="2592000" /><!-- 30天 -->
</bean>
<!-- rememberMe管理器  如需要记住功能 可删掉相关配置--><bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
<property name="cipherKey"
value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" />
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- 会话DAO -->
<bean id="sessionDAO"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache" />
<property name="sessionIdGenerator" ref="sessionIdGenerator" />
</bean>
<!-- 会话验证调度器 -->
<!-- <bean id="sessionValidationScheduler" -->
<!-- class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler"> -->
<!-- <property name="sessionValidationInterval" value="900000" /> -->
<!-- <property name="sessionManager" ref="sessionManager" /> -->
<!-- </bean> -->
<!-- 会话管理器 -->
<!-- <bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="900000" />
<property name="deleteInvalidSessions" value="true" />
<property name="sessionValidationSchedulerEnabled" value="true" />
<property name="sessionValidationScheduler" ref="sessionValidationScheduler" />
<property name="sessionDAO" ref="sessionDAO" />
<property name="sessionIdCookieEnabled" value="true" />
<property name="sessionIdCookie" ref="sessionIdCookie" />
</bean> -->
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms"><list><ref local="casRealm"/></list></property>
<!-- <property name="sessionManager" ref="sessionManager" /> -->
<property name="cacheManager" ref="cacheManager" />
<property name="rememberMeManager" ref="rememberMeManager" />
<property name="subjectFactory" ref="casSubjectFactory" />
</bean>
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
<bean id="casFilter" class="com.techstar.shiro.filter.ShiroCasFilter">
<property name="failureUrl" value="/casFailure.jsp" />
</bean><bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter" /><bean id="userFilter" class="com.techstar.modules.shiro.web.filter.authc.UserFilter"></bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/><!-- shiro redisManager -->
<!-- <bean id="redisManager" class="org.crazycake.shiro.RedisManager"> -->
<!--     <property name="host" value="127.0.0.1"/> -->
<!--     <property name="port" value="6379"/> -->
<!--     <property name="expire" value="1800"/> -->
<!--     optional properties: -->
<!--     <property name="timeout" value="1800"/> -->
<!--     <property name="password" value="L$RGE7NOuDTZ"/> --><!-- </bean> -->
<!-- redisSessionDAO -->
<!-- <bean id="redisSessionDAO" class="org.crazycake.shiro.RedisSessionDAO"><property name="redisManager" ref="redisManager" />
</bean> -->
<!-- sessionManager -->
<!-- <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"><property name="sessionDAO" ref="redisSessionDAO" />
</bean> -->
<!-- cacheManager -->
<!-- <bean id="cacheManager" class="org.crazycake.shiro.RedisCacheManager"><property name="redisManager" ref="redisManager" />
</bean> -->
<!-- 用户授权信息Cache, 采用EhCache -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:security/ehcache-shiro.xml"/>
</bean>
</beans>

配置文件里面主要的java 代码如下:

/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements.  See the NOTICE file* distributed with this work for additional information* regarding copyright ownership.  The ASF licenses this file* to you under the Apache License, Version 2.0 (the* "License"); you may not use this file except in compliance* with the License.  You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing,* software distributed under the License is distributed on an* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied.  See the License for the* specific language governing permissions and limitations* under the License.*/
package com.techstar.security.service;import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;import com.techstar.security.entity.Organization;
import com.techstar.security.entity.Permission;
import com.techstar.security.entity.Role;
import com.techstar.security.entity.User;public class ShiroDbRealm extends CasRealm{private static final Logger log = LoggerFactory.getLogger(ShiroDbRealm.class);@Autowiredprotected UserService userService;/*** 认证回调函数,登录时调用.*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {CasToken casToken = (CasToken) authcToken;if (authcToken == null) {return null;}String ticket = (String) casToken.getCredentials();if (StringUtils.isEmpty(ticket)) {return null;}TicketValidator ticketValidator = ensureTicketValidator();Assertion casAssertion = null;try {casAssertion = ticketValidator.validate(ticket, getCasService());AttributePrincipal casPrincipal = casAssertion.getPrincipal();String userName = casPrincipal.getName();User user = userService.findOne("loginName", userName);if (user != null) {if (user.getStatus().equals("disabled")) {throw new DisabledAccountException();}Map attributes = casPrincipal.getAttributes();casToken.setUserId(userName);String rememberMeAttributeName = getRememberMeAttributeName();String rememberMeStringValue = (String) attributes.get(rememberMeAttributeName);boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);if (isRemembered){casToken.setRememberMe(true);}ShiroUser shiroUser = new ShiroUser(user.getLoginName(), user.getName(), user.getPassword(), user.getId(),user.getOrganizations() == null ? null : user.getOrganizations());// List<Object> principals = Arrays.asList(new Object[] { userName, shiroUser });PrincipalCollection principalCollection = new SimplePrincipalCollection(shiroUser, getName());return new SimpleAuthenticationInfo(principalCollection, ticket);}} catch (TicketValidationException e) {throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [").append(ticket).append("]").toString(),e);}return null;}/*** 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();User user = userService.findOne("loginName", shiroUser.getLoginName());SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();for (Role role : user.getRoles()) {// 基于Role的权限信息info.addRole(role.getName());for (Permission permission : role.getPermissions()) {// 基于Permission的权限信息info.addStringPermission(permission.getName());}}for (Permission permission : user.getPermissions()) {info.addStringPermission(permission.getName());}// 超级用户可以赋权超级权限if (userService.isSupervisor(user)) {info.addStringPermission("admin");}log.info("当前登录用户" + shiroUser.getLoginName() + "所拥有的权限:" + info.getStringPermissions());return info;}/*** 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息.*/public static class ShiroUser extends com.techstar.modules.shiro.domain.ShiroUser implements Serializable {private static final long serialVersionUID = -1373760761780840081L;public Set<Organization> organizations;private Map<String, Object> cacheMap;public ShiroUser(String loginName, String name, String password, String id, Set<Organization> organizations) {super(id, loginName, password, name);this.organizations = organizations;}public Map<String, Object> getCacheMap() {if (cacheMap == null) {cacheMap = new HashMap<String, Object>();}return cacheMap;}public Set<Organization> getOrganizations() {return this.organizations;}/*** 本函数输出将作为默认的<shiro:principal/>输出.*/@Overridepublic String toString() {return this.getLoginName();}/*** 重载equals,只计算loginName;*/@Overridepublic int hashCode() {return HashCodeBuilder.reflectionHashCode(this, "loginName");}/*** 重载equals,只比较loginName*/@Overridepublic boolean equals(Object obj) {return EqualsBuilder.reflectionEquals(this, obj, "loginName");}}
}
/** Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file* to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the* License. You may obtain a copy of the License at* * http://www.apache.org/licenses/LICENSE-2.0* * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the* specific language governing permissions and limitations under the License.*/
package com.techstar.shiro.filter;import java.io.Serializable;
import java.util.Deque;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.servlet.AdviceFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** Simple Filter that, upon receiving a request, will immediately log-out the currently executing* {@link #getSubject(ServletRequest, ServletResponse) subject} and then redirect them to a* configured {@link #getRedirectUrl() redirectUrl}.* * @since 1.2*/
public class LogoutFilter extends AdviceFilter {private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class);/*** The default redirect URL to where the user will be redirected after logout. The value is {@code "/"}, Shiro's* representation of the web application's context root.*/public static final String DEFAULT_REDIRECT_URL = "/";/*** The URL is Logout address.*/private String casServerLogoutUrl;//    private Cache<String, Deque<Serializable>> cache;/*** The URL to where the user will be redirected after logout.*/private String redirectUrl = DEFAULT_REDIRECT_URL;/*** Acquires the currently executing {@link #getSubject(ServletRequest, ServletResponse)* subject}, a potentially Subject or request-specific* {@link #getRedirectUrl(ServletRequest, ServletResponse, Subject)* redirectUrl}, and redirects the end-user to that redirect url.* * @param request the incoming ServletRequest* @param response the outgoing ServletResponse* @return {@code false} always as typically no further interaction should be done after user logout.* @throws Exception if there is any error.*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {Subject subject = getSubject(request, response);String redirectUrl = getRedirectUrl(request, response, subject);HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;
//        String basePath = this.getWebBasePathNoPort(req);String casServerLogoutUrl = getCasServerLogoutUrl() + "?service=" + redirectUrl;//try/catch added for SHIRO-298:try {
//            Session session = subject.getSession();
//            String username = (String) subject.getPrincipal();
//            Serializable sessionId = session.getId();//TODO 同步控制//            Deque<Serializable> deque = cache.get(username);//            if (deque != null) {//                deque.remove(sessionId);//                cache.put(username, deque);//            }subject.logout();} catch (SessionException ise) {log.debug("Encountered session exception during logout.  This can generally safely be ignored.", ise);}//        issueRedirect(request, response, redirectUrl);resp.sendRedirect(casServerLogoutUrl);return false;}/*** Returns the currently executing {@link Subject}. This implementation merely defaults to calling* {@code SecurityUtils.}{@link SecurityUtils#getSubject() getSubject()}, but can be overridden by* subclasses for different retrieval strategies.* * @param request the incoming Servlet request* @param response the outgoing Servlet response* @return the currently executing {@link Subject}.*/protected Subject getSubject(ServletRequest request, ServletResponse response) {return SecurityUtils.getSubject();}/*** Issues an HTTP redirect to the specified URL after subject logout. This implementation simply calls* {@code WebUtils.}* {@link WebUtils#issueRedirect(ServletRequest, ServletResponse, String)* issueRedirect(request,response,redirectUrl)}.* * @param request the incoming Servlet request* @param response the outgoing Servlet response* @param redirectUrl the URL to where the browser will be redirected immediately after Subject logout.* @throws Exception if there is any error.*/protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl) throws Exception {WebUtils.issueRedirect(request, response, redirectUrl);}/*** Returns the redirect URL to send the user after logout. This default implementation ignores the arguments and* returns the static configured {@link #getRedirectUrl() redirectUrl} property, but this method may be overridden* by subclasses to dynamically construct the URL based on the request or subject if necessary.* <p/>* Note: the Subject is <em>not</em> yet logged out at the time this method is invoked. You may access the Subject's* session if one is available and if necessary.* <p/>* Tip: if you need to access the Subject's session, consider using the {@code Subject.}* {@link Subject#getSession(boolean) getSession(false)} method to ensure a new session isn't created unnecessarily.* If a session would be created, it will be immediately stopped after logout, not providing any value and* unnecessarily taxing session infrastructure/resources.* * @param request the incoming Servlet request* @param response the outgoing ServletResponse* @param subject the not-yet-logged-out currently executing Subject* @return the redirect URL to send the user after logout.*/protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject) {return getRedirectUrl();}public String getCasServerLogoutUrl() {return casServerLogoutUrl;}public void setCasServerLogoutUrl(String casServerLogoutUrl) {this.casServerLogoutUrl = casServerLogoutUrl;}/*** Returns the URL to where the user will be redirected after logout. Default is the web application's context root,* i.e. {@code "/"}* * @return the URL to where the user will be redirected after logout.*/public String getRedirectUrl() {return redirectUrl;}/*** Sets the URL to where the user will be redirected after logout. Default is the web application's context root,* i.e. {@code "/"}* * @param redirectUrl the url to where the user will be redirected after logout*/public void setRedirectUrl(String redirectUrl) {this.redirectUrl = redirectUrl;}//    public void setCacheManager(CacheManager cacheManager) {
//        this.cache = cacheManager.getCache("shiro-kickout-session");
//    }/*** 返回不带"/"带端口的网站根路径* * @return 不带"/"带端口的网站根路径* @author fushihua*/private String getWebBasePath(HttpServletRequest request) {String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();return basePath;}/*** 返回不带"/"不带端口的网站根路径* * @return 不带"/"不带端口的网站根路径* @author fushihua*/private String getWebBasePathNoPort(HttpServletRequest request) {String basePath = request.getScheme() + "://" + request.getServerName() + request.getContextPath();return basePath;}}
package com.techstar.shiro.filter;import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;import com.techstar.shiro.BaseDomain;/*** <p>* User: Zhang Kaitao* <p>* Date: 14-2-18* <p>* Version: 1.0*/
public class KickoutSessionControlFilter extends AccessControlFilter {private String kickoutUrl; //踢出后到的地址private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户private int maxSession = 1; //同一个帐号最大会话数 默认1private SessionManager sessionManager;private Cache<String, Deque<Serializable>> cache;/*** The URL is Logout address.*/private String casServerLogoutUrl;public void setKickoutUrl(String kickoutUrl) {this.kickoutUrl = kickoutUrl;}public void setKickoutAfter(boolean kickoutAfter) {this.kickoutAfter = kickoutAfter;}public void setMaxSession(int maxSession) {this.maxSession = maxSession;}public void setSessionManager(SessionManager sessionManager) {this.sessionManager = sessionManager;}public void setCacheManager(CacheManager cacheManager) {this.cache = cacheManager.getCache("shiro-kickout-session");}@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {Subject subject = getSubject(request, response);if (!subject.isAuthenticated() && !subject.isRemembered()) {//如果没有登录,直接进行之后的流程return true;}Session session = subject.getSession();String username = (String) subject.getPrincipal();Serializable sessionId = session.getId();//TODO 同步控制Deque<Serializable> deque = cache.get(username);if (deque == null) {deque = new LinkedList<Serializable>();cache.put(username, deque);}//如果队列里没有此sessionId,且用户没有被踢出;放入队列if (!deque.contains(sessionId) && session.getAttribute("kickout") == null) {deque.push(sessionId);}//如果队列里的sessionId数超出最大会话数,开始踢人while (deque.size() > maxSession) {Serializable kickoutSessionId = null;if (kickoutAfter) { //如果踢出后者kickoutSessionId = deque.removeFirst();} else { //否则踢出前者kickoutSessionId = deque.removeLast();}try {Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if (kickoutSession != null) {//设置会话的kickout属性表示踢出了kickoutSession.setAttribute("kickout", true);}} catch (Exception e) {//ignore exception}}//如果被踢出了,直接退出,重定向到踢出后的地址if (session.getAttribute("kickout") != null) {//会话被踢出了try {subject.logout();} catch (Exception e) { //ignore}saveRequest(request);//            WebUtils.issueRedirect(request, response, kickoutUrl);HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;String basePath = BaseDomain.getWebBasePathNoPort(req);String casServerLogoutUrl = getCasServerLogoutUrl() + "?service=" + basePath + "/" + kickoutUrl;resp.sendRedirect(casServerLogoutUrl);return false;}return true;}public String getCasServerLogoutUrl() {return casServerLogoutUrl;}public void setCasServerLogoutUrl(String casServerLogoutUrl) {this.casServerLogoutUrl = casServerLogoutUrl;}
}

转载于:https://my.oschina.net/u/2325281/blog/659606

cas+shiro+spring 单点登录相关推荐

  1. 学习CAS实现SSO单点登录

    学习CAS实现SSO单点登录 网上找了几篇比较详细的教程,在这记录一下: 原理: CAS实现SSO单点登录原理 教程: 1.CAS实现单点登录(SSO)经典完整教程 2.SSO之CAS单点登录实例演示 ...

  2. CAS 实现的单点登录

    --任何的成功都不是一蹴而就,经验需要慢慢积累.沉淀! 项目里面用到了CAS单点登录,最近也简单的从应用层面简单研究了下CAS这个开源架构. 1. 基本概念:cookie, session, 会话co ...

  3. CAS+SSO配置单点登录完整案例

    CAS+SSO配置单点登录完整案例 目录 CAS+SSO配置单点登录完整案例 部署环境 环境说明 安全证书配置 1.打开cmd命令窗口(管理员身份打开) 2.生成证书,在cmd窗口输入以下命令: 3. ...

  4. xxl-sso kisso cas三个单点登录系统分析

    xxl-sso kisso cas三个单点登录系统分析 xxl-sso 统一在一个地方登录 客户端和服务端同时连接一个redis 登录成功后将用户信息写入redis. 登录成功返回 sessionId ...

  5. spring + shiro + cas 实现sso单点登录

    sso-shiro-cas spring下使用shiro+cas配置单点登录,多个系统之间的访问,每次只需要登录一次,项目源码 系统模块说明 cas: 单点登录模块,这里直接拿的是cas的项目改了点样 ...

  6. spring boot整合Shiro实现单点登录

    默认情况下,Shiro已经为我们实现了和Cas的集成,我们加入集成的一些配置就ok了 1.加入shiro-cas包 <!-- shiro整合cas单点 --><dependency& ...

  7. cas 单点登录_47 使用cas完成单点登录-02 搭建cas client完成单点登录

    上一节我们搭建了cas server.这一节我们将搭建两个客户端完成单点登录的测试. 1.前提约束 完成搭建cas serverhttps://www.jianshu.com/p/ed0c1359b8 ...

  8. cas跨域单点登录原理_CAS实现SSO单点登录原理

    1.      CAS 简介 1.1.  What is CAS ? CAS ( Central Authentication Service ) 是 Yale 大学发起的一个企业级的.开源的项目,旨 ...

  9. Liferay门户与CAS实现SSO单点登录

    http://blog.csdn.net/yang_19790212/article/details/6635778 1.1 准备工作 1.1.1  安装JDK1.6.0.20 JAVA 1.6.0以 ...

最新文章

  1. vbs脚本在服务器上虚拟按键,iisvdir.vbs iis虚拟目录管理脚本使用介绍
  2. JavaStuNote 4
  3. SAP EWM - 包装主数据 - 包装明细 -2
  4. RobotFrameWork控制流之if语句——Run Keyword If
  5. 素数计算之埃氏筛法、欧拉筛法
  6. Azkaban安装部署,配置文件配置,启动等
  7. 如何访问 SAP Screen Personas 培训系统以及完成一个最简单的例子
  8. 抽奖 | 送树莓派PICO开发板、机械键盘、声控鼠标
  9. SQLite剖析之异步IO模式、共享缓存模式和解锁通知
  10. python自动化_python自动化办公?学这些就够用了
  11. python opcua_理解python中的免费OPC/UA代码
  12. 【显卡天梯图】2014年最新显卡天梯图 – 【迄今最全系列显卡】
  13. 【软件】XPS格式文件怎么打开,用XPSViewer(百度云免费下载链接)
  14. 笔记本拆机后无法开机是什么原因
  15. php计算macd,macd计算公式?MACD指标的原理是什么
  16. NCCL (NVIDIA Collective Communications Library)
  17. 2014年国人开发的最热门的开源软件TOP 100
  18. python爬虫 关于加速乐(_jsl)
  19. WriteFile 错误(GetLastError)返回998
  20. 使用Python使用大气校正法计算地表温度

热门文章

  1. Netty笔记(一)第一个程序
  2. 体绘制(Volume Rendering)概述介绍
  3. ^_^ 真是Android Framework的BUG
  4. [转]linux解压 tar命令
  5. 看!我写的关于“简单异或”加密的破解分析演示程序!
  6. XIV Open Cup named after E.V. Pankratiev. GP of Europe
  7. Linux下的man命令
  8. Linux下SVN服务器同时支持Apache的http和https及svnserve独立服务器三种模式且使用相同的访问权限账号...
  9. FJ省队集训DAY4 T1
  10. 在JBuilder8在使用ANT