Spring Shiro CAS 客戶端集成配置

若是不熟悉Shiro 和CAS的概念,能夠在網上搜索一下這方面的資料,java

在配置CAS客戶端配置以前,首先要進行CAS服務端配置  web

配置以前須要引入一些jar包具體以下:spring

<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登陸apache

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核心配置cookie

<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配置以下:session

<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
        <!-- 配置驗證錯誤時的失敗頁面  -->
        <property name="failureUrl" value="${cas.client}"/>
    </bean>

MyCasFilter 的核心代碼:app

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 dom

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;
	}
	
}

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

(二)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
相關文章
相關標籤/搜索