Spring shiro 初次使用小結

  首先引入一段關於shiro的介紹:html

  開發系統中,少不了權限,目前java裏的權限框架有SpringSecurity和Shiro(之前叫作jsecurity),對於SpringSecurity:功能太過強大以致於功能比較分散,使用起來也比較複雜,跟Spring結合的比較好。對於初學Spring Security者來講,曲線仍是較大,須要深刻學習其源碼和框架,配置起來也須要費比較大的力氣,擴展性也不是特別強。java

  對於新秀Shiro來講,好評仍是比較多的,使用起來比較簡單,功能也足夠強大,擴展性也較好。據說連Spring的官方都不用Spring Security,用的是Shiro,足見Shiro的優秀。網上找到兩篇介紹:http://www.infoq.com/cn/articles/apache-shiro http://www.ibm.com/developerworks/cn/opensource/os-cn-shiro/,http://itindex.net/detail/50410-apache-shiro-%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C,官網http://shiro.apache.org/ ,使用和配置起來仍是比較簡單。web

  下面只是簡單介紹下咱們是如何配置和使用Shiro的。redis

  pom.xml引入相關jar包spring

 1             <!-- spring結合 -->
 2             <dependency>
 3                 <groupId>org.apache.shiro</groupId>
 4                 <artifactId>shiro-spring</artifactId>
 5                 <version>1.4.0</version>
 6             </dependency>
 7             <!--緩存包-->
 8             <dependency>
 9                 <groupId>org.apache.shiro</groupId>
10                 <artifactId>shiro-ehcache</artifactId>
11                 <version>1.4.0</version>
12             </dependency>
13             <!--核心包-->
14             <dependency>
15                 <groupId>org.apache.shiro</groupId>
16                 <artifactId>shiro-core</artifactId>
17                 <version>1.4.0</version>
18             </dependency>

 

web.xml增長過濾數據庫

 1     <!-- shiro 權限控制的過濾器 -->
 2     <filter>
 3         <filter-name>shiroFilter</filter-name>
 4         <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 5     </filter>
 6 
 7     <filter-mapping>
 8         <filter-name>shiroFilter</filter-name>
 9         <url-pattern>/*</url-pattern>
10     </filter-mapping>

 

增長一個shiro.xml的配置文件apache

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
 4     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:util="http://www.springframework.org/schema/util"
 6     xsi:schemaLocation="
 7     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
 8     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 
 9     http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd 
10     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
11     http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
12     default-lazy-init="false">
13 
14     <!-- 緩存管理器 使用memory實現 -->
15 
16 
17     <!--rememberMe 30天 -->
18     <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
19         <constructor-arg value="COOKIE_NAME" />
20         <property name="httpOnly" value="true" />
21         <property name="maxAge" value="2592000" />
22 
23     </bean>
24 
25     <!-- rememberMe管理器 -->
26     <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
27         <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" />
28         <property name="cookie" ref="rememberMeCookie" />
29     </bean>
30 
31     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
32         <!-- 繼承AuthorizingRealm的類-->
33         <property name="realm" ref="userRealm" />
34         <property name="rememberMeManager" ref="rememberMeManager" />
35     </bean>
36 
37     <!-- Shiro Filter -->
38     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
39         <property name="securityManager" ref="securityManager" />
40         <property name="loginUrl" value="/openid" />
41         <property name="successUrl" value="/manage" />
42         <property name="unauthorizedUrl" value="/openid" />
43         <property name="filterChainDefinitions">
44             <value>
45                 /api/**=anon
46                 /res/**=anon
47                 /src/**=anon
48                 /health/**=anon
49                 /logout=authc
50                 /openid=anon
51                 /callback=anon
52                 /=authc
53                 /**=anon
54             </value>
55         </property>
56     </bean>
57 
58 
59     <!-- Shiro生命週期處理器 -->
60     <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
61 
62 </beans>
View Code

 

對bean的掃描配置api

1     <!-- shiro相關的配置文件和路徑掃描的配置必需要放在項目的mvc的配置文件(即xxx-servlet.xml)裏 -->
2     <aop:config proxy-target-class="true" />
3 
4     <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
5         <property name="securityManager" ref="securityManager" />
6     </bean>

 

UserRealm數組

 1 @Component
 2 public class UserRealm extends AuthorizingRealm {
 3 
 4     private Logger logger = org.slf4j.LoggerFactory.getLogger(UserRealm.class);
 5 
 6     public final static String CREDENTIALS = "openid";
 7 
 8     @Autowired
 9     private SessionService sessionService;
10     @Autowired
11     private PermissionService permissionService;
12 
13     // 記錄是否已經設置過PemissionResover
14     private boolean hasSetPemissionResover = false;
15 
16     @Override
17     public PermissionResolver getPermissionResolver() {
18         if (!hasSetPemissionResover) {
19             setPermissionResolver(new WildcardExtPermissionResolver());
20             hasSetPemissionResover = true;
21         }
22         return super.getPermissionResolver();
23     }
24 
25     /**
26      * 獲取受權信息
27      *
28      * @param principals
29      * @return
30      */
31     @Override
32     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
33         try {
34             Iterator<String> iter = principals.fromRealm(getName()).iterator();
35             if (!iter.hasNext()) {
36                 logger.info("shiro 驗證 無權限");
37                 return null;
38             }
39             String email = iter.next();
40             if (!Strings.isNullOrEmpty(email)) {
41                 // set session
42                 SessionObject so = sessionService.getSession(email);
43                 if (so == null) {
44                     logger.info("so 緩存爲空");
45                     return null;
46                 }
47                 SessionUtils.setSo(so);
48 
49                 // set auth
50                 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
51                 info.addStringPermissions(permissionService.getPermsForUser(so.getRoleId()));
52                 return info;
53             }
54             logger.info("郵箱爲空");
55             return null;
56         } catch (Exception e) {
57             logger.error("shiro 權限獲取異常:", e);
58             return null;
59         }
60     }
61 
62     /**
63      * 獲取身份驗證相關信息:
64      *
65      * @param authcToken
66      * @return
67      * @throws AuthenticationException
68      */
69     @Override
70     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
71         try {
72             UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
73             String email = token.getUsername();
74             String password = new String(token.getPassword());
75             if (!StringUtils.isEmpty(email) && CREDENTIALS.equals(password)) {
76                 SessionObject so = SessionUtils.getSo();
77                 sessionService.addOrUpdateSession(so);
78                 return new SimpleAuthenticationInfo(email, CREDENTIALS, getName());
79             }
80             logger.info("登陸驗證失敗,shiro 不添加權限信息");
81             return null;
82         } catch (Exception e) {
83             logger.error("shiro 身份驗證異常:", e);
84             return null;
85         }
86     }
87 
88 
89 }
View Code

