關於Apache Shiro權限框架的一些使用誤區的解釋


   多了不說了,進入正題,shiro是個權限框架提供權限管理等功能,網上的教程一般都是互相抄,比如<shiro:principal property="xxx"/>這個標簽,網上教程告訴你可以用來獲取登錄用戶的任何屬性,但現實中如果你這么寫,並且按照開濤教程上寫的登陸邏輯,肯定百分百報錯,這是為什么呢?因為網上教程的登錄部分一般這么寫:

  這是重寫authorizingrealm的dogetAuthenticationinfo方法:

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// TODO Auto-generated method stub
String username=(String) token.getPrincipal();
User user=userService.getUserByUsername(username);
if(user==null){
throw new UnknownAccountException();
}
if(user.getUserState()==4){
throw new LockedAccountException();
}
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()),
this.getName()
);
 
return info;


網上很多教程都是這么教給大家的, 然后在controller里用:
UsernameAndPasswordToken token=new UsernameAndPasswordToken(username,password);
subject.login(token);
....
這一系列邏輯來進行登陸,登陸成功后在頁面通過<shiro:principal property="username"/>來獲取登錄用戶屬性,然后你這么做了就報錯了。
為什么?來看源碼,源碼一是一手鞋,官方文檔是二手鞋,網上教程是三手鞋,鞋都穿三手了,興許會有腳氣。。。

    public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
        this.principals = new SimplePrincipalCollection(principal, realmName);
        this.credentials = hashedCredentials;
        this.credentialsSalt = credentialsSalt;

    }
上面是SimpleAuthenticationInfo源碼的一個構造方法,這里第一個參數就是你剛才傳入的用戶名,第二個參數就是你傳入的密碼,但是方法定義中這兩個參數都是Object類型,尤其是第一個principal參數,它的意義遠遠不止用戶名那么簡單,它是用戶的所有認證信息集合,登陸成功后,<shiro:principal/>標簽一旦有property屬性,PrincipalTag類也就是標簽的支持類,會從Subject的principalcollection里將principal取出,取出的就是你傳入的第一個參數,如果你傳了個string類型的用戶名,那么你只能獲取用戶名。
仔細看那個this.principals=new SimplePrincipalCollection,這一行,這一行構造了一個新的對象並將引用給了principals,而principals就是principalcollection。
再來看Principal那個標簽<shiro:principal property=""/>那個,打開PrincipalTag類,看到onDoStartTag方法:

    public int onDoStartTag() throws JspException {
        String strValue = null;
 
        if (getSubject() != null) {
 
            // Get the principal to print out
            Object principal;
 
            if (type == null) {
                principal = getSubject().getPrincipal();
            } else {
                principal = getPrincipalFromClassName();
            }
 
            // Get the string value of the principal
            if (principal != null) {
                if (property == null) {
                    strValue = principal.toString();
                } else {
                    strValue = getPrincipalProperty(principal, property);
                }
            }
 
        }
 
        // Print out the principal value if not null
        if (strValue != null) {
            try {
                pageContext.getOut().write(strValue);
            } catch (IOException e) {
                throw new JspTagException("Error writing [" + strValue + "] to JSP.", e);
            }
        }
 
        return SKIP_BODY;

    } 
看到那個Object principal這個方法變量了么?如果標簽里沒有type屬性,那么就直接調用getPrincipal方法,再打開這個方法,看到subject里是這么寫的:

    public Object getPrincipal() {
        return getPrimaryPrincipal(getPrincipals());

    } 
getPrincipals是獲取principalcollection,getprimaryprincipal就是獲取這個principalcollection的第一個元素,用的是迭代器的方式獲取。具體的我不列出來了,請參見SimplePrincipalcollection的源代碼。
第一個元素你最初放的是用戶名,所以你可以取得這個用戶名。 如果type不為空,就會去principalcollection中找和這個type類型一致的一個對象,看源碼:

    private Object getPrincipalFromClassName() {
        Object principal = null;
 
        try {
            Class cls = Class.forName(type);
            principal = getSubject().getPrincipals().oneByType(cls);
        } catch (ClassNotFoundException e) {
            if (log.isErrorEnabled()) {
                log.error("Unable to find class for name [" + type + "]");
            }
        }
        return principal;

    } 
那個oneByType方法就是在principalcollection中找和type屬性一致的那個類的對象,將其作為principal,如果<shiro:property/>標簽有了property屬性,然后用java內省機制獲取bean的屬性,參見getPrincipalProperty方法,因為方法體太長我就不列出了。 
所以你現在知道該怎么做了吧?
在你的realm里這么寫:

List<Object> principals=new ArrayList<Object>();
principals.add(user.getUsername());
principals.add(user);
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(
principals,
user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()),
this.getName()

);
構建一個list,第一個元素是用戶名,第二個元素是user對象。第一個元素用來登陸,第二個元素用來獲取登陸后user對象的屬性。
他們到底怎么判斷登陸的呢?
先參見HashedCredentialsMatcher類的這個方法,這是用來判斷是否可以登錄成功的方法:  
  public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

        Object tokenHashedCredentials = hashProvidedCredentials(token, info);
        Object accountCredentials = getCredentials(info);
        return equals(tokenHashedCredentials, accountCredentials);

    } 
其中第一個參數token就是controller里封裝的token,第二個參數info就是你剛才的simpleauthenticationinfo,因為源碼層次比較深所以簡單點說,這個方法就是再判斷兩個密碼是否相等,因為兩個用戶名肯定是相等的info的用戶名是token傳過來的,所以只對比密碼就可以了。 
就寫這么多吧,希望大家看了能有啟發。 

   不是我寫的,是我在開發中遇到問題,特此復制別人解決辦法的博客  附上 大牛的鏈接  http://blog.csdn.net/ccdust/article/details/52300287


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM