shiro实现单点登录

需求:实现同一个账号同一个时刻只能在一个IP浏览器登录,后者把前者挤掉线。
实现:通过shiro的session管理机制进行实现。
我的理解:shrio有一套sessionmanager的管理器,用来管理登录用户的session。不同的用户每次登录都会生成一个sessionid,保存在shrio框架中。我只需要实现根据登录用户的账号,判断正在登录的用户session是否已经存在于当前活跃的session列表中,如果存在的话,把那个session给删除,就可以成功实现将前一个登录的用户挤掉了。
这一段的逻辑代码是:

在自定义的ShiroJdbcRealm类中的doGetAuthenticationInfo--认证回调函数中加上:
        long loginOnTime = new Date().getTime();
        String tempSessionId = SecurityUtils.getSubject().getSession().getId().toString();//获取当前正在登录的用户的sessionID(已经登录了的,这是回调函数)
        logger.info("正在登陆---登陆用户{}",tempSessionId);
        AuthUserDetails userSession = null;
        int count = 0;
        if(authAccount != null){
            //获取在线的session,获取当前所有活跃的用户;包括正在登陆的这个用户
            Collection<Session> sessionCollection = sessionDAO.getActiveSessions();
            for (Session session : sessionCollection){
                // 获取simpleAuthenticationInfo的第一个参数的值
                if(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) != null) {
                    //根据session build出一个subject
                    Subject subject = new Subject.Builder().session(session).buildSubject();
                    //循环遍历拿到已经登录的对象
                    userSession = (AuthUserDetails) subject.getPrincipal();
                    logger.info("缓存用户{}",userSession);
                    //判断已经登录的对象的code和现在正在登陆的code是否一致 ,这里的code用的是登录的账号
                    if (authAccount.getAuthUid().equals(userSession.getAuthUid())) {
                    	//同一个账户登录的时候,除了当前正在登录的用户,其他的session都要移除。
                    	if (session.getId()!=tempSessionId) {
							sessionDAO.delete(session);//从活跃的session列表中移除同一个账号的其他session,除了当前正在登录的session
							count++;
						}
                    }
                }
            }
        }
        System.out.println("---------------------------"+count);

由于每一个http请求都会生成一个sessionID,所以实际上会移除很多session,但是都是这个账号的,所以无所谓了。

其中sessionDAO.getActiveSessions();这一行是会获取到当前活跃的所有的session(也就是没有过期的包括当前正在登录的用户的session)

shiro自己的配置文件讲解:

