序
在實際項目中,經常需要用到角色權限區分,以此來為不同的角色賦予不同的權利,分配不同的任務。比如,普通用戶只能瀏覽;會員可以瀏覽和評論;超級會員可以瀏覽、評論和看視頻課等;實際應用場景很多。毫不誇張的說,幾乎每個完整的項目都會設計到權限管理。
在 Spring Boot 中做權限管理,一般來說,主流的方案是 Spring Security ,但是由於 Spring Security 過於龐大和復雜,只要能滿足業務需要,大多數公司還是會選擇 Apache Shiro 來使用。
一般來說,Spring Security 和 Shiro 的區別如下:
Spring Security | Apache Shiro |
---|---|
重量級的安全管理框架 | 輕量級的安全管理框架 |
概念復雜,配置繁瑣 | 概念簡單、配置簡單 |
功能強大 | 功能簡單 |
因此,這篇文章,阿淼首先會帶大家了解 Apache Shiro ,然后和大家一起將 shiro 權限框架整合到 SpringBoot 中,以達到快速的實現整合權限管理的功能。
走進 Apache Shiro
官網認知
照例又去官網扒了扒介紹:
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro™是一個強大且易用的Java安全框架,能夠用於身份驗證、授權、加密和會話管理。Shiro擁有易於理解的API,您可以快速、輕松地獲得任何應用程序——從最小的移動應用程序到最大的網絡和企業應用程序。
簡而言之,Apache Shiro 是一個強大靈活的開源安全框架,可以完全處理身份驗證、授權、加密和會話管理。
Shiro能到底能做些什么呢?
- 驗證用戶身份
- 用戶訪問權限控制,比如:1、判斷用戶是否分配了一定的安全角色。2、判斷用戶是否被授予完成某個操作的權限
- 在非 Web 或 EJB 容器的環境下可以任意使用Session API
- 可以響應認證、訪問控制,或者 Session 生命周期中發生的事件
- 可將一個或以上用戶安全數據源數據組合成一個復合的用戶 “view”(視圖)
- 支持單點登錄(SSO)功能
- 支持提供“Remember Me”服務,獲取用戶關聯信息而無需登錄
···
為什么今天還要使用Apache Shiro?
對此,官方給出了詳細的解釋:http://shiro.apache.org/
自2003年以來,框架環境發生了很大變化,因此今天仍然有充分的理由使用Shiro。實際上有很多原因。Apache Shiro是:
- 易於使用 -易於使用是該項目的最終目標。應用程序安全性可能非常令人困惑和沮喪,並被視為“必要的邪惡”。如果您使它易於使用,以使新手程序員可以開始使用它,那么就不必再痛苦了。
- 全面 -Apache Shiro聲稱沒有其他具有范圍廣度的安全框架,因此它可能是滿足安全需求的“一站式服務”。
- 靈活 -Apache Shiro可以在任何應用程序環境中工作。盡管它可以在Web,EJB和IoC環境中運行,但並不需要它們。Shiro也不要求任何規范,甚至沒有很多依賴性。
- 具有Web功能 -Apache Shiro具有出色的Web應用程序支持,使您可以基於應用程序URL和Web協議(例如REST)創建靈活的安全策略,同時還提供一組JSP庫來控制頁面輸出。
- 可插拔 -Shiro干凈的API和設計模式使它易於與許多其他框架和應用程序集成。您會看到Shiro與Spring,Grails,Wicket,Tapestry,Mule,Apache Camel,Vaadin等框架無縫集成。
- 受支持 -Apache Shiro是Apache Software Foundation(Apache軟件基金會)的一部分,該組織被證明以其社區的最大利益行事。項目開發和用戶群體友好的公民隨時可以提供幫助。如果需要,像Katasoft這樣的商業公司也可以提供專業的支持和服務。
Shiro 核心概念
Apache Shiro 是一個全面的、蘊含豐富功能的安全框架。
下圖為描述 Shiro 功能的框架圖:
如圖所示,功能包括:
- Authentication(認證):用戶身份識別,通常被稱為用戶“登錄”
- Authorization(授權):訪問控制。比如某個用戶是否具有某個操作的使用權限。
- Session Management(會話管理):特定於用戶的會話管理,甚至在非web 或 EJB 應用程序。
- Cryptography(加密):在對數據源使用加密算法加密的同時,保證易於使用。
並且 Shiro 還有通過增加其他的功能來支持和加強這些不同應用環境下安全領域的關注點。
特別是對以下的功能支持:
- Web支持:Shiro 提供的 Web 支持 api ,可以很輕松的保護 Web 應用程序的安全。
- 緩存:緩存是 Apache Shiro 保證安全操作快速、高效的重要手段。
- 並發:Apache Shiro 支持多線程應用程序的並發特性。
- 測試:支持單元測試和集成測試,確保代碼和預想的一樣安全。
- "Run As":這個功能允許用戶假設另一個用戶的身份(在許可的前提下)。
- "Remember Me":跨 session 記錄用戶的身份,只有在強制需要時才需要登錄。
注意: Shiro 不會去維護用戶、維護權限,這些需要我們自己去設計/提供,然后通過相應的接口注入給 Shiro
使用案例 Demo
1.新建 maven 項目
為方便我們初始化項目,Spring Boot給我們提供一個項目模板生成網站。
- 1、打開瀏覽器,訪問:https://start.spring.io/
- 2、根據頁面提示,選擇構建工具,開發語言,項目信息等。
2.導入 springboot 父依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
3.相關 jar 包
web 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
shiro-spring 包就是此篇文章的核心
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
shiro 注解會用到 aop
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
數據庫相關包使用的是mybatisplus
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
4.數據庫
建表語句在項目中有,項目地址: https://github.com/mmzsblog/mmzsblog-util
5.自定義 realm
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils
// .getSubject()).getServletRequest();//這個可以用來獲取在登錄的時候提交的其他額外的參數信息
String username = (String) principals.getPrimaryPrincipal();
// 受理權限
// 角色
Set<String> roles = new HashSet<String>();
Role role = roleService.getRoleByUserName(username);
System.out.println(role.getRoleName());
roles.add(role.getRoleName());
authorizationInfo.setRoles(roles);
// 權限
Set<String> permissions = new HashSet<String>();
List<Permission> querypermissions = permissionService.getPermissionsByRoleId(role.getId());
for (Permission permission : querypermissions) {
permissions.add(permission.getPermissionName());
}
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
String loginName = (String) authcToken.getPrincipal();
// 獲取用戶密碼
User user = userService.getOne(new QueryWrapper<User>().eq("username", loginName));
if (user == null) {
// 沒找到帳號
throw new UnknownAccountException();
}
String password = new String((char[]) authcToken.getCredentials());
String inpass = (new Md5Hash(password, user.getUsername())).toString();
if (!user.getPassword().equals(inpass)) {
throw new IncorrectCredentialsException();
}
// 交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginName, user.getPassword(),
ByteSource.Util.bytes(loginName), getName());
return authenticationInfo;
}
}
6.shiro 配置類
@Configuration
public class ShiroConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
/**
* Shiro的Web過濾器Factory 命名:shiroFilter
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,這個屬性是必須的
shiroFilterFactoryBean.setSecurityManager(securityManager);
//需要權限的請求,如果沒有登錄則會跳轉到這里設置的url
shiroFilterFactoryBean.setLoginUrl("/login.html");
//設置登錄成功跳轉url,一般在登錄成功后自己代碼設置跳轉url,此處基本沒用
shiroFilterFactoryBean.setSuccessUrl("/main.html");
//設置無權限跳轉界面,此處一般不生效,一般自定義異常
shiroFilterFactoryBean.setUnauthorizedUrl("/error.html");
Map<String, Filter> filterMap = new LinkedHashMap<>();
// filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/*
* 定義shiro過濾鏈 Map結構
* Map中key(xml中是指value值)的第一個'/'代表的路徑是相對於HttpServletRequest.getContextPath()的值來的
* anon:它對應的過濾器里面是空的,什么都沒做,這里.do和.jsp后面的*表示參數,比方說login.jsp?main這種
* authc:該過濾器下的頁面必須驗證后才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.
* FormAuthenticationFilter
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/*
* 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊; authc:所有url都必須認證通過才可以訪問;
* anon:所有url都都可以匿名訪問
*/
filterChainDefinitionMap.put("/login.html", "authc");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 權限管理
*/
@Bean
public SecurityManager securityManager() {
logger.info("=======================shiro=======================");
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(MyShiroRealm());
// securityManager.setRememberMeManager(rememberMeManager);
return securityManager;
}
/**
* Shiro Realm 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證用戶登錄的類為自定義的
*/
@Bean
public MyShiroRealm MyShiroRealm() {
MyShiroRealm userRealm = new MyShiroRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 憑證匹配器 密碼驗證
*/
@Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:這里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次數,比如散列兩次,相當於 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(1);
// storedCredentialsHexEncoded默認是true,此時用的是密碼加密用的是Hex編碼;false時用Base64編碼
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* 開啟Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類,並在必要時進行安全邏輯驗證
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
7.測試類
@RestController
public class UserController {
@PostMapping("login")
public String name(String username, String password) {
String result = "已登錄";
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
if (!currentUser.isAuthenticated()) {
try {
currentUser.login(token);// 會觸發com.shiro.config.MyShiroRealm的doGetAuthenticationInfo方法
result = "登錄成功";
} catch (UnknownAccountException e) {
result = "用戶名錯誤";
} catch (IncorrectCredentialsException e) {
result = "密碼錯誤";
}
}
return result;
}
@GetMapping("logout")
public void logout() {
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
}
@RequiresPermissions("role:update")
@GetMapping("/role")
public String name() {
return "hello";
}
@RequiresPermissions("user:select")
@GetMapping("/role2")
public String permission() {
return "hello sel";
}
}
7.1 登錄測試
數據庫賬號(密碼經過md5加鹽加密)
7.2 權限測試
8.說明
8.1 無權限時的處理
無權限時自定義了一個異常。所以,權限測試的時候沒有權限就會提示配置的提示語 “沒有權限”。
@ControllerAdvice
public class ShiroException {
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public String name() {
return "沒有權限";
}
}
8.2 角色權限測試與權限測試相同
權限設置可在shiro配置類中shiro過濾鏈設置,也可用注解方式設置,本文使用注解方式。
8.3 shiro 的 session 和 cache
shiro 的 session 和 cache 管理可以自定義,本文用的是默認的,推薦自定義,方便管理。
小結
- Apache Shiro是Java的一個安全框架
- Shiro是一個強大的簡單易用的Java安全框架,主要用來更便捷的認證、授權、加密、會話管理、與Web集成、緩存等
- Shiro使用起來小而簡單
- spring中有spring security ,是一個權限框架,它和spring依賴過於緊密,沒有shiro使用簡單。
- shiro不依賴於spring,shiro不僅可以實現web應用的權限管理,還可以實現c/s系統,分布式系統權限管理,
- shiro屬於輕量框架,越來越多企業項目開始使用shiro.
參考: