基於Spirng的Shiro安全框架與CAS SSO的整合
阿新 • • 發佈:2019-01-01
首先需要在maven的pom檔案中新增依賴
< dependency>
<groupId >org.apache.shiro </groupId >
<artifactId > shiro-cas </artifactId >
<version >${shiro.version} </version >
<exclusions >
<exclusion >
<artifactId > servlet-api </artifactId >
<groupId >javax.servlet </groupId >
</exclusion >
<exclusion >
<artifactId >commons-logging </artifactId >
<groupId >commons-logging </groupId >
</exclusion >
</exclusions >
</dependency >
<dependency >
<groupId >org.apache.shiro </groupId >
<artifactId > shiro-spring</ artifactId>
<version >${shiro.version} </version >
</dependency >
<dependency >
<groupId >org.apache.shiro </groupId >
<artifactId > shiro-ehcache </artifactId >
<version >${shiro.version} </version >
</dependency >
在web.xml中新增相應的filter和listener
<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.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>
<filter-name>singleSignOutFilter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter-mapping>
<filter-name>singleSignOutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
先來看一下shiro的property檔案
shiro.session.timeout=1800000
shiro.session.validate.timespan=1800000
shiro.login.url=http://login.51idc.cn/user/login?service=http://localhost:8086/tickets/shiro-cas
shiro.logout.url=http://login.51idc.cn/user/logout?service=http://localhost:8086/tickets/ticket/create
shiro.login.success.url=http://localhost:8086/home
shiro.casServer.url=http://login.51idc.cn/user
shiro.client.cas=http://localhost:8086/tickets/shiro-cas
shiro.failureUrl=/error.jsp
下面進行applicationContext-shiro.xml的配置,記得在sping的主配置檔案中引用此配置
<!-- Shiro Filter
anon:匿名過濾器,不用登入也可以訪問
authc:如果繼續操作,需要做對應的表單驗證否則不能通過
authcBasic:基本http驗證過濾,如果不通過,跳轉登入頁面
logout:登入退出過濾器
noSessionCreation:沒有session建立過濾器
perms:許可權過濾器
port:埠過濾器,可以設定是否是指定埠如果不是跳轉到登入頁面
rest:http方法過濾器,可以指定如post不能進行訪問等
roles: 角色過濾器,判斷當前使用者是否指定角色
ssl:請求需要通過ssl,如果不是跳轉回登入頁
user:如果訪問一個已知使用者,比如記住我功能,走這個過濾器
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!--沒有單點登入下的配置:沒有許可權或者失敗後跳轉的頁面 -->
<!-- <property name="loginUrl" value="/login/toLoginAction"/> -->
<!--有單點登入的配置:登入 CAS 服務端地址,引數 service 為服務端的返回地址 -->
<property name="loginUrl" value="${shiro.login.url}" />
<property name="successUrl" value="${shiro.login.success.url}" />
<property name="filters">
<map>
<entry key="casFilter" value-ref="casFilter" />
<entry key="anon" value-ref="anonymousFilter" />
<entry key="logout" value-ref="logoutFilter" />
<entry key="loginSuccessFilter" value-ref="loginSuccessFilter" />
</map>
</property>
<property name="filterChainDefinitions">
<value>
/shiro-cas = casFilter
/**/*.js = anon
/**/*.css = anon
/static/** = anon
/api/** = anon
/logout = logout
/admin/** = loginSuccessFilter,roles[admin]
/** =loginSuccessFilter,roles[employee]
</value>
</property>
</bean>
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<!--配置驗證錯誤時的失敗頁面(Ticket 校驗不通過時展示的錯誤頁面) -->
<property name="failureUrl" value="${shiro.failureUrl}" />
</bean>
<bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<property name="redirectUrl" value="${shiro.logout.url}" />
</bean>
<bean id="anonymousFilter" class="org.apache.shiro.web.filter.authc.AnonymousFilter" />
<!-- 自定義的filter-->
<bean id="loginSuccessFilter" class="com.anchnet.tickets.web.filters.LoginSuccessFilter" />
<!-- Shiro's main business-tier object for web-enabled applications -->
<property name="sessionManager" ref="defaultWebSessionManager"/>
<property name="realms">
<list>
<ref bean="casRealm" />
</list>
</property>
<property name="subjectFactory" ref="casSubjectFactory" />
<property name="cacheManager" ref="shiroEhcacheManager" />
</bean>
<bean id="casRealm" class="com.anchnet.tickets.service.account.CustomCASRealm">
<property name="defaultRoles" value="ROLE_USER" />
<property name="casServerUrlPrefix" value="${shiro.casServer.url}" />
<!--客戶端的回撥地址設定,必須和上面的shiro-cas過濾器攔截的地址一致 -->
<property name="casService" value="${shiro.client.cas}" />
</bean>
<!--Define the realm you want to use to connect to your back-end security
datasource: -->
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />
<!-- default web session manager -->
<bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="${shiro.session.timeout}"/>
<property name="sessionIdCookie" ref="simpleCookie"/>
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="deleteInvalidSessions" value="true"/>
</bean>
<bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg index="0" value="JSESSIONID_COOKIE"/>
<property name="httpOnly" value="true"/>
</bean>
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
<property name="sessionManager" ref="defaultWebSessionManager"/>
<property name="interval" value="${shiro.session.validate.timespan}"/>
</bean>
<!-- 使用者授權資訊Cache, 採用EhCache -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:cache/ehcache-shiro.xml" />
</bean>
<!-- 保證實現了Shiro內部lifecycle函式的bean執行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!--AOP式方法級許可權檢查 -->
<!--Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!--the lifecycleBeanProcessor has run: -->
<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>
上面的使用者授權的cache採用ehcache,以下是ehcache-shiro.xml的配置
<ehcache updateCheck="false" name="shiroCache">
<!-- http://ehcache.org/ehcache.xml -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
下面詳細介紹 CasRealm bean的實現
import java.io.Serializable;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
public class CustomCASRealm extends CasRealm {
private static Logger logger = LoggerFactory
.getLogger(CustomCASRealm.class);
// @Autowired
protected AccountService accountService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String loginName = (String) principals.getPrimaryPrincipal();
if (loginName == null) {
throw new UnauthorizedException("無效的憑證");
}
try {
Map rolesMaps = (Map) principals.asList().get(1);
String roles = (String) rolesMaps.get("role");
if (roles.length() > 0) {
roles = roles.substring(1);
roles = roles.substring(0, roles.length() - 1);
logger.debug("!!!!! get roles:{}", roles);
authorizationInfo.addRoles(ImmutableList.copyOf(StringUtils.split(roles, ", ")));
} else {
logger.debug("user {} has no roles!!!", loginName);
}
} catch (Exception e) {
e.printStackTrace();
throw new UnauthorizedException("無效的憑證");
}
return authorizationInfo;
}