Shiro是什么
Shiro是一個Java平臺的開源權限框架,用于認證和訪問授權。具體來說,滿足對如下元素的支持:
- 用戶,角色,權限(僅僅是操作權限,數據權限必須與業務需求緊密結合),資源(url)。
- 用戶分配角色,角色定義權限。
- 訪問授權時支持角色或者權限,并且支持多級的權限定義。
Q:對組的支持?
A:shiro默認不支持對組設置權限。
Q:是否可以滿足對組進行角色分配的需求?
A:擴展Realm,可以支持對組進行分配角色,其實就是給該組下的所有用戶分配權限。
Q:對數據權限的支持? 在業務系統中定義?
A:shiro僅僅實現對操作權限的控制,用于在前端控制元素隱藏或者顯示,以及對資源訪問權限進行檢查。數據權限與具體的業務需求緊密關聯,shiro本身無法實現對數據權限的控制。
Q:動態權限分配?
A:擴展org.apache.shiro.realm.Realm,支持動態權限分配。
Q:與Spring集成?
A:可以支持與Spring集成,shiro還支持jsp標簽。
前面的博客中,我們說道了Shiro的兩個最大的特點,認證和授權,而單點登錄也是屬于認證的一部分,默認情況下,Shiro已經為我們實現了和Cas的集成,我們加入集成的一些配置就ok了。
1、加入shiro-cas包
1
2
3
4
5
6
|
<!-- shiro整合cas單點 --> < dependency > < groupId >org.apache.shiro</ groupId > < artifactId >shiro-cas</ artifactId > < version >1.2.4</ version > </ dependency > |
2、加入單點登錄的配置
這里,我將所有的配置都貼出來,方便參考,配置里面已經加了詳盡的說明。
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
package com.chhliu.springboot.shiro.config; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.Filter; 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.mgt.DefaultWebSecurityManager; import org.jasig.cas.client.session.SingleSignOutFilter; import org.jasig.cas.client.session.SingleSignOutHttpSessionListener; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 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.context.annotation.DependsOn; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.filter.DelegatingFilterProxy; /** * Shiro 配置 * * Apache Shiro 核心通過 Filter 來實現,就好像SpringMvc 通過DispachServlet 來主控制一樣。 既然是使用 * Filter 一般也就能猜到,是通過URL規則來進行過濾和權限校驗,所以我們需要定義一系列關于URL的規則和訪問權限。 * * @author chhliu */ @Configuration public class ShiroConfiguration { // cas server地址 public static final String casServerUrlPrefix = "http://127.0.0.1" ; // Cas登錄頁面地址 public static final String casLoginUrl = casServerUrlPrefix + "/login" ; // Cas登出頁面地址 public static final String casLogoutUrl = casServerUrlPrefix + "/logout" ; // 當前工程對外提供的服務地址 public static final String shiroServerUrlPrefix = "http://127.0.1.28:8080" ; // casFilter UrlPattern public static final String casFilterUrlPattern = "/index" ; // 登錄地址 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=" +loginUrl; // 登錄成功地址 // public static final String loginSuccessUrl = "/index"; // 權限認證失敗跳轉地址 public static final String unauthorizedUrl = "/error/403.html" ; /** * 實例化SecurityManager,該類是shiro的核心類 * @return */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroCasRealm()); // <!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 --> securityManager.setCacheManager(getEhCacheManager()); // 指定 SubjectFactory,如果要實現cas的remember me的功能,需要用到下面這個CasSubjectFactory,并設置到securityManager的subjectFactory中 securityManager.setSubjectFactory( new CasSubjectFactory()); return securityManager; } /** * 配置緩存 * @return */ @Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile( "classpath:config/ehcache-shiro.xml" ); return em; } /** * 配置Realm,由于我們使用的是CasRealm,所以已經集成了單點登錄的功能 * @param cacheManager * @return */ @Bean public MyShiroRealm myShiroCasRealm() { MyShiroRealm realm = new MyShiroRealm(); // cas登錄服務器地址前綴 realm.setCasServerUrlPrefix(ShiroConfiguration.casServerUrlPrefix); // 客戶端回調地址,登錄成功后的跳轉地址(自己的服務地址) realm.setCasService(ShiroConfiguration.shiroServerUrlPrefix + ShiroConfiguration.casFilterUrlPattern); // 登錄成功后的默認角色,此處默認為user角色 realm.setDefaultRoles( "user" ); return realm; } /** * 注冊單點登出的listener * @return */ @SuppressWarnings ({ "rawtypes" , "unchecked" }) @Bean @Order (Ordered.HIGHEST_PRECEDENCE) // 優先級需要高于Cas的Filter public ServletListenerRegistrationBean<?> singleSignOutHttpSessionListener(){ ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean(); bean.setListener( new SingleSignOutHttpSessionListener()); bean.setEnabled( true ); return bean; } /** * 注冊單點登出filter * @return */ @Bean public FilterRegistrationBean singleSignOutFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setName( "singleSignOutFilter" ); bean.setFilter( new SingleSignOutFilter()); bean.addUrlPatterns( "/*" ); bean.setEnabled( true ); return bean; } /** * 注冊DelegatingFilterProxy(Shiro) */ @Bean public 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; } /** * 該類可以保證實現了org.apache.shiro.util.Initializable接口的shiro對象的init或者是destory方法被自動調用, * 而不用手動指定init-method或者是destory-method方法 * 注意:如果使用了該類,則不需要手動指定初始化方法和銷毀方法,否則會出錯 * @return */ @Bean (name = "lifecycleBeanPostProcessor" ) public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 下面兩個配置主要用來開啟shiro aop注解支持. 使用代理方式;所以需要開啟代碼支持; * @return */ @Bean @DependsOn ( "lifecycleBeanPostProcessor" ) public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass( true ); return daap; } /** * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * CAS過濾器 * @return */ @Bean (name = "casFilter" ) public CasFilter getCasFilter() { CasFilter casFilter = new CasFilter(); casFilter.setName( "casFilter" ); casFilter.setEnabled( true ); // 登錄失敗后跳轉的URL,也就是 Shiro 執行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer驗證tiket casFilter.setFailureUrl(loginUrl); // 我們選擇認證失敗后再打開登錄頁面 casFilter.setLoginUrl(loginUrl); return casFilter; } /** * 使用工廠模式,創建并初始化ShiroFilter * @param securityManager * @param casFilter * @return */ @Bean (name = "shiroFilter" ) public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必須設置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl(loginUrl); /* * 登錄成功后要跳轉的連接,不設置的時候,會默認跳轉到前一步的url * 比如先在瀏覽器中輸入了http: //localhost:8080/userlist,但是現在用戶卻沒有登錄,于是會跳轉到登錄頁面,等登錄認證通過后, * 頁面會再次自動跳轉到http: //localhost:8080/userlist頁面而不是登錄成功后的index頁面 * 建議不要設置這個字段 */ // shiroFilterFactoryBean.setSuccessUrl(loginSuccessUrl); // 設置無權限訪問頁面 shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl); /* * 添加casFilter到shiroFilter中,注意,casFilter需要放到shiroFilter的前面, * 從而保證程序在進入shiro的login登錄之前就會進入單點認證 */ Map<String, Filter> filters = new LinkedHashMap<>(); filters.put( "casFilter" , casFilter); // logout已經被單點登錄的logout取代 // filters.put("logout",logoutFilter()); shiroFilterFactoryBean.setFilters(filters); loadShiroFilterChain(shiroFilterFactoryBean); return shiroFilterFactoryBean; } /** * 加載shiroFilter權限控制規則(從數據庫讀取然后配置),角色/權限信息由MyShiroCasRealm對象提供doGetAuthorizationInfo實現獲取來的 * 生產中會將這部分規則放到數據庫中 * @param shiroFilterFactoryBean */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){ /////////////////////// 下面這些規則配置最好配置到配置文件中,注意,此處加入的filter需要保證有序,所以用的LinkedHashMap /////////////////////// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter" ); //2.不攔截的請求 filterChainDefinitionMap.put( "/css/**" , "anon" ); filterChainDefinitionMap.put( "/js/**" , "anon" ); filterChainDefinitionMap.put( "/login" , "anon" ); // 此處將logout頁面設置為anon,而不是logout,因為logout被單點處理,而不需要再被shiro的logoutFilter進行攔截 filterChainDefinitionMap.put( "/logout" , "anon" ); filterChainDefinitionMap.put( "/error" , "anon" ); //3.攔截的請求(從本地數據庫獲取或者從casserver獲取(webservice,http等遠程方式),看你的角色權限配置在哪里) filterChainDefinitionMap.put( "/user" , "authc" ); //需要登錄 //4.登錄過的不攔截 filterChainDefinitionMap.put( "/**" , "authc" ); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } } |
部分配置參考:http://shiro.apache.org/spring.html
3、編寫Realm
由于需要集成單點登錄的功能,所以需要集成CasRealm類,該類已經為我們實現了單點認證的功能,我們要做的就是實現授權部分的功能,示例代碼如下:
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
|
package com.chhliu.springboot.shiro.config; import javax.annotation.Resource; 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 com.chhliu.springboot.shiro.mode.SysPermission; import com.chhliu.springboot.shiro.mode.SysRole; import com.chhliu.springboot.shiro.mode.UserInfo; import com.chhliu.springboot.shiro.service.UserInfoService; /** * 權限校驗核心類; 由于使用了單點登錄,所以無需再進行身份認證 只需要授權即可 * * @author chhliu */ public class MyShiroRealm extends CasRealm { @Resource private UserInfoService userInfoService; /** * 1、CAS認證 ,驗證用戶身份 * 2、將用戶基本信息設置到會話中,方便獲取 * 3、該方法可以直接使用CasRealm中的認證方法,此處僅用作測試 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { // 調用父類中的認證方法,CasRealm已經為我們實現了單點認證。 AuthenticationInfo authc = super .doGetAuthenticationInfo(token); // 獲取登錄的賬號,cas認證成功后,會將賬號存起來 String account = (String) authc.getPrincipals().getPrimaryPrincipal(); // 將用戶信息存入session中,方便程序獲取,此處可以將根據登錄賬號查詢出的用戶信息放到session中 SecurityUtils.getSubject().getSession().setAttribute( "no" , account); return authc; } /** * 此方法調用 hasRole,hasPermission的時候才會進行回調. * * 權限信息.(授權): 1、如果用戶正常退出,緩存自動清空; 2、如果用戶非正常退出,緩存自動清空; * 3、如果我們修改了用戶的權限,而用戶不退出系統,修改的權限無法立即生效。 (需要手動編程進行實現;放在service進行調用) * 在權限修改后調用realm中的方法,realm已經由spring管理,所以從spring中獲取realm實例, 調用clearCached方法; * :Authorization 是授權訪問控制,用于對用戶進行的操作授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。 * * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println( "權限配置-->MyShiroRealm.doGetAuthorizationInfo()" ); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 獲取單點登陸后的用戶名,也可以從session中獲取,因為在認證成功后,已經將用戶名放到session中去了 String userName = (String) super .getAvailablePrincipal(principals); // principals.getPrimaryPrincipal(); 這種方式也可以獲取用戶名 // 根據用戶名獲取該用戶的角色和權限信息 UserInfo userInfo = userInfoService.findByUsername(userName); // 將用戶對應的角色和權限信息打包放到AuthorizationInfo中 for (SysRole role : userInfo.getRoleList()) { authorizationInfo.addRole(role.getRole()); for (SysPermission p : role.getPermissions()) { authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } } |
下面,我們就可以進行驗證測試了!
在瀏覽器輸入http:127.0.1.28:8080/userInfo/userList 我們會發現,會自動跳轉到單點的登錄頁面
然后我們輸入用戶名和密碼,就會自動跳轉到http:127.0.1.28:8080/userInfo/userList頁面了。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://blog.csdn.net/liuchuanhong1/article/details/76850181