<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                在做一些企業內部項目時或一些互聯網后臺時;可能會涉及到集中權限管理,統一進行多項目的權限管理;另外也需要統一的會話管理,即實現單點身份認證和授權控制。 學習本章之前,請務必先學習《第十章 會話管理》和《第十六章 綜合實例》,本章代碼都是基于這兩章的代碼基礎上完成的。 本章示例是同域名的場景下完成的,如果跨域請參考《第十五章 單點登錄》和《第十七章 OAuth2 集成》了解使用 CAS 或 OAuth2 實現跨域的身份驗證和授權。另外比如客戶端 / 服務器端的安全校驗可參考《第二十章 無狀態 Web 應用集成》。 ## 部署架構 ![](https://box.kancloud.cn/24b0af5a885dc390a782a33762e72372_351x215.png) 1. 有三個應用:用于用戶 / 權限控制的 Server(端口:8080);兩個應用 App1(端口 9080)和 App2(端口 10080); 2. 使用 Nginx 反向代理這三個應用,nginx.conf 的 server 配置部分如下: ``` server { listen 80; server_name localhost; charset utf-8; location ~ ^/(chapter23-server)/ { proxy_pass http://127.0.0.1:8080; index /; proxy_set_header Host $host; } location ~ ^/(chapter23-app1)/ { proxy_pass http://127.0.0.1:9080; index /; proxy_set_header Host $host; } location ~ ^/(chapter23-app2)/ { proxy_pass http://127.0.0.1:10080; index /; proxy_set_header Host $host; } } ``` 如訪問 `http://localhost/chapter23-server` 會自動轉發到 `http://localhost:8080/chapter23-server`; 訪問 `http://localhost/chapter23-app1` 會自動轉發到 `http://localhost:9080/chapter23-app1`; 訪問 `http://localhost/chapter23-app3` 會自動轉發到 `http://localhost:10080/chapter23-app3`; Nginx 的安裝及使用請自行搜索學習,本文不再闡述。 ## 項目架構 ![](https://box.kancloud.cn/182f0161625cd297b80ac5add37d67d6_376x227.png) 1. 首先通過用戶 / 權限 Server 維護用戶、應用、權限信息;數據都持久化到 MySQL 數據庫中; 2. 應用 App1 / 應用 App2 使用客戶端 Client 遠程調用用戶 / 權限 Server 獲取會話及權限信息。 此處使用 Mysql 存儲會話,而不是使用如 Memcached/Redis 之類的,主要目的是降低學習成本;如果換成如 Redis 也不會很難;如: ![](https://box.kancloud.cn/37e6069f7b9585e73120a7e933ff6fab_413x253.png) 使用如 Redis 還一個好處就是無需在用戶 / 權限 Server 中開會話過期調度器,可以借助 Redis 自身的過期策略來完成。 ## 模塊關系依賴 ![](https://box.kancloud.cn/185e7afea3100d6ed3da6ea4a21ed4a4_379x244.png) ![](https://box.kancloud.cn/8f54c62e0c82783b2ef1075c17cd9bb8_336x116.png) 1、shiro-example-chapter23-pom 模塊:提供了其他所有模塊的依賴;這樣其他模塊直接繼承它即可,簡化依賴配置,如 shiro-example-chapter23-server: ``` xml <parent> <artifactId>shiro-example-chapter23-pom</artifactId> <groupId>com.github.zhangkaitao</groupId> <version>1.0-SNAPSHOT</version> </parent> ``` 2、shiro-example-chapter23-core 模塊:提供給 shiro-example-chapter23-server、shiro-example-chapter23-client、`shiro-example-chapter23-app *` 模塊的核心依賴,比如遠程調用接口等; 3、shiro-example-chapter23-server 模塊:提供了用戶、應用、權限管理功能; 4、shiro-example-chapter23-client 模塊:提供給應用模塊獲取會話及應用對應的權限信息; 5、`shiro-example-chapter23-app *` 模塊:各個子應用,如一些內部管理系統應用;其登錄都跳到 shiro-example-chapter23-server 登錄;另外權限都從 shiro-example-chapter23-server 獲取(如通過遠程調用)。 ## shiro-example-chapter23-pom 模塊 其 pom.xml 的 packaging 類型為 pom,并且在該 pom 中加入其他模塊需要的依賴,然后其他模塊只需要把該模塊設置為 parent 即可自動繼承這些依賴,如 shiro-example-chapter23-server 模塊: ``` xml <parent> <artifactId>shiro-example-chapter23-pom</artifactId> <groupId>com.github.zhangkaitao</groupId> <version>1.0-SNAPSHOT</version> </parent> ``` 簡化其他模塊的依賴配置等。 ## shiro-example-chapter23-core 模塊 提供了其他模塊共有的依賴,如遠程調用接口: ``` java public interface RemoteServiceInterface { public Session getSession(String appKey, Serializable sessionId); Serializable createSession(Session session); public void updateSession(String appKey, Session session); public void deleteSession(String appKey, Session session); public PermissionContext getPermissions(String appKey, String username); } ``` 提供了會話的 CRUD,及根據應用 key 和用戶名獲取權限上下文(包括角色和權限字符串);shiro-example-chapter23-server 模塊服務端實現;shiro-example-chapter23-client 模塊客戶端調用。 另外提供了 com.github.zhangkaitao.shiro.chapter23.core.ClientSavedRequest,其擴展了 org.apache.shiro.web.util.SavedRequest;用于 shiro-example-chapter23-app \* 模塊當訪問一些需要登錄的請求時,自動把請求保存下來,然后重定向到 shiro-example-chapter23-server 模塊登錄;登錄成功后再重定向回來;因為 SavedRequest 不保存 URL 中的 `schema://domain:port` 部分;所以才需要擴展 SavedRequest;使得 ClientSavedRequest 能保存 `schema://domain:port`;這樣才能從一個應用重定向另一個(要不然只能在一個應用內重定向): ``` public String getRequestUrl() { String requestURI = getRequestURI(); if(backUrl != null) {//1 if(backUrl.toLowerCase().startsWith("http://") || backUrl.toLowerCase().startsWith("https://")) { return backUrl; } else if(!backUrl.startsWith(contextPath)) {//2 requestURI = contextPath + backUrl; } else {//3 requestURI = backUrl; } } StringBuilder requestUrl = new StringBuilder(scheme);//4 requestUrl.append("://"); requestUrl.append(domain);//5 //6 if("http".equalsIgnoreCase(scheme) && port != 80) { requestUrl.append(":").append(String.valueOf(port)); } else if("https".equalsIgnoreCase(scheme) && port != 443) { requestUrl.append(":").append(String.valueOf(port)); } //7 requestUrl.append(requestURI); //8 if (backUrl == null && getQueryString() != null) { requestUrl.append("?").append(getQueryString()); } return requestUrl.toString(); } ``` 1. 如果從外部傳入了 successUrl(登錄成功之后重定向的地址),且以 `http://` 或 `https://` 開頭那么直接返回(相應的攔截器直接重定向到它即可); 2. 如果 successUrl 有值但沒有上下文,拼上上下文; 3. 否則,如果 successUrl 有值,直接賦值給 requestUrl 即可;否則,如果 successUrl 沒值,那么 requestUrl 就是當前請求的地址; 4. 拼上 url 前邊的 schema,如 http 或 https; 5. 拼上域名; 6. 拼上重定向到的地址(帶上下文); 7. 如果 successUrl 沒值,且有查詢參數,拼上; 8. 返回該地址,相應的攔截器直接重定向到它即可。 ## shiro-example-chapter23-server 模塊 **簡單的實體關系圖** ![](https://box.kancloud.cn/10e9a8d2eb8716b10f424f625ef62340_336x141.png) **簡單數據字典** 用戶 (sys\_user) | 名稱 | 類型 | 長度 | 描述 | | --- | --- | --- | --- | | id | bigint | | 編號 主鍵 | | role | varchar | 100 | 角色名稱 | | description | varchar | 100 | 角色描述 | | resource_ids | varchar | 100 | 授權的資源 | | available | bool | | 是否可用 | 應用 (sys\_app) | 名稱 | 類型 | 長度 | 描述 | | --- | --- | --- | --- | | id | bigint | | 編號 主鍵 | | role | varchar | 100 | 角色名稱 | | description | varchar | 100 | 角色描述 | | resource_ids | varchar | 100 | 授權的資源 | | available | bool | | 是否可用 | 授權 (sys\_authorization) | 名稱 | 類型 | 長度 | 描述 | | --- | --- | --- | --- | | id | bigint | | 編號 主鍵 | | role | varchar | 100 | 角色名稱 | | description | varchar | 100 | 角色描述 | | resource_ids | varchar | 100 | 授權的資源 | | available | bool | | 是否可用 | **用戶**:比《第十六章 綜合實例》少了 role\_ids,因為本章是多項目集中權限管理;所以授權時需要指定相應的應用;而不是直接給用戶授權;所以不能在用戶中出現 role\_ids 了; **應用**:所有集中權限的應用;在此處需要指定應用 key(app\_key) 和應用安全碼(app\_secret),app 在訪問 server 時需要指定自己的 app\_key 和用戶名來獲取該 app 對應用戶權限信息;另外 app\_secret 可以認為 app 的密碼,比如需要安全訪問時可以考慮使用它,可參考《第二十章 無狀態 Web 應用集成》。另外 available 屬性表示該應用當前是否開啟;如果 false 表示該應用當前不可用,即不能獲取到相應的權限信息。 **授權**:給指定的用戶在指定的 app 下授權,即角色是與用戶和 app 存在關聯關系。 因為本章使用了《第十六章 綜合實例》代碼,所以還有其他相應的表結構(本章未使用到)。 **表 / 數據 SQL** 具體請參考 * sql/shiro-schema.sql (表結構) * sql/shiro-data.sql (初始數據) **實體** 具體請參考 com.github.zhangkaitao.shiro.chapter23.entity 包下的實體,此處就不列舉了。 **DAO** 具體請參考 com.github.zhangkaitao.shiro.chapter23.dao 包下的 DAO 接口及實現。 **Service** 具體請參考 com.github.zhangkaitao.shiro.chapter23.service 包下的 Service 接口及實現。以下是出了基本 CRUD 之外的關鍵接口: ``` java public interface AppService { public Long findAppIdByAppKey(String appKey);// 根據appKey查找AppId } ``` ``` java public interface AuthorizationService { //根據AppKey和用戶名查找其角色 public Set<String> findRoles(String appKey, String username); //根據AppKey和用戶名查找權限字符串 public Set<String> findPermissions(String appKey, String username); } ``` 根據 AppKey 和用戶名查找用戶在指定應用中對于的角色和權限字符串。 **UserRealm** ``` java protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String)principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles( authorizationService.findRoles(Constants.SERVER_APP_KEY, username)); authorizationInfo.setStringPermissions( authorizationService.findPermissions(Constants.SERVER_APP_KEY, username)); return authorizationInfo; } ``` 此處需要調用 AuthorizationService 的 findRoles/findPermissions 方法傳入 AppKey 和用戶名來獲取用戶的角色和權限字符串集合。其他的和《第十六章 綜合實例》代碼一樣。 **ServerFormAuthenticationFilter** ``` java public class ServerFormAuthenticationFilter extends FormAuthenticationFilter { protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception { String fallbackUrl = (String) getSubject(request, response) .getSession().getAttribute("authc.fallbackUrl"); if(StringUtils.isEmpty(fallbackUrl)) { fallbackUrl = getSuccessUrl(); } WebUtils.redirectToSavedRequest(request, response, fallbackUrl); } } ``` 因為是多項目登錄,比如如果是從其他應用中重定向過來的,首先檢查 Session 中是否有 “authc.fallbackUrl” 屬性,如果有就認為它是默認的重定向地址;否則使用 Server 自己的 successUrl 作為登錄成功后重定向到的地址。 **MySqlSessionDAO** 將會話持久化到 Mysql 數據庫;此處大家可以將其實現為如存儲到 Redis/Memcached 等,實現策略請參考《第十章 會話管理》中的會話存儲 / 持久化章節的 MySessionDAO,完全一樣。 **MySqlSessionValidationScheduler** 和《第十章 會話管理》中的會話驗證章節部分中的 MySessionValidationScheduler 完全一樣。如果使用如 Redis 之類的有自動過期策略的 DB,完全可以不用實現 SessionValidationScheduler,直接借助于這些 DB 的過期策略即可。 **RemoteService** ``` java public class RemoteService implements RemoteServiceInterface { @Autowired private AuthorizationService authorizationService; @Autowired private SessionDAO sessionDAO; public Session getSession(String appKey, Serializable sessionId) { return sessionDAO.readSession(sessionId); } public Serializable createSession(Session session) { return sessionDAO.create(session); } public void updateSession(String appKey, Session session) { sessionDAO.update(session); } public void deleteSession(String appKey, Session session) { sessionDAO.delete(session); } public PermissionContext getPermissions(String appKey, String username) { PermissionContext permissionContext = new PermissionContext(); permissionContext.setRoles(authorizationService.findRoles(appKey, username)); permissionContext.setPermissions(authorizationService.findPermissions(appKey, username)); return permissionContext; } } ``` 將會使用 HTTP 調用器暴露為遠程服務,這樣其他應用就可以使用相應的客戶端調用這些接口進行 Session 的集中維護及根據 AppKey 和用戶名獲取角色 / 權限字符串集合。此處沒有實現安全校驗功能,如果是局域網內使用可以通過限定 IP 完成;否則需要使用如《第二十章 無狀態 Web 應用集成》中的技術完成安全校驗。 然后在 spring-mvc-remote-service.xml 配置文件把服務暴露出去: ``` xml <bean id="remoteService" class="com.github.zhangkaitao.shiro.chapter23.remote.RemoteService"/> <bean name="/remoteService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="remoteService"/> <property name="serviceInterface" value="com.github.zhangkaitao.shiro.chapter23.remote.RemoteServiceInterface"> </bean> ``` **Shiro 配置文件 spring-config-shiro.xml** 和《第十六章 綜合實例》配置類似,但是需要在 shiroFilter 中的 filterChainDefinitions 中添加如下配置,即遠程調用不需要身份認證: `/remoteService = anon` 對于 userRealm 的緩存配置直接禁用;因為如果開啟,修改了用戶權限不會自動同步到緩存;另外請參考《第十一章 緩存機制》進行緩存的正確配置。 **服務器端數據維護** 1、首先開啟 ngnix 反向代理;然后就可以直接訪問 [](http://localhost/chapter23-server/)<http://localhost/chapter23-server/>; 2、輸入默認的用戶名密碼:admin/123456 登錄 3、應用管理,進行應用的 CRUD,主要維護應用 KEY(必須唯一)及應用安全碼;客戶端就可以使用應用 KEY 獲取用戶對應應用的權限了。 ![](https://box.kancloud.cn/b3f0a8d9dae52572159485caa02f5f5b_843x190.png) 4、授權管理,維護在哪個應用中用戶的角色列表。這樣客戶端就可以根據應用 KEY 及用戶名獲取到對應的角色 / 權限字符串列表了。 ![](https://box.kancloud.cn/0b9062c78bd474e2945b4a63e68f8d9a_848x195.png) ![](https://box.kancloud.cn/50f9a20c1ed07d44cb9b5b563fa21b7f_684x201.png) ## shiro-example-chapter23-client 模塊 Client 模塊提供給其他應用模塊依賴,這樣其他應用模塊只需要依賴 Client 模塊,然后再在相應的配置文件中配置如登錄地址、遠程接口地址、攔截器鏈等等即可,簡化其他應用模塊的配置。 **配置遠程服務 spring-client-remote-service.xml** ``` xml <bean id="remoteService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <property name="serviceUrl" value="${client.remote.service.url}"/> <property name="serviceInterface" value="com.github.zhangkaitao.shiro.chapter23.remote.RemoteServiceInterface"/> </bean> ``` client.remote.service.url 是遠程服務暴露的地址;通過相應的 properties 配置文件配置,后續介紹。然后就可以通過 remoteService 獲取會話及角色 / 權限字符串集合了。 **ClientRealm** ``` java public class ClientRealm extends AuthorizingRealm { private RemoteServiceInterface remoteService; private String appKey; public void setRemoteService(RemoteServiceInterface remoteService) { this.remoteService = remoteService; } public void setAppKey(String appKey) { this.appKey = appKey; } protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); PermissionContext context = remoteService.getPermissions(appKey, username); authorizationInfo.setRoles(context.getRoles()); authorizationInfo.setStringPermissions(context.getPermissions()); return authorizationInfo; } protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //永遠不會被調用 throw new UnsupportedOperationException("永遠不會被調用"); } } ``` ClientRealm 提供身份認證信息和授權信息,此處因為是其他應用依賴客戶端,而這些應用不會實現身份認證,所以 doGetAuthenticationInfo 獲取身份認證信息直接無須實現。另外獲取授權信息,是通過遠程暴露的服務 RemoteServiceInterface 獲取,提供 appKey 和用戶名獲取即可。 **ClientSessionDAO** ``` java public class ClientSessionDAO extends CachingSessionDAO { private RemoteServiceInterface remoteService; private String appKey; public void setRemoteService(RemoteServiceInterface remoteService) { this.remoteService = remoteService; } public void setAppKey(String appKey) { this.appKey = appKey; } protected void doDelete(Session session) { remoteService.deleteSession(appKey, session); } protected void doUpdate(Session session) { remoteService.updateSession(appKey, session); } protected Serializable doCreate(Session session) { Serializable sessionId = remoteService.createSession(session); assignSessionId(session, sessionId); return sessionId; } protected Session doReadSession(Serializable sessionId) { return remoteService.getSession(appKey, sessionId); } } ``` Session 的維護通過遠程暴露接口實現,即本地不維護會話。 **ClientAuthenticationFilter** ``` java public class ClientAuthenticationFilter extends AuthenticationFilter { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { Subject subject = getSubject(request, response); return subject.isAuthenticated(); } protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { String backUrl = request.getParameter("backUrl"); saveRequest(request, backUrl, getDefaultBackUrl(WebUtils.toHttp(request))); return false; } protected void saveRequest(ServletRequest request, String backUrl, String fallbackUrl) { Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); HttpServletRequest httpRequest = WebUtils.toHttp(request); session.setAttribute("authc.fallbackUrl", fallbackUrl); SavedRequest savedRequest = new ClientSavedRequest(httpRequest, backUrl); session.setAttribute(WebUtils.SAVED_REQUEST_KEY, savedRequest); } private String getDefaultBackUrl(HttpServletRequest request) { String scheme = request.getScheme(); String domain = request.getServerName(); int port = request.getServerPort(); String contextPath = request.getContextPath(); StringBuilder backUrl = new StringBuilder(scheme); backUrl.append("://"); backUrl.append(domain); if("http".equalsIgnoreCase(scheme) && port != 80) { backUrl.append(":").append(String.valueOf(port)); } else if("https".equalsIgnoreCase(scheme) && port != 443) { backUrl.append(":").append(String.valueOf(port)); } backUrl.append(contextPath); backUrl.append(getSuccessUrl()); return backUrl.toString(); } } ``` ClientAuthenticationFilter 是用于實現身份認證的攔截器(authc),當用戶沒有身份認證時; 1. 首先得到請求參數 backUrl,即登錄成功重定向到的地址; 2. 然后保存保存請求到會話,并重定向到登錄地址(server 模塊); 3. 登錄成功后,返回地址按照如下順序獲取:backUrl、保存的當前請求地址、defaultBackUrl(即設置的 successUrl); **ClientShiroFilterFactoryBean** ``` java public class ClientShiroFilterFactoryBean extends ShiroFilterFactoryBean implements ApplicationContextAware { private ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public void setFiltersStr(String filters) { if(StringUtils.isEmpty(filters)) { return; } String[] filterArray = filters.split(";"); for(String filter : filterArray) { String[] o = filter.split("="); getFilters().put(o[0], (Filter)applicationContext.getBean(o[1])); } } public void setFilterChainDefinitionsStr(String filterChainDefinitions) { if(StringUtils.isEmpty(filterChainDefinitions)) { return; } String[] chainDefinitionsArray = filterChainDefinitions.split(";"); for(String filter : chainDefinitionsArray) { String[] o = filter.split("="); getFilterChainDefinitionMap().put(o[0], o[1]); } } } ``` 1. setFiltersStr:設置攔截器,設置格式如 “filterName=filterBeanName; filterName=filterBeanName”;多個之間分號分隔;然后通過 applicationContext 獲取 filterBeanName 對應的 Bean 注冊到攔截器 Map 中; 2. setFilterChainDefinitionsStr:設置攔截器鏈,設置格式如 “url=filterName1[config],filterName2; url=filterName1[config],filterName2”;多個之間分號分隔; **Shiro 客戶端配置 spring-client.xml** 提供了各應用通用的 Shiro 客戶端配置;這樣應用只需要導入相應該配置即可完成 Shiro 的配置,簡化了整個配置過程。 ``` xml <context:property-placeholder location= "classpath:client/shiro-client-default.properties,classpath:client/shiro-client.properties"/> ``` 提供給客戶端配置的 properties 屬性文件,client/shiro-client-default.properties 是客戶端提供的默認的配置;classpath:client/shiro-client.properties 是用于覆蓋客戶端默認配置,各應用應該提供該配置文件,然后提供各應用個性配置。 ``` xml <bean id="remoteRealm" class="com.github.zhangkaitao.shiro.chapter23.client.ClientRealm"> <property name="cachingEnabled" value="false"/> <property name="appKey" value="${client.app.key}"/> <property name="remoteService" ref="remoteService"/> </bean> ``` appKey:使用 ${client.app.key} 占位符替換,即需要在之前的 properties 文件中配置。 ``` xml <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="${client.session.id}"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="-1"/> <property name="domain" value="${client.cookie.domain}"/> <property name="path" value="${client.cookie.path}"/> </bean> ``` Session Id Cookie,cookie 名字、域名、路徑等都是通過配置文件配置。 ``` xml <bean id="sessionDAO" class="com.github.zhangkaitao.shiro.chapter23.client.ClientSessionDAO"> <property name="sessionIdGenerator" ref="sessionIdGenerator"/> <property name="appKey" value="${client.app.key}"/> <property name="remoteService" ref="remoteService"/> </bean> ``` SessionDAO 的 appKey,也是通過 ${client.app.key} 占位符替換,需要在配置文件配置。 ``` xml <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionValidationSchedulerEnabled" value="false"/>//省略其他 </bean> ``` 其他應用無須進行會話過期調度,所以 sessionValidationSchedulerEnabled=false。 ``` xml <bean id="clientAuthenticationFilter" class="com.github.zhangkaitao.shiro.chapter23.client.ClientAuthenticationFilter"/> ``` 應用的身份認證使用 ClientAuthenticationFilter,即如果沒有身份認證,則會重定向到 Server 模塊完成身份認證,身份認證成功后再重定向回來。 ``` xml <bean id="shiroFilter" class="com.github.zhangkaitao.shiro.chapter23.client.ClientShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="${client.login.url}"/> <property name="successUrl" value="${client.success.url}"/> <property name="unauthorizedUrl" value="${client.unauthorized.url}"/> <property name="filters"> <util:map> <entry key="authc" value-ref="clientAuthenticationFilter"/> </util:map> </property> <property name="filtersStr" value="${client.filters}"/> <property name="filterChainDefinitionsStr" value="${client.filter.chain.definitions}"/> </bean> ``` ShiroFilter 使用我們自定義的 ClientShiroFilterFactoryBean,然后 loginUrl(登錄地址)、successUrl(登錄成功后默認的重定向地址)、unauthorizedUrl(未授權重定向到的地址)通過占位符替換方式配置;另外 filtersStr 和 filterChainDefinitionsStr 也是使用占位符替換方式配置;這樣就可以在各應用進行自定義了。 **默認配置 client/shiro-client-default.properties** ``` properties #各應用的appKey client.app.key= #遠程服務URL地址 client.remote.service.url=http://localhost/chapter23-server/remoteService #登錄地址 client.login.url=http://localhost/chapter23-server/login #登錄成功后,默認重定向到的地址 client.success.url=/ #未授權重定向到的地址 client.unauthorized.url=http://localhost/chapter23-server/unauthorized #session id 域名 client.cookie.domain= #session id 路徑 client.cookie.path=/ #cookie中的session id名稱 client.session.id=sid #cookie中的remember me名稱 client.rememberMe.id=rememberMe #過濾器 name=filter-ref;name=filter-ref client.filters= #過濾器鏈 格式 url=filters;url=filters client.filter.chain.definitions=/**=anon ``` 在各應用中主要配置 client.app.key、client.filters、client.filter.chain.definitions。 ## shiro-example-chapter23-app \* 模塊 繼承 shiro-example-chapter23-pom 模塊 ``` xml <parent> <artifactId>shiro-example-chapter23-pom</artifactId> <groupId>com.github.zhangkaitao</groupId> <version>1.0-SNAPSHOT</version> </parent> ``` **依賴 shiro-example-chapter23-client 模塊** ``` xml <dependency> <groupId>com.github.zhangkaitao</groupId> <artifactId>shiro-example-chapter23-client</artifactId> <version>1.0-SNAPSHOT</version> </dependency> ``` **客戶端配置 client/shiro-client.properties** **配置 shiro-example-chapter23-app1** ``` properties client.app.key=645ba612-370a-43a8-a8e0-993e7a590cf0 client.success.url=/hello client.filter.chain.definitions=/hello=anon;/login=authc;/**=authc ``` client.app.key 是 server 模塊維護的,直接拷貝過來即可;client.filter.chain.definitions 定義了攔截器鏈;比如訪問 / hello,匿名即可。 **配置 shiro-example-chapter23-app2** ``` properties client.app.key=645ba613-370a-43a8-a8e0-993e7a590cf0 client.success.url=/hello client.filter.chain.definitions=/hello=anon;/login=authc;/**=authc ``` 和 app1 類似,client.app.key 是 server 模塊維護的,直接拷貝過來即可;client.filter.chain.definitions 定義了攔截器鏈;比如訪問 / hello,匿名即可。 **web.xml** ``` xml <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:client/spring-client.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> ``` 指定加載客戶端 Shiro 配置,client/spring-client.xml。 ``` 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-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` 配置 ShiroFilter 攔截器。 **控制器** shiro-example-chapter23-app1 ``` java @Controller public class HelloController { @RequestMapping("/hello") public String hello() { return "success"; } @RequestMapping(value = "/attr", method = RequestMethod.POST) public String setAttr( @RequestParam("key") String key, @RequestParam("value") String value) { SecurityUtils.getSubject().getSession().setAttribute(key, value); return "success"; } @RequestMapping(value = "/attr", method = RequestMethod.GET) public String getAttr( @RequestParam("key") String key, Model model) { model.addAttribute("value", SecurityUtils.getSubject().getSession().getAttribute(key)); return "success"; } @RequestMapping("/role1") @RequiresRoles("role1") public String role1() { return "success"; } } ``` shiro-example-chapter23-app2 的控制器類似,role2 方法使用 @RequiresRoles("role2") 注解,即需要角色 2。 其他配置請參考源碼。 ## 測試 **1、安裝配置啟動 nginx** 1、首先到 `http://nginx.org/en/download.html` 下載,比如我下載的是 windows 版本的; 2、然后編輯 conf/nginx.conf 配置文件,在 server 部分添加如下部分: ``` location ~ ^/(chapter23-server)/ { proxy_pass http://127.0.0.1:8080; index /; proxy_set_header Host $host; } location ~ ^/(chapter23-app1)/ { proxy_pass http://127.0.0.1:9080; index /; proxy_set_header Host $host; } location ~ ^/(chapter23-app2)/ { proxy_pass http://127.0.0.1:10080; index /; proxy_set_header Host $host; } ``` 3、最后雙擊 nginx.exe 啟動 Nginx 即可。 已經配置好的 nginx 請到 shiro-example-chapter23-nginx 模塊下下周 nginx-1.5.11.rar 即可。 **2、安裝依賴** 1、首先安裝 shiro-example-chapter23-core 依賴,到 shiro-example-chapter23-core 模塊下運行 mvn install 安裝 core 模塊。 2、接著到 shiro-example-chapter23-client 模塊下運行 mvn install 安裝客戶端模塊。 **3、啟動 Server 模塊** 到 shiro-example-chapter23-server 模塊下運行 mvn jetty:run 啟動該模塊;使用 `http://localhost:8080/chapter23-server/` 即可訪問,因為啟動了 nginx,那么可以直接訪問 `http://localhost/chapter23-server/`。 **4、啟動 App\* 模塊** 到 shiro-example-chapter23-app1 和 shiro-example-chapter23-app2 模塊下分別運行 mvn jetty:run 啟動該模塊;使用 `http://localhost:9080/chapter23-app1/` 和 `http://localhost:10080/chapter23-app2/` 即可訪問,因為啟動了 nginx,那么可以直接訪問 `http://localhost/chapter23-app1/` 和 `http://localhost/chapter23-app2/`。 **5、服務器端維護** 1、訪問 `http://localhost/chapter23-server/`; 2、輸入默認的用戶名密碼:admin/123456 登錄 3、應用管理,進行應用的 CRUD,主要維護應用 KEY(必須唯一)及應用安全碼;客戶端就可以使用應用 KEY 獲取用戶對應應用的權限了。 ![](https://box.kancloud.cn/b3f0a8d9dae52572159485caa02f5f5b_843x190.png) 4、授權管理,維護在哪個應用中用戶的角色列表。這樣客戶端就可以根據應用 KEY 及用戶名獲取到對應的角色 / 權限字符串列表了。 ![](https://box.kancloud.cn/0b9062c78bd474e2945b4a63e68f8d9a_848x195.png) ![](https://box.kancloud.cn/b765a4f00457e6dbe87ab590bf3d598d_579x170.png) \**6、App* 模塊身份認證及授權\*\* 1、在未登錄情況下訪問 `http://localhost/chapter23-app1/hello`,看到下圖: ![](https://box.kancloud.cn/f8cb48c97ffb3fca1b662e35d2508849_228x43.png) 2、登錄地址是 `http://localhost/chapter23-app1/login?backUrl=/chapter23-app1`,即登錄成功后重定向回 `http://localhost/chapter23-app1`(這是個錯誤地址,為了測試登錄成功后重定向地址),點擊登錄按鈕后重定向到 Server 模塊的登錄界面: ![](https://box.kancloud.cn/965316396125ee1bb21028d4041512c6_277x106.png) 3、登錄成功后,會重定向到相應的登錄成功地址;接著訪問 `http://localhost/chapter23-app1/hello`,看到如下圖: ![](https://box.kancloud.cn/fbdf758d751042a5832306eccee97dd7_513x276.png) 4、可以看到 admin 登錄,及其是否擁有 role1/role2 角色;可以在 server 模塊移除 role1 角色或添加 role2 角色看看頁面變化; 5、可以在 `http://localhost/chapter23-app1/hello` 頁面設置屬性,如 key=123;接著訪問 `http://localhost/chapter23-app2/attr?key=key` 就可以看到剛才設置的屬性,如下圖: ![](https://box.kancloud.cn/07f8e4b665b4da60fb1f8aa5c16c5def_528x278.png) 另外在 app2,用戶默認擁有 role2 角色,而沒有 role1 角色。 到此整個測試就完成了,可以看出本示例實現了:會話的分布式及權限的集中管理。 ## 本示例缺點 1. 沒有加緩存; 2. 客戶端每次獲取會話 / 權限都需要通過客戶端訪問服務端;造成服務端單點和請求壓力大;單點可以考慮使用集群來解決;請求壓力大需要考慮配合緩存服務器(如 Redis)來解決;即每次會話 / 權限獲取時首先查詢緩存中是否存在,如果有直接獲取即可;否則再查服務端;降低請求壓力; 3. 會話的每次更新(比如設置屬性 / 更新最后訪問時間戳)都需要同步到服務端;也造成了請求壓力過大;可以考慮在請求的最后只同步一次會話(需要對 Shiro 會話進行改造,通過如攔截器在執行完請求后完成同步,這樣每次請求只同步一次); 4. 只能同域名才能使用,即會話 ID 是從同一個域名下獲取,如果跨域請考慮使用 CAS/OAuth2 之實現。 所以實際應用時可能還是需要改造的,但大體思路是差不多的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看