?一、權限管理
1.1 什么是權限管理
- 基本上只要涉及到用戶參數的系統(tǒng)都要進行權限管理,使用權限管理實現了對用戶訪問系統(tǒng)的控制,不同的用戶訪問不同的資源。按照安全規(guī)則或者安全策略控制用戶訪問資源,而且只能訪問被授權的資源
- 權限管理包括認證和授權兩部分,當用戶訪問資源時先對其進行身份的認證,認證通過后即可訪問已經授權的資源。
1.2 身份認證
- 用來判斷一個用戶是否合法的處理過程。用過用戶輸入的用戶名或者口令來和系統(tǒng)中存儲進行比較,從而認證用戶的身份是否正確
對于身份認證,也就是之前做的的登錄
1.3 授權
- 用來控制認證過后的用戶可以訪問哪些資源。用戶身份認證后需要給該用戶分配可訪問的資源,如果沒有某個資源的權限,那么將無法訪問
二、Shiro架構
2.1 Shiro的理解
是一個功能強大且易實現的Java安全框架,使用Shiro可以執(zhí)行認證、授權、加密和會話管理。使用Shiro中提供的API可以快速輕松的保護任何程序
Shiro不依賴于WEB,即使是一個測試程序也能夠使用Shiro中的功能
2.2 Shiro的體系
官方圖示:
原理:
Subject
- 表示主體,外部應用和Subject進行交互。Subject中記錄了當前操作用戶,這個用戶可以是一個發(fā)送請求的用戶,也可以是一個運行的程序
- Subject在Shiro是一個接口,定義了很多認證授權的相關方法,外部程序通過Subject進行認證授權,Subject又是通過SecurityManager安全管理器就行認證管理
SecurityManager
- 表示安全管理器,是Shiro中的核心,用來協調其托管的組件,以保證它們能夠順利協同工作。
- 可以進行會話管理等
Authenticator
- 表示身份認證器,負責執(zhí)行和響應用戶的身份認證嘗試的組件。當用戶進行登錄操作時,此組件進行處理
Authorizer
- 表示授權器,負責控制用戶在系統(tǒng)中可以訪問哪些資源。在訪問資源時都需要該組件進行判斷當前用戶是否擁有這個資源的權限
Realm
- 表示領域,充當Shiro與應用程序的安全數據之間的橋梁或者連接器,和DataSource數據源差不多,當需要和安全相關的數據(如用戶賬號)進行實際交互從而執(zhí)行認證和授權時,Shiro會從應用配置的一個或者多個Realm中查詢其中的內容
- SecurityManager進行安全認證時需要通過Realm獲取到用戶權限數據
- Realm不只是從數據庫取數據,還有認證和授權的相關邏輯代碼
SessionManager
- 表示會話管理,知道如果創(chuàng)建和管理用戶生命周期,以便為所欲環(huán)境中的用戶提供強大的會話體驗
- 不依賴WEB容器,所以Shiro可以使用在非WEB應用中也可以將分布式應用的會話集中在一點管理,次特征可以使它實現單點登錄
SessionDao
- 表示會話Dao,是對session會話操作的一套接口
CacheManager
- 表示緩存管理器,將用戶權限存儲到緩存中,從而提高性能
Cryptography
- 表示密碼管理,Shiro中提供了一套加密/解密的組件,方便開發(fā)
三、Shiro中的認證
3.1 認證中的關鍵對象
- Subject:主體
訪問系統(tǒng)的每一個用戶或者應用程序,經過認證的都成為主體
- Principal:身份信息
是主體(Subject)進行身份證認證的表示,表示必須具有唯一性。比如用戶名/手機號/郵箱,一個主體(Subject)中可以有多個身份信息,但必須有一個主身份
- Credential:憑證信息
3.2 認證流程
圖示:
文字:
- 收集使用者的Principal和Credential
- 提交進行身份驗證
- 如果驗證成功就允許訪問,否則重試身份證驗證或者阻止訪問
3.3 環(huán)境搭建
- 引入Shiro依賴
<dependency> ????<groupId>org.apache.shiro</groupId> ????<artifactId>shiro-core</artifactId> ????<version>1.9.1</version> </dependency>
- 在resources下創(chuàng)建.ini的配置文件,來臨時模擬數據庫存儲用戶的身份信息和憑證信息
[users] admin=1234 tom=222 jack=456
- 編寫認證代碼
public class ShiroAuthentication { ????public static void main(String[] args) { ????????// 1.創(chuàng)建SecurityManager安全管理器的實現類 ????????DefaultSecurityManager securityManager = new DefaultSecurityManager(); ????????// 2.將Realm中的數據設置到安全管理器中 ????????securityManager.setRealm(new IniRealm("classpath:shiro.ini")); // Realm去讀取ini中的主體與憑證信息 ????????// 3.將安全管理器設置到全局安全工具類中 ????????SecurityUtils.setSecurityManager(securityManager); ????????// 4.獲取主體 ????????Subject subject = SecurityUtils.getSubject(); ????????// 5.認證 ????????if (!subject.isAuthenticated()) { // 是否已經認證 ????????????// 如果沒有認證過,那么證明該用戶第一次登錄.收集使用者的身份信息和憑證信息 ????????????UsernamePasswordToken token = new UsernamePasswordToken("admin","1234"); // 模擬前臺輸入 ????????????// 提交身份信息和憑證信息 ????????????subject.login(token); ????????} ????} }
3.4 處理結果
如果提交成功,執(zhí)行后續(xù)的邏輯代碼;提交過程中出現錯誤,那么Shiro將以拋異常的形式聲明錯誤
異常列表:
- UnknowAccountException ?--> 未知賬號異?!居脩裘e誤】
- IncorrectCredentialsException ?--> 憑證信息異?!久艽a錯誤】
- LockedAccountException ?--> 鎖定賬號異常
- ExcessiveAttemptsException ?--> 過度嘗試異常
- AuthenticationException ?--> 身份認證異常
修改以上程序:
try { ????// 開始認證 ????subject.login(token); }catch (UnknownAccountException e){ ????System.out.println("賬號錯誤"); }catch (IncorrectCredentialsException e){ ????System.out.println("密碼錯誤"); }
3.5 獲取主體身份信息與注銷
此操作必須保證認證通過
- 獲取主體身份信息
if (subject.isAuthenticated()){ // 認證通過 ????Object principal = subject.getPrincipal(); // 6. 獲取身份信息 ????System.out.println(principal); // admin }
- 注銷
subject.logout();
3.6 底層實現
身份信息校驗
- 在SimpleAccountRealm類中的doGetAuthenticationInfo方法判斷身份信息是否一致
- 如果用戶名錯誤,那么返回的info==null,系統(tǒng)拋出UnknowAccountException異常
密碼校驗
- 在AuthenticatingRealm類中的assertCredentialsMatch方法進行密碼(憑證信息)的校驗,Shiro中憑證信息默認的校驗規(guī)則是equals
- 如果密碼錯誤,拋出IncorrectCredentialsException異常
3.7 自定義Realm
以后校驗用戶名肯定不能使用Shiro中定義的,需要連接數據庫,通過用戶名查詢。所以Shiro中也可以讓我們自定義Realm
Realm繼承圖
全部
主要部分
認證方法和授權方法都在AuthorizingRealm定義為抽象方法,等待子類繼承并重寫這兩個方法。
SimpleAccountRealm繼承了AuthorizingRealm,所以這個類里面有認證和授權功能,其兩個功能對應的方法為:
- doGetAuthenticationInfo:認證
- doGetAuthorizationInfo:授權
以后自定義的Realm只需要繼承AuthorizingRealm,然后重寫這兩個方法
/** * 自定義Realm,繼承AuthorizingRealm類 */ public class LoginRealm extends AuthorizingRealm { ????/* ???? 授權 ????*/ ????@Override ????protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ????????return null; ????} ????/* ???? 認證 ????*/ ????@Override ????protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { ????????return null; ????} }
在doGetAuthenticationInfo方法中獲取用戶的身份信息,然后校驗是否和數據庫中的一致
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { ????// getPrincipal()獲取身份信息 ????String username = (String) token.getPrincipal(); ????if ("admin".equals(username)){ ????????// 正確 返回AuthenticationInfo的實現類 ????????// 參數:1.當前用戶的身份信息 2.驗證主體的憑證信息[如果和前臺傳入的不一致,拋出IncorrectCredentialsException異常] 3.當前Realm ????????return new SimpleAuthenticationInfo(username,"1234",super.getName()); ????} ????// 不正確返回一個null, info == null 拋出UnknownAccountException異常 ????return null; }
3.8 加密
測試程序:
public class MD5Test { ????public static void main(String[] args) { ????????// 加密 ????????Md5Hash md5Hash = new Md5Hash("1234"); ????????// 加密+salt(鹽) ????????Md5Hash md5Hash1 = new Md5Hash("1234", "f5gy"); ????????// 加密+salt(鹽)+散列次數 ????????Md5Hash md5Hash2 = new Md5Hash("1234","f5gy",1024); ????} }
整合認證:
- 修改認證方法
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { ????// getPrincipal()獲取身份信息 ????String username = (String) token.getPrincipal(); ????if ("admin".equals(username)){ ????????// 正確 返回AuthenticationInfo的實現類 ????????/* ????????參數: ????????????1.當前用戶的身份信息 ????????????2.驗證主體的憑證信息[如果和前臺傳入的不一致,拋出IncorrectCredentialsException異常] ????????????3.鹽 ????????????4.當前Realm ????????*/ ????????String password = "75b323294effa42ed07f895f37f9a192"; ????????String salt = "f5gy"; ????????return new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(salt),super.getName()); ????} ????// 不正確返回一個null, info == null 拋出UnknownAccountException異常 ????return null; }
- 修改密碼比較器
Shiro默認實用的是simpleCredentialsMatcher中的doCredentialsMatcher方法,這個方法使用的是equals的方式進行比較密碼。
CredentialsMatcher繼承圖:
使用HashedCredentialsMatcher這個類
LoginRealm realm = new LoginRealm(); HashedCredentialsMatcher hash = new HashedCredentialsMatcher(); // 設置算法 hash.setHashAlgorithmName("MD5"); // 設置散列次數 hash.setHashIterations(1024); // 設置到Realm中 realm.setCredentialsMatcher(hash);
四、Shiro中的授權
4.1 授權中的關鍵對象
- Who
表示主體,主題需要系統(tǒng)中的資源
- What
表示資源,這個資源可以是一個按鈕、菜單等。資源又分為資源實例和資源類型
- How
表示權限/許可,控制主體對資源的訪問
4.2 授權方式
- 基于角色的訪問控制(Role-Based Access Control):以角色為中心進行權限控制
- 基于資源的訪問控制(Resource-Based Access Control):以資源為中心進行權限控制
4.3 權限字符串
權限字符串的規(guī)則:資源標識符:操作,意思是對哪個資源進行哪些操作。":"是分割符,權限字符串可以使用"*"來表示通配符
4.4 校驗角色
- 在doGetAuthorizationInfo方法中設置當前主體的角色
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ????// 獲取主題中的身份信息[用戶名] ????String principal = (String) principals.getPrimaryPrincipal(); ????// 返回AuthorizationInfo的實現類 ????SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); ????// 給當前主體添加角色 ????info.addRole("admin"); ????info.addRole("user"); ????return info; }
- 模擬前臺測試
if (subject.isAuthenticated()){ ????// 校驗單個角色 ????System.out.println(subject.hasRole("admin")); // 是否有admin角色 ????// 校驗多個角色 ????System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user"))); // 是否同時有admin user角色 ????// 校驗 多次 角色 ????boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super")); ????for (boolean b : booleans) { ????????System.out.println(b); ????} }
4.5 校驗權限字符串
- 在doGetAuthorizationInfo方法中設置當前主體的權限字符串
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ????// 獲取主題中的身份信息[用戶名] ????String principal = (String) principals.getPrimaryPrincipal(); ????// 返回AuthorizationInfo的實現類 ????SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); ????// 給當前主體添加權限字符串 ????info.addStringPermission("user:update"); ????info.addStringPermission("product:select"); ????return info; }
- 模擬前臺測試
五、整合SpringBoot
5.1 整合思路
5.2 環(huán)境搭建
- 導入springboot和shiro整合的依賴包
<dependency> ????<groupId>org.apache.shiro</groupId> ????<artifactId>shiro-spring-boot-starter</artifactId> ????<version>1.4.0</version> </dependency>
- 創(chuàng)建一個類,繼承AuthorizingRealm類,重寫授權和認證方法
public class MyRealm extends AuthorizingRealm { ????@Override ????protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ????????return null; ????} ????@Override ????protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { ????????return null; ????} }
- 編寫shiro和springboot整合的配置
@Configuration public class ShiroConfig { ????@Bean ????public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ????????ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); ????????// 將SecurityManager設置到Filter中 ????????shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); ????????return shiroFilterFactoryBean; ????} ????@Bean ????public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm){ ????????DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); ????????// 給安全管理器設置Realm ????????defaultWebSecurityManager.setRealm(realm); ????????return defaultWebSecurityManager; ????} ????@Bean ????public Realm Realm(){ ????????LoginRealm loginRealm = new LoginRealm(); ????????return loginRealm; ????} }
- 在ShiroFilter過濾器中配置需要攔截的資源URL
@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ????ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); ????// 將SecurityManager設置到Filter中 ????shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); ???? ????//設置受限資源 ????Map<String,String> map = new HashMap<>(); ????/** ????* authc:該路徑資源需要認證和授權 ????*/ ????map.put("/**","authc"); // "/**"代表所有的資源路徑都攔截 ????shiroFilterFactoryBean.setFilterChainDefinitionMap(map); ????return shiroFilterFactoryBean; }
- 訪問資源跳轉到login.jsp,修改默認跳轉路徑
shiroFilterFactoryBean.setLoginUrl("/doLogin"); @Controller public class IndexController { ????@GetMapping("/doLogin") ????public String doLogin(){ ????????return "login"; ????} }
- 攔截后訪問/doLogin路徑
5.3 ShiroFilter過濾列表
Shiro中提供了多個默認的過濾器,用這些過濾器來控制指定URL路徑下的資源
配置縮寫
對應過濾器
描述
anon
AnonymousFilter
指定URL路徑下的資源可以匿名訪問
authc
FormAuthenticationFilter
指定URL路徑下的資源需要認證過后才能訪問
authcBasic
BasicHttpAuthenticationFilter
指定URL路徑下的資源需要basic登錄
logout
LogoutFilter
注銷過濾器,只需配置對應的URL路徑即可實現
noSessionCreation
NoSessionCreationFilter
禁止創(chuàng)建Session會話
perms
PermissionsAuthorizationFilter
需要有該URL資源對應的權限字符串才能訪問
port
PortFilter
指定某個端口可以訪問
rest
HttpMethodPermissionFilter
將HTTP請求轉換成相對應的動詞來構建權限字符串
roles
RolesAuthorizationFilter
需要有指定角色才能訪問
ssl
SslFilter
需要https請求才能訪問
user
UserFilter
需要已登錄或者"記住我"的用戶才能訪問
六、連接數據庫完成認證
6.1 注冊
- 表設計
- 注冊頁面
<!DOCTYPE html> <html lang="en"> ??<head> ????<meta charset="UTF-8"> ????<title>Title</title> ??</head> ??<body> ????<h1>注冊頁面</h1> ????<hr> ????<form action="/user/register" method="post"> ??????用戶名 : <input type="text" name="username"><br> ??????密碼 : <input type="password" name="password"> <br> ???????<input type="submit" value="注冊"> ????</form> ??</body> </html>
- 實體類POJO
@Data public class User { ????private Long id; ????private String username; ????private String password; ????private String salt; }
- 編寫Controller,調用service處理業(yè)務邏輯
@Controller @RequestMapping("/user") public class UserController { ???? ????@Autowired ????private UserService userService; ????@GetMapping("/doRegister") ????public String doRegister(){ ????????return "register"; ????} ???? ????@PostMapping("/register") ????public String register(User user){ ????int count = userService.register(user); ????????if (count > 0) ????????????return "redirect:/doLogin"; ????????else ????????????return "redirect:/doRegister"; ????} }
- 在ShiroFilter放過這些URL路徑
map.put("/user/register","anon"); map.put("/user/doRegister","anon");
- service中完成加密和散列鹽,調用Mapper完成注冊
@Service public class UserServiceImpl implements UserService { ????@Autowired ????private UserMapper userMapper; ????@Override ????public int register(User user) { ????????String salt = "ga*n"; ????????Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024); ????????user.setPassword(md5Hash.toHex()); ????????user.setSalt(salt); ????????return userMapper.insert(user); ????} }
- Mapper接口和SQL語句
public interface UserMapper { ????int insert(User user); }
<mapper namespace="com.jiuxiao.mapper.UserMapper"> ??<insert id="insert"> ????insert into t_user(id,username,password,salt) values(null,#{username},#{password},#{salt}) ??</insert> </mapper>
- 啟動類上添加@MapperScan注解,掃描Mapper包
@SpringBootApplication @MapperScan("com.jiuxiao.mapper") public class ShiroApp { ????public static void main(String[] args) { ????????SpringApplication.run(ShiroApp.class,args); ????} }
- 注冊頁面輸入用戶信息后,完成注冊
6.2 認證
- 在Shiro配置中修改密碼比較器
@Bean public Realm realm(){ ????LoginRealm loginRealm = new LoginRealm(); ????HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); ????matcher.setHashAlgorithmName("MD5"); ????matcher.setHashIterations(1024); ????loginRealm.setCredentialsMatcher(matcher); ????return loginRealm; }
- 在自定義的Realm的doGetAuthenticationInfo方法中編寫認證邏輯
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { ???? ????@Autowired ????private UserService userService; ???? ????// 獲取主體中的身份信息 ????String principal = (String) authenticationToken.getPrincipal(); ????// 調用service查詢數據庫 ????User user = userService.selectByUsername(principal); ????if (!ObjectUtils.isEmpty(user)){ ??????? // 如果可以查詢到,校驗密碼 ????????return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),super.getName()); ????} ????// 查詢不到直接返回null ????return null; }
- service調用Mapper
@Override public User selectByUsername(String principal) { ????return userMapper.selectByUsername(principal); }
- Mapper接口與SQL語句
User selectByUsername(String principal);
<select id="selectByUsername" resultType="com.jiuxiao.pojo.User"> ?? select * from t_user where username = #{username} </select>
- 登錄頁面代碼
<!DOCTYPE html> <html lang="en"> ??<head> ????<meta charset="UTF-8"> ????<title>$Title$</title> ??</head> ??<body> ????<h1>登錄頁面</h1> ????<hr> ????<form action="/user/login" method="post"> ??????用戶名: <input type="text" name="username"> <br> ??????密碼: <input type="password" name="password"> <br> ??????<input type="submit" value="登錄"> ????</form> ??</body> </html>
- 在Controller中編寫對應URL,封裝token,并處理異常結果
@Controller @RequestMapping("/user") public class UserController { ????@PostMapping("/login") ????public String login(String username,String password){ ????????Subject subject = SecurityUtils.getSubject(); ????????UsernamePasswordToken token = new UsernamePasswordToken(username, password); ????????try { ????????????subject.login(token); ????????????return "index"; // 登陸成功跳轉到首頁 ????????} catch (UnknownAccountException e) { ????????????e.printStackTrace(); ????????????System.out.println("用戶名錯誤"); ????????}catch (IncorrectCredentialsException e){ ????????????e.printStackTrace(); ????????????System.out.println("密碼錯誤"); ????????} ????????return "login"; // 登錄失敗跳轉到登錄頁面 ????} }
- 首頁代碼
<!DOCTYPE html> <html lang="en"> <head> ??<meta charset="UTF-8"> ??<title>$Title$</title> </head> <body> ??<h1>首頁</h1> ??<hr> ??<ul> ????<li><a href="">用戶管理</a></li> ????<li><a href="">商品管理</a></li> ????<li><a href="">菜單管理</a></li> ????<li><a href="">物流管理</a></li> ??</ul> </body> </html>
- 在ShiroFilter過濾器中放過登錄URL
map.put("/user/login","anon");
- 頁面登錄成功后,進入到index.html
6.3 注銷
① 配置方式
- 在ShiroFilter過濾器中添加注銷的URL
map.put("/user/logout","logout");
- 在頁面直接輸入這個URL即可注銷
② 代碼方式
調用subject的logout方法完成注銷,頁面訪問該Controller的URL
七、授權的基本使用
7.1 校驗角色
在自定義的Realm的doGetAuthorizationInfo方法中給當前主體賦予角色
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ????// 獲取主體中的身份信息 ????String principal = (String) principalCollection.getPrimaryPrincipal(); ????if ("jiuxiao".equals(principal)){ // 給jiuxiao用戶賦予admin和user角色 ????????SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); ????????// 增加admin和user角色 ????????simpleAuthorizationInfo.addRole("admin"); ????????simpleAuthorizationInfo.addRole("user"); ????????return simpleAuthorizationInfo; ????} ????return null; }
① 編碼方式
單個角色:使用subject中的hasRole方法
@Controller @RequestMapping("/user") public class UserController { ???? ????@GetMapping("/save") ????@ResponseBody ????public String save(){ ????????Subject subject = SecurityUtils.getSubject(); ????????if (!subject.hasRole("admin")) { // 校驗當前角色是否有admin這個角色 ????????????return "權限不足"; ????????}else{ ????????? return "訪問成功"; ?? ????????} ????} }
多個角色:使用subject中的hasAllRoles方法【這些角色都有才能訪問】
@Controller @RequestMapping("/user") public class UserController { ???? ????@GetMapping("/save") ????@ResponseBody ????public String save(){ ????????List<String> roles = Arrays.asList("admin", "user"); ????????if (SecurityUtils.getSubject().hasAllRoles(roles)) { ????????????return "訪問成功"; ????????}else{ ????????????return "權限不足"; ????????} ????} }
② 注解方式
單個角色:直接在注解參數中寫入對應的角色即可
@Controller @RequestMapping("/user") public class UserController { ???? ????@GetMapping("/save") ????@ResponseBody ????@RequiresRoles("admin") ????public String save(){ ????????return "訪問成功"; ????} }
多個角色:在注解中以數組的形式寫入多個角色【這些角色都有才能訪問】
@GetMapping("/save") @ResponseBody @RequiresRoles(value = {"admin","user"}) public String save(){ ????return "訪問成功"; }
7.2 校驗權限字符串
在自定義的Realm的doGetAuthorizationInfo方法中給當前主體賦予權限字符串
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ????String principal = (String) principalCollection.getPrimaryPrincipal(); ????System.out.println("執(zhí)行授權:"+principal); ????if ("jiuxiao".equals(principal)){ ????????SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); ????????// 添加權限字符串 ????????simpleAuthorizationInfo.addStringPermission("user:update"); ????????simpleAuthorizationInfo.addStringPermission("product:create"); ????????return simpleAuthorizationInfo; ????} ????return null; }
① 編碼方式
調用subject中的isPermittedAll方法,參數為可變長參數(可以傳一個或者多個)【如果是多個,那么這個主體需要擁有參數里面所有的權限字符串才能訪問】
@Controller @RequestMapping("/user") public class UserController { ???? ????@GetMapping("/save") ????@ResponseBody ????public String save(){ ????????Subject subject = SecurityUtils.getSubject(); ????????// if (subject.isPermittedAll("user:update")){ // 判斷當前主體使用擁有對user資源的001實例的更新操作 ????????if (subject.isPermittedAll("user:update","product:update")){ ????????????return "訪問成功"; ????????}else{ ????????????return "權限不足"; ????????} ????} }
② 注解方式
單個權限字符串:直接在注解參數中寫入需要校驗的權限字符串即可
@GetMapping("/save") @ResponseBody @RequiresPermissions("user:update") public String save(){ ????return "訪問成功"; }
多個權限字符串:在注解中以數組的形式寫入多個權限字符串【當前主體主要擁有這些權限字符串才能訪問】
@GetMapping("/save") @ResponseBody @RequiresPermissions(value = {"user:update","product:create"}) public String save(){ ????return "訪問成功"; }
③ 配置方式
在ShiroFilter過濾器使用perms進行權限的校驗
map.put("/user/save","perms[user:update,product:delete]"); // 數組中添加權限字符串
如果權限不足,頁面拋出401錯誤
在ShiroFilter中定義權限不足后跳轉的URL
注意:定義權限不足跳轉URL的方式只限制配置方式,別的方式都不能使用,權限不足時會拋出AuthorizationException異常
八、連接數據庫完成授權
8.1 表設計:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for role_perms -- ---------------------------- DROP TABLE IF EXISTS `role_perms`; CREATE TABLE `role_perms` ?( ??`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', ??`roleid` int(11) NULL DEFAULT NULL COMMENT '角色id', ??`permid` int(11) NULL DEFAULT NULL COMMENT '權限id', ??PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for role_user -- ---------------------------- DROP TABLE IF EXISTS `role_user`; CREATE TABLE `role_user` ?( ??`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', ??`roleid` int(11) NULL DEFAULT NULL COMMENT '角色id', ??`userid` int(11) NULL DEFAULT NULL COMMENT '用戶id', ??PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for t_perms -- ---------------------------- DROP TABLE IF EXISTS `t_perms`; CREATE TABLE `t_perms` ?( ??`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', ??`perm` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '權限字符串', ??`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '資源URL', ??PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for t_role -- ---------------------------- DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ?( ??`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', ??`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '角色', ??PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ?( ??`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵', ??`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '用戶名(身份信息)', ??`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '密碼(憑證信息)', ??`salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '鹽', ??PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
8.2 POJO
User
@Data public class User { ????private Long id; ????private String username; ????private String password; ????private String salt; ????private List<Role> roles; }
Role文章來源:http://www.zghlxwxcb.cn/news/detail-419135.html
@Data public class Role { ????private Integer id; ????private String name; ????private List<Perms> perms; }
Perms文章來源地址http://www.zghlxwxcb.cn/news/detail-419135.html
@Data public class Perms { ????private Integer id; ????private String perm; ????private String url; }
8.3 授權角色
- Mapper接口與SQL語句
List<Role> selectRoleNameByUserId(String username);
<select id="selectRoleNameByUserId" resultType="com.jiuxiao.pojo.Role"> ????select r.id,r.name ????from t_user u ????left join role_user ru on ru.userid = u.id ????left join t_role r on r.id = ru.roleid ????where u.username = #{username} </select>
- Service
@Override public List<Role> getRoleNameByUsername(String username) { ????return userMapper.selectRoleNameByUsername(username); }
- 自定義Realm中進行授權
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ????String principal = (String) principalCollection.getPrimaryPrincipal(); ????List<Role> roles = userService.getRoleNameByUsername(principal); ????if(!CollectionUtils.isEmpty(roles)){ ????????SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); ????????roles.forEach(role-> simpleAuthorizationInfo.addRole(role.getName())); ????????return simpleAuthorizationInfo; ????} ????return null; }
8.4 授權字符串
- Mapper接口與SQL語句
?List<String> selectPermByRoleId(Integer id);
<select id="selectPermByRoleId" resultType="string"> ????select p.perm ????from t_role r ????left join role_perms rp on r.id = rp.roleid ????left join t_perms p on rp.permid = p.id ????where r.id = #{id} </select>
- Service
@Override public List<String> getPermByRoleId(Integer id) { ????return userMapper.selectPermByRoleId(id); }
- 自定義Realm進行授權
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ????String principal = (String) principalCollection.getPrimaryPrincipal(); ????List<Role> roles = userService.getRoleNameByUsername(principal); ????if(!CollectionUtils.isEmpty(roles)){ ????????SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); ????????roles.forEach(role-> simpleAuthorizationInfo.addRole(role.getName())); ????????roles.forEach(role->{ ????????????List<String> perms = userService.getPermByRoleId(role.getId()); ????????????simpleAuthorizationInfo.addStringPermissions(perms); ????????}); ????????return simpleAuthorizationInfo; ????} ????return null; }
九、Shiro與thymeleaf整合
9.1 導入依賴
<dependency> ????<groupId>com.github.theborakompanioni</groupId> ????<artifactId>thymeleaf-extras-shiro</artifactId> ????<version>2.0.0</version> </dependency>
9.2 配置方言
@Bean public ShiroDialect shiroDialect(){ ????return new ShiroDialect(); }
9.3 引入工作空間
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
9.4 常用標簽使用
<!-- 驗證當前用戶是否為"訪客",即未認證的用戶 --> <p shiro:guest="">未認證</p> <!-- 認證通過或者已經"記住我"的用戶 --> <p shiro:user="">hello</p> <!-- 認證通過的用戶 --> <p shiro:authenticated="">hello</p> <!-- 輸出當前用戶信息,通常為賬號登錄信息 --> <p shiro:principal></p> <!-- 判斷當前用戶是否擁有該角色 --> <p shiro:hasRole="admin">擁有該角色</p> <!-- 當前用戶沒有該角色認證通過 --> <p shiro:lacksRole="user">沒有改角色</p> <!-- 判斷當前用戶是否擁有以下所有角色 --> <p shiro:hasAllRoles="admin,user"></p> <!-- 判斷當前用戶是否擁有以下任意一個角色 --> <p shiro:hasAnyRoles="admin,user"></p> <!-- 判斷當前用戶是否擁有以下權限字符串 --> <p shiro:hasPermission="user:add"></p> <!-- 當前用戶沒有該權限字符串認證通過 --> <p shiro:lacksPermission="user:add"></p>
到了這里,關于Shiro安全框架簡介的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!