若是不熟悉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的編碼有如下方式