亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Java 權限框架 Shiro 實戰一:理論基礎

2019-11-15 00:44:11
字體:
來源:轉載
供稿:網友
java 權限框架 Shiro 實戰一:理論基礎

Apache Shiro 官網地址:http://shiro.apache.org/

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterPRise applications.

shiro是一個強大而且簡單易用的Java安全框架,主要功能有認證(就是登陸驗證),授權(就是權限管理),加密(就是密碼加密),session管理。適用于各種大型或者小型企業應用。和Spring Security比較而言,確實更加簡單而且靈活易懂。

1. shiro中的重要概念

要理解shiro,先要理解框架的幾個概念:

1) Subject: 代表當前登陸或者訪問的用戶;

2)Principals:一般指用戶名等,唯一表明Subject身份也就是當前用戶身份的東西;

3)Credentials:憑證,一般指密碼,對當前登陸用戶進行驗證;

4)Realms:域,一般是指存儲用戶信息(用戶名,密碼,權限,角色)的數據庫,也就是保存用戶權限等信息的數據源;

5)SecurityManager:shiro安全管理的頂級對象。它集合或者說調用所有其它相關組件,負責所有安全和權限相關處理過程,就像一個中央集權政府;

2. shiro的子系統

上面我們說到shiro的主要功能有:認證,授權,加密,session管理等。而每一個主要功能對應于shiro的一個子系統:

下面正對每一個子系統分別介紹。

3. Authentication認證子系統

認證子系統,就是處理用戶登錄,驗證用戶登錄。我們前面講到Subject代表當前用戶,而Principal和credential分別就代表用戶名和密碼。登錄認證時,其實就是調用的 Subject.login(AuthenticationToken token)方法,AuthenticationToken是一個接口:

public interface AuthenticationToken extends Serializable {    /**     * Returns the account identity submitted during the authentication process.*/    Object getPrincipal();    /**     * Returns the credentials submitted by the user during the authentication process that verifies     * the submitted {@link #getPrincipal() account identity}.*/    Object getCredentials();}

登錄時就會分別調用它的實現類的 getPrincipal() 和 getCredentials() 來獲得用戶名(Principal:主體)和密碼(Credentials:憑證)。一般實際中我們傳給Subject.login()方法的是UsernamePassWordToken 類的對象,它實現了該接口:

一般我們new一個UsernamePasswordToken的對象:UsernamePasswordToken token = new UsernamePasswordToken("xxxusername", "xxxpassword");, 然后 subject.login(token); 就前去登錄。相關代碼一般如下:

    @RequestMapping(value="/loginController", method=RequestMethod.POST)    public String login(String userName, String password, String rememberMe, String type, HttpServletRequest req) {        String error = null;        Subject subject = SecurityUtils.getSubject();        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);        if(rememberMe != null && "true".equals(rememberMe))            token.setRememberMe(true);    // 記住我                try {            subject.login(token);        } catch (UnknownAccountException | IncorrectCredentialsException e1) {            error = "用戶名或密碼錯誤";        }catch(ExcessiveAttemptsException e){            userService.lockAccountByNo(no);     // 鎖定賬戶            error = "超過了嘗試登錄的次數,您的賬戶已經被鎖定。";        }catch (AuthenticationException e) {    // 其他錯誤            if(e.getMessage() != null)                error = "發生錯誤:" + e.getMessage();            else                error = "發生錯誤,無法登錄。";        }        // .. ...

Authentication 子系統會將password加密,然后使用username和加密之后的password和從Realm(一般是數據庫)中根據usename獲得的密碼進行比較,相同就登錄成功,不相同同就登錄失敗,或者用戶名不存在也登錄失敗。就怎么簡單。當然從Realm中根據用戶名查找用戶的過程是需要我們自己編碼實現的。該功能的實現,shiro提供了抽象類 AuthenticatingRealm 專門用于從Realm中獲得認證信息。所以我們可以繼承抽象類 AuthenticatingRealm,然后實現其中的抽象方法:

