Apache Shiro 是一個功能強大且靈活的開放源代碼安全框架,可以細粒度地處理認證(Authentication),授權(Authorization),會話(Session) 管理和加密(cryptography) 等企業級應用中常見的安全控制流程。 Apache Shiro 的首要目標是易於使用和理解。 有時候安全性的流程控制會非常複雜,對開發人員來說是件很頭疼的事情,但並不一定如此。 框架就應該盡可能地掩蓋複雜性,並公開一個簡潔而直觀的API,從而簡化開發人員的工作,確保其應用程序安全性。這次我們聊一聊如何在Spring Web 應用中使用Shiro 實現權限控制。
功能
Apache Shiro 是一個具有許多功能的綜合型應用程序安全框架。 下圖為Shiro 中的最主要的幾個功能:
Shiro 的主要目標是“應用安全的四大基石” - 認證,授權,會話管理和加密:
架構
從整體概念上理解,Shiro 的體系架構有三個主要的概念:Subject (主體,也就是用戶),Security Manager (安全管理器)和Realms (領域)。 下圖描述了這些組件之間的關係:
這幾大組件可以這樣理解:
數據準備
在Web 應用中,對安全的控制主要有角色、資源、權限(什麼角色能訪問什麼資源)幾個概念,一個用戶可以有多個角色,一個角色也可以訪問多個資源,也就是角色可以對應多個權限。落實到數據庫設計上,我們至少需要建5 張表:用戶表、角色表、資源表、角色-資源表、用戶-角色表,這5 張表的結構如下:
用戶表:
| id | username | password |
|---|---|---|
| 1 | 張三 | 123456 |
| 2 | 李四 | 666666 |
| 3 | 王五 | 000000 |
角色表:
| id | rolename |
|---|---|
| 1 | 管理員 |
| 2 | 主管 |
| 3 | 員工 |
資源表:
| id | resname |
|---|---|
| 1 | /user/add |
| 2 | /user/delete |
| 3 | /compony/info |
角色-資源表:
| id | roleid | resid |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
用戶-角色表:
| id | userid | roleid |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
對應的POJO 類如下:
/** * 用戶*/public class User { private Integer id; private String username; private String password; //getter & setter...} /** * 角色*/public class Role { private String id; private String rolename;} /** * 資源*/public class Resource { private String id; private String resname;} /** * 角色-資源*/public class RoleRes { private String id; private String roleid; private String resid;} /** * 用戶-角色*/public class UserRole { private String id; private String userid; private String roleid;} Spring 與Shiro 整合的詳細步驟,請參閱我的博客《 Spring 應用中整合Apache Shiro 》 。 這裡補充一下:需要提前引入Shiro 的依賴,打開mvnrepository.com,搜索Shiro,我們需要前三個依賴,也就是Shiro-Core、Shiro-Web 以及Shiro-Spring,以Maven 項目為例,在pom.xml中的<dependencies>節點下添加如下依賴:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version></dependency><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version></dependency><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version></dependency>
在application-context.xml中需要這樣配置shiroFilter bean:
<!-- 配置shiro的過濾器工廠類,id- shiroFilter要和我們在web.xml中配置的過濾器一致--><bean id="shiroFilter"> <property name="securityManager" ref="securityManager"/> <!-- 登錄頁面--> <property name="loginUrl" value="/login"/> <!-- 登錄成功後的頁面--> <property name="successUrl" value="/index"/> <!-- 非法訪問跳轉的頁面--> <property name="unauthorizedUrl" value="/403"/> <!-- 權限配置--> <property name="filterChainDefinitions"> <value> <!-- 無需認證即可訪問的靜態資源,還可以添加其他url --> /static/** = anon <!-- 除了上述忽略的資源,其他所有資源都需要認證後才能訪問--> /** = authc </value> </property></bean>
接下來就需要定義Realm 了,自定義的Realm 集成自AuthorizingRealm類:
public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 驗證權限*/ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String loginName = SecurityUtils.getSubject().getPrincipal().toString(); if (loginName != null) { String userId = SecurityUtils.getSubject().getSession().getAttribute("userSessionId").toString(); // 權限信息對象,用來存放查出的用戶的所有的角色及權限SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 用戶的角色集合ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal(); info.setRoles(shiroUser.getRoles()); info.addStringPermissions(shiroUser.getUrlSet()); return info; } return null; } /** * 認證回調函數,登錄時調用*/ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { String username = (String) token.getPrincipal(); User user = new User(); sysuser.setUsername(username); try { List<SysUser> users = userService.findByNames(user); List<String> roleList= userService.selectRoleNameListByUserId(users.get(0).getId()); if (users.size() != 0) { String pwd = users.get(0).getPassword(); // 當驗證都通過後,把用戶信息放在session 裡Session session = SecurityUtils.getSubject().getSession(); session.setAttribute("userSession", users.get(0)); session.setAttribute("userSessionId", users.get(0).getId()); session.setAttribute("userRoles", org.apache.commons.lang.StringUtils.join(roleList,",")); return new SimpleAuthenticationInfo(username,users.get(0).getPassword()); } else { // 沒找到該用戶throw new UnknownAccountException(); } } catch (Exception e) { System.out.println(e.getMessage()); } return null; } /** * 更新用戶授權信息緩存. */ public void clearCachedAuthorizationInfo(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); } /** * 更新用戶信息緩存. */ public void clearCachedAuthenticationInfo(PrincipalCollection principals) { super.clearCachedAuthenticationInfo(principals); } /** * 清除用戶授權信息緩存. */ public void clearAllCachedAuthorizationInfo() { getAuthorizationCache().clear(); } /** * 清除用戶信息緩存. */ public void clearAllCachedAuthenticationInfo() { getAuthenticationCache().clear(); } /** * 清空所有緩存*/ public void clearCache(PrincipalCollection principals) { super.clearCache(principals); } /** * 清空所有認證緩存*/ public void clearAllCache() { clearAllCachedAuthenticationInfo(); clearAllCachedAuthorizationInfo(); }}最後定義一個用戶登錄的控制器,接受用戶的登錄請求:
@Controllerpublic class UserController { /** * 用戶登錄*/ @PostMapping("/login") public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){ try { if(bindingResult.hasErrors()){ return "login"; } //使用權限工具進行認證,登錄成功後跳到shiroFilter bean 中定義的successUrl SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword())); return "redirect:index"; } catch (AuthenticationException e) { redirectAttributes.addFlashAttribute("message","用戶名或密碼錯誤"); return "redirect:login"; } } /** * 註銷登錄*/ @GetMapping("/logout") public String logout(RedirectAttributes redirectAttributes ){ SecurityUtils.getSubject().logout(); return "redirect:login"; }}以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。