登陸調用緩存

             UsernamePasswordToken token = new UsernamePasswordToken(
                     "username", "password", true);
 
             SecurityUtils.getSubject().login(token);
View Code

退出調用

1 SecurityUtils.getSubject().logout();
View Code

權限註解

@RequiresPermissions(value = {"ROLE_KEY"})

 

以上的配置走完之後就能夠用,下面講講我的需求,以及踩過的坑:

一、如何修改cookie的名稱,默認名稱「rememberMe」太醜了有木有?

首先丟一篇文章,關於該cookie的:http://blog.csdn.net/lhacker/article/details/19341735

個人理解是Shiro 的 SimpleCookie 其實提供了修改cookie名稱的方法,方法有兩個:

一是直接經過構造函數的方法修改cookie的名稱,就像我展現的配置文件shiro的中同樣:

 1 <constructor-arg value="COOKIE_NAME" /> 

其實調用的是SimpleCookie這個方法:

1     public SimpleCookie(String name) {
2         this();
3         this.name = name;
4     }
SimpleCookie 其實還提供了其它構造方法可使用,詳見源碼。

第二個方法則是經過給SimpleCookie的name字段賦值的方法,這個方法就和我給的連接中的方法同樣:

 1 <property name="name" value="COOKIE_NAME" /> 

除此以外,還能夠配置SimpleCookie的其它字段,源碼中給出的能夠配置字段以下:

1     private String name;
2     private String value;
3     private String comment;
4     private String domain;
5     private String path;
6     private int maxAge;
7     private int version;
8     private boolean secure;
9     private boolean httpOnly;
View Code

 

二、明明配置了權限註解,爲何沒有調用doGetAuthorizationInfo獲取受權信息的方法?

註解生效須要在servlet.xml中配置以下:

 

    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

 

 

 

三、一個接口能夠被不一樣的頁面調用,但這個頁面分別屬於不一樣的權限?

咱們在使用權限註解時,每每會遇到這樣的問題,接口能夠重複使用,權限卻要分開,怎麼辦呢?

首先看看@RequiresPermissions註解的源碼:

 1 @Target({ElementType.TYPE, ElementType.METHOD})
 2 @Retention(RetentionPolicy.RUNTIME)
 3 public @interface RequiresPermissions {
 4 
 5     /**
 6      * The permission string which will be passed to {@link org.apache.shiro.subject.Subject#isPermitted(String)}
 7      * to determine if the user is allowed to invoke the code protected by this annotation.
 8      */
 9     String[] value();
10     
11     /**
12      * The logical operation for the permission checks in case multiple roles are specified. AND is the default
13      * @since 1.1.0
14      */
15     Logical logical() default Logical.AND; 
16 
17 }