/** * A top-level abstract implementation of the Realm interface that only implements authentication support * (log-in) Operations and leaves authorization (access control) behavior to subclasses. */public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {    //TODO - complete JavaDoc    private static final Logger log = LoggerFactory.getLogger(AuthenticatingRealm.class);    // ... ...    /**     * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given     * authentication token.     * <p/>     * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing     * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific     * log-in logic in addition to just retrieving data - it is up to the Realm implementation.*/    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

我們只要實現 protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 方法就可以了,其它的shiro會回調該方法,進行登錄認證。而實現該方法就是直接從數據源中根據 AuthenticationToken 獲得數據就行了。

除了這種方法之外,其實我們也可以使用 AuthenticatingRealm 的子類 AuthorizingRealm,它本來是用于權限認證的Realm,但是因為他繼承了 AuthenticatingRealm,所以實際上我們只要繼承 AuthorizingRealm,然后實現它的抽象方法就行了。同時搞定 登錄認證 和 權限認證(訪問控制):

public class UserRealm extends AuthorizingRealm {    @Autowired    private UserService userService;    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {        String userName = (String)principals.getPrimaryPrincipal();        User user = userService.getUserByName(userName);        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();        authorizationInfo.setRoles(userService.findRolesByUserId(user.getId()));        authorizationInfo.setStringPermissions(userService.findPermissionsByUserId(user.getId()));        return authorizationInfo;    }        @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {        String userName= (String)token.getPrincipal();        User user = userService.getUserByName(userName);        if(user == null) {            throw new UnknownAccountException();//沒找到賬戶        }        if(user.getLocked() == 0) {            throw new LockedAccountException(); //帳號鎖定        }        if(user.getLocked() == 2){            throw new AuthenticationException("account was inactive");        }                SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(                user.getUserName(), // 用戶名                user.getPassword(), // 密碼                ByteSource.Util.bytes(user.getCredentialsSalt()),    // salt                getName()  // realm name        );                return authenticationInfo;    }    @Override    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {        super.clearCachedAuthorizationInfo(principals);    }    @Override    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {        super.clearCachedAuthenticationInfo(principals);    }    @Override    public void clearCache(PrincipalCollection principals) {        super.clearCache(principals);    }    public void clearAllCachedAuthorizationInfo() {        getAuthorizationCache().clear();    }    public void clearAllCachedAuthenticationInfo() {        getAuthenticationCache().clear();    }    public void clearAllCache() {        clearAllCachedAuthenticationInfo();        clearAllCachedAuthorizationInfo();    }}

上面的 doGetAuthorizationInfo 方法,會在權限認證也就是訪問控制時,被回調,而 doGetAuthenticationInfo 方法會在登錄認證時被回調,返回的 AuthenticationInfo類型的對象,會和用戶登錄時輸入的 用戶名和密碼(加密之后的)進行比較,相同則登錄成功,反之則登錄失敗。

其實還有更加簡單的方法,因為shiro提供了實現了 AuthorizingRealm 中的抽象方法的子類:

比如在數據庫環境中,我們就可以直接使用 JdbcRealm,一是可以配置它的相關SQL語句,二是繼承它,覆蓋它的方法。CasRealm用戶單點登錄環境。

/** * Realm that allows authentication and authorization via JDBC calls.  The default queries suggest a potential schema * for retrieving the user's password for authentication, and querying for a user's roles and permissions.  The * default queries can be overridden by setting the query properties of the realm. * <p/> * If the default implementation * of authentication and authorization cannot handle your schema, this class can be subclassed and the * appropriate methods overridden. (usually {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)}, * {@link #getRoleNamesForUser(java.sql.Connection,String)}, and/or {@link #getPermissions(java.sql.Connection,String,java.util.Collection)} * <p/> * This realm supports caching by extending from {@link org.apache.shiro.realm.AuthorizingRealm}.*/public class JdbcRealm extends AuthorizingRealm {    //TODO - complete JavaDoc    /*--------------------------------------------    |             C O N S T A N T S             |    ============================================*/    /**     * The default query used to retrieve account data for the user.     */    protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";        /**     * The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.     */    protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?/**     * The default query used to retrieve the roles that apply to a user.     */    protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";    /**     * The default query used to retrieve permissions that apply to a particular role.     */    protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";    private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);        /**     * Password hash salt configuration. <ul>     *   <li>NO_SALT - password hashes are not salted.</li>     *   <li>CRYPT - password hashes are stored in unix crypt format.</li>     *   <li>COLUMN - salt is in a separate column in the database.</li>      *   <li>EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(String)} will be called     *       to get the salt</li></ul>     */    public enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL};    /*--------------------------------------------    |    I N S T A N C E   V A R I A B L E S    |    ============================================*/    protected DataSource dataSource;    protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;    protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;    protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;    protected boolean permissionsLookupEnabled = false;        protected SaltStyle saltStyle = SaltStyle.NO_SALT;

我們可以對上面給出的sql語句進行配置,修改成對應于我們數據庫中表的sql語句,也可以繼承該類,然后覆蓋doGetAuthenticationInfo, getRoleNamesForUser(), getPermissions()三個方法。

4. Authorization 授權子系統(訪問控制)

上一節中我們已經介紹了如何獲得用戶所擁有的權限,在需要判斷用戶是否有某權限或者角色時,會自動回調方法 doGetAuthorizationInfo 來獲得用戶的角色和權限,我們只需要在 該方法中從Realm也就是數據庫表中獲得相關信息。我們先看一下shiro是如何表示角色和權限的,這一點比較重要:

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {        String no = (String)principals.getPrimaryPrincipal();        User user = userService.getUserByNo(no);        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();        authorizationInfo.setRoles(userService.findRolesByUserId(user.getId()));        authorizationInfo.setStringPermissions(userService.findPermissionsByUserId(user.getId()));        return authorizationInfo;    }    

我們看到 doGetAuthorizationInfo 方法中使用了 SimpleAuthorizationInfo 類封裝 Role 和 Permission.

/** * Simple POJO implementation of the {@link AuthorizationInfo} interface that stores roles and permissions as internal * attributes. * @see org.apache.shiro.realm.AuthorizingRealm * @since 0.9 */public class SimpleAuthorizationInfo implements AuthorizationInfo {    /**     * The internal roles collection.     */    protected Set<String> roles;    /**     * Collection of all string-based permissions associated with the account.     */    protected Set<String> stringPermissions;    /**     * Collection of all object-based permissions associaed with the account.     */    protected Set<Permission> objectPermissions;    /**     * Default no-argument constructor.     */    public SimpleAuthorizationInfo() {    }

我們看到,roles 和 stringPermissions 都是 String 類型的 Set, 也就是說,它們都是使用字符串來表示你擁有某個角色或者擁有某個權限的

1) 兩種訪問控制方式:

SimpleAuthorizationInfo 封裝了角色和權限,其實這也說明了實現“訪問控制”兩種方式:一是 “基于角色的訪問控制”;而是“基于資源的訪問控制”。所謂的訪問控制,是指對于某個資源,當前用戶是否有訪問的權限?;诮巧脑L問控制是一種比較粗粒度的訪問控制方式,只要你具有了某個或某幾個角色,那么你就可以訪問某資源。而基于資源的訪問控制,是判斷你針對該資源是否有某權限,有才能訪問,粒度更細,你是否有某權限,可以根據你有哪些角色,然后改角色有哪些權限來判斷的,當然也可以不引入角色的概念,直接判斷你是否擁有某些權限。當然兩種訪問方式可以單獨使用,也可以混合使用。比如對于比較簡單的權限控制,你可以僅僅只使用基于角色的訪問控制,僅僅引入角色表,不需要權限表都可以?;旌鲜褂檬侵?,你可以同時要求用戶具有某角色并且具有某些權限,才能訪問某資源。所以shiro的權限控制時極其靈活的(當然也可以不引入角色表,僅僅引入權限表)。

2)權限的字符串表示方式

上面說到 角色 和 權限 都是使用字符串來表示的,其實 shiro 提供了一套比較強大有點復雜的權限字符串表示格式(分為:分割的三個部分):

資源:操作:對象實例ID” 表示:對那個資源的哪個實例可以進行哪些操作,支持通配符。

多個操作需要使用 “,” 逗號分割,而 “*” 放在三個位置上,分別表示:任意資源,任意操作,任意實例。

比如:"user:delete:1" 就表示 對user表的id等于1對應的數據或者對象,可以進行刪除操作。其實資源表現實現可以是對象,其實最終是對應到數據庫表中的記錄。

在比如:"user:update,delete" 就表示 對user表(的任意實例)進行更新和刪除操作。"user:update,delete" 其實就等價于 “user:update,delete:*”

所以 shiro 的訪問控制可以控制到具體實例,或者說具體哪條數據庫記錄,也可以在表級別控制。如果省略掉 對象實例ID部分,就是在表級別控制。

3)權限相關表的設計

1> 如果對于簡單的情況,可以只使用“基于角色的訪問控制”粗粒度方式,不涉及到權限,僅僅只通過判斷是否有某角色來判斷訪問控制,那么就只需要增加一個角色表(roles) 和 一個角色(roles)和用戶(user)的多對多的一個中間表——用戶角色表(user_role)。

2> 如果僅僅使用權限來控制訪問,那么就可以僅僅只增加一個權限表(priv)和一個用戶和權限的多對多的一個中間表——用戶權限表(user_priv).

3> 如果既要用到角色,又要用到權限(權限根據角色推算出來),那么就要增加:角色表,用戶角色表,權限表,角色權限表。

4> 其實還有一種情況:就是角色和權限沒有關系,那么就可以增加:角色表,用戶角色表,權限表,用戶權限表。不過這種方式不同符合常規。

5. Cryptography 加密子系統

shiro提供了很完備而且十分易用的加密解密功能。該子系統分為兩個部分:一是基于hash的單向加密算法;二是基于經典加密解密算法,密碼是可以解密的出明文的;一般而言,對于登錄用戶的密碼的加密都是采用單向的hash加密算法,因為如果密碼可以被解密的話,一旦數據庫被攻破了,那么所有用戶的密碼就都可以被解密成明文;但是單向的hash加密算法,沒有這樣的風險。單向的hash加密算法,就算你獲得了數據庫的中保存的密碼密文,知道了密文對應的salt,甚至知道了使用的是什么hash算法,你都無法反向推算出密碼的明文!因為hash是單向的,它沒有對應的反向推算算法(也就是沒有解密方法)。那么知道了密文,你是無法反推出密碼明文的。這也是單向hash加密算法的妙處。

1)單向hash加密算法

shiro提供的單向hash加密算法的相關工具類如下:

我們看到提供了 Md2, md5, Sha1, Sha256, Sha384, Sha512 等等的hash算法。一般而言Md2/Md5系列的算法已經被證實安全性存在不足。所以一般使用Sha系列的算法。其實看下源碼的話,就知道上面所有的hash算法都是繼承與 SimpleHash 類,SimpleHash 才是真正的實現者,而其他的比如 Sha256Hash 不過是傳入本算法需要的參數,然后調用了 SimpleHash 中hash加密算法而已,看下源碼:

public class Sha256Hash extends SimpleHash {    public static final String ALGORITHM_NAME = "SHA-256";    public Sha256Hash() {        super(ALGORITHM_NAME);    }    public Sha256Hash(Object source) {        super(ALGORITHM_NAME, source);    }    public Sha256Hash(Object source, Object salt) {        super(ALGORITHM_NAME, source, salt);    }    public Sha256Hash(Object source, Object salt, int hashIterations) {        super(ALGORITHM_NAME, source, salt, hashIterations);    }    public static Sha256Hash fromHexString(String hex) {        Sha256Hash hash = new Sha256Hash();        hash.setBytes(Hex.decode(hex));        return hash;    }    public static Sha256Hash fromBase64String(String base64) {        Sha256Hash hash = new Sha256Hash();        hash.setBytes(Base64.decode(base64));        return hash;    }}

我們看到都是使用 super() 調用父類的方法。根據上面截圖中提高的相關類,可以有三種方法來實現密碼鎖需要的hash加密過程:

1> 直接使用 Sha256Hash/Md5Hash 等類,比如:

String sha256 = new Sha256Hash("admin", "11d23ccf28fc1e8cbab8fea97f101fc1d", 2).toString();

根據Sha256Hash的構造函數,"admin" 為需要加密的密碼明文,"11d23ccf28fc1e8cbab8fea97f101fc1d" 為加密需要的salt, 2 是迭代次數,也就是hash次數。最后調用 .toString() 就獲得了密文。很簡單。

2> 使用 Sha256Hash/Md5Hash 等類 父類 SimpleHash ,比如:

sha1 = new SimpleHash("sha-256", "admin", "11d23ccf28fc1e8cbab8fea97f101fc1d", 2).toString();

看到,我們傳入了hash算法的名稱 "sha-256", 剩下的參數和 Sha256Hash 的一樣。

3> 使用 DefaultHashService 和 HashRequest 二者結合來加密:

        DefaultHashService hashService = new DefaultHashService();//        hashService.setHashAlgorithmName("SHA-256"); //        hashService.setPrivateSalt(new SimpleByteSource("123"));//        hashService.setGeneratePublicSalt(false);//        hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator()); //        hashService.setHashIterations(2); //                HashRequest hashRequest = new HashRequest.Builder()        .setSource(ByteSource.Util.bytes("admin112358"))        .setSalt("11d23ccf28fc1e8cbab8fea97f101fc1d")        .setAlgorithmName("SHA-256")        .setIterations(2).build();        System.out.println(hashService.computeHash(hashRequest).toHex());

我們看到 HashRequest 類專門提供各種加密需要的參數,密碼明文,salt, hash算法,迭代次數。這里有個坑,不要調用DefaultHashService的方法來設置各種加密需要的參數(特別是salt相關的參數),而使用專門的類 HashRequest來提供各種參數,因為使用 DefaultHashService 你是無法設置對 salt 的,也無法獲得 salt ,而最終我們是需要將 salt 存放入數據庫的,DefaultHashService只能設置 privateSalt, 它hash時最終使用的salt是privateSlat 和 自動生成的 publicSalt,二者合成得到的,合成的結果并沒有提供方法來使我們獲得它。另外DefaultHashService有一個坑:如果你調用方法hashService.setPrivateSalt(new SimpleByteSource("123"));設置了privateSalt, 即使你調用了hashService.setGeneratePublicSalt(false);方法,它還是會隨機生成publicSalt的。另外 HashRequest 中提供的參數會覆蓋DefaultHashService設置的相應參數。

相比較而言,肯定是直接使用 Sha256Hash/Md5Hash 等類來得最簡單而直接。

2)雙向經典加密/解密算法

主要提供了 AES 和 Blowfish兩種加密解密算法。

1> AES:

        AesCipherService aesCipherService = new AesCipherService();         aesCipherService.setKeySize(128); // 設置key長度         // 生成key         Key key = aesCipherService.generateNewKey();   // 加密        String encrptText = aesCipherService.encrypt(text.getBytes(), key.getEncoded()).toHex();         // 解密        String text2 = new String(aesCipherService.decrypt(Hex.decode(encrptText), key.getEncoded()).getBytes());         System.out.println(text2.equals(text));

Key 表示 秘鑰,就相當于 Hash 算法中的 salt,秘鑰不同,最終的密文也就不同。不同的是解密時是需要使用加密時相同的秘鑰才能解密成功。

2> Blowfish:

        BlowfishCipherService blowfishService = new BlowfishCipherService();        blowfishService.setKeySize(128);        Key bKey = blowfishService.generateNewKey();        String encrpt = blowfishService.encrypt("admin".getBytes(), bKey.getEncoded()).toHex();        String dec = new String(blowfishService.decrypt(Hex.decode(encrpt), bKey.getEncoded()).getBytes());        System.out.println("admin".equals(dec));

3> 使用 DefaultBlockCipherService 實現加密解密:

        //使用Java的JCA(javax.crypto.Cipher)加密API,常見的如 AES, Blowfish        DefaultBlockCipherService cipherService = new DefaultBlockCipherService("AES");        cipherService.setKeySize(128);        //生成key        bKey = cipherService.generateNewKey();        text = "admin";        //加密        encrptText = cipherService.encrypt(text.getBytes(), key.getEncoded()).toHex();        //解密        text2 = new String(cipherService.decrypt(Hex.decode(encrptText), key.getEncoded()).getBytes());        System.out.println(text.equals(text2));

DefaultBlockCipherService(BlockCipher)是分組加密的意思,分組是指加密的過程是先進行分組,然后加密。AES 和 Blowfish都是分組加密算法。

3) 密碼加密和密碼驗證

