Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。
shiro有個核心元件,分別為Subject、SecurityManager和Realms
Subject:相當於目前操作的」用戶「,這個使用者不一定是一個具體的人,是一個抽象的概念,表明的是和當前程式進行互動的任何東西,例如爬蟲、腳本、等等。所有的Subject都綁定到SecurityManager上,與 Subject 的所有互動都會委託給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者。
SecurityManager:這個是shiro框架的核心,所有與安全相關的操作都會與它進行交互,它管理者所有的Subject。
Realms:充當了Shiro與應用程式安全資料間的」橋樑“,當對使用者執行認證(登入)和授權(存取控制)驗證時,SecurityManager 需要從Realm 取得相應的使用者進行比較以確定使用者身分是否合法;也需要從Realm 得到使用者對應的角色/ 權限進行驗證使用者是否能進行操作。
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 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、admin角色所有權限測試
1.3. 2.無角色有權限測試
1.3.3、無角色無權限測試
以上是SpringBoot怎麼整合Shiro實現權限控制的詳細內容。更多資訊請關注PHP中文網其他相關文章!