Shiro-學習總結-認證之自定義realm

用戶自定義認證需要實現Realm,其實現類如下圖所示:


推薦去繼續org.apache.shiro.realm.AuthorizingRealm

public class ShiroRealm extends AuthorizingRealm{//AuthenticatingRealm {//implements Realm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// 獲取用戶用戶身份驗證信息
		return null;
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// 獲取用戶身份獲取授權信息
		return null;
	}
}

簡單舉例:

pom.xml文件配置:

<dependencies>
	<dependency>
	    <groupId>junit</groupId>
	    <artifactId>junit</artifactId>
	    <version>4.11</version>
	</dependency>
    <dependency>
           <groupId>org.apache.shiro</groupId>
           <artifactId>shiro-core</artifactId>
           <version>1.2.2</version>
       </dependency>
        <dependency>
	    <groupId>log4j</groupId>
	    <artifactId>log4j</artifactId>
	    <version>1.2.17</version>
	</dependency>
	<dependency>
	    <groupId>org.slf4j</groupId>
	    <artifactId>slf4j-log4j12</artifactId>
	    <version>1.7.12</version>
	</dependency>
	
	<dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>3.0.1</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>jstl</artifactId>
           <version>1.2</version>
       </dependency>
	<dependency>
	    <groupId>javax.servlet.jsp</groupId>
	    <artifactId>javax.servlet.jsp-api</artifactId>
	    <version>2.3.1</version>
	</dependency>
	<dependency>
	    <groupId>commons-logging</groupId>
	    <artifactId>commons-logging</artifactId>
	    <version>1.1.1</version>
	</dependency>
  </dependencies>
  


創建MyRealm繼承AuthorizingRealm

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//從token中取出用戶身份信息
		String userName = (String) token.getPrincipal();
		
		//根據userName去數據庫中查詢
		//....
		//模擬從數據庫中查詢的密碼
		String password = "123456";
		
		//查詢不到返回Null
		
		//查詢到返回認證信息AuthorizationInfo
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, 
				password, getName());
		
		return simpleAuthenticationInfo;
		
	}


創建shiroRealm.ini配置文件:

[main]
myRealm = Lee.realm.MyRealm
securityManager.realms = $myRealm

創建測試程序:

public class Quickstart {

    private static final Logger log = LoggerFactory.getLogger(Quickstart.class);

    public static void main(String[] args) {
    	  //構造SecurityManager
		  //Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    	  	  Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiroRealm.ini");
		  SecurityManager securityManager = factory.getInstance();
				 
		  SecurityUtils.setSecurityManager(securityManager);
		 //獲取當前subject
		  Subject currentUser = SecurityUtils.getSubject();
		
		 //測試當前用戶是否已經登錄即執行認證
		  if (!currentUser.isAuthenticated()) {
			  //用戶名、密碼封裝爲UsernamePasswordToken
		      UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "123456");
		      //記住我
		      token.setRememberMe(true);
		      try {
		    	  //登錄即執行認證
		          currentUser.login(token);
		      } catch (UnknownAccountException uae) {//沒有此賬戶
		          log.info("There is no user with username of " + token.getPrincipal());
		          System.out.println("==================="+uae+"===================");
		      } catch (IncorrectCredentialsException ice) {//密碼錯誤
		          log.info("Password for account " + token.getPrincipal() + " was incorrect!");
		          System.out.println("==================="+ice+"===================");
		      } catch (LockedAccountException lae) {//賬戶被鎖定
		          log.info("The account for username " + token.getPrincipal() + " is locked.  " +
		                  "Please contact your administrator to unlock it.");
		          System.out.println("==================="+lae+"===================");
		      }catch (AuthenticationException ae) {
		    	  ae.printStackTrace();
		      }
		  }	 
		  	System.out.println("是否認證通過"+currentUser.isAuthenticated());
			 //退出登錄      
		      currentUser.logout();
		      System.exit(0);
		  }
}

密碼認證過程中沒有加入鹽值,只是簡單的認證。認證流程大體如下:

進入org.apache.shiro.authc.pam.ModularRealmAuthenticator類的下面方法:

    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);//單個realm
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);//多個realm
        }
    }

然後調用org.apache.shiro.realm.AuthenticatingRealm類的下面方法:

    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            info = doGetAuthenticationInfo(token);//獲取用戶身份信息,該方法由我們自定義的MyRealm覆蓋
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            assertCredentialsMatch(token, info);//獲取用戶身份信息後,進行密碼的比對。token爲用戶輸入的信息,info爲我們從數據源中查詢的信息
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

之後調用本類中的assertCredentialsMatch方法:

    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = getCredentialsMatcher();//默認爲SimpleCredentialsMatcher
        if (cm != null) {
            if (!cm.doCredentialsMatch(token, info)) {//進行密碼比對
                //not successful - throw an exception to indicate this:
                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                throw new IncorrectCredentialsException(msg);
            }
        } else {
            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
                    "credentials during authentication.  If you do not wish for credentials to be examined, you " +
                    "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
        }
    }


密碼比對會調用org.apache.shiro.authc.credential.SimpleCredentialsMatcher類的如下方法:

    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenCredentials = getCredentials(token);
        Object accountCredentials = getCredentials(info);
        return equals(tokenCredentials, accountCredentials);//進行簡單的equals比較
    }

上面的舉例中,密碼比對爲明文。開發中肯定不會這樣使用。一般數據源中存儲的都是加密後的密碼及鹽值,下面我們對上面的例子進行更改,以md5爲例:


需要修改ini配置文件,及realm
md5Shiro.ini配置文件:
[main]
#定義憑證匹配器
credentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
#設置散列算法
credentialsMatcher.hashAlgorithmName=MD5
#散列次數
credentialsMatcher.hashIterations=1024
#是否加鹽
credentialsMatcher.hashSalted=true
#將憑證匹配器設置到realm中
md5Realm = Lee.realm.Md5Realm
md5Realm.credentialsMatcher=$credentialsMatcher

securityManager.realms = $md5Realm

Md5Realm代碼如下:
public class Md5Realm extends AuthorizingRealm{

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//根據用戶輸入的賬戶模擬查詢數據庫
		//數據庫中保存的用戶密碼
		String dbaPassword="b8d63fc060e2b5651e8cb4e71ba61e6f";
		//數據庫中保存的鹽值
		ByteSource credentialsSalt=ByteSource.Util.bytes("aaa");
		
		//用戶輸入的登錄密碼
		Object principals=token.getPrincipal();
		
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principals, dbaPassword, credentialsSalt,getName());
		
		return authenticationInfo;
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}




提供小程序用於生成數據庫中加密後的密碼:
	public static void main(String[] args) {
		//加密方式
		String hashAlgorithmName = "MD5";
		//用戶輸入的密碼
		Object credentials = "123456";
		//鹽值
		ByteSource credentialsSalt=ByteSource.Util.bytes("aaa");
		//參數一:散列算法	參數二:原始密碼   參數三:鹽值    參數四:散列次數
		System.out.println(new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, 1024));
		
		//參數一:原始密碼   參數二:鹽值    參數三:散列次數
		Md5Hash md5Hash = new Md5Hash("123456", "aaa", 1024);
		System.out.println(md5Hash.toString());
	}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章