注冊時一般涉及到密碼加密,登錄時涉及到密碼驗證。通過上面介紹的 加密算法,完全可以自己實現密碼加密和密碼驗證。但是其實shiro也提供了相應的類:

DefaultPasswordService 和 HashedCredentialsMatcher。雖然提供了,其實 DefaultPasswordService 卵用都沒有,因為他沒有提供獲取或者設置 salt 的方法,而 salt 是我們需要存入數據庫的。所以密碼加密我們是不使用 DefaultPasswordService 的,而是根據前面的介紹自己寫。至于密碼驗證我們應該繼承 HashedCredentialsMatcher,然后重寫它的 doCredentialsMatch() 方法即可:

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {    private Cache<String, AtomicInteger> passwordRetryCache;    public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {        passwordRetryCache = cacheManager.getCache("passwordRetryCache");    }    @Override    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {        String username = (String)token.getPrincipal();        AtomicInteger retryCount = passwordRetryCache.get(username);        if(retryCount == null) {            retryCount = new AtomicInteger(0);            passwordRetryCache.put(username, retryCount);        }        if(retryCount.incrementAndGet() > 5) {            throw new ExcessiveAttemptsException("超過了嘗試登錄的次數,您的賬戶已經被鎖定。");        }        boolean matches = super.doCredentialsMatch(token, info);        if(matches) {            passwordRetryCache.remove(username);        }        return matches;    }}

