1.添加maven依賴(先安裝好cas-server-3.5.2,安裝步驟請查看本文參考文章)
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<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> |
2.啟動累添加@ServletComponentScan注解
1
|
2
3
4
5
6
7
8
9
10
11
12
13
|
@SpringBootApplication public class Application { /** * main function * @param args params */ public static void main(String[] args){ ConfigurableApplicationContext context = SpringApplication.run(Application. class ,args); //test if a xml bean inject into springcontext successful User user = (User)context.getBean( "user1" ); System.out.println(JSONObject.toJSONString(user)); } } |
3.配置shiro+cas
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
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" ; // 當(dāng)前工程對外提供的服務(wù)地址 public static final String shiroServerUrlPrefix = " http://localhost:8081 " ; // casFilter UrlPattern public static final String casFilterUrlPattern = "/cas" ; // 登錄地址 public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern; // 登出地址 public static final String logoutUrl = casLogoutUrl+ "?service=" +shiroServerUrlPrefix; // 登錄成功地址 public static final String loginSuccessUrl = "/home" ; // 權(quán)限認(rèn)證失敗跳轉(zhuǎn)地址 public static final String unauthorizedUrl = "/error/403.html" ; @Bean public 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); // 客戶端回調(diào)地址 //realm.setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern); return realm; } /** * 注冊單點登出listener * @return */ @Bean public ServletListenerRegistrationBean singleSignOutHttpSessionListener(){ ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean(); bean.setListener( new SingleSignOutHttpSessionListener()); // bean.setName(""); //默認(rèn)為bean name bean.setEnabled( true ); //bean.setOrder(Ordered.HIGHEST_PRECEDENCE); //設(shè)置優(yōu)先級 return bean; } /** * 注冊單點登出filter * @return */ @Bean public 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日 */ @Bean public FilterRegistrationBean delegatingFilterProxy() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter( new DelegatingFilterProxy( "shiroFilter" )); // 該值缺省為false,表示生命周期由SpringApplicationContext管理,設(shè)置為true則表示由ServletContainer管理 filterRegistration.addInitParameter( "targetFilterLifecycle" , "true" ); filterRegistration.setEnabled( true ); filterRegistration.addUrlPatterns( "/*" ); return filterRegistration; } @Bean (name = "lifecycleBeanPostProcessor" ) public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public 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); // <!-- 用戶授權(quán)/認(rèn)證信息Cache, 采用EhCache 緩存 --> dwsm.setCacheManager(getEhCacheManager()); // 指定 SubjectFactory dwsm.setSubjectFactory( new CasSubjectFactory()); return dwsm; } @Bean public 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 ); // 登錄失敗后跳轉(zhuǎn)的URL,也就是 Shiro 執(zhí)行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer驗證tiket casFilter.setFailureUrl(loginUrl); // 我們選擇認(rèn)證失敗后再打開登錄頁面 return casFilter; } /** * ShiroFilter<br/> * 注意這里參數(shù)中的 StudentService 和 IScoreDao 只是一個例子,因為我們在這里可以用這樣的方式獲取到相關(guān)訪問數(shù)據(jù)庫的對象, * 然后讀取數(shù)據(jù)庫相關(guān)配置,配置到 shiroFilterFactoryBean 的訪問規(guī)則中。實際項目中,請使用自己的Service來處理業(yè)務(wù)邏輯。 * * @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(); // 必須設(shè)置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不設(shè)置默認(rèn)會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl(loginUrl); // 登錄成功后要跳轉(zhuǎn)的連接 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權(quán)限控制規(guī)則(從數(shù)據(jù)庫讀取然后配置),角色/權(quán)限信息由MyShiroCasRealm對象提供doGetAuthorizationInfo實現(xiàn)獲取來的 * * @author SHANHY * @create 2016年1月14日 */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, UserDao userDao){ /////////////////////// 下面這些規(guī)則配置最好配置到配置文件中 /////////////////////// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // authc:該過濾器下的頁面必須登錄后才能訪問,它是Shiro內(nèi)置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter // anon: 可以理解為不攔截 // user: 登錄了就不攔截 // roles["admin"] 用戶擁有admin角色 // perms["permission1"] 用戶擁有permission1權(quán)限 // filter順序按照定義順序匹配,匹配到就驗證,驗證完畢結(jié)束。 // url匹配通配符支持:? * **,分別表示匹配1個,匹配0-n個(不含子路徑),匹配下級所有路徑 //1.shiro集成cas后,首先添加該規(guī)則 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.攔截的請求(從本地數(shù)據(jù)庫獲取或者從casserver獲取(webservice,http等遠(yuǎn)程方式),看你的角色權(quán)限配置在哪里) filterChainDefinitionMap.put( "/user" , "authc" ); //需要登錄 filterChainDefinitionMap.put( "/user/add/**" , "authc,roles[admin]" ); //需要登錄,且用戶角色為admin filterChainDefinitionMap.put( "/user/delete/**" , "authc,perms[\"user:delete\"]" ); //需要登錄,且用戶有權(quán)限為user:delete //4.登錄過的不攔截 filterChainDefinitionMap.put( "/**" , "user" ); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } } |
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
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. * 安全數(shù)據(jù)源 */ public class MyShiroCasRealm extends CasRealm{ private static final Logger logger = LoggerFactory.getLogger(MyShiroCasRealm. class ); @Autowired private UserDao userDao; @PostConstruct public void initProperty(){ // setDefaultRoles("ROLE_USER"); setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix); // 客戶端回調(diào)地址 setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern); } // /** // * 1、CAS認(rèn)證 ,驗證用戶身份 // * 2、將用戶基本信息設(shè)置到會話中(不用了,隨時可以獲取的) // */ // @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; // } /** * 權(quán)限認(rèn)證,為當(dāng)前登錄的Subject授予角色和權(quán)限 * @see 經(jīng)測試:本例中該方法的調(diào)用時機為需授權(quán)資源被訪問時 * @see 經(jīng)測試:并且每次訪問需授權(quán)資源時都會執(zhí)行該方法中的邏輯,這表明本例中默認(rèn)并未啟用AuthorizationCache * @see 經(jīng)測試:如果連續(xù)訪問同一個URL(比如刷新),該方法不會被重復(fù)調(diào)用,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),超過這個時間間隔再刷新頁面,該方法會被執(zhí)行 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info( "##################執(zhí)行Shiro權(quán)限認(rèn)證##################" ); //獲取當(dāng)前登錄輸入的用戶名,等價于(String) principalCollection.fromRealm(getName()).iterator().next(); String loginName = (String) super .getAvailablePrincipal(principalCollection); //到數(shù)據(jù)庫查是否有此對象(1.本地查詢 2.可以遠(yuǎn)程查詢casserver 3.可以由casserver帶過來角色/權(quán)限其它信息) User user=userDao.getByName(loginName); // 實際項目中,這里可以根據(jù)實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內(nèi)不會重復(fù)執(zhí)行該方法 if (user!= null ){ //權(quán)限信息對象info,用來存放查出的用戶的所有的角色(role)及權(quán)限(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( "李四" )){ //給用戶添加權(quán)限(讓shiro去驗證) info.addStringPermission( "user:delete" ); } // 或者按下面這樣添加 //添加一個角色,不是配置意義上的添加,而是證明該用戶擁有admin角色 // simpleAuthorInfo.addRole("admin"); //添加權(quán)限 // simpleAuthorInfo.addStringPermission("admin:manage"); // logger.info("已為用戶[mike]賦予了[admin]角色和[admin:manage]權(quán)限"); return info; } // 返回null的話,就會導(dǎo)致任何用戶訪問被攔截的請求時,都會自動跳轉(zhuǎn)到unauthorizedUrl指定的地址 return null ; } } |
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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. * 跳轉(zhuǎn)至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; } } |
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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 { @Autowired UserService 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" ; } } |
4.運行驗證
登錄
訪問:http://localhost:8081/home
跳轉(zhuǎn)至:https://localhost:8443/cas/login?service=http://localhost:8081/cas
輸入正確用戶名密碼登錄跳轉(zhuǎn)回:http://localhost:8081/cas?ticket=ST-203-GUheN64mOZec9IWZSH1B-cas01.example.org
最終跳回:http://localhost:8081/home
登出
訪問:http://localhost:8081/logout
跳轉(zhuǎn)至:https://localhost:8443/cas/logout?service=http://localhost:8081
由于未登錄,又執(zhí)行登錄步驟,所以最終返回https://localhost:8443/cas/login?service=http://localhost:8081/cas
這次登錄成功后返回:http://localhost:8081/
cas server端登出(也行)
訪問:https://localhost:8443/cas/logout
再訪問:http://localhost:8081/home 會跳轉(zhuǎn)至登錄頁,perfect!
以上所述是小編給大家介紹的spring boot 1.5.4 集成shiro+cas,實現(xiàn)單點登錄和權(quán)限控制,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對服務(wù)器之家網(wǎng)站的支持!
原文鏈接:http://www.cnblogs.com/hdwang/archive/2017/06/22/7064492.html