*******完整代碼在文章最下面,轉載請說明出處,謝謝 *******
簡單的web應用進行身份認證的流程:
1.對未認證的用戶請求進行攔截,跳轉到認證頁面。
2.用戶通過用戶名+密碼及其他憑證進行身份認證最住,認證成功跳轉成功頁面,認證失敗提示相關失敗信息怠惶。
根據流程涨缚,采用springboot+shiro進行快速開發(fā)。
使用到的相關pom依賴:
<!-- web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- thymeleaf 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- shiro相關依賴 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
1.對未認證的用戶請求進行攔截策治,跳轉到認證頁面脓魏。
(0) 這里需要shiro的攔截器配置,新建ShiroConfig配置類览妖,配置過濾器
/**
* @Description springboot中的Shiro配置類
* @Author 張小黑的貓
* @data 2019-05-22 17:17
*/
@Configuration
public class ShiroConfig {
/**
* 配置Shiro的Web過濾器轧拄,攔截瀏覽器請求并交給SecurityManager處理
* @return
*/
@Bean
public ShiroFilterFactoryBean webFilter(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//配置攔截鏈 使用LinkedHashMap,因為LinkedHashMap是有序的,shiro會根據添加的順序進行攔截
// Map<K,V> K指的是攔截的url V值的是該url是否攔截
Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
//authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問,先配置anon再配置authc讽膏。
filterChainMap.put("/login","anon");
filterChainMap.put("/**", "authc");
//設置攔截請求后跳轉的URL.
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
}
- Shiro的用戶認證
先說下shiro認證的流程:
創(chuàng)建SecurityManager
安全管理器 > 主體Subject
提交認證信息 >SecurityManager
安全管理器認證 >SecurityManager
調用Authenticator
認證器認證 >Realm
驗證
有幾個概念:
- Subject:主體檩电,代表了當前“用戶”;所有
Subject
都綁定到SecurityManager
府树,與Subject
的所有交互都會委托給SecurityManager
俐末;可以把Subject
認為是一個門面;SecurityManager
才是實際的執(zhí)行者奄侠; - SecurityManager安全管理器:所有與安全有關的操作都會與
SecurityManager
交互卓箫;且它管理著所有Subject
;負責與后邊介紹的其他組件進行交互垄潮。(類似于SpringMVC
中的DispatcherServlet
控制器) - Realm:域烹卒,Shiro從從
Realm
獲取安全數據(如用戶闷盔、角色、權限)旅急,就是說SecurityManager
要驗證用戶身份逢勾,那么它需要從Realm
獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm
得到用戶相應的角色/權限進行驗證用戶是否能進行操作藐吮;可以把Realm看成DataSource
溺拱,即安全數據源。
(1)首先在ShiroConfig配置類中創(chuàng)建SecurityManager
安全管理器
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
注意:SecurityManager
導包的時候選org.apache.shiro.mgt.SecurityManager;
而不是java.lang.SecurityManager
(2)SecurityManager
安全管理器需要到realm中去驗證認證信息谣辞,所以給SecurityManager
設置Realm
迫摔。
Shiro的Realm
分為IniRealm
、JdbcRealm
以及自定義realm
泥从,我們這里使用自定義的realm
實現業(yè)務邏輯句占。新建自定義Realm類CustomRealm
繼承AuthorizingRealm
。
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
(3)重寫AuthorizingRealm
中的認證方法doGetAuthenticationInfo
歉闰。
@Override
/**
* 認證
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.獲取用戶輸入的賬號
String username = (String)token.getPrincipal();
//2.通過username從數據庫中查找到user實體
User user = getUserByUserName(username);
if(user == null){
return null;
}
//3.通過SimpleAuthenticationInfo做身份處理
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(user,user.getPassword(),getName());
//4.用戶賬號狀態(tài)驗證等其他業(yè)務操作
if(!user.getAvailable()){
throw new AuthenticationException("該賬號已經被禁用");
}
//5.返回身份處理對象
return simpleAuthenticationInfo;
}
上面的token憑證是來自主體Subject.login(token)
提交時的token,主體的提交下面會說到辖众。
這里還有個比較有意思的點:new SimpleAuthenticationInfo(user,user.getPassword(),getName());
中的參數問題卓起。第一個參數是從數據庫中獲取的User對象
和敬,第二個參數是數據庫獲取的密碼,第三個參數是當前Realm的名稱戏阅。其中第一個參數傳username也可以昼弟,那么傳username
和User對象
的區(qū)別是啥呢?Shiro為我們提供了獲取當前用戶信息的方法:
* shiro獲取當前用戶
* @return
*/
private User currentUser(){
User currentUser = (User) SecurityUtils.getSubject().getPrincipal();
return currentUser;
}
有些業(yè)務場景需要獲取當前用戶的信息(用戶的狀態(tài)等等)進行業(yè)務操作奕筐,那么上面的區(qū)別就顯而易見了舱痘。如果傳的是username
那么用戶的其他信息就拿不到了。
(4) 將自定義Realm注入到SecurityManager
安全管理器中
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//將自定義的realm交給SecurityManager管理
securityManager.setRealm(new CustomRealm());
return securityManager;
}
(5) Shiro配置類的過濾器中啟用安全管理器离赫,即shiroFilterFactoryBean
中配置SecurityManager
//設置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager());
(6) 主體提交認證信息芭逝,即登錄請求
@PostMapping("/login")
public String login(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject currentUser = SecurityUtils.getSubject();
try {
//主體提交登錄請求到SecurityManager
currentUser.login(token);
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密碼不正確");
}catch(UnknownAccountException uae){
model.addAttribute("msg","賬號不存在");
}catch(AuthenticationException ae){
model.addAttribute("msg","狀態(tài)不正常");
}
if(currentUser.isAuthenticated()){
System.out.println("認證成功");
model.addAttribute("currentUser",currentUser());
return "success";
}else{
token.clear();
return "login";
}
}
到這基本認證的流程就已經通了。在這里貼一下項目結構和全部代碼渊胸,供大家參考:(不建議直接全部復制粘貼)
pom.xml
:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro相關依賴 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
ShiroConfig.java:
/**
* @Description springboot中的Shiro配置類
* @Author 張小黑的貓
* @data 2019-05-22 17:17
*/
@Configuration
public class ShiroConfig {
/**
* 配置Shiro核心 安全管理器 SecurityManager
* SecurityManager安全管理器:所有與安全有關的操作都會與SecurityManager交互旬盯;且它管理著所有Subject;負責與后邊介紹的其他組件進行交互翎猛。(類似于SpringMVC中的DispatcherServlet控制器)
*/
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//將自定義的realm交給SecurityManager管理
securityManager.setRealm(new CustomRealm());
return securityManager;
}
/**
* 配置Shiro的Web過濾器胖翰,攔截瀏覽器請求并交給SecurityManager處理
* @return
*/
@Bean
public ShiroFilterFactoryBean webFilter(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//設置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager());
//配置攔截鏈 使用LinkedHashMap,因為LinkedHashMap是有序的,shiro會根據添加的順序進行攔截
// Map<K,V> K指的是攔截的url V值的是該url是否攔截
Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
//配置退出過濾器logout切厘,由shiro實現
filterChainMap.put("/logout","logout");
//authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問,先配置anon再配置authc萨咳。
filterChainMap.put("/login","anon");
filterChainMap.put("/**", "authc");
//設置默認登錄的URL.
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
}
CustomRealm.java:
/**
* @Description: shiro的自定義realm
* Realm:域,Shiro從從Realm獲取安全數據(如用戶疫稿、角色培他、權限)鹃两,就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法舀凛;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作怔毛;可以把Realm看成DataSource,即安全數據源腾降。
* @Author 張小黑的貓
* @data 2019-05-22 17:51
*/
public class CustomRealm extends AuthorizingRealm {
@Override
/**
* 認證
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.獲取用戶輸入的賬號
String username = (String)token.getPrincipal();
//2.通過username從數據庫中查找到user實體
User user = getUserByUserName(username);
if(user == null){
return null;
}
//3.通過SimpleAuthenticationInfo做身份處理
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(user,user.getPassword(),getName());
//4.用戶賬號狀態(tài)驗證等其他業(yè)務操作
if(!user.getAvailable()){
throw new AuthenticationException("該賬號已經被禁用");
}
//5.返回身份處理對象
return simpleAuthenticationInfo;
}
@Override
/**
* 授權
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
return null;
}
/**
* 模擬通過username從數據庫中查找到user實體
* @param username
* @return
*/
private User getUserByUserName(String username){
List<User> users = getUsers();
for(User user : users){
if(user.getUsername().equals(username)){
return user;
}
}
return null;
}
/**
* 模擬數據庫數據
* @return
*/
private List<User> getUsers(){
List<User> users = new ArrayList<>();
users.add(new User("張小黑的貓","123qwe",true));
users.add(new User("張小黑的狗","123qwe",false));
return users;
}
}
User.java
/**
* @Description 用戶
* @Author 張小黑的貓
* @data 2019-05-22 19:18
*/
public class User {
private String username;
private String password;
private Boolean available;
public User(String username, String password, Boolean available) {
this.username = username;
this.password = password;
this.available = available;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
}
LoginController.java:
/**
* @Description 登錄
* @Author 張小黑的貓
* @data 2019-05-22 18:17
*/
@Controller
public class LoginController {
@GetMapping("/login")
public String login(){
return "login";
}
@PostMapping("/login")
public String login(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject currentUser = SecurityUtils.getSubject();
try {
//主體提交登錄請求到SecurityManager
currentUser.login(token);
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密碼不正確");
}catch(UnknownAccountException uae){
model.addAttribute("msg","賬號不存在");
}catch(AuthenticationException ae){
model.addAttribute("msg","狀態(tài)不正常");
}
if(currentUser.isAuthenticated()){
System.out.println("認證成功");
model.addAttribute("username",username);
return "success";
}else{
token.clear();
return "login";
}
}
}
login.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
<span th:text="${msg}" style="color: red"></span><br>
用戶名:<input type="text" name="username"><br>
密 碼:<input type="password" name="password"><br>
<input type="submit" value="Login">
</form>
</body>
</html>
success.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>success</title>
</head>
<body>
<span th:text="'歡迎你,'+${username}"></span>
</body>
</html>
共同學習拣度,歡迎指正修改~ 喵喵喵?
下一篇文章:Springboot整合Shiro: 詳細的權限管理