上一篇[shiro-初體驗]中講解了Shiro的簡單用法, 實現了URL是否須要登陸訪問, 當未登陸訪問URL時自動跳轉至登陸頁.git
本篇主要講解在Shiro如何實現登陸處理. 先簡單說一下Shiro的登陸處理流程.github
Shiro的登陸處理是在authc
過濾器中. authc
會判斷若是是登陸請求會單獨處理, 所以登陸請求必需要配置成authc
.數據庫
其中, 登陸請求包括兩個:apache
Shiro判斷是不是登陸請求時認這兩個登陸請求必須是同一個地址, 而且GET
爲訪問登陸頁, POST
爲提交登陸安全
// 登陸請求(包括訪問登陸頁和提交登陸)
if (isLoginRequest(request, response)) {
// 提交登陸
if (isLoginSubmission(request, response)) {
// 提交登陸, 執行Shiro的登陸邏輯
return executeLogin(request, response);
} else {
// 訪問登陸請求, 繼續執行進入控制器
return true;
}
}
複製代碼
上一篇中, 咱們訪問登陸頁的請求爲/login.jsp
, 直接訪問登陸JSP, 若是咱們在用POST
訪問JSP顯然是不合理的使用JSP了.bash
所以, 咱們將登陸請求修改成/login
, 在控制器中對GET
和POST
進行處理. 當修改了登陸請求地址時須要在Shiro配置一下app
// Shiro核心配置
shiroFilter(ShiroFilterFactoryBean) {
// 登陸URL(包括請求登陸頁和提交登陸)
// 自定義的登陸URL必須單獨設置
loginUrl = "/login"
// ....
}
複製代碼
相應的,在控制器中也增長兩個方法分別處理登陸請求和提交登陸jsp
// 處理請求登陸頁面
@GetMapping("/login")
public String toLogin() {
return "/login";
}
// 處理提交登陸
@PostMapping("/login")
public String login() {
System.out.println("處理提交登陸");
return "/success";
}
複製代碼
登陸頁面: login.jspide
<form action="/login" method="POST">
<input type="text" name="username" placeholder="用戶名" value="" />
<input type="password" name="password" placeholder="密碼" value="" />
<input type="submit" value="當即登陸" />
</form>
複製代碼
完成上述操做後啓動項目, 訪問/page/a
時, 因爲未登陸Shiro會重定向至/login
, 在登陸頁面輸入用戶名和密碼, 點擊當即登陸按鈕後會以POST
方式提交至/login
, Shiro就會處理本次登陸請求了.post
那麼, 問題來了, Shiro怎麼知道輸入的用戶名和密碼是否正確呢?
答案必定是不知道, 所以, 須要咱們對用戶名和密碼進行驗證後將結果告訴Shiro. 那麼如何實現自定義驗證呢?
Shiro對Realm
的定義: 一個能夠訪問系統安全相關信息(例如用戶, 角色, 權限等)的組件. 通俗的說, 就是在Realm
實現寫查詢用戶, 角色, 權限等系統安全相關的數據的方法.
用戶信息通常會保存在數據庫中, 咱們能夠在Realm
中經過登陸頁面傳遞的用戶名去數據庫查詢用戶, 將結果返回給Shiro.
然而Shiro並不知道用戶名和密碼是否正確, 因此提供了Realm
組件, 讓咱們在Realm
中查詢用戶相關信息並返回, Shiro根據Realm
返回結果判斷是否登陸成功.
舉個例子
你在相親的時候要請女生吃飯, 你也不知道每次相親的女生喜歡吃什麼. 但針對每一個菜系都你準備好了相應的餐廳. 聰明的你準備了一個小盒子, 相親時讓女生把想吃的寫好放到盒子裏面, 而後你根據盒子裏面的內容到事先準備好的餐廳去吃飯. 至於女生是用鉛筆寫的, 仍是鋼筆寫的你根本不會關心, 你只關心女生想吃什麼.
上例中的你至關於Shiro, 準備好各類餐廳至關於實現了各類登陸的邏輯, 小盒子就至關於Realm
, 女生寫的紙條至關於實現了一個Realm, 紙條上的內容至關於查詢到的用戶信息. 至因而用鉛筆仍是鋼筆寫則至關於用戶信息獲取方式(數據庫,文件或其餘).
Shiro只關心返回的結果, 不會關心Realm查詢用戶信息的實現過程. 下面咱們來實現一個Realm
// 自定義查詢用戶信息的Realm
public class UserRealm extends AuthenticatingRealm {
// 獲取用戶信息的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 登陸用戶名
// Shiro會將提交登陸傳入的用戶名和密碼封裝到UsernamePasswordToken中
String username = ((UsernamePasswordToken) token).getUsername();
// 根據用戶名從數據庫或其餘存儲中查詢用戶信息
// 模擬數據庫查詢, 返回用戶信息
User dbUser = getUser(username);
// 用戶不存在,當返回null時Shiro會認爲用信息不存在
if (dbUser == null) {
return null;
}
// 將查詢到用戶信息返回給Shiro
// 參數1: Shiro會將該參數做爲當前登陸用戶的信息保存,隨時可取
// 參數2: 當前用戶的密碼,Shiro使用該參數和提交登陸傳遞的密碼進行判斷
// 參數3: Realm名稱,暫不處理
return new SimpleAuthenticationInfo(dbUser, dbUser.getPassword(), "");
}
}
複製代碼
本例未鏈接數據庫, 模擬代碼:
// 模擬根據用戶名在數據庫查詢用戶信息
private User getUser(String username) {
// 使用"atd681"做爲登陸密碼才能查到信息
if (!"atd681".equals(username)) {
return null;
}
User dbUser = new User();
dbUser.setUserId(1L);
dbUser.setUsername(username);
dbUser.setPassword("123");
return dbUser;
}
複製代碼
自定義Realm
後須要告知Shiro哪一個Realm
是查詢用戶信息的, 即將Realm
配置到Shiro中
// 安全管理器
securityManager(DefaultWebSecurityManager) {
realm = ref("userRealm")
}
// 定義Realm
userRealm(UserRealm)
複製代碼
啓動項目, 訪問/page/a
, 未登陸時Shiro重定向至登陸頁面. 輸入atd681/123
便可登陸成功並跳轉/page/a
當登陸成功後, Shiro會重定向到成功頁面
/page/a
)跳轉至登陸時, 登陸成功會跳轉至目標頁面(/page/a
)Shiro默認成功頁爲/
, 可自定義默認成功頁
// Shiro核心配置
shiroFilter(ShiroFilterFactoryBean) {
// 默認登陸成功後跳轉的頁面地址
successUrl = "/index"
// 其餘配置...
}
複製代碼
登陸成功後並無執行到控制器中的處理POST
登陸的方法. 輸入atd681
之外的帳號或輸入錯誤密碼會致使登陸失敗, 卻會執行控制器中的處理POST
登陸的方法. 爲何呢???
Shiro的登陸邏輯:
登陸失敗時, Shiro用異常表示失敗緣由, 並將失敗緣由保存在Request
中, key爲shiroLoginFailure
, Shiro登陸邏輯中會拋出以下異常:
org.apache.shiro.authc.UnknownAccountException
org.apache.shiro.authc.IncorrectCredentialsException
同時內置了以下異常, 方便用戶自行驗證時拋出:
org.apache.shiro.authc.DisabledAccountException
org.apache.shiro.authc.LockedAccountException
org.apache.shiro.authc.ExcessiveAttemptsException
org.apache.shiro.authc.ConcurrentAccessException
登陸失敗時能夠根據異常在頁面中顯示相應的錯誤提示信息, 本例登陸失敗時返回登陸頁並顯示錯誤信息
<!-- 有登陸錯誤信息時,根據異常顯示對應的提示信息 -->
<c:if test="${shiroLoginFailure != null}">
<c:if test="${shiroLoginFailure == 'org.apache.shiro.authc.UnknownAccountException'}">用戶不存在</c:if>
<c:if test="${shiroLoginFailure == 'org.apache.shiro.authc.IncorrectCredentialsException'}">密碼不正確</c:if>
</c:if>
<!-- 無登陸錯誤時 -->
<c:if test="${shiroLoginFailure == null}">你訪問的頁面須要先進行登陸</c:if>
<form action="/login" method="post">
<input type="text" name="username" placeholder="用戶名" value="" />
<input type="password" name="password" placeholder="密碼" value="" />
<input type="submit" value="當即登陸" />
</form>
複製代碼
配置登出URL使用logout
過濾器便可. Shiro登出後默認重定向至登陸頁.
// Shiro核心配置
shiroFilter(ShiroFilterFactoryBean) {
// 配置URL規則
// 有請求訪問時Shiro會根據此規則找到對應的過濾器處理
filterChainDefinitionMap = [
"/page/n" : "anon", // /page/n不須要登陸便可訪問
"/logout" : "logout", // 登出使用logout過濾器
"/**": "authc" // 其他全部頁面須要認證(authc爲認證過濾器)
]
// 其餘配置 ....
}
複製代碼
如登出後自定義重定向頁面, 須要在配置文件中手動定義logout
過濾器(未定義時Shiro會經過Spring自動加載)
// 手動定義Logout過濾器
// 未定義時Shiro會經過Spring自動加載
logout(LogoutFilter){
redirectUrl = "/logout_success.jsp"
}
複製代碼
同時, 必須配置logout_success.jsp
不須要登陸也能夠訪問(anon
), 若是不配置, 登出後進入logout_success.jsp
不須要時會被Shiro攔截(此時未登陸)並重定向至登陸(登陸成功後會重定向至logout_success.jsp
)
"/logout_success.jsp" : "anon", // 登出成功頁不須要認證
複製代碼
在1) 自定義Realm
中提到獲取的登陸用戶信息在登陸成功後會被Shiro保存. Shiro提供了能夠獲取登陸用戶信息的方法.
@RequestMapping("/page/a")
public String toPageA(ModelMap map) {
// Shiro提供的獲取當前登陸用戶信息的靜態方法
// 用戶信息對象爲在Realm中保存的對象
User user = (User) SecurityUtils.getSubject().getPrincipal();
// 獲取用戶ID,用戶名
map.put("userId", user.getUserId());
map.put("userName", user.getUsername());
return "/page_a";
}
複製代碼
獲取到的用戶對象必須和在Realm
中返回SimpleAuthenticationInfo
對象中第一個參數一致
至此, 基於Shiro認證的示例配置完成.