文档章节

Spring Shiro CAS 客户端集成配置

zbbmaster
 zbbmaster
发布于 2017/05/12 18:28
字数 1476
阅读 102
收藏 2

如果不熟悉Shiro 和CAS的概念,可以在网上搜索一下这方面的资料,

在配置CAS客户端配置之前,首先要进行CAS服务端配置  

配置之前需要引入一些jar包具体如下:

        <dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-aspectj</artifactId>
			<version>1.2.0</version>
		</dependency>
		
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-cas</artifactId>
			<version>1.2.0</version>
		</dependency>

(一)cas登录

web.xml配置

 <filter>
      <filter-name>shiroFilter</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      <init-param>
          <param-name>targetFilterLifecycle</param-name>
          <param-value>true</param-value>
      </init-param>
  </filter>
  <filter-mapping>
      <filter-name>shiroFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
      <dispatcher>FORWARD</dispatcher>
      <dispatcher>INCLUDE</dispatcher>
  </filter-mapping>

shiro-cas.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.xsd  
         http://www.springframework.org/schema/context   
         http://www.springframework.org/schema/context/spring-context-3.0.xsd  
         http://www.springframework.org/schema/util  
         http://www.springframework.org/schema/util/spring-util-3.0.xsd"
	default-lazy-init="true">

	<description>Shiro Security Config</description>

	<!-- Shiro Filter -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!-- 设定角色的登录链接,这里为cas登录页面的链接可配置回调地址 -->
		<property name="loginUrl" value="http://localhost:8089/login?service=http://localhost:8080/sso" />
		<property name="filters">
			<util:map>
				<!-- 添加casFilter到shiroFilter -->
				<entry key="casFilter" value-ref="casFilter" />
			</util:map>
		</property>
		<property name="filterChainDefinitions">
			<value>
				/sso = casFilter
				/center/** = authc
			</value>
		</property>
	</bean>
	<bean id="casFilter" class="com.shiro.cas.MyCasFilter">
		<!-- 配置验证错误时的失败页面 -->
		<property name="failureUrl" value="http://localhost:8080/login" />
	</bean>

	<bean id="casRealm" class="com.shiro.MyCasRealm">
		<property name="dataSource" ref="dataSource"></property>
		<property name="defaultRoles" value="MEMBER" />
		<property name="casServerUrlPrefix" value="http://localhost:8089/login" />

		<!-- 客户端的回调地址设置,必须和下面的shiro-cas过滤器拦截的地址一致 -->
		<property name="casService" value="http://localhost:8080/sso" />
	</bean>


    <!-- 如果要实现cas的remember me的功能,需要用到下面这个bean,并设置securityManager的subjectFactory中 -->
	<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />

	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="casRealm" />
		<property name="subjectFactory" ref="casSubjectFactory" />
		
	</bean>
	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

	<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
		<property name="staticMethod"
				  value="org.apache.shiro.SecurityUtils.setSecurityManager" />
		<property name="arguments" ref="securityManager" />
	</bean>
</beans>  

说明一下上面的配置,上面的配置中采用自定义casFilter : MyCasFilter ,  也可以采用默认的CasFilter,自定义的MyCasFilter 可以在登录成功的时候增加一些自己的业务,比如将用户名写入cookie中,保存登录IP,保存登录日志等等业务,默认CasFilter配置如下:

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

MyCasFilter 的核心代码:

public class MyCasFilter extends CasFilter {
    
    private static Logger logger = LoggerFactory.getLogger(KsdCasFilter.class);

    @Override
    protected boolean executeLogin(ServletRequest request,
    		ServletResponse response) throws Exception {
    	return super.executeLogin(request, response);
    }
    
   
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return super.onAccessDenied(request, response);
    }
    
    
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
                                     ServletResponse response) throws Exception {
    	
        Member member = (Member) subject.getPrincipal();
        SecurityUtils.getSubject().getSession().setAttribute("member", member);
        
        String domain = getRootDomain(request);
        String nickname = URLEncoder.encode(member.getNickname(), "utf-8");
        CookieUtils.setCookie((HttpServletResponse)response, "nickname",nickname, -1, domain, "/");
        
         
        WebUtils.redirectToSavedRequest(request, response,getSuccessUrl());
        return false;
    }
    
    public static String getRootDomain(ServletRequest request) {
    	String domain = request.getServerName();
		if(domain.equals("127.0.0.1") || domain.equalsIgnoreCase("localhost")) {
			domain = "gongxi.net";
		} else {
			String [] hostArr = domain.split("\\.");
			int length = hostArr.length;
			domain = hostArr.length >= 2 ? (hostArr[length - 2] + "." + hostArr[length - 1]) : domain;
		}
		return domain;
    }
    
    
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
                                     ServletResponse response) {
    	logger.info("redirecting user to the CAS error page");
        return super.onLoginFailure(token, ae, request, response);
    }
    
}

实现自定义的Cas Realm :MyCasRealm 

public class MyCasRealm extends CasRealm {
	

	protected DataSource dataSource;
	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	private static final Logger log = LoggerFactory.getLogger(MyCasRealm.class);


	protected String userRolesQuery = "SELECT r.role_name FROM sys_user u,ka_sys_user_role ur,ka_sys_role r WHERE u.user_id=ur.user_id AND ur.role_id=r.role_id AND u.login_name=?";

	protected String permissionsQuery = "SELECT * FROM sys_menu m,sys_role_menu rm,sys_role r WHERE m.menu_id=rm.menu_id AND rm.role_id=r.role_id AND r.role_name=?";

	/* 
	 * 身份认证
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		// super.doGetAuthenticationInfo(token);
		CasToken casToken = (CasToken) token;
		if (token == null) {
			return null;
		}
		String ticket = (String) casToken.getCredentials();
		if (!StringUtils.hasText(ticket)) {
			return null;
		}
		
		TicketValidator ticketValidator = ensureTicketValidator();
		Assertion casAssertion = null;
		try {
			casAssertion = ticketValidator.validate(ticket, getCasService());
		} catch (TicketValidationException e1) {
			throw new AuthenticationException(e1);
		}
		
		AttributePrincipal casPrincipal = casAssertion.getPrincipal();
		String username = casPrincipal.getName();
		// Null username is invalid
		if (!StringUtils.hasText(username)) {
			throw new InvalidAccountException("Null usernames are not allowed by this realm.");
		}
		AuthenticationInfo info = null;
		try {
			
			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);
			}

			info = new SimpleAuthenticationInfo(username, ticket,username);
		} catch (Exception e) {
			final String message = "There was an error while authenticating user [" + username + "]";
			if (log.isErrorEnabled()) {
				log.error(message, e);
			}
			// Rethrow any SQL errors as an authentication exception
			throw new AuthenticationException(message, e);
		}
		return info;
	}

	/*
	 * 权限查询
	 */
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		// add default roles
		//addRoles(simpleAuthorizationInfo, split(getDefaultRoles()));
		addRoles(simpleAuthorizationInfo,split(getDefaultRoles()));
		// add default permissions
		addPermissions(simpleAuthorizationInfo, split(getDefaultPermissions()));
		
		return simpleAuthorizationInfo;
	}
	

	private void addPermissions(
			SimpleAuthorizationInfo simpleAuthorizationInfo,
			List<String> permissions) {
		for (String permission : permissions) {
			simpleAuthorizationInfo.addStringPermission(permission);
		}
	}

	private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo,
			List<String> roles) {
		for (String role : roles) {
			simpleAuthorizationInfo.addRole(role);
		}
	}

	private List<String> split(String s) {
		List<String> list = new ArrayList<String>();
		String[] elements = StringUtils.split(s, ',');
		if (elements != null && elements.length > 0) {
			for (String element : elements) {
				if (StringUtils.hasText(element)) {
					list.add(element.trim());
				}
			}
		}
		return list;
	}
	
}

