1.安裝cas-server-3.5.2
官網:https://github.com/apereo/cas/releases/tag/v3.5.2
下載地址:cas-server-3.5.2-release.zip
安裝參考文章:http://blog.csdn.net/xuxuchuan/article/details/54924933
注意:
2.配置ehcache緩存
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" name="shiroCache"><defaultCachemaxElementsInMemory="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="false"diskPersistent="false"diskExpiryThreadIntervalSeconds="120"/> </ehcache>
?
3.添加maven依賴
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.4</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.4</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-cas</artifactId><version>1.2.4</version></dependency>
4.啟動類添加@ServletComponentScan注解
@ServletComponentScan
@SpringBootApplication public class Application {
public static void main(String[] args){SpringApplication.run(Application.class,args);} }
?
5.配置shiro+cas
package com.hdwang.config.shiroCas;import com.hdwang.dao.UserDao; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.cas.CasFilter; import org.apache.shiro.cas.CasSubjectFactory; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.authc.LogoutFilter; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.jasig.cas.client.session.SingleSignOutFilter; import org.jasig.cas.client.session.SingleSignOutHttpSessionListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.filter.DelegatingFilterProxy;import javax.servlet.Filter; import javax.servlet.annotation.WebListener; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map;/*** Created by hdwang on 2017/6/20.* shiro+cas 配置*/ @Configuration public class ShiroCasConfiguration {private static final Logger logger = LoggerFactory.getLogger(ShiroCasConfiguration.class);// cas server地址public static final String casServerUrlPrefix = "https://localhost:8443/cas";// Cas登錄頁面地址public static final String casLoginUrl = casServerUrlPrefix + "/login";// Cas登出頁面地址public static final String casLogoutUrl = casServerUrlPrefix + "/logout";// 當前工程對外提供的服務地址public static final String shiroServerUrlPrefix = "http://localhost:8081";// casFilter UrlPatternpublic static final String casFilterUrlPattern = "/cas";// 登錄地址public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;// 登出地址(casserver啟用service跳轉功能,需在webapps\cas\WEB-INF\cas.properties文件中啟用cas.logout.followServiceRedirects=true)public static final String logoutUrl = casLogoutUrl+"?service="+shiroServerUrlPrefix;// 登錄成功地址public static final String loginSuccessUrl = "/home";// 權限認證失敗跳轉地址public static final String unauthorizedUrl = "/error/403.html";@Beanpublic EhCacheManager getEhCacheManager() {EhCacheManager em = new EhCacheManager();em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");return em;}@Bean(name = "myShiroCasRealm")public MyShiroCasRealm myShiroCasRealm(EhCacheManager cacheManager) {MyShiroCasRealm realm = new MyShiroCasRealm();realm.setCacheManager(cacheManager);//realm.setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix);// 客戶端回調地址//realm.setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern);return realm;}/*** 注冊單點登出listener* @return*/@Beanpublic ServletListenerRegistrationBean singleSignOutHttpSessionListener(){ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();bean.setListener(new SingleSignOutHttpSessionListener()); // bean.setName(""); //默認為bean namebean.setEnabled(true);//bean.setOrder(Ordered.HIGHEST_PRECEDENCE); //設置優先級return bean;}/*** 注冊單點登出filter* @return*/@Beanpublic FilterRegistrationBean singleSignOutFilter(){FilterRegistrationBean bean = new FilterRegistrationBean();bean.setName("singleSignOutFilter");bean.setFilter(new SingleSignOutFilter());bean.addUrlPatterns("/*");bean.setEnabled(true);//bean.setOrder(Ordered.HIGHEST_PRECEDENCE);return bean;}/*** 注冊DelegatingFilterProxy(Shiro)** @return* @author SHANHY* @create 2016年1月13日*/@Beanpublic FilterRegistrationBean delegatingFilterProxy() {FilterRegistrationBean filterRegistration = new FilterRegistrationBean();filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));// 該值缺省為false,表示生命周期由SpringApplicationContext管理,設置為true則表示由ServletContainer管理filterRegistration.addInitParameter("targetFilterLifecycle", "true");filterRegistration.setEnabled(true);filterRegistration.addUrlPatterns("/*");return filterRegistration;}@Bean(name = "lifecycleBeanPostProcessor")public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();daap.setProxyTargetClass(true);return daap;}@Bean(name = "securityManager")public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroCasRealm myShiroCasRealm) {DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();dwsm.setRealm(myShiroCasRealm); // <!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 --> dwsm.setCacheManager(getEhCacheManager());// 指定 SubjectFactorydwsm.setSubjectFactory(new CasSubjectFactory());return dwsm;}@Beanpublic AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();aasa.setSecurityManager(securityManager);return aasa;}/*** CAS過濾器** @return* @author SHANHY* @create 2016年1月17日*/@Bean(name = "casFilter")public CasFilter getCasFilter() {CasFilter casFilter = new CasFilter();casFilter.setName("casFilter");casFilter.setEnabled(true);// 登錄失敗后跳轉的URL,也就是 Shiro 執行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer驗證tiketcasFilter.setFailureUrl(loginUrl);// 我們選擇認證失敗后再打開登錄頁面return casFilter;}/*** ShiroFilter<br/>* 注意這里參數中的 StudentService 和 IScoreDao 只是一個例子,因為我們在這里可以用這樣的方式獲取到相關訪問數據庫的對象,* 然后讀取數據庫相關配置,配置到 shiroFilterFactoryBean 的訪問規則中。實際項目中,請使用自己的Service來處理業務邏輯。** @param securityManager* @param casFilter* @param userDao* @return* @author SHANHY* @create 2016年1月14日*/@Bean(name = "shiroFilter")public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter, UserDao userDao) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必須設置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager);// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl(loginUrl);// 登錄成功后要跳轉的連接 shiroFilterFactoryBean.setSuccessUrl(loginSuccessUrl);shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);// 添加casFilter到shiroFilter中Map<String, Filter> filters = new HashMap<>();filters.put("casFilter", casFilter);// filters.put("logout",logoutFilter()); shiroFilterFactoryBean.setFilters(filters);loadShiroFilterChain(shiroFilterFactoryBean, userDao);return shiroFilterFactoryBean;}/*** 加載shiroFilter權限控制規則(從數據庫讀取然后配置),角色/權限信息由MyShiroCasRealm對象提供doGetAuthorizationInfo實現獲取來的** @author SHANHY* @create 2016年1月14日*/private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, UserDao userDao){/// 下面這些規則配置最好配置到配置文件中 ///Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// authc:該過濾器下的頁面必須登錄后才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter// anon: 可以理解為不攔截// user: 登錄了就不攔截// roles["admin"] 用戶擁有admin角色// perms["permission1"] 用戶擁有permission1權限// filter順序按照定義順序匹配,匹配到就驗證,驗證完畢結束。// url匹配通配符支持:? * **,分別表示匹配1個,匹配0-n個(不含子路徑),匹配下級所有路徑//1.shiro集成cas后,首先添加該規則filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");//filterChainDefinitionMap.put("/logout","logout"); //logut請求采用logout filter//2.不攔截的請求filterChainDefinitionMap.put("/css/**","anon");filterChainDefinitionMap.put("/js/**","anon");filterChainDefinitionMap.put("/login", "anon");filterChainDefinitionMap.put("/logout","anon");filterChainDefinitionMap.put("/error","anon");//3.攔截的請求(從本地數據庫獲取或者從casserver獲取(webservice,http等遠程方式),看你的角色權限配置在哪里)filterChainDefinitionMap.put("/user", "authc"); //需要登錄filterChainDefinitionMap.put("/user/add/**", "authc,roles[admin]"); //需要登錄,且用戶角色為adminfilterChainDefinitionMap.put("/user/delete/**", "authc,perms[\"user:delete\"]"); //需要登錄,且用戶有權限為user:delete//4.登錄過的不攔截filterChainDefinitionMap.put("/**", "user");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);}}
package com.hdwang.config.shiroCas;import javax.annotation.PostConstruct;import com.hdwang.dao.UserDao; import com.hdwang.entity.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cas.CasRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired;import java.util.HashSet; import java.util.Set;/*** Created by hdwang on 2017/6/20.* 安全數據源*/ public class MyShiroCasRealm extends CasRealm{private static final Logger logger = LoggerFactory.getLogger(MyShiroCasRealm.class);@Autowiredprivate UserDao userDao;@PostConstructpublic void initProperty(){ // setDefaultRoles("ROLE_USER"); setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix);// 客戶端回調地址setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern);}// /** // * 1、CAS認證 ,驗證用戶身份 // * 2、將用戶基本信息設置到會話中(不用了,隨時可以獲取的) // */ // @Override // protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { // // AuthenticationInfo authc = super.doGetAuthenticationInfo(token); // // String account = (String) authc.getPrincipals().getPrimaryPrincipal(); // // User user = userDao.getByName(account); // //將用戶信息存入session中 // SecurityUtils.getSubject().getSession().setAttribute("user", user); // // return authc; // }/*** 權限認證,為當前登錄的Subject授予角色和權限* @see 經測試:本例中該方法的調用時機為需授權資源被訪問時* @see 經測試:并且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中默認并未啟用AuthorizationCache* @see 經測試:如果連續訪問同一個URL(比如刷新),該方法不會被重復調用,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),超過這個時間間隔再刷新頁面,該方法會被執行*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {logger.info("##################執行Shiro權限認證##################");//獲取當前登錄輸入的用戶名,等價于(String) principalCollection.fromRealm(getName()).iterator().next();String loginName = (String)super.getAvailablePrincipal(principalCollection);//到數據庫查是否有此對象(1.本地查詢 2.可以遠程查詢casserver 3.可以由casserver帶過來角色/權限其它信息)User user=userDao.getByName(loginName);// 實際項目中,這里可以根據實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重復執行該方法if(user!=null){//權限信息對象info,用來存放查出的用戶的所有的角色(role)及權限(permission)SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();//給用戶添加角色(讓shiro去驗證)Set<String> roleNames = new HashSet<>();if(user.getName().equals("boy5")){roleNames.add("admin");}info.setRoles(roleNames);if(user.getName().equals("李四")){//給用戶添加權限(讓shiro去驗證)info.addStringPermission("user:delete");}// 或者按下面這樣添加//添加一個角色,不是配置意義上的添加,而是證明該用戶擁有admin角色 // simpleAuthorInfo.addRole("admin");//添加權限 // simpleAuthorInfo.addStringPermission("admin:manage"); // logger.info("已為用戶[mike]賦予了[admin]角色和[admin:manage]權限");return info;}// 返回null的話,就會導致任何用戶訪問被攔截的請求時,都會自動跳轉到unauthorizedUrl指定的地址return null;}}
package com.hdwang.controller;import com.hdwang.config.shiroCas.ShiroCasConfiguration; import com.hdwang.entity.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;import javax.servlet.http.HttpSession;/*** Created by hdwang on 2017/6/21.* 跳轉至cas server去登錄(一個入口)*/ @Controller @RequestMapping("") public class CasLoginController {/*** 一般用不到* @param model* @return*/@RequestMapping(value="/login",method= RequestMethod.GET)public String loginForm(Model model){model.addAttribute("user", new User()); // return "login";return "redirect:" + ShiroCasConfiguration.loginUrl;}@RequestMapping(value = "logout", method = { RequestMethod.GET,RequestMethod.POST })public String loginout(HttpSession session){return "redirect:"+ShiroCasConfiguration.logoutUrl;} }
package com.hdwang.controller;import com.alibaba.fastjson.JSONObject; import com.hdwang.entity.User; import com.hdwang.service.datajpa.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.mgt.SecurityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession;/*** Created by hdwang on 2017/6/19.*/ @Controller @RequestMapping("/home") public class HomeController {@AutowiredUserService userService;@RequestMapping("")public String index(HttpSession session, ModelMap map, HttpServletRequest request){ // User user = (User) session.getAttribute("user"); System.out.println(request.getUserPrincipal().getName());System.out.println(SecurityUtils.getSubject().getPrincipal());User loginUser = userService.getLoginUser();System.out.println(JSONObject.toJSONString(loginUser));map.put("user",loginUser);return "home";}}
6.運行驗證
登錄?
訪問:http://localhost:8081/home
跳轉至:https://localhost:8443/cas/login?service=http://localhost:8081/cas
輸入正確用戶名密碼登錄跳轉回:http://localhost:8081/cas?ticket=ST-203-GUheN64mOZec9IWZSH1B-cas01.example.org
最終跳回:http://localhost:8081/home
?
登出
訪問:http://localhost:8081/logout
跳轉至:https://localhost:8443/cas/logout?service=http://localhost:8081
由于未登錄,又執行登錄步驟,所以最終返回https://localhost:8443/cas/login?service=http://localhost:8081/cas
這次登錄成功后返回:http://localhost:8081/
?
cas server端登出(也行)
訪問:https://localhost:8443/cas/logout
再訪問:http://localhost:8081/home 會跳轉至登錄頁,perfect!
?
7.項目源碼:https://github.com/hdwang123/springboottest_onedb
?
參考文章
1.?Spring Boot 集成Shiro和CAS
2.?Cas Server 與Cas Client 的配置與部署
3.第一章 Shiro簡介——《跟我學Shiro》
4.shiro-cas 單點退出
5.?CAS Shiro做單點登錄不能退出的問題
?