大致過程
用前臺的用戶名、明文密碼等信息創建驗證的token令牌對象
執行subject.login(token);登陸時,會跳轉到自定義的Realm的認證方法,一般在認證方法裏面查詢數據庫,獲取數據庫對應的user信息(包括加密後的密碼),將user信息封裝到info對象裏
Shiro內部驗證的時候,會將token的明文密碼加密後,與info裏user的密碼(也就是數據庫中用戶密碼)進行比較,結果一致則返回true,登陸成功
執行過程
1 .前臺頁面登陸後,執行登陸驗證方法subject.login(token);實際調用的是securityManager.login(this, token)
1-1.securityManager.login(this, token)方法裏調用 authenticate(token);方法獲取需要匹配的AuthenticationInfo info對象
1-2.authenticate方法裏調用doAuthenticate(token);來獲取需要匹配的AuthenticationInfo info對象
1-3.在doAuthenticate方法裏面,會獲取自定義的Realm,本例只設置一個Realm,所以跳轉單Realm的認證方法doSingleRealmAuthentication
1-4.在doSingleRealmAuthentication方法裏,會跳轉getAuthenticationInfo方法:
1-5.在getAuthenticationInfo方法中,先從緩存中獲取匹配的AuthenticationInfo info對象,本例沒有設置緩存,所以往下通過自定義Realm的doGetAuthenticationInfo(token)方法獲取info對象:
1-6.往下會跳轉到自定義Realm重寫的doGetAuthenticationInfo方法裏。方法返回SimpleAuthenticationInfo的info對象,SimpleAuthenticationInfo(,,,,)有四個參數:
1).第一個Object principal,可以是用戶名,也可以是user對象
2).第二個Object hashedCredentials,需要認證的密碼,前臺輸入的密碼會和此密碼匹配
3).第三個ByteSource credentialsSalt,加密認證是的salt
4).第四個String realmName,就是realm的名字,即shiroConfig配置的relam
1-7.執行完自定義Realm獲得info對象,info對象包含了數據庫的密碼(加密過的密碼),然後執行assertCredentialsMatch(token, info);進行密碼驗證:
1-8.進入密碼驗證方法assertCredentialsMatch先獲取密碼比較器,然後再調用密碼比較方法doCredentialsMatch:
1-9.在doCredentialsMatch方法裏,hashProvidedCredentials(token, info);方法主要是將前臺的明文密碼通過加密算法SimpleHash後,獲取加密後的密碼。
1-10.前臺明文密碼加密後密碼tokenHashedCredentials與自定義Realm從數據庫中獲取的加密密碼進行匹配:
1-11.匹配成功subject.login(token);返回true;
2.登陸成功重定向到userList.do的控制器,進行成功頁面跳轉:
2-1.userList頁面中使用了shiro:hasPermission標籤做權限控制,頁面有三個shiro:hasPermission權限標籤,則會執行三次自定義Realm裏的授權方法doGetAuthorizationInfo:
2-2.具體源碼沒怎麼看,但主要和驗證差不多,都是先從緩存取授權的info對象,沒有再跑自定義Realm的授權方法去獲取info對象。我想大概就是shiro:hasPermission標籤的值和查詢數據庫獲取的權限列表perms進行匹配,不涉及加密,匹配成功則顯示標籤內容:
代碼示例
代碼地址:
https://github.com/OooooOz/SpringBoot-Shiro
前臺登陸頁面:
<body>
<h2>登錄頁面</h2>
<form action="login.do" method="post" name="loginForm">
用戶名稱:<input type="text" name="username" value=""><br>
用戶密碼:<input type="password" name="password" value=""><br>
<input type="submit" name="log" value="登錄"/>
<input type="checkbox" name="rememberMe" />記住我<br/>
</form>
<a href="/unlockAccount.do">解鎖admin用戶</a></button>
<div th:text="${msg}"></div>
</body>
控制器Controller:
@Controller
public class LoginController {
@Resource
IUserService userService;
@RequestMapping("/toLogin.do")
public String toLogin() {
return "login";
}
@RequestMapping("/login.do")
public String login(String username, String password,boolean rememberMe, Model model) {
// 判斷用戶名密碼是否爲空
if (username != null && !"".equals(username) && password != null && !"".equals(password)) {
// 1.獲取subject
Subject subject = SecurityUtils.getSubject();
// 2.創建驗證用的令牌對象
UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe);
try {
//3.登陸認證
subject.login(token);
boolean flag = subject.isAuthenticated(); //判斷是否通過認證
//4.認證結果處理
if(flag){
System.out.println("登錄成功!");
User user = (User)subject.getPrincipal();
model.addAttribute("user",user);
return "redirect:userList.do";
}else{
model.addAttribute("msg","登錄認證失敗!");
return "login";
}
} catch (Exception e) {
model.addAttribute("msg","登錄認證失敗!");
if (e instanceof LockedAccountException) {
model.addAttribute("msg","失敗次數過多,用戶已鎖定,五分鐘後再試");
}
}
}
return "login";
}
@RequestMapping("/userList.do")
//@ResponseBody
public String userList(Model model){
//查詢所有的用戶信息並且顯示到頁面上
Subject subject = SecurityUtils.getSubject();
System.out.println("==============是否通過認證:"+subject.isAuthenticated());
System.out.println("==============是否記住我:"+subject.isRemembered());
List<User> list = userService.findAll();
model.addAttribute("userList", list);
return "userList";
}
@RequestMapping("/unFunc.do")
public String noFunc(){
return "unFunc";
}
@RequestMapping("/logout.do")
public String logout(){
//清除session
return "login";
}
}
ShiroConfig的過濾器:
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證
shiroFilterFactoryBean.setLoginUrl("/toLogin.do");
//通過unauthorizedUrl指定沒有權限操作時跳轉頁面
shiroFilterFactoryBean.setUnauthorizedUrl("/unFunc.do");
//-----------------------------過慮器鏈定義------------------------------//
LinkedHashMap<String, String> perms = new LinkedHashMap<>();
perms.put("/login.do","anon");
//其他資源都需要認證 authc 表示需要認證才能進行訪問 user表示配置記住我或認證通過可以訪問的地址
perms.put("/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(perms); //把權限過濾map設置shiroFilterFactoryBean
return shiroFilterFactoryBean;
}
自定義Realm:
/**
* 認證方法
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("執行了認證方法。。。。");
String username = token.getPrincipal().toString();
System.out.println("username:" + username);
//需要通過用戶名查詢用戶密碼
User user = userService.findByUsername(username);
//把user對象封裝的AuthenticationInfo中返回
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(user.getSalt()),"shiroRealm");
return authenticationInfo;
}
登陸成功跳轉頁面:
<table>
<caption>
<div shiro:hasPermission="function:add"><a href="/toAdd.do">添加</a></div>
<div shiro:hasPermission="function:update"><a href="/toUpdate.do">修改</a></div>
<div shiro:hasPermission="function:delete"><a href="/toDelete.do">刪除</a></div>
</caption>
<tr>
<td>用戶ID</td><td>用戶名稱</td><td>電話</td><td>email</td><td>狀態</td><td>創建時間</td><td>備註</td>
</tr>
<tr th:each="user : ${userList}">
<td th:text="${user.userId}"></td>
<td th:text="${user.userName}"></td>
<td th:text="${user.phone}"></td>
<td th:text="${user.email}"></td>
<td th:if="${user.status == 1 ?'正常':'異常'}"></td>
<td th:text="${user.createTime}"></td>
<td th:text="${user.note}"></td>
</tr>
</table>