源碼很簡單,不難發現,註解中權限的key存放在value數組中,另外還有一個Logical用來存放多個權限之間關係,因此當咱們一個方法須要知足多個權限時,能夠這樣:

@RequiresPermissions(value = { "key1", "key2" }, logical = Logical.AND)
    

當一個方法知足任意權限key時,能夠這樣

@RequiresPermissions(value = { "key1", "key2" }, logical = Logical.OR)

 

四、採用非用戶密碼的方式登陸怎麼辦?

遇到這個問題,個人處理辦法是驗證方式咱們事先處理掉,而後再調用subject的login,大體流程以下:

// 處理登陸邏輯
驗證手機驗證碼等......
// 調用subject login
UsernamePasswordToken token = new UsernamePasswordToken(
                    email, "openid", true);
// username 字段能夠放id/帳號/手機號/郵箱等惟一值
// password  則存放一個當前帳號必帶的一個信息,如有在驗證過程當中有密碼則能夠存放密碼,能夠直接存聽任意string,這個都不要緊,這裏就存放了一個string字符"openid"
SecurityUtils.getSubject().login(token);

subject 的 login方法會間接調用到doGetAuthenticationInfo獲取驗證相關信息的方法,上面給出了一個方法的源碼

 1 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
 2         try {
 3             UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
 4             String email = token.getUsername();
 5             String password = new String(token.getPassword());
 6             if (!StringUtils.isEmpty(email) && "openid".equals(password)) {
 7 // 這裏作簡單的判斷,username不爲空,且 password 爲咱們給定的字符串
 8 // 若是是則能夠進行權限驗證,不然失敗
 9 // 這裏除了放username 還能夠放其它內容,也能夠將整個user的信息從數據庫取出來,放入SimpleAuthenticationInfo的principal字段中
10                 return new SimpleAuthenticationInfo("但願存入cookie的內容","openid", getName());
11             }
12             logger.info("登陸驗證失敗,shiro 不添加權限信息");
13             return null;
14         } catch (Exception e) {
15             logger.error("shiro 身份驗證異常:", e);
16             return null;
17         }
18     }

 

五、存入cookie的數據沒法序列化?

放入放入SimpleAuthenticationInfo的principal字段經常遇到序列化的問題,這是因爲咱們平時寫的model都沒有實現Serializable,因此咱們只要讓對應的model實現一下便可,如:

1 public class User implements Serializable{
2      ......      
3 }

 

六、shiro.xml中配置和註解分別作啥用?

 註解和shiro.xml中配置的filter是shiro提供的兩套權限驗證流程,他們的調用並不同

這篇文檔對filter中的相關注解錯了比較詳細的介紹:http://blog.csdn.net/clj198606061111/article/details/24185023

我截取一段關於shiro.xml中關於ShiroFilterFactoryBean配置的說明,以下:

 

securityManager:這個屬性是必須的。

 

loginUrl:沒有登陸的用戶請求須要登陸的頁面時自動跳轉到登陸頁面,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的」/login.jsp」頁面。

 

successUrl:登陸成功默認跳轉頁面,不配置則跳轉至」/」。若是登錄前點擊的一個須要登陸的頁面,則在登陸自動跳轉到那個須要登陸的頁面。不跳轉到此。

 

unauthorizedUrl:沒有權限默認跳轉的頁面。

 

 

 

過濾器簡稱

對應的java類

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port

org.apache.shiro.web.filter.authz.PortFilter

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl

org.apache.shiro.web.filter.authz.SslFilter

user

org.apache.shiro.web.filter.authc.UserFilter

logout

org.apache.shiro.web.filter.authc.LogoutFilter

 

 

 

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是受權過濾器

 

 

註解中的權限信息,則是經過獲取受權信息方法實現:

 

@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        try {
            Iterator<String> iter = principals.fromRealm(getName()).iterator();
            if (!iter.hasNext()) {
                logger.info("shiro 驗證 無權限");
                return null;
            }
            String email = iter.next();
            if (!Strings.isNullOrEmpty(email)) {
                // 經過email能夠實時獲取權限信息,固然也能夠在iter中自己就帶有權限信息,不在進行數據庫或者redis的查詢
        // set auth
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                info.addStringPermissions(new ArrayList<String>("權限集合"));
                return info;
            }
            logger.info("郵箱爲空");
            return null;
        } catch (Exception e) {
            logger.error("shiro 權限獲取異常:", e);
            return null;
        }
    }

 

總結:

第一次使用shiro作權限驗證,我的感受shiro的權限驗證比較靈活易懂,且比較適合新手接入,權限的控制也比較簡單。上面提出的幾個問題,便是本人在隨着項目的所遇到的問題,只是給出了我的的解決方法,如有更適合的方法還請指出,多謝。

相關文章
相關標籤/搜索