內容安排:
1、權限概述(認證、授權)
2、常見的權限控制的方式(URL攔截、方法註解)
3、權限的數據模型(權限表、角色表、用戶表、角色權限關係表、用戶角色關係表)
4、apache shiro框架
5、將shiro應用到bos項目中(認證、授權)
1. 權限概述
系統中提供了很多功能,並不是所有的用戶都可以操作這些功能,需要對功能的訪問進行控制。
認證:系統提供的用於識別用戶身份的功能,通常是登錄功能(你是誰???)
授權:系統提供的賦予用戶可以操作系統某些功能能力(你能做什麼???)
菜單和按鈕是訪問某個功能的入口,都是發起一次請求,由服務端進行相應的操作。
2. 常見的權限控制方式
2.1 URL攔截進行權限校驗
![](http://static.javashuo.com/static/loading.gif)
2.2 方法註解權限控制
![](http://static.javashuo.com/static/loading.gif)
3. 權限的數據模型
權限表
角色表:引入角色是爲了簡化授權
用戶表
角色權限關係表(多對多中間表)
用戶角色關係表(多對多中間表)
![](http://static.javashuo.com/static/loading.gif)
關鍵字:註解後面可以使用
路徑:訪問action的路徑
菜單:有些資源會掛到菜單上
優先級:用戶菜單排序
父權限編號:權限的上下級,指向本表的主鍵
實體類
權限實體
- /**
- * 權限實體
- *
- */
- public class Function implements java.io.Serializable {
-
- // Fields
-
- private String id;//編號
- private Function parentFunction;//當前權限對應的上一級權限
- private String name;//權限名稱
- private String code;//關鍵字
- private String description;//描述
- private String page;//訪問URL
- private String generatemenu;//當前權限是否生成到菜單 1:生成 0:不生成
- private Integer zindex;//排序
- private Set children = new HashSet(0);//當前權限對應的多個下級權限
- private Set roles = new HashSet(0);//當前權限對應的多個角色
角色實體
- /**
- * 角色實體
- *
- */
- public class Role implements java.io.Serializable {
-
- // Fields
-
- private String id;//編號
- private String name;//角色名稱
- private String code;//關鍵字
- private String description;//描述
- private Set functions = new HashSet(0);//當前角色對應的多個權限
- private Set users = new HashSet(0);//當前角色對應的多個用戶
用戶
- public class User implements java.io.Serializable {
-
- private String id;
- private String username;
- private String password;
- private Double salary;
- private Timestamp birthday;
- private String gender;
- private String station;
- private String telephone;
- private String remark;
- private Set noticebills = new HashSet(0);
- private Set roles = new HashSet(0);//當前用戶對應的多個角色
4. apache shiro框架
提供的進行權限控制的方式:
1、URL攔截進行權限控制
2、方法註解權限控制
3、頁面標籤權限控制
4、代碼方式權限控制(瞭解)
shiro提供的功能:
![](http://static.javashuo.com/static/loading.gif)
Shiro的4大部分——身份驗證,授權,會話管理和加密
• Authentication:身份驗證,簡稱「登錄」。
• Authorization:授權,給用戶分配角色或者權限資源
• Session Management:用戶session管理器,可以讓CS程序也使用session來控制權限
• Cryptography:把JDK中複雜的密碼加密方式進行封裝
除了以上功能,shiro還提供很多擴展
• Web Support:主要針對web應用提供一些常用功能。
• Caching:緩存可以使應用程序運行更有效率。
• Concurrency:多線程相關功能。
• Testing:幫助我們進行測試相關功能
• "Run As":一個允許用戶假設爲另一個用戶身份(如果允許)的功能,有時候在管理腳本很有用。
• 「Remember Me」:記住用戶身份,提供類似購物車功能。
shiro框架運行流程圖:
![](http://static.javashuo.com/static/loading.gif)
Application Code:應用程序代碼,由開發人員負責
Subject:
由框架提供的,是與程序進行交互的對象,可以是人也可以是服務或者其他,通常就理解爲用戶。
所有Subject 實例都必須綁定到一個SecurityManager上。我們與一個 Subject
交互,運行時shiro會自動轉化爲與 SecurityManager交互的特定 subject的交互
SecurityManager:
安全管理器,由框架提供的,SecurityManager 是 Shiro的核心,初始化時協調各個模塊運行。然而,一旦 SecurityManager協調完畢,SecurityManager 會被單獨留下,且我們只需要去操作Subject即可,無需操作SecurityManager 。 但是我們得知道,當我們正與一個 Subject 進行交互時,實質上是 SecurityManager在處理 Subject 安全操作。
Realm:
安全數據橋,類似於項目中的DAO,訪問安全數據的,框架提供,開發人員也可自己編寫
Realms在 Shiro中作爲應用程序和安全數據之間的「橋樑」或「連接器」。他獲取安全數據來判斷subject是否能夠登錄,subject擁有什麼權限。他有點類似DAO。在配置realms時,需要至少一個realm。而且Shiro提供了一些常用的 Realms來連接數據源,如LDAP數據源的JndiLdapRealm,JDBC數據源的JdbcRealm,ini文件數據源的IniRealm,properties文件數據源的PropertiesRealm,等等。我們也可以插入自己的 Realm實現來代表自定義的數據源。 像其他組件一樣,Realms也是由SecurityManager控制
小結
![](http://static.javashuo.com/static/loading.gif)
1.Subject(org.apache.shiro.subject.Subject):簡稱用戶
2.SecurityManager(org.apache.shiro.mgt.SecurityManager)
如上所述,SecurityManager是shiro的核心,協調shiro的各個組件
3.Authenticator(org.apache.shiro.authc.Authenticator):
登錄控制
注:AuthenticationStrategy
(org.apache.shiro.authc.pam.AuthenticationStrategy)
如果存在多個realm,則接口AuthenticationStrategy會確定什麼樣算是登錄成功(例如,如果一個Realm成功,而其他的均失敗,是否登錄成功?)。
4.Authorizer(org.apache.shiro.authz.Authorizer):
決定subject能擁有什麼樣角色或者權限。
5.SessionManager(org.apache.shiro.session.SessionManager):
創建和管理用戶session。通過設置這個管理器,shiro可以在任何環境下使用session。
6.CacheManager(org.apahce.shiro.cache.CacheManager):
緩存管理器,可以減少不必要的後臺訪問。提高應用效率,增加用戶體驗。
7.Cryptography(org.apache.shiro.crypto.*) :
Shiro的api大幅度簡化Java api中繁瑣的密碼加密。
8.Realms(org.apache.shiro.realm.Realm) :程序與安全數據的橋樑
5.將shiro應用到bos項目中
因爲官方例子雖然中有更加簡潔的ini配置形式,但是使用ini配置無法與spring整合
第一步:導入shiro的jar包到項目中
shiro-all-1.2.2.jar
第二步:在web.xml中配置一個過濾器,是由spring提供的,用於整合shiro
注意:配置到struts2核心過濾器的前面
filter-name 與spring xml 配置的 bean id一致
org.springframework.web.filter.DelegatingFilterProxy
- <!-- 配置spring提供的過濾器,用於整合shiro框架 ,
- 在項目啓動過程中,當前過濾器會從spring工廠中獲取一個和當前過濾器同名的bean對象-->
- <filter>
- <filter-name>shiroFilter</filter-name>
- <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>shiroFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
第三步:在applicationContext.xml中配置bean,ID必須爲shiroFilter
![](http://static.javashuo.com/static/loading.gif)
org.apache.shiro.spring.web.ShiroFilterFactoryBean
- <!-- 配置shiro的過濾器對象,當前對象用於創建shiro框架需要的過濾器對象 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <!-- 注入安全管理器 -->
- <property name="securityManager" ref="securityManager"/>
- <!-- 注入系統的登錄訪問路徑 -->
- <property name="loginUrl" value="/login.jsp"/>
- <!-- 成功頁面 -->
- <property name="successUrl" value="/index.jsp"/>
- <!-- 權限不足提示頁面 -->
- <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>
- <!-- 基於URL攔截權限控制 -->
- <property name="filterChainDefinitions">
- <value>
- /css/** = anon
- /js/** = anon
- /images/** = anon
- /validatecode.jsp* = anon
- /login.jsp* = anon
- /userAction_login.action = anon
- /page_base_staff.action = perms["staff"]
- /* = authc
- </value>
- </property>
- </bean>
securityManager:這個屬性是必須的。
![](http://static.javashuo.com/static/loading.gif)
安全管理器配置代碼
- <!-- 定義安全管理器 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <!-- 注入realm -->
- <property name="realm" ref="bosRealm"></property>
- </bean>
-
- <!-- 自定義Realm -->
- <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>
loginUrl :沒有登錄的用戶請求需要登錄的頁面時自動跳轉到登錄頁面,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的」/login.jsp」頁面。
successUrl :登錄成功默認跳轉頁面,不配置則跳轉至」/」。如果登陸前點擊的一個需要登錄的頁面,則在登錄自動跳轉到那個需要登錄的頁面。不跳轉到此。
unauthorizedUrl :沒有權限默認跳轉的頁面。
過濾器
![](http://static.javashuo.com/static/loading.gif)
anon:
例子/admins/**=anon沒有參數,表示可以匿名使用。
authc:
例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數
roles:
例子/admins/user/**=roles[admin],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過纔算通過,相當於hasAllRoles()方法。
perms:
例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。
Rest:
例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method],其中method爲post,get,delete等。
port:
例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString
是你訪問的url裏的?後面的參數。
authcBasic:
例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:
例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https
user:
例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查
注:
anon,authcBasic,auchc,user是認證過濾器,
perms,roles,ssl,rest,port是授權過濾器
第四步:修改UserAction中的login方法,使用shiro提供的方式進行認證
過程:
1.通過subjec對象的login方法進行認證
2.Subject會調用securityManager安全管理器,安全管理器會調用Realm
3.securityManager安全管理器不是通過返回參數的方式而是通過拋異常來做出判定結果
用戶名不存在異常:
org.apache.shiro.authc.UnknownAccountException
密碼錯誤異常:
org.apache.shiro.authc.IncorrectCredentialsException
代碼
- //提供屬性接收驗證碼
- private String checkcode;
- public void setCheckcode(String checkcode) {
- this.checkcode = checkcode;
- }
- /**
- * 使用shiro方式進行認證
- */
- public String login(){
- //從session中獲取自動生成的驗證碼
- String key = (String) ActionContext.getContext().getSession().get("key");
- if(StringUtils.isNotBlank(checkcode) && checkcode.equals(key)){
- //使用shiro方式進行認證
- String username = model.getUsername();
- String password = model.getPassword();
- password = MD5Utils.md5(password);
- Subject subject = SecurityUtils.getSubject();//主體,當前狀態爲「未認證」狀態
- AuthenticationToken token = new UsernamePasswordToken(username, password);//用戶名密碼令牌
- try{
- subject.login(token);//使用subject調用SecurityManager,安全管理器調用Realm
- User user = (User) subject.getPrincipal();
- //登錄成功,將User對象放入session
- ActionContext.getContext().getSession().put("loginUser", user);
- }catch (UnknownAccountException e) {//用戶名不存在異常
- e.printStackTrace();
- return "login";
- }catch (IncorrectCredentialsException e) {//密碼錯誤異常
- e.printStackTrace();
- return "login";
- }
- return "home";
- }else{
- //驗證碼有誤,添加錯誤信息,跳轉到登錄頁面
- this.addActionError(this.getText("checkcodeError"));
- return "login";
- }
- }
第五步:自定義一個Realm,實現認證方法
![](http://static.javashuo.com/static/loading.gif)
繼承抽象類 重寫認證與授權方法
- public class BOSRealm extends AuthorizingRealm {
- // 注入dao
- @Resource
- private IUserDao userDao;
-
- // 認證方法
- protected AuthenticationInfo doGetAuthenticationInfo(
- AuthenticationToken token) throws AuthenticationException {
- System.out.println("認證方法。。。。");
- UsernamePasswordToken pwdToken = (UsernamePasswordToken) token;
- String username = pwdToken.getUsername();
- // 根據用戶名查詢密碼,由安全管理器負責比對查詢出的數據庫中的密碼和頁面輸入的密碼是否一致
- User user = userDao.findUserByUsername(username);
- if(user == null){
- return null;
- }
- String dbPassword = user.getPassword();
- //參數一:
- AuthenticationInfo info = new SimpleAuthenticationInfo(user,
- dbPassword, this.getClass().getSimpleName());
- return info;
- }
-
- //授權方法
- protected AuthorizationInfo doGetAuthorizationInfo(
- PrincipalCollection principals) {
- //根據當前登陸用戶,查詢當前用戶的角色,根據角色獲取相應的權限,把權限添加到授權信息對象中
- //三種獲得當前登陸用戶的方式
- User user1 = (User) principals.getPrimaryPrincipal();
- User user2 = (User) SecurityUtils.getSubject().getPrincipal();
- //User user3 = (User) ActionContext.getContext().getSession().get("session_user");
-
-
- //由於添加權限 角色的 相應方法還沒學習編碼 暫用僞代碼添加權限
- //spring是shrio配置中filterChainDefinitions 添加了/page_base_staff.action=perms["staff"]
- SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
- info.addStringPermission("staff");
- // info.addStringPermission("region.query");
-
- return info;
-
- }
第六步:在applicationContext.xml中註冊Realm,並注入給安全管理器
- <!-- 定義安全管理器 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <!-- 注入realm -->
- <property name="realm" ref="bosRealm"></property>
- </bean>
-
- <!-- 自定義Realm -->
- <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>
第七步:在自定義Realm中實現授權方法
*授權方法執行的時機:爲當訪問的路徑需要權限認證的時候,才執行授權
*這樣每次訪問權限路徑都執行一次授權方法,效率很低,需要增加緩存管理器
- public class BosRealm extends AuthorizingRealm{
- @Resource
- private IUserDao userDao;
- @Resource
- private IRoleDao roleDao;
- @Resource
- private IFunctionDao functionDao;
-
- //認證方法
- protected AuthenticationInfo doGetAuthenticationInfo(
- AuthenticationToken token) throws AuthenticationException {
- System.out.println("認證方法。。。");
-
- UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
- String username = usernamePasswordToken.getUsername();
-
- //1 根據用戶名查詢密碼
- User user = userDao.findUserByUserName(username);
- if(user == null){
- // 用戶名不存在
- return null;
- }
-
- //2 返回AuthenticationInfo對象
- Object principal = user;//將當前查詢到的用戶對象放入SimpleAuthenticationInfo中,可以通過subject獲得
- Object credentials = user.getPassword();//密碼,shiro負責比較查詢到的密碼和用戶輸入的密碼是否一致
- String realmName = super.getName();
- AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, credentials, realmName);
- return authenticationInfo;
- }
-
- //授權方法
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- System.out.println("授權...");
- // 獲取當前登陸用戶 ,根據當前登陸用戶,查詢對應角色信息
- Subject subject = SecurityUtils.getSubject();
- User user = (User) subject.getPrincipal();
-
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- if (user.getUsername().equals("admin")) {
- // 如果admin ,查詢所有角色和所有權限
- List<Role> roles = roleDao.findAll();
- for (Role role : roles) {
- authorizationInfo.addRole(role.getCode());
- }
- List<Function> functions = functionDao.findAll();
- for (Function function : functions) {
- authorizationInfo.addStringPermission(function.getCode());
- }
- } else {
- // 普通用戶 , 根據當前用戶,查詢具有角色,通過角色獲取權限
- List<Role> roles = roleDao.findRolesByUser(user);
- // 添加角色
- for (Role role : roles) {
- authorizationInfo.addRole(role.getCode());
- // 添加角色對應權限
- Set<Function> functions = role.getFunctions();
- for (Function function : functions) {
- authorizationInfo.addStringPermission(function.getCode());
- }
- }
- }
- return authorizationInfo;
- }
-
- protected AuthorizationInfo doGetAuthorizationInfo_bak(
- PrincipalCollection principals) {
- System.out.println("授權方法。。。");
- User user = (User) principals.getPrimaryPrincipal();
- System.out.println(user);
- //根據當前登錄用戶查詢對應的權限和角色
-
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- authorizationInfo.addStringPermission("staff:query");
- authorizationInfo.addStringPermission("abc");
- authorizationInfo.addRole("admin");
- return authorizationInfo;
- }
-
- }
6. 基於shiro註解實現權限控制
第一步:在spring配置文件中開啓shiro註解功能
- <!-- 開啓shiro註解自動代理 -->
- <bean id="defaultAdvisorAutoProxyCreator"
- class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
- <!-- 指定強制使用cglib爲Action創建代理對象 -->
- <property name="proxyTargetClass" value="true"/>
- </bean>
-
- <!-- 切面 -->
- <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>
第二步:在Action方法上使用註解進行權限控制
- /**
- * 分頁查詢方法
- * @throws IOException
- */
- @RequiresPermissions(value="region.query")//執行這個方法需要region.query權限
- //@RequiresRoles(value="admin")
- public String pageQuery() throws IOException{
- regionService.pageQuery(pageBean);
- this.writePageBean2Json(pageBean, new String[]{"currentPage","pageSize","detachedCriteria","subareas"});
- return NONE;
- }
控制精度:
註解方式控制權限只能是在方法上控制,無法控制類級別訪問。
過濾器方式控制是根據訪問的URL進行控制。允許使用*匹配URL,所以可以進行粗粒度,也可以進行細粒度控制。
其他註解
@RequiresAuthentication
驗證用戶是否登錄,等同於方法subject.isAuthenticated() 結果爲true時。
@ RequiresUser
驗證用戶是否被記憶,user有兩種含義:
一種是成功登錄的(subject.isAuthenticated() 結果爲true);
另外一種是被記憶的( subject.isRemembered()結果爲true)。
@ RequiresGuest
驗證是否是一個guest的請求,與@ RequiresUser完全相反。
換言之,RequiresUser == ! RequiresGuest 。
此時subject.getPrincipal() 結果爲null.
@ RequiresRoles
例如:@RequiresRoles("aRoleName");
void someMethod();
如果subject中有aRoleName角色纔可以訪問方法someMethod。如果沒有這個權限則會拋出異常AuthorizationException。
@RequiresPermissions
例如: @RequiresPermissions( {"file:read", "write:aFile.txt"} ) void someMethod();
要求subject中必須同時含有file:read和write:aFile.txt的權限才能執行方法someMethod()。否則拋出異常AuthorizationException。
第三步:修改BaseAction的構造方法
- // 構造方法
- @SuppressWarnings("unchecked")
- public BaseAction() {
- //獲取父類類型(baseAction)
- ParameterizedType superclass = null;
- Type type = this.getClass().getGenericSuperclass();
- //獲取父類泛型 強制轉化參數類型 需要判斷 生成action是否使用代理 既父類是否是參數化類型
- if(type instanceof ParameterizedType){
- //沒有使用代理直接強制轉換
- superclass = (ParameterizedType) type;
- }else{
- //type 不屬於參數化類型 則action是cglib代理生成
- //CGLib是針對目標類生產子類,需要獲取父類的父類的類型再強轉成參數化類型
- superclass = (ParameterizedType) this.getClass().getSuperclass().getGenericSuperclass();
- }
- ...
第四步:在自定義Realm中授權
略
內容安排:
1、權限概述(認證、授權)
2、常見的權限控制的方式(URL攔截、方法註解)
3、權限的數據模型(權限表、角色表、用戶表、角色權限關係表、用戶角色關係表)
4、apache shiro框架
5、將shiro應用到bos項目中(認證、授權)
1. 權限概述
系統中提供了很多功能,並不是所有的用戶都可以操作這些功能,需要對功能的訪問進行控制。
認證:系統提供的用於識別用戶身份的功能,通常是登錄功能(你是誰???)
授權:系統提供的賦予用戶可以操作系統某些功能能力(你能做什麼???)
菜單和按鈕是訪問某個功能的入口,都是發起一次請求,由服務端進行相應的操作。
2. 常見的權限控制方式
2.1 URL攔截進行權限校驗
![](http://static.javashuo.com/static/loading.gif)
2.2 方法註解權限控制
![](http://static.javashuo.com/static/loading.gif)
3. 權限的數據模型
權限表
角色表:引入角色是爲了簡化授權
用戶表
角色權限關係表(多對多中間表)
用戶角色關係表(多對多中間表)
![](http://static.javashuo.com/static/loading.gif)
關鍵字:註解後面可以使用
路徑:訪問action的路徑
菜單:有些資源會掛到菜單上
優先級:用戶菜單排序
父權限編號:權限的上下級,指向本表的主鍵
實體類
權限實體
- /**
- * 權限實體
- *
- */
- public class Function implements java.io.Serializable {
-
- // Fields
-
- private String id;//編號
- private Function parentFunction;//當前權限對應的上一級權限
- private String name;//權限名稱
- private String code;//關鍵字
- private String description;//描述
- private String page;//訪問URL
- private String generatemenu;//當前權限是否生成到菜單 1:生成 0:不生成
- private Integer zindex;//排序
- private Set children = new HashSet(0);//當前權限對應的多個下級權限
- private Set roles = new HashSet(0);//當前權限對應的多個角色
角色實體
- /**
- * 角色實體
- *
- */
- public class Role implements java.io.Serializable {
-
- // Fields
-
- private String id;//編號
- private String name;//角色名稱
- private String code;//關鍵字
- private String description;//描述
- private Set functions = new HashSet(0);//當前角色對應的多個權限
- private Set users = new HashSet(0);//當前角色對應的多個用戶
用戶
- public class User implements java.io.Serializable {
-
- private String id;
- private String username;
- private String password;
- private Double salary;
- private Timestamp birthday;
- private String gender;
- private String station;
- private String telephone;
- private String remark;
- private Set noticebills = new HashSet(0);
- private Set roles = new HashSet(0);//當前用戶對應的多個角色
4. apache shiro框架
提供的進行權限控制的方式:
1、URL攔截進行權限控制
2、方法註解權限控制
3、頁面標籤權限控制
4、代碼方式權限控制(瞭解)
shiro提供的功能:
![](http://static.javashuo.com/static/loading.gif)
Shiro的4大部分——身份驗證,授權,會話管理和加密
• Authentication:身份驗證,簡稱「登錄」。
• Authorization:授權,給用戶分配角色或者權限資源
• Session Management:用戶session管理器,可以讓CS程序也使用session來控制權限
• Cryptography:把JDK中複雜的密碼加密方式進行封裝
除了以上功能,shiro還提供很多擴展
• Web Support:主要針對web應用提供一些常用功能。
• Caching:緩存可以使應用程序運行更有效率。
• Concurrency:多線程相關功能。
• Testing:幫助我們進行測試相關功能
• "Run As":一個允許用戶假設爲另一個用戶身份(如果允許)的功能,有時候在管理腳本很有用。
• 「Remember Me」:記住用戶身份,提供類似購物車功能。
shiro框架運行流程圖:
![](http://static.javashuo.com/static/loading.gif)
Application Code:應用程序代碼,由開發人員負責
Subject:
由框架提供的,是與程序進行交互的對象,可以是人也可以是服務或者其他,通常就理解爲用戶。
所有Subject 實例都必須綁定到一個SecurityManager上。我們與一個 Subject
交互,運行時shiro會自動轉化爲與 SecurityManager交互的特定 subject的交互
SecurityManager:
安全管理器,由框架提供的,SecurityManager 是 Shiro的核心,初始化時協調各個模塊運行。然而,一旦 SecurityManager協調完畢,SecurityManager 會被單獨留下,且我們只需要去操作Subject即可,無需操作SecurityManager 。 但是我們得知道,當我們正與一個 Subject 進行交互時,實質上是 SecurityManager在處理 Subject 安全操作。
Realm:
安全數據橋,類似於項目中的DAO,訪問安全數據的,框架提供,開發人員也可自己編寫
Realms在 Shiro中作爲應用程序和安全數據之間的「橋樑」或「連接器」。他獲取安全數據來判斷subject是否能夠登錄,subject擁有什麼權限。他有點類似DAO。在配置realms時,需要至少一個realm。而且Shiro提供了一些常用的 Realms來連接數據源,如LDAP數據源的JndiLdapRealm,JDBC數據源的JdbcRealm,ini文件數據源的IniRealm,properties文件數據源的PropertiesRealm,等等。我們也可以插入自己的 Realm實現來代表自定義的數據源。 像其他組件一樣,Realms也是由SecurityManager控制
小結
![](http://static.javashuo.com/static/loading.gif)
1.Subject(org.apache.shiro.subject.Subject):簡稱用戶
2.SecurityManager(org.apache.shiro.mgt.SecurityManager)
如上所述,SecurityManager是shiro的核心,協調shiro的各個組件
3.Authenticator(org.apache.shiro.authc.Authenticator):
登錄控制
注:AuthenticationStrategy
(org.apache.shiro.authc.pam.AuthenticationStrategy)
如果存在多個realm,則接口AuthenticationStrategy會確定什麼樣算是登錄成功(例如,如果一個Realm成功,而其他的均失敗,是否登錄成功?)。
4.Authorizer(org.apache.shiro.authz.Authorizer):
決定subject能擁有什麼樣角色或者權限。
5.SessionManager(org.apache.shiro.session.SessionManager):
創建和管理用戶session。通過設置這個管理器,shiro可以在任何環境下使用session。
6.CacheManager(org.apahce.shiro.cache.CacheManager):
緩存管理器,可以減少不必要的後臺訪問。提高應用效率,增加用戶體驗。
7.Cryptography(org.apache.shiro.crypto.*) :
Shiro的api大幅度簡化Java api中繁瑣的密碼加密。
8.Realms(org.apache.shiro.realm.Realm) :程序與安全數據的橋樑
5.將shiro應用到bos項目中
因爲官方例子雖然中有更加簡潔的ini配置形式,但是使用ini配置無法與spring整合
第一步:導入shiro的jar包到項目中
shiro-all-1.2.2.jar
第二步:在web.xml中配置一個過濾器,是由spring提供的,用於整合shiro
注意:配置到struts2核心過濾器的前面
filter-name 與spring xml 配置的 bean id一致
org.springframework.web.filter.DelegatingFilterProxy
- <!-- 配置spring提供的過濾器,用於整合shiro框架 ,
- 在項目啓動過程中,當前過濾器會從spring工廠中獲取一個和當前過濾器同名的bean對象-->
- <filter>
- <filter-name>shiroFilter</filter-name>
- <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>shiroFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
第三步:在applicationContext.xml中配置bean,ID必須爲shiroFilter
![](http://static.javashuo.com/static/loading.gif)
org.apache.shiro.spring.web.ShiroFilterFactoryBean
- <!-- 配置shiro的過濾器對象,當前對象用於創建shiro框架需要的過濾器對象 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <!-- 注入安全管理器 -->
- <property name="securityManager" ref="securityManager"/>
- <!-- 注入系統的登錄訪問路徑 -->
- <property name="loginUrl" value="/login.jsp"/>
- <!-- 成功頁面 -->
- <property name="successUrl" value="/index.jsp"/>
- <!-- 權限不足提示頁面 -->
- <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>
- <!-- 基於URL攔截權限控制 -->
- <property name="filterChainDefinitions">
- <value>
- /css/** = anon
- /js/** = anon
- /images/** = anon
- /validatecode.jsp* = anon
- /login.jsp* = anon
- /userAction_login.action = anon
- /page_base_staff.action = perms["staff"]
- /* = authc
- </value>
- </property>
- </bean>
securityManager:這個屬性是必須的。
![](http://static.javashuo.com/static/loading.gif)
安全管理器配置代碼
- <!-- 定義安全管理器 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <!-- 注入realm -->
- <property name="realm" ref="bosRealm"></property>
- </bean>
-
- <!-- 自定義Realm -->
- <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>
loginUrl :沒有登錄的用戶請求需要登錄的頁面時自動跳轉到登錄頁面,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的」/login.jsp」頁面。
successUrl :登錄成功默認跳轉頁面,不配置則跳轉至」/」。如果登陸前點擊的一個需要登錄的頁面,則在登錄自動跳轉到那個需要登錄的頁面。不跳轉到此。
unauthorizedUrl :沒有權限默認跳轉的頁面。
過濾器
![](http://static.javashuo.com/static/loading.gif)
anon:
例子/admins/**=anon沒有參數,表示可以匿名使用。
authc:
例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數
roles:
例子/admins/user/**=roles[admin],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過纔算通過,相當於hasAllRoles()方法。
perms:
例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。
Rest:
例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method],其中method爲post,get,delete等。
port:
例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString
是你訪問的url裏的?後面的參數。
authcBasic:
例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:
例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https
user:
例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查
注:
anon,authcBasic,auchc,user是認證過濾器,
perms,roles,ssl,rest,port是授權過濾器
第四步:修改UserAction中的login方法,使用shiro提供的方式進行認證
過程:
1.通過subjec對象的login方法進行認證
2.Subject會調用securityManager安全管理器,安全管理器會調用Realm
3.securityManager安全管理器不是通過返回參數的方式而是通過拋異常來做出判定結果
用戶名不存在異常:
org.apache.shiro.authc.UnknownAccountException
密碼錯誤異常:
org.apache.shiro.authc.IncorrectCredentialsException
代碼
- //提供屬性接收驗證碼
- private String checkcode;
- public void setCheckcode(String checkcode) {
- this.checkcode = checkcode;
- }
- /**
- * 使用shiro方式進行認證
- */
- public String login(){
- //從session中獲取自動生成的驗證碼
- String key = (String) ActionContext.getContext().getSession().get("key");
- if(StringUtils.isNotBlank(checkcode) && checkcode.equals(key)){
- //使用shiro方式進行認證
- String username = model.getUsername();
- String password = model.getPassword();
- password = MD5Utils.md5(password);
- Subject subject = SecurityUtils.getSubject();//主體,當前狀態爲「未認證」狀態
- AuthenticationToken token = new UsernamePasswordToken(username, password);//用戶名密碼令牌
- try{
- subject.login(token);//使用subject調用SecurityManager,安全管理器調用Realm
- User user = (User) subject.getPrincipal();
- //登錄成功,將User對象放入session
- ActionContext.getContext().getSession().put("loginUser", user);
- }catch (UnknownAccountException e) {//用戶名不存在異常
- e.printStackTrace();
- return "login";
- }catch (IncorrectCredentialsException e) {//密碼錯誤異常
- e.printStackTrace();
- return "login";
- }
- return "home";
- }else{
- //驗證碼有誤,添加錯誤信息,跳轉到登錄頁面
- this.addActionError(this.getText("checkcodeError"));
- return "login";
- }
- }
第五步:自定義一個Realm,實現認證方法
![](http://static.javashuo.com/static/loading.gif)
繼承抽象類 重寫認證與授權方法
- public class BOSRealm extends AuthorizingRealm {
- // 注入dao
- @Resource
- private IUserDao userDao;
-
- // 認證方法
- protected AuthenticationInfo doGetAuthenticationInfo(
- AuthenticationToken token) throws AuthenticationException {
- System.out.println("認證方法。。。。");
- UsernamePasswordToken pwdToken = (UsernamePasswordToken) token;
- String username = pwdToken.getUsername();
- // 根據用戶名查詢密碼,由安全管理器負責比對查詢出的數據庫中的密碼和頁面輸入的密碼是否一致
- User user = userDao.findUserByUsername(username);
- if(user == null){
- return null;
- }
- String dbPassword = user.getPassword();
- //參數一:
- AuthenticationInfo info = new SimpleAuthenticationInfo(user,
- dbPassword, this.getClass().getSimpleName());
- return info;
- }
-
- //授權方法
- protected AuthorizationInfo doGetAuthorizationInfo(
- PrincipalCollection principals) {
- //根據當前登陸用戶,查詢當前用戶的角色,根據角色獲取相應的權限,把權限添加到授權信息對象中
- //三種獲得當前登陸用戶的方式
- User user1 = (User) principals.getPrimaryPrincipal();
- User user2 = (User) SecurityUtils.getSubject().getPrincipal();
- //User user3 = (User) ActionContext.getContext().getSession().get("session_user");
-
-
- //由於添加權限 角色的 相應方法還沒學習編碼 暫用僞代碼添加權限
- //spring是shrio配置中filterChainDefinitions 添加了/page_base_staff.action=perms["staff"]
- SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
- info.addStringPermission("staff");
- // info.addStringPermission("region.query");
-
- return info;
-
- }
第六步:在applicationContext.xml中註冊Realm,並注入給安全管理器
- <!-- 定義安全管理器 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <!-- 注入realm -->
- <property name="realm" ref="bosRealm"></property>
- </bean>
-
- <!-- 自定義Realm -->
- <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>
第七步:在自定義Realm中實現授權方法
*授權方法執行的時機:爲當訪問的路徑需要權限認證的時候,才執行授權
*這樣每次訪問權限路徑都執行一次授權方法,效率很低,需要增加緩存管理器
- public class BosRealm extends AuthorizingRealm{
- @Resource
- private IUserDao userDao;
- @Resource
- private IRoleDao roleDao;
- @Resource
- private IFunctionDao functionDao;
-
- //認證方法
- protected AuthenticationInfo doGetAuthenticationInfo(
- AuthenticationToken token) throws AuthenticationException {
- System.out.println("認證方法。。。");
-
- UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
- String username = usernamePasswordToken.getUsername();
-
- //1 根據用戶名查詢密碼
- User user = userDao.findUserByUserName(username);
- if(user == null){
- // 用戶名不存在
- return null;
- }
-
- //2 返回AuthenticationInfo對象
- Object principal = user;//將當前查詢到的用戶對象放入SimpleAuthenticationInfo中,可以通過subject獲得
- Object credentials = user.getPassword();//密碼,shiro負責比較查詢到的密碼和用戶輸入的密碼是否一致
- String realmName = super.getName();
- AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, credentials, realmName);
- return authenticationInfo;
- }
-
- //授權方法
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- System.out.println("授權...");
- // 獲取當前登陸用戶 ,根據當前登陸用戶,查詢對應角色信息
- Subject subject = SecurityUtils.getSubject();
- User user = (User) subject.getPrincipal();
-
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- if (user.getUsername().equals("admin")) {
- // 如果admin ,查詢所有角色和所有權限
- List<Role> roles = roleDao.findAll();
- for (Role role : roles) {
- authorizationInfo.addRole(role.getCode());
- }
- List<Function> functions = functionDao.findAll();
- for (Function function : functions) {
- authorizationInfo.addStringPermission(function.getCode());
- }
- } else {
- // 普通用戶 , 根據當前用戶,查詢具有角色,通過角色獲取權限
- List<Role> roles = roleDao.findRolesByUser(user);
- // 添加角色
- for (Role role : roles) {
- authorizationInfo.addRole(role.getCode());
- // 添加角色對應權限
- Set<Function> functions = role.getFunctions();
- for (Function function : functions) {
- authorizationInfo.addStringPermission(function.getCode());
- }
- }
- }
- return authorizationInfo;
- }
-
- protected AuthorizationInfo doGetAuthorizationInfo_bak(
- PrincipalCollection principals) {
- System.out.println("授權方法。。。");
- User user = (User) principals.getPrimaryPrincipal();
- System.out.println(user);
- //根據當前登錄用戶查詢對應的權限和角色
-
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- authorizationInfo.addStringPermission("staff:query");
- authorizationInfo.addStringPermission("abc");
- authorizationInfo.addRole("admin");
- return authorizationInfo;
- }
-
- }
6. 基於shiro註解實現權限控制
第一步:在spring配置文件中開啓shiro註解功能
- <!-- 開啓shiro註解自動代理 -->
- <bean id="defaultAdvisorAutoProxyCreator"
- class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
- <!-- 指定強制使用cglib爲Action創建代理對象 -->
- <property name="proxyTargetClass" value="true"/>
- </bean>
-
- <!-- 切面 -->
- <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>
第二步:在Action方法上使用註解進行權限控制
- /**
- * 分頁查詢方法
- * @throws IOException
- */
- @RequiresPermissions(value="region.query")//執行這個方法需要region.query權限
- //@RequiresRoles(value="admin")
- public String pageQuery() throws IOException{
- regionService.pageQuery(pageBean);
- this.writePageBean2Json(pageBean, new String[]{"currentPage","pageSize","detachedCriteria","subareas"});
- return NONE;
- }
控制精度:
註解方式控制權限只能是在方法上控制,無法控制類級別訪問。
過濾器方式控制是根據訪問的URL進行控制。允許使用*匹配URL,所以可以進行粗粒度,也可以進行細粒度控制。
其他註解
@RequiresAuthentication
驗證用戶是否登錄,等同於方法subject.isAuthenticated() 結果爲true時。
@ RequiresUser
驗證用戶是否被記憶,user有兩種含義:
一種是成功登錄的(subject.isAuthenticated() 結果爲true);
另外一種是被記憶的( subject.isRemembered()結果爲true)。
@ RequiresGuest
驗證是否是一個guest的請求,與@ RequiresUser完全相反。
換言之,RequiresUser == ! RequiresGuest 。
此時subject.getPrincipal() 結果爲null.
@ RequiresRoles
例如:@RequiresRoles("aRoleName");
void someMethod();
如果subject中有aRoleName角色纔可以訪問方法someMethod。如果沒有這個權限則會拋出異常AuthorizationException。
@RequiresPermissions
例如: @RequiresPermissions( {"file:read", "write:aFile.txt"} ) void someMethod();
要求subject中必須同時含有file:read和write:aFile.txt的權限才能執行方法someMethod()。否則拋出異常AuthorizationException。
第三步:修改BaseAction的構造方法
- // 構造方法
- @SuppressWarnings("unchecked")
- public BaseAction() {
- //獲取父類類型(baseAction)
- ParameterizedType superclass = null;
- Type type = this.getClass().getGenericSuperclass();
- //獲取父類泛型 強制轉化參數類型 需要判斷 生成action是否使用代理 既父類是否是參數化類型
- if(type instanceof ParameterizedType){
- //沒有使用代理直接強制轉換
- superclass = (ParameterizedType) type;
- }else{
- //type 不屬於參數化類型 則action是cglib代理生成
- //CGLib是針對目標類生產子類,需要獲取父類的父類的類型再強轉成參數化類型
- superclass = (ParameterizedType) this.getClass().getSuperclass().getGenericSuperclass();
- }
- ...
第四步:在自定義Realm中授權
略