【1】認證
① 身份認證
身份認證是第一道門戶,進去之後才能談論授權的問題。
身份驗證,一般需要提供如身份ID 等一些辨別資訊來表明登入者的身份,如提供email,使用者名/密碼來證明。
在shiro中,使用者需要提供principals (身份)和credentials(證明)給shiro,進而應用能驗證使用者身份:
- principals:身份,即主體的辨別屬性,可以是任何屬性,如使用者名、郵箱等,唯一即可。一個主體可以有多個principals,但隻有一個Primary principals,一般是使用者名/郵箱/手機号。
- credentials:證明/憑證,即隻有主體知道的安全值,如密碼/數字證書等。
最常見的principals 和credentials 組合就是使用者名/密碼了。
② 身份認證流程
1)收集使用者身份/憑證,即如使用者名/密碼;
2)調用Subject.login進行登入,如果失敗将得到相應的AuthenticationException異常,根據異常提示使用者錯誤資訊;否則登入成功;
第2)步細節如下:
- 建立自定義的Realm 類,繼承org.apache.shiro.realm.AuthorizingRealm類,實作doGetAuthenticationInfo() 方法;
- Shiro将會調用自定義的Realm 類的doGetAuthenticationInfo()方法,然後正常傳回或者抛出異常;
如下圖所示:

如下示例:
③ 認證過程詳解
步驟如下:
- 首先調用Subject.login(token) 進行登入,其會自動委托給SecurityManager
- SecurityManager負責真正的身份驗證邏輯;它會委托給Authenticator 進行身份驗證;
- Authenticator 才是真正的身份驗證者,ShiroAPI 中核心的身份認證入口點,此處可以自定義插入自己的實作;
- Authenticator 可能會委托給相應的AuthenticationStrategy進行多Realm 身份驗證,預設ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm 身份驗證;
- Authenticator 會把相應的token 傳入Realm(通常是你的自定義CustomRealm.doGetAuthenticationInfo()),從Realm 擷取身份驗證資訊,如果沒有傳回/抛出異常表示身份驗證失敗了。此處可以配置多個Realm,将按照相應的順序及政策進行通路。
- 密碼的比對:通過 AuthenticatingRealm 的 credentialsMatcher 屬性來進行的密碼的比對!
④ AuthenticationException
如果身份驗證失敗請捕獲AuthenticationException或其子類。最好使用如“使用者名/密碼錯誤”而不是“使用者名錯誤”/“密碼錯誤”,防止一些惡意使用者非法掃描帳号庫。
⑤ Realm
Shiro從Realm 擷取安全資料(如使用者、角色、權限),即SecurityManager要驗證使用者身份,那麼它需要從Realm 擷取相應的使用者進行比較以确定使用者身份是否合法;也需要從Realm得到使用者相應的角色/權限進行驗證使用者是否能進行操作。
Realm接口如下:
String getName();//傳回一個唯一的Realm名字
boolean supports(AuthenticationToken token);//判斷此reaml是否支援此Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws
AuthenticationException;//根據Token擷取認證資訊
一般繼承AuthorizingRealm(授權)即可;其繼承了AuthenticatingRealm(即身份驗證),而且也間接繼承了CachingRealm(帶有緩存實作)。
Realm 的繼承關系如下圖示:
⑥ Authenticator
Authenticator 的職責是驗證使用者帳号,是ShiroAPI 中身份驗證核心的入口點。如果驗證成功,将傳回AuthenticationInfo驗證資訊;此資訊中包含了身份及憑證;如果驗證失敗将抛出相應的AuthenticationException異常。
SecurityManager接口繼承了Authenticator接口,另外還有一個ModularRealmAuthenticator實作,其委托給多個Realm 進行驗證,驗證規則通過AuthenticationStrategy接口指定。
如下圖示所示:
Authenticator繼承關系圖如下:
⑦ AuthenticationStrategy
AuthenticationStrategy接口的預設實作:
- FirstSuccessfulStrategy:隻要有一個Realm 驗證成功即可,隻傳回第一個Realm 身份驗證成功的認證資訊,其他的忽略;
- AtLeastOneSuccessfulStrategy:隻要有一個Realm驗證成功即可,和FirstSuccessfulStrategy不同,将傳回所有Realm身份驗證成功的認證資訊;
- AllSuccessfulStrategy:所有Realm驗證成功才算成功,且傳回所有Realm身份驗證成功的認證資訊,如果有一個失敗就失敗了。
如下圖所示:
ModularRealmAuthenticator預設是AtLeastOneSuccessfulStrategy政策。看不懂沒關系,繼續往下看。
⑦ 代碼示例
登入方法如下:
@RequestMapping(value="/doLogin" )
public String doLogin(Model model, HttpSession session, HttpServletRequest request, HttpServletResponse response, String userName, String password, String randomCode) throws Exception{
String msg;
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
log.debug("UserNamePasswordToken----:"+JSON.toJSONString(token));
token.setRememberMe(true);
request.setAttribute("userName", userName);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
if (subject.isAuthenticated()) {
return "redirect:/index";
}
} catch (UserNameException e) {
msg = e.getMessage();//Password for account " + token.getPrincipal() + " was incorrect.
model.addAttribute("message", msg);
} catch (PasswordException e) {
msg = e.getMessage();//Password for account " + token.getPrincipal() + " was incorrect.
model.addAttribute("message", msg);
} catch (IncorrectCredentialsException e) {
msg = "登入密碼錯誤. ";//Password for account " + token.getPrincipal() + " was incorrect.
model.addAttribute("message", msg);
} catch (ExcessiveAttemptsException e) {
msg = "登入失敗次數過多.";
model.addAttribute("message", msg);
} catch (LockedAccountException e) {
msg = "帳号已被鎖定,如有疑問請聯系管理者. ";//The account for username " + token.getPrincipal() + " was locked.
model.addAttribute("message", msg);
} catch (DisabledAccountException e) {
msg = "帳号已被禁用. ";//The account for username " + token.getPrincipal() + " was disabled.
model.addAttribute("message", msg);
} catch (ExpiredCredentialsException e) {
msg = "帳号已過期. ";//the account for username " + token.getPrincipal() + " was expired.
model.addAttribute("message", msg);
} catch (UnknownAccountException e) {
msg = "帳号不存在. ";//There is no user with username of " + token.getPrincipal()
model.addAttribute("message", msg);
} catch (UnauthorizedException e) {
msg = "您沒有得到相應的授權!" + e.getMessage();
model.addAttribute("message", msg);
}
return "forward:/login";
}
自定義CustomRealm.doGetAuthenticationInfo()方法如下:
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//擷取頁面傳來的使用者賬号
String loginName = token.getUsername();
//根據登入賬号從資料庫查詢使用者資訊
SysUser user = sysUserService.getUserByLoginCode(loginName);
System.out.println("從資料庫查詢到的使用者資訊 : "+user);
//一些異常新娘西
if (null == user) {
throw new UnknownAccountException();//沒找到帳号
}
if (user.getStatus()==null||user.getStatus()==0) {
throw new LockedAccountException();//帳号被鎖定
}
//其他異常...
//傳回AuthenticationInfo的實作類SimpleAuthenticationInfo
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
}
需要注意的是在登入成功後,登出登入一定要走Shiro過濾器 logout:
<!--請求logout,shrio擦除sssion-->
/logout=logout
或者自定義登出過濾器,總之要完成如下的功能:任何現有的Session都将會失效,而且任何身份都将會失去關聯。在web應用程式中,RememberMe cookie也将被删除。
否則,雖然登出成功了,Shiro緩存中還認為該使用者認證成功,使用者通路需要認證的連接配接時,将會直接通過!
【2】多Realm認證與配置
多Realm認證需要将ModularRealmAuthenticator作為securityManager屬性注冊到容器中。
① 如下配置兩個自定義Realm:
<!-- 自定義Realm -->
<bean id="customRealm" class="com.web.maven.shiro.CustomRealm">
<!-- 将憑證比對器設定到realm中,realm按照憑證比對器的要求進行散列 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="1"/>
</bean>
</property>
</bean>
<!-- 自定義SecondRealm -->
<bean id="customRealm2" class="com.web.maven.shiro.CustomRealm2">
<!-- 将憑證比對器設定到realm中,realm按照憑證比對器的要求進行散列 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA1"/>
<property name="hashIterations" value="1"/>
</bean>
</property>
</bean>
② 将兩個自定義Realm配置為bean-authenticator-class為ModularRealmAuthenticator的屬性
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="customRealm"/>
<ref bean="customRealm2"/>
</list>
</property>
</bean>
③ 将配置的authenticator注入到securityManager中
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入緩存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
<!--注入認證器-->
<property name="authenticator" ref="authenticator"></property>
</bean>
④ 多Realm下就需要考慮上面說的認證政策了
在authenticator中配置如下:
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="customRealm"/>
<ref bean="customRealm2"/>
</list>
</property>
<!--ModularRealmAuthenticator預設認證政策即為AtLeastOneSuccessfulStrategy
可不用顯示配置 -->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>
【3】多Realm配置方式2
上面是把realms配置給authenticator,當然也可以直接把realms配置給securityManager。
securityManager此時配置如下所示:
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入緩存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
<property name="authenticator" ref="authenticator"></property>
<property name="realms">
<list>
<ref bean="jdbcRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
</bean>
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>