javascript
Shiro在SpringBoot中的应用
Apache Shiro是一個功能強大、靈活的,開源的安全框架。它可以干凈利落地處理身份驗證、授權、企業會話管理和加密。越來越多的企業使用Shiro作為項目的安全框架,保證項目的平穩運行。
1.數據庫表
?
2.pom依賴
? ?<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.16</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--shiro和spring整合--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><!--shiro核心包--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><!--shiro與redis整合--><dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>3.0.0</version></dependency></dependencies>3.自定義Realm
Realm域:Shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源
public class CustomRealm extends AuthorizingRealm { ?@Overridepublic void setName(String name) {super.setName("customRealm");} ?@Autowiredprivate UserService userService; ?/*** 授權方法* 操作的時候,判斷用戶是否具有響應的權限* 先認證 -- 安全數據* 再授權 -- 根據安全數據獲取用戶具有的所有操作權限*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//1.獲取已認證的用戶數據User user = (User) principalCollection.getPrimaryPrincipal();//得到唯一的安全數據//2.根據用戶數據獲取用戶的權限信息(所有角色,所有權限)SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();Set<String> roles = new HashSet<>();//所有角色Set<String> perms = new HashSet<>();//所有權限for (Role role : user.getRoles()) {roles.add(role.getName());for (Permission perm : role.getPermissions()) {perms.add(perm.getCode());}}info.setStringPermissions(perms);info.setRoles(roles);return info;} ? ?/*** 認證方法* 參數:傳遞的用戶名密碼*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//1.獲取登錄的用戶名密碼(token)UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;String username = upToken.getUsername();String password = new String(upToken.getPassword());//2.根據用戶名查詢數據庫User user = userService.findByName(username);//3.判斷用戶是否存在或者密碼是否一致if (user != null && user.getPassword().equals(password)) {//4.如果一致返回安全數據//構造方法:安全數據,密碼,realm域名return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());}//5.不一致,返回null(拋出異常)return null;} ? }4.Shiro的配置
SecurityManager 是 Shiro 架構的心臟,用于協調內部的多個組件完成全部認證授權的過程。例如通過調用realm完成認證與登錄。使用基于springboot的配置方式完成SecurityManager,Realm的裝配。
@Configuration public class ShiroConfiguration { ?//1.創建realm@Beanpublic CustomRealm getRealm() {return new CustomRealm();} ?//2.創建安全管理器@Beanpublic SecurityManager getSecurityManager(CustomRealm realm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(realm); ?//將自定義的會話管理器注冊到安全管理器中securityManager.setSessionManager(sessionManager());//將自定義的redis緩存管理器注冊到安全管理器中securityManager.setCacheManager(cacheManager()); ?return securityManager;} ?//3.配置shiro的過濾器工廠/*** 在web程序中,shiro進行權限控制全部是通過一組過濾器集合進行控制*/@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {//1.創建過濾器工廠ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();//2.設置安全管理器filterFactory.setSecurityManager(securityManager);//3.通用配置(跳轉登錄頁面,為授權跳轉的頁面)filterFactory.setLoginUrl("/autherror?code=1");//跳轉url地址filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授權的url//4.設置過濾器集合 ?/*** 設置所有的過濾器:有順序map* ? ? key = 攔截的url地址* ? ? value = 過濾器類型**/Map<String, String> filterMap = new LinkedHashMap<>();//filterMap.put("/user/home","anon");//當前請求地址可以匿名訪問 ?//具有某中權限才能訪問//使用過濾器的形式配置請求地址的依賴權限//filterMap.put("/user/home","perms[user-home]"); //不具備指定的權限,跳轉到setUnauthorizedUrl地址 ?//使用過濾器的形式配置請求地址的依賴角色//filterMap.put("/user/home","roles[系統管理員]"); ?filterMap.put("/user/**", "authc");//當前請求地址必須認證之后可以訪問 ?filterFactory.setFilterChainDefinitionMap(filterMap); ?return filterFactory;} ?/*** 開啟對shiro注解的支持*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;} }5.Shiro中的過濾器
?
注意:anon, authc, authcBasic, user 是第一組認證過濾器,perms, port, rest, roles, ssl 是第二組授權過濾器,要通過授權過濾器,就先要完成登陸認證操作(即先要完成認證才能前去尋找授權) 才能走第二組授權器(例如訪問需要 roles 權限的 url,如果還沒有登陸的話,會直接跳轉到shiroFilterFactoryBean.setLoginUrl()設置的 url )
6.授權
(1)基于配置的授權
//配置請求連接過濾器配置 //匿名訪問(所有人員可以使用) filterMap.put("/user/home", "anon"); //具有指定權限訪問 filterMap.put("/user/find", "perms[user-find]"); //認證之后訪問(登錄之后可以訪問) filterMap.put("/user/**", "authc"); //具有指定角色可以訪問 filterMap.put("/user/**", "roles[系統管理員]");(2)基于注解的授權
配置到方法上,表明執行此方法必須具有指定的權限
//查詢 @RequiresPermissions(value = "user-find") public String find() { return "查詢用戶成功"; }配置到方法上,表明執行此方法必須具有指定的角色
//查詢 @RequiresRoles(value = "系統管理員") public String find() { return "查詢用戶成功"; }7.統一異常處理
基于注解的配置方式進行授權,一旦操作用戶不具備操作權限,目標方法不會被執行,而且會拋出AuthorizationException 異常。所以需要做好統一異常處理完成未授權處理
/*** 自定義的公共異常處理器* ? ? 1.聲明異常處理器* ? ? 2.對異常統一處理*/ @ControllerAdvice public class BaseExceptionHandler { ?@ExceptionHandler(value = AuthorizationException.class)@ResponseBodypublic String error(HttpServletRequest request, HttpServletResponse response,AuthorizationException e) {return "未授權";} }8.會話管理
在shiro里所有的用戶的會話信息都會由Shiro來進行控制,shiro提供的會話可以用于JavaSE/JavaEE環境,不依賴于任何底層容器,可以獨立使用,是完整的會話模塊。通過Shiro的會話管理器(SessionManager)進行統一的會話管理。
(1)什么是shiro的會話管理
SessionManager(會話管理器):管理所有Subject的session包括創建、維護、刪除、失效、驗證等工作。
SessionManager是頂層組件,由SecurityManager管理 shiro提供了三個默認實現:
DefaultSessionManager:用于JavaSE環境
ServletContainerSessionManager:用于Web環境,直接使用servlet容器的會話。
DefaultWebSessionManager:用于web環境,自己維護會話(自己維護著會話,直接廢棄了Servlet容器的會話管理)。
(2)應用場景
在分布式系統或者微服務架構下,都是通過統一的認證中心進行用戶認證。如果使用默認會話管理,用戶信息只會保存到一臺服務器上。那么其他服務就需要進行會話的同步。
?
(3)Shiro結合redis的統一會話管理
自定義shiro會話管理器
/*** 自定義的sessionManager*/ public class CustomSessionManager extends DefaultWebSessionManager { ? ?/*** 頭信息中具有sessionid* ? ? 請求頭:Authorization: sessionid** 指定sessionId的獲取方式*/@Overrideprotected Serializable getSessionId(ServletRequest request, ServletResponse response) { ?//獲取請求頭Authorization中的數據String id = WebUtils.toHttp(request).getHeader("Authorization");if(StringUtils.isEmpty(id)) {//如果沒有攜帶,生成新的sessionIdreturn super.getSessionId(request,response);}else{//返回sessionId;request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return id;}} }配置Shiro基于redis的會話管理
@Configuration public class ShiroConfiguration { ?@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private int port; ?/*** 1.redis的控制器,操作redis*/public RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost(host);redisManager.setPort(port);return redisManager;} ?/*** 2.sessionDao*/public RedisSessionDAO redisSessionDAO() {RedisSessionDAO sessionDAO = new RedisSessionDAO();sessionDAO.setRedisManager(redisManager());return sessionDAO;} ?/*** 3.會話管理器*/public DefaultWebSessionManager sessionManager() {CustomSessionManager sessionManager = new CustomSessionManager();sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;} ?/*** 4.緩存管理器*/public RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());return redisCacheManager;} }9.測試方法
整體架構為SpringBoot+SpringMVC+Spring Data Jpa
service,dao,entity已省略,controller層代碼如下:
@RestController public class UserController { ?@Autowiredprivate UserService userService; ?//個人主頁//使用shiro注解鑒權//@RequiresPermissions() -- 訪問此方法必須具備的權限//@RequiresRoles() -- 訪問此方法必須具備的角色 ?/*** 1.過濾器:如果權限信息不匹配setUnauthorizedUrl地址* 2.注解:如果權限信息不匹配,拋出異常*/@RequiresPermissions("user-home")@RequestMapping(value = "/user/home")public String home() {return "訪問個人主頁成功";} ?//添加@RequestMapping(value = "/user",method = RequestMethod.POST)public String add() {return "添加用戶成功";}//查詢@RequestMapping(value = "/user",method = RequestMethod.GET)public String find() {return "查詢用戶成功";}//更新@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)public String update(String id) {return "更新用戶成功";}//刪除@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)public String delete() {return "刪除用戶成功";} ?/*** 1.傳統登錄* ? ? 前端發送登錄請求 => 接口部分獲取用戶名密碼 => 程序員在接口部分手動控制* 2.shiro登錄* ? ? 前端發送登錄請求 => 接口部分獲取用戶名密碼 => 通過subject.login => realm域的認證方法**///用戶登錄@RequestMapping(value="/login")public String login(String username,String password) {//構造登錄令牌try { ?/*** 密碼加密:* ? ? shiro提供的md5加密* ? ? Md5Hash:* ? ? 參數一:加密的內容* ? ? ? ? ? ? 111111 ? --- abcd* ? ? 參數二:鹽(加密的混淆字符串)(用戶登錄的用戶名)* ? ? ? ? ? ? 111111+混淆字符串* ? ? 參數三:加密次數**/password = new Md5Hash(password,username,3).toString(); ?UsernamePasswordToken upToken = new UsernamePasswordToken(username,password);//1.獲取subjectSubject subject = SecurityUtils.getSubject(); ?//獲取sessionString sid = (String) subject.getSession().getId(); ?//2.調用subject進行登錄subject.login(upToken);return "登錄成功";}catch (Exception e) {return "用戶名或密碼錯誤";}} }配置文件如下:
server:port: 8081 spring:application:name: shiro-test #指定服務名datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/shiro_db?useUnicode=true&characterEncoding=utf8username: rootpassword: rootjpa:database: MySQLshow-sql: trueopen-in-view: trueredis:host: 127.0.0.1port: 6379?
總結
以上是生活随笔為你收集整理的Shiro在SpringBoot中的应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前后端分离中使用基于jwt的token进
- 下一篇: Linux之Shell脚本入门