這次我給大家講講如何在shiro中整合cas框架,以及擴(kuò)展自定義的角色和資源體系,啰嗦話不多說了,直接上代碼說明
第一步,搭建cas服務(wù)器,我也不說拉,這個大家用現(xiàn)有的cas服務(wù)就行了
第二步,先加入cas-client的包到我們的項目,然后再下載個shiro-cas.jar也放到項目里
第三步配置shiro中的cas設(shè)置
- <description>shiro配置</description>
-
- <!-- 安全管理器 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <property name="cacheManager" ref="shiroCacheManager" />
- <property name="sessionManager" ref="sessionManager" />
- <property name="realm" ref="casRealm" />
- <property name="subjectFactory" ref="casSubjectFactory" />
- <!-- <property name="realm" ref="simpleUserRealm" /> -->
- </bean>
-
- <!-- 會話管理器 -->
- <bean id="sessionManager"
- class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
- <property name="sessionValidationSchedulerEnabled" value="false" />
- <property name="sessionDAO" ref="sessionDAO" />
- <property name="globalSessionTimeout" value="600000" />
- </bean>
-
- <!-- 緩存管理器 -->
- <bean id="shiroCacheManager"
- class="com.silvery.security.shiro.cache.SimpleShiroCacheManager">
- <property name="cache" ref="shiroCache" />
- </bean>
-
- <!-- 緩存實現(xiàn)類,注入自定義緩存機(jī)制 -->
- <bean id="shiroCache" class="com.silvery.security.shiro.cache.SimpleShiroCache">
- <property name="cacheManager" ref="simpleCacheManager" />
- </bean>
-
- <!-- 會話讀寫實現(xiàn)類 -->
- <bean id="sessionDAO" class="com.silvery.security.shiro.session.CacheSessionDAO" />
-
- <!-- 用戶認(rèn)證實現(xiàn) -->
- <bean id="simpleUserRealm" class="com.silvery.security.shiro.realm.SimpleUserRealm" />
-
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
-
- <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
- <!-- 配置驗證錯誤時的失敗頁面 -->
- <property name="failureUrl"
- value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />
- </bean>
-
- <bean id="casRealm" class="com.silvery.security.shiro.realm.SimpleCasRealm">
- <property name="defaultRoles" value="ROLE_USER" />
- <property name="casServerUrlPrefix" value="https://cas.test.com:8443" />
- <!-- 客戶端的回調(diào)地址設(shè)置,必須和下面的過濾器攔截的地址一致 -->
- <property name="casService" value="http://test.com/mh/cas/login.do" />
- </bean>
-
- <!-- 如果要實現(xiàn)cas的remember me的功能,需要用到下面這個bean,并設(shè)置到securityManager的subjectFactory中 -->
- <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />
-
- <bean
- class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
- <property name="staticMethod"
- value="org.apache.shiro.SecurityUtils.setSecurityManager" />
- <property name="arguments" ref="securityManager" />
- </bean>
-
- <!-- 過濾鏈配置 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <property name="securityManager" ref="securityManager" />
- <property name="loginUrl"
- value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />
- <property name="filters">
- <map>
- <entry key="cas" value-ref="casFilter" />
- <entry key="role">
- <bean
- class="com.silvery.security.shiro.filter.SimpleRoleAuthorizationFilter" />
- </entry>
- <entry key="authc">
- <bean
- class="com.silvery.security.shiro.filter.SimpleFormAuthenticationFilter" />
- </entry>
- <entry key="exec">
- <bean class="com.silvery.security.shiro.filter.SimpleExecutiveFilter" />
- </entry>
- </map>
- </property>
- </bean>
-
- <!-- 權(quán)限資源配置 -->
- <bean id="filterChainDefinitionsService"
- class="com.silvery.security.shiro.service.ini.impl.SimpleFilterChainDefinitionsService">
- <property name="definitions">
- <value>
- /mh/cas/login.do = cas
- /mh/casUrl.do = role[ROLE_USER]
- /static/** = anon
- /** = exec
- </value>
- </property>
- </bean>
關(guān)于這一個步驟的配置里面注釋寫的比較清楚了,至于一些類是自己重寫的,可以自己參考前面的文章,/mh/cas/login.do其實就是cas攔截器的指定路徑,如果想登錄就請求這個路徑即可,如果沒有登錄他會跳轉(zhuǎn)cas的login頁面
第四步就是需要重寫我們的casrealm,你可以看到上面的配置有SimpleCasRealm,這個類是我自己重寫的,是為了方便分配自己本地系統(tǒng)的權(quán)限體系,因為shiro-cas提供的默認(rèn)CasRealm功能比較有限,不能動態(tài)角色體系,下面可以看看這個原始的CasRealm源碼
- public class CasRealm extends AuthorizingRealm
- {
-
- public CasRealm()
- {
- validationProtocol = "CAS";
- rememberMeAttributeName = "longTermAuthenticationRequestTokenUsed";
- setAuthenticationTokenClass(org/apache/shiro/cas/CasToken);
- }
-
- protected void onInit()
- {
- super.onInit();
- ensureTicketValidator();
- }
-
- protected TicketValidator ensureTicketValidator()
- {
- if(ticketValidator == null)
- ticketValidator = createTicketValidator();
- return ticketValidator;
- }
-
- protected TicketValidator createTicketValidator()
- {
- String urlPrefix = getCasServerUrlPrefix();
- if("saml".equalsIgnoreCase(getValidationProtocol()))
- return new Saml11TicketValidator(urlPrefix);
- else
- return new Cas20ServiceTicketValidator(urlPrefix);
- }
-
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
- throws AuthenticationException
- {
- CasToken casToken = (CasToken)token;
- if(token == null)
- return null;
- String ticket = (String)casToken.getCredentials();
- if(!StringUtils.hasText(ticket))
- return null;
- TicketValidator ticketValidator = ensureTicketValidator();
- try
- {
- Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
- AttributePrincipal casPrincipal = casAssertion.getPrincipal();
- String userId = casPrincipal.getName();
- log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[] {
- ticket, getCasServerUrlPrefix(), userId
- });
- Map attributes = casPrincipal.getAttributes();
- casToken.setUserId(userId);
- String rememberMeAttributeName = getRememberMeAttributeName();
- String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
- boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
- if(isRemembered)
- casToken.setRememberMe(true);
- List principals = CollectionUtils.asList(new Object[] {
- userId, attributes
- });
- PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
- return new SimpleAuthenticationInfo(principalCollection, ticket);
- }
- catch(TicketValidationException e)
- {
- throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [").append(ticket).append("]").toString(), e);
- }
- }
-
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
- {
- SimplePrincipalCollection principalCollection = (SimplePrincipalCollection)principals;
- List listPrincipals = principalCollection.asList();
- Map attributes = (Map)listPrincipals.get(1);
- SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
- addRoles(simpleAuthorizationInfo, split(defaultRoles));
- addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
- List attributeNames = split(roleAttributeNames);
- String value;
- for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addRoles(simpleAuthorizationInfo, split(value)))
- {
- String attributeName = (String)i$.next();
- value = (String)attributes.get(attributeName);
- }
-
- attributeNames = split(permissionAttributeNames);
- String value;
- for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addPermissions(simpleAuthorizationInfo, split(value)))
- {
- String attributeName = (String)i$.next();
- value = (String)attributes.get(attributeName);
- }
-
- return simpleAuthorizationInfo;
- }
-
- private List split(String s)
- {
- List list = new ArrayList();
- String elements[] = StringUtils.split(s, ',');
- if(elements != null && elements.length > 0)
- {
- String arr$[] = elements;
- int len$ = arr$.length;
- for(int i$ = 0; i$ < len$; i$++)
- {
- String element = arr$[i$];
- if(StringUtils.hasText(element))
- list.add(element.trim());
- }
-
- }
- return list;
- }
-
- private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles)
- {
- String role;
- for(Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))
- role = (String)i$.next();
-
- }
-
- private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions)
- {
- String permission;
- for(Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo.addStringPermission(permission))
- permission = (String)i$.next();
-
- }
-
- public String getCasServerUrlPrefix()
- {
- return casServerUrlPrefix;
- }
-
- public void setCasServerUrlPrefix(String casServerUrlPrefix)
- {
- this.casServerUrlPrefix = casServerUrlPrefix;
- }
-
- public String getCasService()
- {
- return casService;
- }
-
- public void setCasService(String casService)
- {
- this.casService = casService;
- }
-
- public String getValidationProtocol()
- {
- return validationProtocol;
- }
-
- public void setValidationProtocol(String validationProtocol)
- {
- this.validationProtocol = validationProtocol;
- }
-
- public String getRememberMeAttributeName()
- {
- return rememberMeAttributeName;
- }
-
- public void setRememberMeAttributeName(String rememberMeAttributeName)
- {
- this.rememberMeAttributeName = rememberMeAttributeName;
- }
-
- public String getDefaultRoles()
- {
- return defaultRoles;
- }
-
- public void setDefaultRoles(String defaultRoles)
- {
- this.defaultRoles = defaultRoles;
- }
-
- public String getDefaultPermissions()
- {
- return defaultPermissions;
- }
-
- public void setDefaultPermissions(String defaultPermissions)
- {
- this.defaultPermissions = defaultPermissions;
- }
-
- public String getRoleAttributeNames()
- {
- return roleAttributeNames;
- }
-
- public void setRoleAttributeNames(String roleAttributeNames)
- {
- this.roleAttributeNames = roleAttributeNames;
- }
-
- public String getPermissionAttributeNames()
- {
- return permissionAttributeNames;
- }
-
- public void setPermissionAttributeNames(String permissionAttributeNames)
- {
- this.permissionAttributeNames = permissionAttributeNames;
- }
-
- public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
- public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
- private static Logger log = LoggerFactory.getLogger(org/apache/shiro/cas/CasRealm);
- private String casServerUrlPrefix;
- private String casService;
- private String validationProtocol;
- private String rememberMeAttributeName;
- private TicketValidator ticketValidator;
- private String defaultRoles;
- private String defaultPermissions;
- private String roleAttributeNames;
- private String permissionAttributeNames;
-
- }
其實跟我們普通用的UserRealm或者是JdbcRealm差別不大,但是里面增加了casToken的驗證,所以我們應(yīng)該直接拿過來用,下面再加載出我們的自己的邏輯即可,所以我們可以選擇繼承當(dāng)前的CasRealm重載一下他的兩個方法
- /**
- *
- * 擴(kuò)展CAS橋接器,訂制角色體系和資源體系
- *
- * @author shadow
- *
- */
- public class SimpleCasRealm extends CasRealm {
-
- @Autowired
- private CacheManager cacheManager;
-
- private final static Logger log = LoggerFactory.getLogger(SimpleCasRealm.class);
-
- public SimpleCasRealm() {
- super();
- setCacheManager(cacheManager);
- }
-
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- CasToken casToken = (CasToken) token;
- if (token == null)
- return null;
- String ticket = (String) casToken.getCredentials();
- if (!StringUtils.hasText(ticket))
- return null;
- TicketValidator ticketValidator = ensureTicketValidator();
- try {
- Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
- AttributePrincipal casPrincipal = casAssertion.getPrincipal();
- String userId = casPrincipal.getName();
- log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[] { ticket,
- getCasServerUrlPrefix(), userId });
- Map attributes = casPrincipal.getAttributes();
- casToken.setUserId(userId);
- String rememberMeAttributeName = getRememberMeAttributeName();
- String rememberMeStringValue = (String) attributes.get(rememberMeAttributeName);
- boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
- if (isRemembered)
- casToken.setRememberMe(true);
- List principals = CollectionUtils.asList(new Object[] { userId, attributes });
- PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
-
- // 這里可以拿到Cas的登錄賬號信息,加載到對應(yīng)權(quán)限體系信息放到緩存中...
-
- return new SimpleAuthenticationInfo(principalCollection, ticket);
- } catch (TicketValidationException e) {
- throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [")
- .append(ticket).append("]").toString(), e);
- }
- }
-
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
- List listPrincipals = principalCollection.asList();
- Map attributes = (Map) listPrincipals.get(1);
- SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
-
- // 這里可以加載緩存的中的數(shù)據(jù)到認(rèn)證實體...
-
- addRoles(simpleAuthorizationInfo, split(getDefaultRoles()));
-
- return simpleAuthorizationInfo;
- }
-
- protected List split(String s) {
- List list = new ArrayList();
- String elements[] = StringUtils.split(s, ',');
- if (elements != null && elements.length > 0) {
- String arr$[] = elements;
- int len$ = arr$.length;
- for (int i$ = 0; i$ < len$; i$++) {
- String element = arr$[i$];
- if (StringUtils.hasText(element))
- list.add(element.trim());
- }
-
- }
- return list;
- }
-
- protected void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles) {
- String role;
- for (Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))
- role = (String) i$.next();
-
- }
-
- protected void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions) {
- String permission;
- for (Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo
- .addStringPermission(permission))
- permission = (String) i$.next();
-
- }
-
- /** 重寫退出時緩存處理方法 */
- protected void doClearCache(PrincipalCollection principals) {
- Object principal = principals.getPrimaryPrincipal();
- try {
- getCache().remove(principal);
- log.debug(new StringBuffer().append(principal).append(" on logout to remove the cache [").append(principal)
- .append("]").toString());
- } catch (CacheException e) {
- log.error(e.getMessage());
- }
- }
-
- /** 獲取緩存管理器的緩存堆實例 */
- protected Cache<Object, Object> getCache() throws CacheException {
- return cacheManager.getCache(CacheEmnu.MEMCACHED_DATA_CACHE);
- }
-
- public CacheManager getCacheManager() {
- return cacheManager;
- }
-
- public void setCacheManager(CacheManager cacheManager) {
- this.cacheManager = cacheManager;
- }
-
- }
值得提醒大家的一個關(guān)鍵點(diǎn),如何獲取cas返回過來的對象信息呢?
- Subject subject = SecurityUtils.getSubject();
- Object principal = subject.getPrincipal();
- PrincipalCollection principals = subject.getPrincipals();
第一個對象是可以獲取到當(dāng)前登錄賬號
第二個對象是一個List集合其中0元素是當(dāng)前登錄賬號,1元素是一個map集合,這里就存放了我們cas服務(wù)給我返回的用戶信息
我們寫的攔截器判斷是否有登錄就用第一個Object判斷是否有null即可
第五步既然有登錄了,那就必須有退出功能,那如何才能完整退出呢?流程應(yīng)該是先執(zhí)行當(dāng)前系統(tǒng)的注銷,然后再執(zhí)行cas的logout,這樣就比較完整了,不會出現(xiàn)莫名其妙的問題
調(diào)用當(dāng)前的shiro的subject.logout();注銷當(dāng)前系統(tǒng)的對象,然后返回到頁面
- @RequestMapping("/mh/cas/logout.do")
- public ModelAndView casLogout(HttpServletRequest request, HttpServletResponse response, UserDetailsVo vo) {
- SimpleUtils.getSubject().logout();
- return createModelAndView("/mh/logout");
- }
頁面再重定向到cas的logout,這樣就把cas的ticket也注銷成功
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>正在注銷...</title>
- <script type="text/javascript" src="${staticHost}/static/plugin/jquery/core.js"></script>
- <script type="text/javascript">
- location.;
- </script>
- </head>
- <body>
- </body>
- </html>
我想改造大概很明白了,其實shiro-cas.jar已經(jīng)大部分?jǐn)r截處理已經(jīng)幫我們做好了,所以我們很安心地按照以往的方式來操控shiro的登錄方式,希望對還沒爬過這個坑的同學(xué)有幫助