jeesite配置shiro与CAS认证

公司最近搞了个单点登录的项目,公司用的是jeesite框架,里面自带集成了shiro 自己花了蛮多时间弄这个东西,总算是好了。分享出来希望大家碰到这个坑的时候可以少花点时间。

一、jesstie.properties中添加如下配置

#单点登录CAS设置
shiro.cas.service=http://127.0.0.1:8080/ems_cas/a/cas
shiro.cas.serverUrlPrefix=http\://www.cas.com
shiro.loginUrl=${shiro.cas.serverUrlPrefix}/login?service=${shiro.cas.service}
shiro.logoutUrl=${shiro.cas.serverUrlPrefix}/logout?service=${shiro.cas.service}

二、修改spring-context-shiro.xml

1、shiroFilter里面,修改loginUrl为配置中的登录地址。然后在filters里面添加

<entry key="cas" value-ref="casFilter"/>

2、casFilter中修改登录失败页面地址(我这里默认为登录页面)

<property name="failureUrl" value="${adminPath}/login"/>

3、logoutFilter修改登出页面地址(我这里默认为登录页面)

<property name="redirectUrl" value="${shiro.logoutUrl}"/>

4、securityManager中修改realm为casRealm

<!--应用登陆realm-->
        <!--<property name="realm" ref="systemAuthorizingRealm"/>-->
        <!--cas登陆realm-->
        <property name="realm" ref="casRealm"/>

5、配置casRealm

<bean id="casRealm" class="com.court.modules.sys.security.CasAuthorizingRealm">
        <!--该地址为cas server地址 -->
        <property name="casServerUrlPrefix" value="${shiro.cas.serverUrlPrefix}"/>
        <!-- 该地址为是当前应用 CAS 服务 URL,即用于接收并处理登录成功后的 Ticket 的, 必须和loginUrl中的service参数保持一致,否则服务器会判断service不匹配 -->
        <property name="casService" value="${shiro.cas.service}"/>
    </bean>

完整配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/context  
		http://www.springframework.org/schema/context/spring-context-4.0.xsd"
       default-lazy-init="true">

    <description>Shiro Configuration</description>

    <!-- 加载配置属性文件 -->
    <context:property-placeholder ignore-unresolvable="true" location="classpath:courts.properties"/>

    <!-- Shiro权限过滤过滤器定义 -->
    <bean name="shiroFilterChainDefinitions" class="java.lang.String">
        <constructor-arg>
            <value>
                /static/** = anon
                /userfiles/** = anon
                ${adminPath}/cas = cas
                ${adminPath}/login = authc
                ${adminPath}/logout = logout
                ${adminPath}/** = user
            </value>
        </constructor-arg>
    </bean>
    <!--##需要修改部分 BEGIN #######################################################################-->
    <!-- 安全认证过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--cas服务登陆-->
        <!-- 设定用户的登录链接,这里为cas登录页面的链接地址可配置回调地址 -->
        <property name="loginUrl" value="${shiro.loginUrl}"/>
        <!--本应用登陆地址-->
        <!--<property name="loginUrl" value="${adminPath}/login" />-->
        <property name="successUrl" value="${adminPath}"/>
        <property name="filters">
            <!-- 自定义filters -->
            <map>
                <entry key="cas" value-ref="casFilter"/>
                <entry key="logout" value-ref="logoutFilter"/>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </map>
        </property>
        <property name="filterChainDefinitions">
            <ref bean="shiroFilterChainDefinitions"/>
        </property>
    </bean>

    <!-- CAS认证过滤器 -->
    <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
        <!-- 配置验证错误时的失败页面 -->
        <property name="failureUrl" value="${adminPath}/login"/>

    </bean>

    <!-- 登出监听 -->
    <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <!--cas登陆服务跳转urL-->
        <property name="redirectUrl" value="${shiro.logoutUrl}"/>
        <!--本应用登陆url-->
        <!--<property name="redirectUrl" value="${adminPath}/login"></property>-->
    </bean>

    <!-- 定义Shiro安全管理配置 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--本应用登陆realm-->
        <!--<property name="realm" ref="systemAuthorizingRealm"/>-->
        <!--cas登陆realm-->
        <property name="realm" ref="casRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="cacheManager" ref="shiroCacheManager"/>
    </bean>

    <bean id="casRealm" class="com.court.modules.sys.security.CasAuthorizingRealm">
        <!--该地址为cas server地址 -->
        <property name="casServerUrlPrefix" value="${shiro.cas.serverUrlPrefix}"/>
        <!-- 该地址为是当前应用 CAS 服务 URL,即用于接收并处理登录成功后的 Ticket 的, 必须和loginUrl中的service参数保持一致,否则服务器会判断service不匹配 -->
        <property name="casService" value="${shiro.cas.service}"/>
    </bean>
    <!--##需要修改部分 END #######################################################################-->

    <!-- 自定义会话管理配置 -->
    <bean id="sessionManager" class="com.court.common.security.shiro.session.SessionManager">
        <property name="sessionDAO" ref="sessionDAO"/>
        <!-- 会话超时时间,单位:毫秒 -->
        <property name="globalSessionTimeout" value="${session.sessionTimeout}"/>
        <!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话 -->
        <property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>
        <!--扫描session线程,负责清理超时会话 -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
        <property name="sessionIdCookieEnabled" value="true"/>
    </bean>

    <!-- 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID,
        当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失! -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg name="name" value="sid"/>
    </bean>

    <!-- 自定义Session存储容器 -->
    <!-- <bean id="sessionDAO" class="com.court.common.security.shiro.session.JedisSessionDAO"> -->
    <!-- <property name="sessionIdGenerator" ref="idGen" /> -->
    <!-- <property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_"/> -->
    <!-- </bean> -->
    <bean id="sessionDAO" class="com.court.common.security.shiro.session.CacheSessionDAO">
        <property name="sessionIdGenerator" ref="idGen"/>
        <property name="activeSessionsCacheName" value="activeSessionsCache"/>
        <property name="cacheManager" ref="shiroCacheManager"/>
    </bean>

    <!-- 定义授权缓存管理器 -->
    <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

    <!-- 缓存配置 -->
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:${ehcache.configFile}"/>
    </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>

CasAuthorizingRealm.java

package com.jeesite.thinkgem.modules.sys.security;


import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.CommonUtils;
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.stereotype.Service;

import com.court.common.config.Global;
import com.court.common.utils.SpringContextHolder;
import com.court.common.web.Servlets;
import com.court.modules.sys.entity.Menu;
import com.court.modules.sys.entity.Role;
import com.court.modules.sys.entity.User;
import com.court.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.court.modules.sys.service.SystemService;
import com.court.modules.sys.utils.LogUtils;
import com.court.modules.sys.utils.UserUtils;

/**
 * 系统安全认证实现类
 * @author ThinkGem
 * @version 2014-7-5
 */
@Service
//@DependsOn({"userDao","roleDao","menuDao"})
public class CasAuthorizingRealm  extends CasRealm {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	private static final SingleSignOutHandler singleSignOutHandler = new SingleSignOutHandler();
	
	private SystemService systemService;
	
	