super.doCredentialsMatch(token, info)調用了父類的方法:

    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {        Object tokenHashedCredentials = hashProvidedCredentials(token, info);        Object accountCredentials = getCredentials(info);        return equals(tokenHashedCredentials, accountCredentials);    }
    protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {        Object salt = null;        if (info instanceof SaltedAuthenticationInfo) {            salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();        } else {            //retain 1.0 backwards compatibility:            if (isHashSalted()) {                salt = getSalt(token);            }        }        return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());    }

我們看到 AuthenticationToken token 加密時需要的 salt 來自于 AuthenticationInfo info:

salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();

AuthenticationToken token 是登錄時頁面傳過來的用戶名,明文密碼等參數,AuthenticationInfo info卻是從數據庫中獲得的用戶名密碼密文,salt等參數。equals(tokenHashedCredentials, accountCredentials); 驗證: 明文密碼使用相同的salt加密之后,獲得的密文是否和數據庫中的密碼密文一致。一致,則密碼驗證通過。

6. Session Management會話管理子系統

shiro中session的最大不同時,它可以使用再非web環境中。對于JavaSE的環境也可以使用session的功能,因為他實現了一套不依賴于web容器的session機制。shiro提供了三個默認的實現:

1> DefaultSessionManager: DefaultSecurityManager使用的默認實現,用于JavaSE環境;

2> ServletContainerSessionManager: DefaultWebSecurityManager使用的默認實現,用于web環境,其直接使用Servlet容器的會話;

3> DefaultWebSessionManager: 用于web環境的實現,可以替代ServletContainerSessionManager,自己維護會話,直接替代Servlet容器的會話管理;

在web環境中默認使用的是 ServletContainerSessionManager,其使用的就是Servlet容器的會話,這一點,我們可以從它的源碼中可以看出來:

public class ServletContainerSessionManager implements WebSessionManager {//TODO - read session timeout value from web.xml    public ServletContainerSessionManager() {    }    public Session start(SessionContext context) throws AuthorizationException {        return createSession(context);    }    public Session getSession(SessionKey key) throws SessionException {        if (!WebUtils.isHttp(key)) {            String msg = "SessionKey must be an HTTP compatible implementation.";            throw new IllegalArgumentException(msg);        }        HttpServletRequest request = WebUtils.getHttpRequest(key);        Session session = null;        HttpSession httpSession = request.getSession(false);        if (httpSession != null) {            session = createSession(httpSession, request.getRemoteHost());        }        return session;    }    protected Session createSession(SessionContext sessionContext) throws AuthorizationException {        if (!WebUtils.isHttp(sessionContext)) {            String msg = "SessionContext must be an HTTP compatible implementation.";            throw new IllegalArgumentException(msg);        }        HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);        HttpSession httpSession = request.getSession();        //SHIRO-240: DO NOT use the 'globalSessionTimeout' value here on the acquired session.        //see: https://issues.apache.org/jira/browse/SHIRO-240        String host = getHost(sessionContext);        return createSession(httpSession, host);    }    public boolean isServletContainerSessions() {        return true;    }}

DefaultWebSecurityManager默認使用的是ServletContainerSessionManager:

/** * Default {@link WebSecurityManager WebSecurityManager} implementation used in web-based applications or any * application that requires HTTP connectivity (SOAP, http remoting, etc). * @since 0.2 */public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager {    //TODO - complete JavaDoc    private static final Logger log = LoggerFactory.getLogger(DefaultWebSecurityManager.class);    @Deprecated    public static final String HTTP_SESSION_MODE = "http";    @Deprecated    public static final String NATIVE_SESSION_MODE = "native";    /**     * @deprecated as of 1.2.  This should NOT be used for anything other than determining if the sessionMode has changed.     */    @Deprecated    private String sessionMode;    public DefaultWebSecurityManager() {        super();        ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());        this.sessionMode = HTTP_SESSION_MODE;        setSubjectFactory(new DefaultWebSubjectFactory());        setRememberMeManager(new CookieRememberMeManager());        setSessionManager(new ServletContainerSessionManager());    }

從 setSessionManager(new ServletContainerSessionManager()); 看到答案。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品福利在线观看| 国产精品美女免费看| 中文字幕精品—区二区| 在线视频国产日韩| 欧美国产欧美亚洲国产日韩mv天天看完整| 久久久av亚洲男天堂| 亚洲www永久成人夜色| 国产日韩欧美91| 日韩在线观看免费高清完整版| 一本久久综合亚洲鲁鲁| 欧美怡红院视频一区二区三区| 成人午夜激情免费视频| 91国语精品自产拍在线观看性色| 久久综合免费视频影院| 欧美一级在线亚洲天堂| 成人免费视频a| 久久天堂av综合合色| 日韩av免费网站| 日本国产一区二区三区| 精品国产成人在线| 欧美日韩不卡合集视频| 97在线精品国自产拍中文| 91九色在线视频| 亚洲成人三级在线| 91精品国产乱码久久久久久久久| 欧美亚洲另类视频| 欧美成人午夜剧场免费观看| 国产成人精品日本亚洲专区61| 国产午夜精品一区二区三区| 亚洲日韩中文字幕在线播放| 国产精品日韩精品| 国产日韩在线亚洲字幕中文| 中文字幕九色91在线| 国产日韩精品综合网站| 国内外成人免费激情在线视频网站| 九九综合九九综合| 亚洲国内高清视频| 欧美成人精品在线观看| www欧美日韩| 久久全国免费视频| 欧美视频一区二区三区…| 久久精品99久久香蕉国产色戒| 国产免费一区二区三区香蕉精| 97香蕉久久超级碰碰高清版| 2021久久精品国产99国产精品| 91欧美视频网站| 久久伊人精品一区二区三区| 欧美极品少妇与黑人| 97色在线观看免费视频| 亚洲精品自拍第一页| 国产亚洲成av人片在线观看桃| 国产aaa精品| 69久久夜色精品国产7777| 亚洲国产免费av| 日韩在线观看视频免费| 国产69久久精品成人| 丝袜美腿精品国产二区| 欧美日韩国产二区| 欧美精品18videos性欧美| 久久综合88中文色鬼| 亚洲欧美国产一区二区三区| 91麻豆国产精品| 国产精品入口日韩视频大尺度| 色偷偷888欧美精品久久久| 欧美一区三区三区高中清蜜桃| 亚洲精品在线看| 精品激情国产视频| 亚洲欧美精品一区| 麻豆国产va免费精品高清在线| 日韩视频亚洲视频| 日韩欧美在线视频免费观看| 亚洲精品98久久久久久中文字幕| 日韩欧美在线视频观看| 国产一区在线播放| 少妇高潮久久久久久潘金莲| 成人在线中文字幕| 亚洲欧美日韩中文在线制服| 懂色aⅴ精品一区二区三区蜜月| 国产精品一区二区久久久久| 欧美亚洲另类在线| 国产精品久久久久久av福利| 国产精品精品国产| 国产不卡在线观看| 国产91精品最新在线播放| 国产999精品视频| 欧美电影在线观看高清| 欧美一区二区三区图| 精品动漫一区二区三区| 国产一区二区av| 欧美激情在线视频二区| 久久免费少妇高潮久久精品99| 伊人伊成久久人综合网小说| 国产69精品久久久久9| 欧美性猛交xxxx黑人猛交| 国产成人91久久精品| 久久国产精品久久久久| 欧美电影免费看| 亚洲视频免费一区| 久久精品中文字幕一区| 国产精品小说在线| 午夜精品久久久久久99热软件| 91精品国产乱码久久久久久久久| 国产91在线高潮白浆在线观看| 亚洲人成亚洲人成在线观看| 精品国产精品自拍| 国产69精品99久久久久久宅男| 91中文字幕一区| 亚洲美女视频网| 高清欧美一区二区三区| 91精品在线国产| 亚洲在线免费观看| 亚洲欧美日本另类| 欧美午夜片欧美片在线观看| 亚洲免费成人av电影| 国产91色在线播放| 成人av资源在线播放| 国产精品自拍偷拍| 亚洲电影免费观看高清完整版在线观看| 国产成人精品一区| 亚洲成人精品视频| 色多多国产成人永久免费网站| www.欧美视频| 久久精视频免费在线久久完整在线看| 成人妇女淫片aaaa视频| 狠狠躁夜夜躁久久躁别揉| 91久久久久久国产精品| 久久久精品美女| 国产欧美欧洲在线观看| 欧美激情三级免费| 亚洲 日韩 国产第一| 亚洲精品久久在线| 久久久人成影片一区二区三区| 欧美电影免费播放| 欧美日本啪啪无遮挡网站| 国产精品91一区| 亚洲a一级视频| 亚洲最大av在线| 欧美最猛性xxxxx(亚洲精品)| 2019精品视频| 亚洲精品在线视频| www日韩欧美| 91精品久久久久久综合乱菊| 精品性高朝久久久久久久| 日韩精品久久久久| 欧美日本黄视频| 亚洲第一精品夜夜躁人人爽| 国产精品久久久久秋霞鲁丝| 日韩高清电影免费观看完整| 一区二区欧美日韩视频| 狠狠躁夜夜躁人人躁婷婷91| 久久久久久国产精品美女| 日韩美女视频在线观看| 日韩欧美国产激情| 亚洲国产成人久久综合一区| 国产精品高潮呻吟久久av黑人| 国产成人精彩在线视频九色| 亚洲最新在线视频| 综合网日日天干夜夜久久| 亚洲最大av网| 欧美成人免费视频| 乱亲女秽乱长久久久| 最新中文字幕亚洲| 精品国产欧美一区二区三区成人| 国产亚洲美女久久|