spring-shiro.xml,这里面就是用来定义bean,然后加载到其他定义bean中当前属性值进行使用,这样的话就不用自己实例化了,在代码里可以直接使用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
	default-lazy-init="true">

	<description>Shiro安全配置</description>

	<!-- shiro cache using Redis 
	<bean id="shiroRedisManager" class="org.crazycake.shiro.RedisManager">
		<property name="host" value="${redis.host}" />
		<property name="port" value="${redis.port}" />
		<property name="expire" value="1800" />
	</bean>

	<bean id="shiroCacheManager" class="org.crazycake.shiro.RedisCacheManager">
		<property name="redisManager" ref="shiroRedisManager" />
	</bean>
	-->
    <!--定义sessiondao,用来查询数据库中的账号信息,根据登录账号,自己实现-->
	<bean id="sessionDao" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO"/>
	
	<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
       <!-- session超时时间 ,这里是半个小时 ref="sessionDao"标识把sessiondao注入到这里-->
        <property name="globalSessionTimeout" value="1800000"/>
        <property name="sessionDAO" ref="sessionDao"/>
      <!--   <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/> -->
        <!-- 删除失效的session -->
        <property name="deleteInvalidSessions" value="true"/>
        <!-- 是否开启会话验证器,默认是开启的 -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <!-- 相隔多久检查一次session的有效性  半个小时,每半个显示去检查一下session是否过期,如果没半个小时无操作,也会认为是失效的session -->
	 	<property name="sessionValidationInterval" value="1800000"/> 
    </bean>
    
    <!-- shiro cache using EhCache 设置depends-on="cacheManager",确保共享模式下优先加载Spring CacheManager -->
	<bean id="shiroCacheManager" class="cc.rengu.ecp.platform.core.security.SharedEhCacheManager" depends-on="cacheManager">
		<property name="cacheManagerConfigFile" value="classpath:ehcache-config.xml" />
		<property name="shared" value="true" />
	</bean>

    <!--自定义realm-->
	<bean id="shiroJdbcRealm" class="cc.rengu.ecp.platform.core.security.ShiroJdbcRealm">
		<property name="passwordService" ref="passwordService" />
		<property name="userService" ref="userService" />
		<property name="sessionDAO" ref="sessionDao" />
	</bean>

	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
		<property name="authenticationStrategy">
			<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
		</property>
	</bean>

	<!-- Shiro's main business-tier object for web-enabled applications 这里是shiro配置的核心,将session管理器、缓存管理器、注入到里面-->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="authenticator" ref="authenticator" />
		<property name="realms">
			<list>
				<ref bean="shiroJdbcRealm" />
			</list>
		</property>
		<property name="cacheManager" ref="shiroCacheManager" />
        <property name="sessionManager" ref="sessionManager"></property>
	</bean>
	
	<!-- spring-shiro.xml加入以下BEAN -->
	<!-- 定义一个拦截器,限制同一用户登录 -->
	<bean id="kickoutSessionControlFilter" class="cc.rengu.ecp.platform.core.security.KickoutSessionControlFilter">  
        <property name="cacheManager" ref="shiroCacheManager"/>  
		<property name="sessionDAO" ref="sessionDao" />
		<property name="securityManager" ref="securityManager"/>
        <property name="kickoutUrl" value="/admin/login"/>  
    </bean>

	<bean id="anyRolesAuthorizationFilter" class="cc.rengu.ecp.platform.core.security.AnyRolesAuthorizationFilter" />

	<bean id="wwwJcaptchaFormAuthenticationFilter" class="cc.rengu.ecp.platform.core.security.JcaptchaFormAuthenticationFilter">
		<property name="loginUrl" value="/w/login" />
		<property name="successUrl" value="/w" />
		<property name="userService" ref="userService" />
	</bean>

	<bean id="mobileJcaptchaFormAuthenticationFilter" class="cc.rengu.ecp.platform.core.security.JcaptchaFormAuthenticationFilter">
		<property name="loginUrl" value="/m/login" />
		<property name="successUrl" value="/m" />
		<property name="userService" ref="userService" />
	</bean>

	<bean id="adminJcaptchaFormAuthenticationFilter" class="cc.rengu.ecp.platform.core.security.JcaptchaFormAuthenticationFilter">
		<property name="loginUrl" value="/admin/login" />
		<property name="successUrl" value="/admin" />
		<property name="userService" ref="userService" />
	</bean>


	<bean id="appFormAuthenticationFilter" class="cc.rengu.ecp.platform.core.security.JcaptchaFormAuthenticationFilter">
		<property name="loginUrl" value="/app/login" />
		<property name="userService" ref="userService" />
	</bean>

	<bean id="appBasicAuthenticationFilter" class="cc.rengu.ecp.platform.core.security.AppBasicAuthenticationFilter">
		<property name="appKeySecrets">
			<props>
				<prop key="auth_app_android">${auth_app_android}</prop>
				<prop key="auth_app_ios">${auth_app_ios}</prop>
				<prop key="auth_app_weixin">${auth_app_weixin}</prop>
				<prop key="auth_api_jenkins">${auth_api_jenkins}</prop>
			</props>
		</property>
	</bean>

	<bean id="bearerTokenAuthenticatingFilter" class="cc.rengu.ecp.platform.core.security.BearerTokenAuthenticatingFilter">

	</bean>

	<bean id="wwwLogoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
		<property name="redirectUrl" value="/w" />
	</bean>

	<bean id="mobileLogoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
		<property name="redirectUrl" value="/m" />
	</bean>

	<bean id="adminLogoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
		<property name="redirectUrl" value="/admin" />
	</bean>

	<bean id="appLogoutFilter" class="cc.rengu.ecp.platform.core.security.AppLogoutFilter">
		<property name="userService" ref="userService" />
	</bean>
	
	<!-- Shiro Filter ,这里是shiro的拦截器链,不同的角色对应不同的拦截器-->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="filters">
			<map>
				<entry key="anyRoles" value-ref="anyRolesAuthorizationFilter" />
				<entry key="authcWww" value-ref="wwwJcaptchaFormAuthenticationFilter" />
				<entry key="authcMobile" value-ref="mobileJcaptchaFormAuthenticationFilter" />
				<entry key="authcA" value-ref="adminJcaptchaFormAuthenticationFilter" />
				<entry key="authcBearerToken" value-ref="bearerTokenAuthenticatingFilter" />
				<entry key="authcAppSecret" value-ref="appBasicAuthenticationFilter" />
				<entry key="authcAppForm" value-ref="appFormAuthenticationFilter" />
				<entry key="logoutWww" value-ref="wwwLogoutFilter" />
				<entry key="logoutMobile" value-ref="mobileLogoutFilter" />
				<entry key="logoutAdmin" value-ref="adminLogoutFilter" />
				<entry key="logoutApp" value-ref="appLogoutFilter" />
				<entry key="kickout" value-ref="kickoutSessionControlFilter" />
			</map>
		</property>
		<property name="securityManager" ref="securityManager" />
		<!--  
		anon	org.apache.shiro.web.filter.authc.AnonymousFilter
		authc	org.apache.shiro.web.filter.authc.FormAuthenticationFilter
		authcBasic	org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
		logout	org.apache.shiro.web.filter.authc.LogoutFilter
		noSessionCreation	org.apache.shiro.web.filter.session.NoSessionCreationFilter
		perms	org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
		port	org.apache.shiro.web.filter.authz.PortFilter
		rest	org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
		roles	org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
		ssl	org.apache.shiro.web.filter.authz.SslFilter
		user	org.apache.shiro.web.filter.authc.UserFilter
        不同的url对应不同的拦截器,实现对不同的url进行不同的拦截处理,这是拦截器链,真正的拦截器执行顺序就是根据这个地址进行拦截
		-->
		<property name="filterChainDefinitions">
			<value>
				/assets/** = anon
				/system/** = anon
				/widgets/** = anon
				
				/w/login = authcWww
				/w/logout = logoutWww
				/w* = anon

				/m/login = authcMobile
				/m/logout = logoutMobile
				/m/** = authcBearerToken,anon
	
				
				
				/admin/login = authcA
				/admin/logout = logoutAdmin
				/admin** = kickout
				/admin/* = kickout
				/admin/*/*=kickout
				/admin** = authcA,anyRoles[ROLE_MGMT_USER]
				/druid/** = authcA,anyRoles[ROLE_SUPER_USER]
				
				

				/app/login = authcAppForm
				/app/logout = logoutApp
				/app/** = authcAppSecret,authcBearerToken

				/api/** = authcAppSecret
				
				/appdvlp/** = anon
				
				
				
				
			</value>
		</property>
	</bean>
</beans>

这个拦截URL配置是有规则的

过滤器角色,可以自定义过滤器key:

  • anon     不需要认证
  • authc     需要认证
  • user     验证通过或RememberMe登录的都可以

URL匹配说明:

  • /admin?=authc      表示可以请求以admin开头的字符串,如xxx/adminfefe,但无法匹配多个,即xxx/admindf/admin是不行的
  • /admin*=authc      表示可以匹配零个或者多个字符,如/admin,/admin1,/admin123,但是不能匹配/admin/abc这种
  • /admin/**=authc      表示可以匹配零个或者多个路径,如/admin,/admin/ad/adfdf等

注意:

/login=anon  不会对http://localhost:8080/login/起效果

/login=anon 和  /login=anon/  不一样

相同url但定义在不同的行,后面覆盖前面

/usr/login.do=test3
/usr/login.do=test1,test2
不会执行test3的filter
同一个url可以匹配不同的规则,但只执行首行,请求url过来的时候,先匹配到哪个过滤器,就执行哪个过滤器,后面的就不会执行了。
/usr/* =test1,test2
/usr/login.do=test3
url = /usr/login.do请求来了,不会执行test3,因为已经匹配了/usr/* =test1,test2
要解答该问题,需要知道每个url的FilterChain是如何获取的,每个url都有自己的filterchain。

本例中

                /admin/login = authcA
                /admin/logout = logoutAdmin
                /admin** = kickout
                /admin/* = kickout
                /admin/*/*=kickout
                /admin** = authcA,anyRoles[ROLE_MGMT_USER]
                /druid/** = authcA,anyRoles[ROLE_SUPER_USER]

这样配置下来,可以拦截/admin下的所有的url请求,所有请求都会经过kickout定义的过滤器,这里就可以实现,判断session是否过期或当前请求的sessionid是否存在了,然后做其他处理。

发布了23 篇原创文章 · 获赞 0 · 访问量 2940

猜你喜欢

转载自blog.csdn.net/kris_lh123/article/details/102699921