	/**
	 * 认证回调函数, 登录时调用
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)  throws AuthenticationException {
		HttpServletRequest request = Servlets.getRequest();
		System.out.println(request.getRemoteUser());
		

		// 单点登录登出句柄(登出时注销session)
		if (singleSignOutHandler.isLogoutRequest(request)) {
			String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest");
			singleSignOutHandler.destroySession(request, logoutRequest);
			return null;
		}
		CasToken casToken = (CasToken) token;
        if (token == null) {
            return null;
        }
        
        String ticket = (String)casToken.getCredentials();
        if (!org.apache.shiro.util.StringUtils.hasText(ticket)) {
            return null;
        }
        
        TicketValidator ticketValidator = ensureTicketValidator();

        try {
            // contact CAS server to validate service ticket
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            // get principal, user id and attributes
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            String userId = casPrincipal.getName();
//	            log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
//	                    ticket, getCasServerUrlPrefix(), userId
//	            });
            Map<String, Object> attributes = casPrincipal.getAttributes();
            // refresh authentication token (user id + remember me)
            casToken.setUserId(userId);
            String rememberMeAttributeName = getRememberMeAttributeName();
            String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
            
            boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
            if (isRemembered) {
                casToken.setRememberMe(true);
            }
            // create simple authentication info
           // List<Object> principals = CollectionUtils.asList(userId, attributes);
            User user = getSystemService().getUserByLoginName(userId);
            
//            PrincipalCollection principalCollection = new SimplePrincipalCollection(new Principal(user, ticket), getName());
//            return new SimpleAuthenticationInfo(principalCollection, ticket);
            return new SimpleAuthenticationInfo(new Principal(user, ticket), ticket, getName());
//	            byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
//				return new SimpleAuthenticationInfo(new Principal(user, false), 
//						ticket, ByteSource.Util.bytes(salt), getName());
        } catch (TicketValidationException e) { 
            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
        }
		
	}

	/**
	 * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		Principal principal = (Principal) getAvailablePrincipal(principals);

		HttpServletRequest request = Servlets.getRequest();

		// 单点登录登出句柄(登录时注入session)
		String ticket = principal.getTicket();
		singleSignOutHandler.recordSession(request, ticket);
		// 获取当前已登录的用户
		if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){
			Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession());
			if (sessions.size() > 0){
				// 如果是登录进来的,则踢出已在线用户
				if (UserUtils.getSubject().isAuthenticated()){
					for (Session session : sessions){
						getSystemService().getSessionDao().delete(session);
					}
				}
				// 记住我进来的,并且当前用户已登录,则退出当前用户提示信息。
				else{
					UserUtils.getSubject().logout();
					throw new AuthenticationException("msg:账号已在其它地方登录,请重新登录。");
				}
			}
		}
		User user = getSystemService().getUserByLoginName(principal.getLoginName());
		if (user != null) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			List<Menu> list = UserUtils.getMenuList();
			for (Menu menu : list){
				if (StringUtils.isNotBlank(menu.getPermission())){
					// 添加基于Permission的权限信息
					for (String permission : StringUtils.split(menu.getPermission(),",")){
						info.addStringPermission(permission);
					}
				}
			}
			// 添加用户权限
			info.addStringPermission("user");
			// 添加用户角色信息
			for (Role role : user.getRoleList()){
				info.addRole(role.getEnname());
			}
			// 更新登录IP和时间
			getSystemService().updateUserLoginInfo(user);
			// 记录登录日志
			LogUtils.saveLog(Servlets.getRequest(), "系统登录");
			return info;
		} else {
			return null;
		}
	}
	
	/**
	 * 获取系统业务对象
	 */
	public SystemService getSystemService() {
		if (systemService == null){
			systemService = SpringContextHolder.getBean(SystemService.class);
		}
		return systemService;
	}
	
	public static SingleSignOutHandler getSingleSignOutHandler() {
        return singleSignOutHandler;
    }
    
    
    /**
	 * 授权用户信息
	 */
	public static class Principal implements Serializable {

		private static final long serialVersionUID = 1L;
		
		private String id; // 编号
		private String loginName; // 登录名
		private String name; // 姓名
		private boolean mobileLogin; // 是否手机登录
		private String ticket;
		
//		private Map<String, Object> cacheMap;

		public Principal(User user, boolean mobileLogin) {
			this.id = user.getId();
			this.loginName = user.getLoginName();
			this.name = user.getName();
			this.mobileLogin = mobileLogin;
		}
		
		public Principal(User user, String ticket) {
			this.id = user.getId();
			this.loginName = user.getLoginName();
			this.name = user.getName();
			this.mobileLogin = mobileLogin;
			this.ticket = ticket;
		}

		public String getId() {
			return id;
		}

		public String getLoginName() {
			return loginName;
		}

		public String getName() {
			return name;
		}

		public boolean isMobileLogin() {
			return mobileLogin;
		}
		
		

//		@JsonIgnore
//		public Map<String, Object> getCacheMap() {
//			if (cacheMap==null){
//				cacheMap = new HashMap<String, Object>();
//			}
//			return cacheMap;
//		}

		public String getTicket() {
			return ticket;
		}

		public void setTicket(String ticket) {
			this.ticket = ticket;
		}

		/**
		 * 获取SESSIONID
		 */
		public String getSessionid() {
			try{
				return (String) UserUtils.getSession().getId();
			}catch (Exception e) {
				return "";
			}
		}
		
		@Override
		public String toString() {
			return id;
		}

	}
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值