至此基本的配置就完成了。

(二)cas登出

       用户发出登出请求后,cas客户端(或者后台action)会给cas服务器会发出登出请求,使ticke过期;在登出的时候同时需要使HttpSession失效

1 web.xml配置

web.xml中需要加入cas的SingleSignOutFilter实现单点登出功能,该过滤器需要放在shiroFilter之前,spring字符集过滤器之后。在实际使用时发现,SingleSignOutFilter如果放在了spring字符集过滤器之前,数据在传输过程中就会出现乱码。

<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置。-->
<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 该过滤器用于实现单点登出功能,可选配置。 -->
<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>

2 登出请求处理

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        SecurityUtils.getSubject().logout();
        
        String logoutUrl = "http://localhost:8089/logout";
        String service = request.getParameter("service");
        if(StringUtils.isNotBlank(service)) {
            logoutUrl += "?service=".concat(service);
        }
        
        return "redirect:" + logoutUrl;
    }

 

 

其他问题:

1 中文昵称登录时频繁重定向,需要设置jvm的编码

    设置JVM的编码有以下方式

  1. 在系统的环境变量中添加一个变量,名为: JAVA_TOOL_OPTIONS, 值为:-Dfile.encoding=UTF-8
  2. 在运行java程序的时候指定参数java -Dfile.encoding=UTF-8

