天天看點

Shiro學習:關于shiro安全架構的登入和擷取使用者對象

最近用在用apache shiro在做權限管理,網上很多部落格的登入部分都是這麼寫的

這是重寫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);
....
           

之前在找相關資料學習的時候,看到SimpleAuthenticationInfo的第一個參數原來可以傳對象,有大作用,于是就思考豈不是不用手動往session中寫User對象了?然後試了一下,發現不知道為什麼,直接傳user對象進去,一直報String的類型錯誤。如下:

SimpleAuthenticationInfo aInfo = new SimpleAuthenticationInfo(user, user.getPassword(),ByteSource.Util.bytes(user.getUsername()), getName());
           

我們看下這個SimpleAuthenticationInfo的一個構造方法,這裡第一個參數就是你剛才傳入的使用者名,第二個參數就是你傳入的密碼,但是方法定義中這兩個參數都是Object類型,尤其是第一個principal參數,它的意義遠遠不止使用者名那麼簡單,它是使用者的所有認證資訊集合,登陸成功後,<shiro:principal/>标簽一旦有property屬性,PrincipalTag類也就是标簽的支援類,會從Subject的principalcollection裡将principal取出,取出的就是你傳入的第一個參數,如果你傳了個string類型的使用者名,那麼你隻能擷取使用者名。此處參考大牛的連結https://blog.csdn.net/ccdust/article/details/52300287

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

是以現在知道該怎麼做了吧?建構一個list,第一個元素是使用者名,第二個元素是user對象。第一個元素用來登陸,第二個元素用來擷取登陸後user對象的屬性。

在realm裡這麼寫:

// 存入使用者資訊
		List<Object> principals = new ArrayList<Object>();
		principals.add(user.getUsername());
		principals.add(user);


		SimpleAuthenticationInfo aInfo = new SimpleAuthenticationInfo(principals, user.getPassword(),
				ByteSource.Util.bytes(user.getUsername()), getName());
           

測試之後終于成功了,擷取user對象資訊如下:

// 測試
		User user =  SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
		System.out.println("user測試:" + user.toString());
           

那麼問題來了,值是進去了,背景程式也能使用,那我們怎麼通過<shiro:principal />标簽在前台擷取user對象裡的屬性?

我們先看<shiro:principal />标簽源碼,打開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方法,因為方法體太長我就不列出了。

看了這些源碼之後,我相信你也知道該怎麼擷取了吧,代碼如下:

<shiro:principal type="com.zl.po.User" property="username"/>
           

也就是根據type去principalcollection中找和type屬性一緻的那個類的對象,然後用getPrincipalProperty擷取對應的屬性

就寫這麼多吧,希望大家看了能有啟發。特此複制和整理了别人解決辦法的部落格  附上 大牛的連結  https://blog.csdn.net/ccdust/article/details/52300287