jeesite整合cas認證
阿新 • • 發佈:2019-01-31
cas和shiro整合,很好的解決了登入及許可權問題。本人最近第一次使用,框架使用的是jeesite開源框架,本身已經集成了shiro,現在將cas整合到專案中。
折騰了三天,終於把cas整合到jeesite中。現將整合過程寫下,供朋友參考。
本專案整合cas的同時還留有登入入口,此時需要多種認證方式,步驟6、7的設定就是針對這個功能的,如不需要可直接跳過。
不做技術好多年了,專案時間緊只能親自上陣,寫的不周全的請多包涵。有問題望指教。
1、新增cas的maven依賴。
<!-- CAS --> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.2.1</version> </dependency>
2、web.xml新增內容。
<!-- 該過濾器對HttpServletRequest請求包裝, 可通過HttpServletRequest的getRemoteUser()方法獲得登入使用者的登入名,可選 -->
<filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class> org.jasig.cas.client.util.HttpServletRequestWrapperFilter </filter-class> </filter> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 該過濾器使得可以通過org.jasig.cas.client.util.AssertionHolder來獲取使用者的登入名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 這個類把Assertion資訊放在ThreadLocal變數中,這樣應用程式不在web層也能夠獲取到當前登入資訊 --> <filter> <filter-name>CAS Assertion Thread Local Filter</filter-name> <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3、配置spring-content-shiro.xml。
整體xml內容如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd" default-lazy-init="true"> <description>Shiro Configuration</description> <!-- 載入配置屬性檔案 --> <context:property-placeholder ignore-unresolvable="true" location="classpath:project.properties" /> <!-- Shiro許可權過濾過濾器定義 --> <bean name="shiroFilterChainDefinitions" class="java.lang.String"> <constructor-arg> <value> /static/** = anon /userfiles/** = anon ${adminPath}/login = authc ${adminPath}/logout = logout ${adminPath}/cas = cas ${adminPath}/** = user </value> </constructor-arg> </bean> <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <property name="redirectUrl" value="${cas.logout.url}"/> </bean> <!-- 安全認證過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="${cas.server.url}/login?service=${cas.project.url}${adminPath}/cas" /> <!--<property name="loginUrl" value="${adminPath}/login" /> --> <property name="successUrl" value="${adminPath}" /> <property name="filters"> <map> <entry key="cas" value-ref="casFilter"/> <entry key="authc" value-ref="formAuthenticationFilter"/> <entry key="logout" value-ref="logout" /> </map> </property> <property name="filterChainDefinitions"> <ref bean="shiroFilterChainDefinitions"/> </property> </bean> <!-- cas和shiro結合 --> <bean id="casRealm" class="com.sinosoft.modules.sys.security.CasLoginRealm"> <property name="casServerUrlPrefix" value="${cas.server.url}"></property> <property name="casService" value="${cas.project.url}${adminPath}/cas"></property> </bean> <!-- CAS認證過濾器 --> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <property name="failureUrl" value="${adminPath}/login"/> </bean> <!-- 定義Shiro安全管理配置 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realms"> <list> <ref bean="casRealm"/> <ref bean="systemAuthorizingRealm"/> </list> </property> <property name="sessionManager" ref="sessionManager" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean> <!-- 配置使用自定義認證器,可以實現多Realm認證,並且可以指定特定Realm處理特定型別的驗證 --> <bean id="authenticator" class="com.sinosoft.modules.sys.security.CustomizedModularRealmAuthenticator"> <!-- 配置認證策略,只要有一個Realm認證成功即可,並且返回所有認證成功資訊 --> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean> </property> </bean> <!-- 自定義會話管理配置 --> <bean id="sessionManager" class="com.sinosoft.common.security.shiro.session.SessionManager"> <property name="sessionDAO" ref="sessionDAO"/> <!-- 會話超時時間,單位:毫秒 --> <property name="globalSessionTimeout" value="${session.sessionTimeout}"/> <!-- 定時清理失效會話, 清理使用者直接關閉瀏覽器造成的孤立會話 --> <property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/> <!-- <property name="sessionValidationSchedulerEnabled" value="false"/> --> <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="jeesite.session.id"/> </bean> <!-- 自定義Session儲存容器 --> <!-- <bean id="sessionDAO" class="com.sinosoft.common.security.shiro.session.JedisSessionDAO"> --> <!-- <property name="sessionIdGenerator" ref="idGen" /> --> <!-- <property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_" /> --> <!-- </bean> --> <bean id="sessionDAO" class="com.sinosoft.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="com.sinosoft.common.security.shiro.cache.JedisCacheManager"> --> <!-- <property name="cacheKeyPrefix" value="${redis.keyPrefix}_cache_" /> --> <!-- </bean> --> <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager"/> </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>
4、project.properties中增加屬性url。
cas.server.url=http://localhost:8080/cas
cas.project.url=http://1ocalhost:8080/infoService
cas.logout.url=http://localhost:8080/cas/logout?server=http://localhost:8080/infoService
5、建立CasLoginRealm類,繼承CasRealm。
package com.sinosoft.modules.sys.security;
import java.util.Collection;
import java.util.List;
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.apache.shiro.subject.SimplePrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
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 com.sinosoft.common.config.Global;
import com.sinosoft.common.utils.SpringContextHolder;
import com.sinosoft.common.web.Servlets;
import com.sinosoft.modules.sys.entity.Menu;
import com.sinosoft.modules.sys.entity.Role;
import com.sinosoft.modules.sys.entity.User;
import com.sinosoft.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.sinosoft.modules.sys.service.SystemService;
import com.sinosoft.modules.sys.utils.LogUtils;
import com.sinosoft.modules.sys.utils.UserUtils;
/**
* <p>Title: cas認證類</p>
* <p>Description: cas認證操作類</p>
* <p>Copyright: Copyright (c) 2017</p>
* <p>Company: </p>
* <p>Date: 2017-7-5</p>
* @author holyspirit
* @version 1.0
*/
public class CasLoginRealm extends CasRealm {
private Logger logger = LoggerFactory.getLogger(getClass());
private SystemService systemService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// return super.doGetAuthenticationInfo(token);
CasToken casToken = (CasToken) token;
if (token == null) {
return null;
}
// 獲取ticket
String ticket = (String) casToken.getCredentials();
if (!org.apache.shiro.util.StringUtils.hasText(ticket)) {
return null;
}
TicketValidator ticketValidator = ensureTicketValidator();
try {
// 回傳ticket到服務端驗證,驗證通過就進入下一行,可以獲取登入後的相關資訊,否則直接拋異常,即驗證不通過
Assertion casAssertion = ticketValidator.validate(ticket,
getCasService());
AttributePrincipal casPrincipal = casAssertion.getPrincipal();
String userId = casPrincipal.getName();
User user = getSystemService().getUserByLoginName(userId);
if (user != null) {
Principal p = new Principal(user, false);
PrincipalCollection principalCollection = new SimplePrincipalCollection(
p, getName());
return new SimpleAuthenticationInfo(principalCollection, ticket);
} else {
return null;
}
} catch (TicketValidationException e) {
logger.error("票據認證失敗", e);
throw new CasAuthenticationException("Unable to validate ticket ["
+ ticket + "]", e);
}
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
Principal principal = (Principal) getAvailablePrincipal(principals);
// 獲取當前已登入的使用者
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;
}
}
/**
*
* Title:獲取系統業務物件
* Description:獲取系統業務物件
* @param
* @return
* @Author:holyspirit
* @Create Date: 2017-7-6
* @Modifier:
* @Modify Date:
*/
public SystemService getSystemService() {
if (systemService == null) {
systemService = SpringContextHolder.getBean(SystemService.class);
}
return systemService;
}
}
6、FormAuthenticationFilter類增加程式碼片段,重寫redirectToLogin方法。
@Override
protected void redirectToLogin(ServletRequest request,
ServletResponse response) throws IOException {
setLoginUrl("/info/login");
WebUtils.issueRedirect(request, response, getLoginUrl());
}
7、新增CustomizedModularRealmAuthenticator類。
package com.sinosoft.modules.sys.security;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import java.util.Collection;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.realm.Realm;
/**
* <p>Title: 自定義Authenticator</p>
* <p>Description:分別定義處理不同登入方式驗證的Realm</p>
* <p>Copyright: Copyright (c) 2017</p>
* <p>Company: </p>
* <p>Date: 2017-7-6</p>
* @author holyspirit
* @version 1.0
*/
public class CustomizedModularRealmAuthenticator extends
ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(
AuthenticationToken authenticationToken)
throws AuthenticationException {
//獲取所有Realm
Collection<Realm> realms = getRealms();
Realm realm = null;
//判斷當前token是castoken還是UsernamePasswordToken
//強轉對應型別token
//根據token型別獲得對應的Realm
if(authenticationToken instanceof CasToken){
authenticationToken = (CasToken)authenticationToken;
realm = getRealm(realms, authenticationToken);
}
if(authenticationToken instanceof UsernamePasswordToken){
authenticationToken = (UsernamePasswordToken)authenticationToken;
realm = getRealm(realms, authenticationToken);
}
//執行響應Realm
return doSingleRealmAuthentication(realm, authenticationToken);
}
//判斷當前token對應的Realm
public Realm getRealm(Collection<Realm> realms, AuthenticationToken token){
for (Realm realm : realms) {
if(realm.supports(token)){
return realm;
}
}
return null;
}
}
8、設定登出事件,退出cas認證同時清除系統session
<a href="${ctx}/logout" title="退出登入">退出</a>
問題集合:
1。中文亂碼問題,所有提交的請求中文變成亂碼,這是過濾器順序所致。cas過濾器和shiro過濾器要放在編碼過濾器後面。