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