1、SpringBoot整合Shiro
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。
1.1、shiro簡介
shiro有個核心組件,分別為Subject、SecurityManager和Realms
- Subject:相當於當前操作的”用戶“,這個用戶不一定是一個具體的人,是一個抽象的概念,表明的是和當前程序進行交互的任何東西,例如爬蟲、腳本、等等。所有的Subject都綁定到SecurityManager上,與 Subject 的所有交互都會委托給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者。
- SecurityManager:這個是shiro框架的核心,所有與安全相關的操作都會與它進行交互,它管理者所有的Subject。
- Realms:充當了Shiro與應用安全數據間的”橋梁“,當對用戶執行認證(登錄)和授權(訪問控制)驗證時,SecurityManager 需要從 Realm 獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應的角色 / 權限進行驗證用戶是否能進行操作。
如果想要更加深入的了解的shrio可以參考:https://www.w3cschool.cn/shiro/co4m1if2.html
1.2、代碼的具體實現
1.2.1、Maven的配置
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
<!--shiro整合thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--shiro緩存-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.7.1</version>
</dependency>
shiro默認是與jsp進行使用的,而這里是shiro整合thymeleaf所有要導入shiro整合thymeleaf的jar包
1.2.2、整合需要實現的類
一般來說整合只需要完成兩個類的實現即可
一個是 ShiroConfig 一個是 CustomerRealm
如果需要添加shiro緩存並且不是自帶的緩存而是redis緩存還需要進行另外兩個類的編寫
一個是 RedisCache 一個是 RedisCacheManager
1.2.3、項目結構

1.2.4、ShiroConfig的實現
未加shiro的緩存
package com.yuwen.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.yuwen.shiro.cache.RedisCacheManager;
import com.yuwen.shiro.realm.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//讓頁面shiro標簽生效
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
//1、創建shiroFilter 負責攔截所有請求
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//給filter設置安全管理
factoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系統的受限資源
//配置系統公共資源 全部都能訪問的設置anon
Map<String,String> map = new HashMap<>();
map.put("/main","authc");//請求這個資源需要認證和授權 authc表示需要認證后才能訪問
map.put("/admin","roles[admin]"); //表示admin角色才能訪問 roles[]表示需要什么角色才能訪問
map.put("/manage","perms[user:*:*]"); //表示需要user:*:*權限才能訪問 perms[]表示需要什么權限才能訪問
//訪問需要認證的頁面如果未登錄會跳轉到/login路由進行登陸
factoryBean.setLoginUrl("/login");
//訪問未授權頁面會自動跳轉到/unAuth路由
factoryBean.setUnauthorizedUrl("/unAuth");
factoryBean.setFilterChainDefinitionMap(map);
return factoryBean;
}
//2、創建安全管理器
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//給安全管理器設置
securityManager.setRealm(realm);
return securityManager;
}
//3、創建自定義的realm
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//修改憑證校驗匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//設置加密算法為md5
credentialsMatcher.setHashAlgorithmName("MD5");
//設置散列次數
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
}
因為一般在數據庫中設置明文密碼不安全,所有我這里對密碼進行了md5加密,我的加密方式為:密碼 = 密碼+鹽+散列次數 而后進行MD5加密 所以這里創建自定義的realm時需要進行設置匹配器這樣登錄時密碼才能匹配成功
1.2.5、CustomerRealm的實現
package com.yuwen.shiro.realm;
import com.yuwen.pojo.User;
import com.yuwen.pojo.vo.ViewPerms;
import com.yuwen.pojo.vo.ViewRole;
import com.yuwen.service.UserService;
import com.yuwen.shiro.salt.MyByteSource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.util.List;
//自定義realm
public class CustomerRealm extends AuthorizingRealm {
@Resource
private UserService userService;
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//獲取身份信息
String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal();
//根據主身份信息獲取角色 和 權限信息
List<ViewRole> roles = userService.findRolesByUsername(primaryPrincipal);
if (!CollectionUtils.isEmpty(roles)){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
roles.forEach(viewRole -> {
simpleAuthorizationInfo.addRole(viewRole.getName());
//權限信息
List<ViewPerms> perms = userService.findPermsByRoleId(viewRole.getName());
if (!CollectionUtils.isEmpty(perms)){
perms.forEach(viewPerms -> {
simpleAuthorizationInfo.addStringPermission(viewPerms.getPName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//獲取登入的身份信息
String principal = (String) authenticationToken.getPrincipal();
User user = userService.findByUsername(principal);
if (!ObjectUtils.isEmpty(user)){
//ByteSource.Util.bytes(user.getSalt()) 通過這個工具將鹽傳入
//如果身份認證驗證成功,返回一個AuthenticationInfo實現;
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new MyByteSource(user.getSalt()),this.getName());
}
return null;
}
}
在登錄時會自動調用這個身份驗證 在驗證時如果出錯,會報異常,我在controller層接收了異常並處理
controller層中登錄時的異常處理
@PostMapping("/login")
public String login(String username,String password){
//獲取主體對象
Subject subject = SecurityUtils.getSubject();
try {
//自動調用CustomerRealm 類中的身份驗證方法
subject.login(new UsernamePasswordToken(username,password));
return "index";
}catch (UnknownAccountException e){ //接收異常並處理
e.printStackTrace();
model.addAttribute("msg","用戶名有誤,請重新登錄");
}catch (IncorrectCredentialsException e){//接收異常並處理
e.printStackTrace();
model.addAttribute("msg","密碼有誤,請重新登錄");
}
return "login";
}
1.2.6、shiro緩存配置
定義了shiro緩存,用戶登錄后,其用戶信息、擁有的角色 / 權限不必每次去查,這樣可以提高效率
默認緩存的配置
在 ShiroConfig中 的 getRealm() 方法中開啟緩存管理
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//開啟緩存管理
customerRealm.setCacheManager(new EhCacheManager());
//開啟全局緩存
customerRealm.setCachingEnabled(true);
//開啟認證緩存
customerRealm.setAuthenticationCachingEnabled(true);
customerRealm.setAuthenticationCacheName("authenticationCache");
//開啟權限緩存
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
}
與reids整合的緩存這里就不說明了,放在源碼里自己查看,源碼在下方
1.2.7、主頁index.html的設置
在這里用標簽來判斷某些區域需要認證或什么角色或者什么權限才能訪問
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>首頁</title>
<link rel="shortcut icon" href="#">
</head>
<body>
<h1>index</h1>
<a href="/logout">退出</a>
<div>
<a href="/main">main</a> | <a href="/manage">manage</a> | <a href="/admin">admin</a>
</div>
<!--獲取認證信息-->
用戶:<span shiro:principal=""></span><hr>
<!--認證處理-->
<span shiro:authenticated=""><hr>
顯示認證通過內容
</span>
<span shiro:notAuthenticated=""><hr>
沒有認證時 顯示
</span>
<!--授權角色-->
<span shiro:hasRole="admin"><hr>
admin角色 顯示
</span>
<span shiro:hasPermission="user:*:*"><hr>
具有用戶模塊的"user:*:*"權限 顯示
</span>
</body>
</html>
1.3、簡單測試

1.3.1、admin角色所有權限測試

1.3.2、無角色有權限測試

1.3.3、無角色無權限測試


