用戶自定義認證需要實現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.");
}
}
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);//進行簡單的equals比較
}
[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());
}