身份驗(yàn)證
一般需要提供如身份 ID 等一下標(biāo)識信息來表名登錄者的身份,如提供email抹蚀,用戶名/密碼來證明剿牺。
在shiro中,用戶需要提供 principals(身份)和 credentials(證明)給shiro环壤,從而應(yīng)用能夠驗(yàn)證用戶身份晒来。
- principals:身份,即主體的標(biāo)識屬性郑现,可以是任何屬性湃崩,如用戶名荧降、郵箱等,唯一即可攒读。一個(gè)主體可以有多個(gè)principals朵诫,但只有一個(gè) Primary principals,一般是用戶名/郵箱/手機(jī)號薄扁;
- credentials 憑證剪返,即只有主體知道的安全值,如密碼等邓梅;
最常見的 principals 和 credentials 組合就是 用戶名/密碼了脱盲。
基本流程
- 獲取當(dāng)前的Subject,調(diào)用SecurityUtils.getSubject();
- 測試當(dāng)前用戶是否已經(jīng)被認(rèn)證.即是否已經(jīng)登錄日缨,調(diào)用Subject#isAuthencticated()方法钱反;
- 若沒有被認(rèn)證,則把用戶名和密碼封裝為UsernamePasswordToken對象匣距;
- 執(zhí)行登錄面哥,調(diào)用Subject#login(AuthenticationToken token)方法,其會自動委托給SecurityManager墨礁;
- SecurityManager 負(fù)責(zé)真正的身份驗(yàn)證邏輯幢竹;它會委托給 Authenticator 進(jìn)行身份驗(yàn)證;
- 自定義Realm方法恩静,從數(shù)據(jù)庫中獲取對應(yīng)的記錄焕毫,返回給Shiro;
-a.實(shí)際上需要繼承 org.apache.shiro.realm.AuthenticatingRealm 類驶乾;
-b.實(shí)現(xiàn) doGetAuthenticationInfo(AuthenticationToken token) 方法. - 由shiro完成對密碼的比對邑飒。
實(shí)現(xiàn)認(rèn)證流程
- 將ShiroRealm修改為以下代碼:
public class ShiroRealm extends AuthenticatingRealm {
//認(rèn)證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("2. " + token.hashCode());
return null;
}
}
- 實(shí)現(xiàn)請求Controller編碼
@Controller
@RequestMapping("/shiro")
public class ShiroController {
@RequestMapping("/shiroLogin")
public String login(@RequestParam("username") String username,@RequestParam("password") String password){
Subject subject = SecurityUtils.getSubject();
if (!subject.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
token.setRememberMe(true);
try {
System.out.println("1. " + token.hashCode());
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
}
}
return "redirect:/list.jsp";
}
}
- login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>login</title>
</head>
<body>
<h4>login page</h4>
<form action="shiro/shiroLogin" method="POST">
username:<input type="text" name="username"/><br><br>
password:<input type="password" name="password"/><br><br>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
4.filterChainDefinitions配置
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/shiro/shiroLogin = anon
/** = authc
</value>
</property>
- 運(yùn)行項(xiàng)目
在登錄頁面隨意輸入用戶名和密碼,登錄失敗级乐,兩次打印的token#hashCode()相等疙咸,后臺出現(xiàn)"org.apache.shiro.authc.UnknownAccountException"異常,這是因?yàn)闆]有具體實(shí)現(xiàn)認(rèn)證過程风科,接下來將完成認(rèn)證的Realm撒轮。
實(shí)現(xiàn)認(rèn)證Realm
例子:
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.把AuthenticationToken轉(zhuǎn)換為UserNamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.從UserNamePasswordToken中獲取username
String username = upToken.getUsername();
//3.調(diào)用dao層方法,從數(shù)據(jù)庫中查詢username對應(yīng)的用戶記錄
System.out.println("從數(shù)據(jù)庫中獲取username:" + username + " 所對應(yīng)的用戶信息贼穆。");
//4.若用戶不存在题山,則可以拋出 UnknownAccountException 異常
if ("unknown".equals(username)) {
throw new UnknownAccountException("用戶不存在");
}
//5.根據(jù)用戶信息的情況,覺得是否需要拋出其他的異常.
if ("monster".equals(username)) {
throw new LockedAccountException("用戶被鎖定");
}
//6.根據(jù)用戶的情況故痊,來構(gòu)造 AuthenticationInfo 對象并返回
//principal認(rèn)證實(shí)體顶瞳,可以是username,也可以是數(shù)據(jù)表對應(yīng)的用戶的實(shí)體類的對象
Object principal = username;
//credentials:密碼
Object credentials = "123456";
//realmName:當(dāng)前realm對象的name.調(diào)用父類的getName()
String realmName = getName();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
return info;
}
啟動項(xiàng)目:
- 在登錄頁面輸入"unknown"作為用戶名時(shí),拋出用戶不存在異常慨菱;
- 輸入"monster"作為用戶名時(shí)焰络,拋出用戶被鎖定異常;
- 輸入其他用戶名+密碼非"123456"時(shí)符喝,認(rèn)證不通過闪彼;
- 輸入其他用戶名+密碼"123456"時(shí),認(rèn)證通過协饲,重定向到list.jsp頁面备蚓;
密碼比對
通過 AuthenticatingRealm 的 credentialsMatcher 屬性來進(jìn)行的密碼的比對!
- 如何把一個(gè)字符串加密為MD5
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
Object source = "123456";
Object salt = null;
int hashIterations = 3;
SimpleHash hash = new SimpleHash(hashAlgorithmName, source, salt, hashIterations);
System.out.println(hash.toString());
}
- 替換當(dāng)前 Realm 的 credentialsMatcher 屬性囱稽,直接使用 HashedCredentialsMatcher 對象,并設(shè)置加密算法即可二跋。
<bean id="shiroRealm" class="org.keyhua.shiro.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法-->
<property name="hashAlgorithmName" value="MD5"></property>
<!--加密次數(shù)-->
<property name="hashIterations" value="3"></property>
</bean>
</property>
</bean>
- MD5鹽值加密
- 認(rèn)證時(shí)返回 SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 構(gòu)造器
- 使用 ByteSource.Util.bytes() 來計(jì)算鹽值
- 鹽值需要唯一:一般使用隨機(jī)字符串或用戶id
- 使用 new SimpleHash(hashAlgorithmName, source, salt, hashIterations) 來計(jì)算鹽值加密后的密碼
修改認(rèn)證實(shí)現(xiàn):
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.把AuthenticationToken轉(zhuǎn)換為UserNamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.從UserNamePasswordToken中獲取username
String username = upToken.getUsername();
//3.調(diào)用dao層方法战惊,從數(shù)據(jù)庫中查詢username對應(yīng)的用戶記錄
System.out.println("從數(shù)據(jù)庫中獲取username:" + username + " 所對應(yīng)的用戶信息。");
//4.若用戶不存在扎即,則可以拋出 UnknownAccountException 異常
if ("unknown".equals(username)) {
throw new UnknownAccountException("用戶不存在");
}
//5.根據(jù)用戶信息的情況吞获,覺得是否需要拋出其他的異常.
if ("monster".equals(username)) {
throw new LockedAccountException("用戶被鎖定");
}
//6.根據(jù)用戶的情況,來構(gòu)造 AuthenticationInfo 對象并返回
//principal認(rèn)證實(shí)體谚鄙,可以是username各拷,也可以是數(shù)據(jù)表對應(yīng)的用戶的實(shí)體類的對象
Object principal = username;
//credentials:密碼
Object credentials = null;
if ("admin".equals(username)){
credentials = "9aa75c4d70930277f59d117ce19188b0";
} else if ("user".equals(username)) {
credentials = "dd957e81b004227af3e0aa4bde869b25";
}
//realmName:當(dāng)前realm對象的name.調(diào)用父類的getName()
String realmName = getName();
//鹽值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = null;
//info = new SimpleAuthenticationInfo(principal, credentials, realmName);
info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
return info;
}
啟動項(xiàng)目,用戶名 user/admin闷营,密碼123456烤黍,認(rèn)證通過。
多Realm驗(yàn)證
再自定義一個(gè)relam:
public class SecondRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("[SecondRealm] doGetAuthenticationInfo");
//1.把AuthenticationToken轉(zhuǎn)換為UserNamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.從UserNamePasswordToken中獲取username
String username = upToken.getUsername();
//3.調(diào)用dao層方法傻盟,從數(shù)據(jù)庫中查詢username對應(yīng)的用戶記錄
System.out.println("從數(shù)據(jù)庫中獲取username:" + username + " 所對應(yīng)的用戶信息速蕊。");
//4.若用戶不存在,則可以拋出 UnknownAccountException 異常
if ("unknown".equals(username)) {
throw new UnknownAccountException("用戶不存在");
}
//5.根據(jù)用戶信息的情況娘赴,覺得是否需要拋出其他的異常.
if ("monster".equals(username)) {
throw new LockedAccountException("用戶被鎖定");
}
//6.根據(jù)用戶的情況规哲,來構(gòu)造 AuthenticationInfo 對象并返回
//principal認(rèn)證實(shí)體,可以是username诽表,也可以是數(shù)據(jù)表對應(yīng)的用戶的實(shí)體類的對象
Object principal = username;
//credentials:密碼
Object credentials = null;
if ("admin".equals(username)){
credentials = "28078bcf86c16b80329aa523afb74da57ffb8a11";
} else if ("user".equals(username)) {
credentials = "393c16607f34db540e1ec19ab2829044e98efaca";
}
//realmName:當(dāng)前realm對象的name.調(diào)用父類的getName()
String realmName = getName();
//鹽值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = null;
//info = new SimpleAuthenticationInfo(principal, credentials, realmName);
info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
return info;
}
}
修改applicationContext.xml配置文件:
<!-- 1.配置SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager" />
<property name="authenticator" ref="authenticator" />
<property name="realms">
<list>
<ref bean="shiroRealm" />
<ref bean="secondRealm"/>
</list>
</property>
</bean>
<!--2.配置CacheManager
2.1.需要引入ehcache的jar及配置文件
-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
</bean>
<!--3.配置realm
3.1 直接實(shí)現(xiàn)了Realm接口的bean
-->
<bean id="shiroRealm" class="org.keyhua.shiro.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法-->
<property name="hashAlgorithmName" value="MD5"></property>
<!--加密次數(shù)-->
<property name="hashIterations" value="3"></property>
</bean>
</property>
</bean>
<bean id="secondRealm" class="org.keyhua.shiro.SecondRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法-->
<property name="hashAlgorithmName" value="SHA1"></property>
<!--加密次數(shù)-->
<property name="hashIterations" value="3"></property>
</bean>
</property>
</bean>
多個(gè)Realm進(jìn)行驗(yàn)證時(shí)唉锌,驗(yàn)證規(guī)則通過 AuthenticationStrategy 接口指定
AuthenticationStrategy接口的默認(rèn)實(shí)現(xiàn):
-FirstSuccessfulStrategy:只要有一個(gè)Realm驗(yàn)證成功即可,只返回第一個(gè)Realm身份驗(yàn)證成功的認(rèn)證信息竿奏,其他的忽略袄简;
-AtLeastOneSuccessfulStrategy:只要有一個(gè)Realm驗(yàn)證成功即可,和 FirstSuccessfulStrategy 不同议双,將返回所有Realm身份驗(yàn)證成功的認(rèn)證信息痘番;
-AllSuccessfulStrategy:所有Realm驗(yàn)證成功才算成功,且返回所有Realm身份驗(yàn)證成功的認(rèn)證信息,如果有一個(gè)失敗就失敗了汞舱;
- ModularRealmAuthenticator 默認(rèn)是 AtLeastOneSuccessfulStrategy 策略
認(rèn)證策略的設(shè)置:
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!-- 設(shè)置認(rèn)證策略 -->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
</property>
<property name="realms">
<list>
<ref bean="shiroRealm" />
<ref bean="secondRealm"/>
</list>
</property>
</bean>