© 著作权归作者所有

共有 人打赏支持
zbbmaster
粉丝 55
博文 67
码字总数 38770
作品 0
西安
高级程序员
私信 提问
在 Web 项目中应用 Apache Shiro

用户权限模型 在揭开 Shiro 面纱之前,我们需要认知用户权限模型。本文所提到用户权限模型,指的是用来表达用户信息及用户权限信息的数据模型。即能证明“你是谁?”、“你能访问多少受保护资...

BenettX
2013/08/28
0
0
在 Web 项目中应用 Apache Shiro

用户权限模型 在揭开 Shiro 面纱之前,我们需要认知用户权限模型。本文所提到用户权限模型,指的是用来表达用户信息及用户权限信息的数据模型。即能证明“你是谁?”、“你能访问多少受保护资...

heroShane
2014/02/09
0
3
Spring Boot 集成Shiro和CAS

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/catoop/article/details/50534006 请大家在看本文之前,先了解如下知识点: 1、Shiro 是什么?怎么用? 2、C...

单红宇
2016/01/17
0
0
在 Web 项目中应用 Apache Shiro 开源权限框架

Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能。认证和授权为权限控制的核心,简单来说,“认证”就是证明你是谁? Web 应用程序一般做法...

IBMdW
2013/02/05
19.1K
38
Apache Shiro 开源权限框架

用户权限模型 在揭开 Shiro 面纱之前,我们需要认知用户权限模型。本文所提到用户权限模型,指的是用来表达用户信息及用户权限信息的数据模型。即能证明“你是谁?”、“你能访问多少 受保护...

村长大神
2016/10/20
37
0

没有更多内容

加载失败,请刷新页面

加载更多

大数据教程(6.1)hadoop生态圈介绍及就业前景

1. HADOOP背景介绍 1.1、什么是HADOOP 1.HADOOP是apache旗下的一套开源软件平台 2.HADOOP提供的功能:利用服务器集群,根据用户的自定义业务逻辑,对海量数据进行分布式处理 3.HADOOP的核心组...

em_aaron
6分钟前
0
0
hadoop垃圾回收站

在生产生,hdfs回收站必须是开启的,一般设置为7天。 fs.trash.interval 为垃圾回收站保留时间,如果为0则禁用回收站功能。 fs.trash.checkpoint.interval 回收站检查点时间,一般设置为小于...

hnairdb
昨天
0
0
腾讯与Github的魔幻会面背后的故事…

10月22日,腾讯开源管理办公室有幸邀请到Github新晋CEO Nat Friedman,前来鹅厂参观交流。目前腾讯已经有近70个项目在Github上开源,共获得17w stars,世界排名11位。Github是腾讯开源的主阵...

腾讯开源
昨天
1
0
单例模式

单例模式(Singleton pattern)属于创建型设计模式。 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对...

NinjaFrog
昨天
1
0
TypeScript基础入门之装饰器(三)

转载 TypeScript基础入门之装饰器(三) 继续上篇文章[TypeScript基础入门之装饰器(二)] 访问器装饰器 Accessor Decorator在访问器声明之前声明。 访问器装饰器应用于访问器的属性描述符,可用...

durban
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部