在實(shí)際項(xiàng)目中,經(jīng)常需要用到角色權(quán)限區(qū)分,以此來(lái)為不同的角色賦予不同的權(quán)利,分配不同的任務(wù)。比如,普通用戶只能瀏覽;會(huì)員可以瀏覽和評(píng)論;超級(jí)會(huì)員可以瀏覽、評(píng)論和看視頻課等;實(shí)際應(yīng)用場(chǎng)景很多。毫不夸張的說(shuō),幾乎每個(gè)完整的項(xiàng)目都會(huì)涉及到權(quán)限管理。
因此,這篇文章,阿淼就帶大家將 shiro 權(quán)限框架整合到 SpringBoot 中,以達(dá)到快速的實(shí)現(xiàn)權(quán)限管理的功能。
序
在 Spring Boot 中做權(quán)限管理,一般來(lái)說(shuō),主流的方案是 Spring Security ,但是由于 Spring Security 過(guò)于龐大和復(fù)雜,只要能滿足業(yè)務(wù)需要,大多數(shù)公司還是會(huì)選擇 Apache Shiro 來(lái)使用。
一般來(lái)說(shuō),Spring Security 和 Shiro 的區(qū)別如下:
Spring SecurityApache Shiro重量級(jí)的安全管理框架輕量級(jí)的安全管理框架概念復(fù)雜,配置繁瑣概念簡(jiǎn)單、配置簡(jiǎn)單功能強(qiáng)大功能簡(jiǎn)單
這篇文章會(huì)首先帶大家了解 Apache Shiro ,然后再給出使用案例 Demo。
走進(jìn) Apache Shiro
官網(wǎng)認(rèn)知
照例又去官網(wǎng)扒了扒介紹:
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™是一個(gè)強(qiáng)大且易用的Java安全框架,能夠用于身份驗(yàn)證、授權(quán)、加密和會(huì)話管理。Shiro擁有易于理解的API,您可以快速、輕松地獲得任何應(yīng)用程序——從最小的移動(dòng)應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序。
簡(jiǎn)而言之,Apache Shiro 是一個(gè)強(qiáng)大靈活的開(kāi)源安全框架,可以完全處理身份驗(yàn)證、授權(quán)、加密和會(huì)話管理。
Shiro能到底能做些什么呢?
- 驗(yàn)證用戶身份
- 用戶訪問(wèn)權(quán)限控制,比如:1、判斷用戶是否分配了一定的安全角色。2、判斷用戶是否被授予完成某個(gè)操作的權(quán)限
- 在非 Web 或 EJB 容器的環(huán)境下可以任意使用Session API
- 可以響應(yīng)認(rèn)證、訪問(wèn)控制,或者 Session 生命周期中發(fā)生的事件
- 可將一個(gè)或以上用戶安全數(shù)據(jù)源數(shù)據(jù)組合成一個(gè)復(fù)合的用戶 “view”(視圖)
- 支持單點(diǎn)登錄(SSO)功能
- 支持提供“Remember Me”服務(wù),獲取用戶關(guān)聯(lián)信息而無(wú)需登錄 ···
為什么今天還要使用Apache Shiro?
對(duì)此,官方給出了詳細(xì)的解釋:shiro.apache.org/
自2003年以來(lái),框架環(huán)境發(fā)生了很大變化,因此今天仍然有充分的理由使用Shiro。實(shí)際上有很多原因。Apache Shiro是:
- 易于使用 -易于使用是該項(xiàng)目的最終目標(biāo)。應(yīng)用程序安全性可能非常令人困惑和沮喪,并被視為“必要的邪惡”。如果您使它易于使用,以使新手程序員可以開(kāi)始使用它,那么就不必再痛苦了。
- 全面 -Apache Shiro聲稱沒(méi)有其他具有范圍廣度的安全框架,因此它可能是滿足安全需求的“一站式服務(wù)”。
- 靈活 -Apache Shiro可以在任何應(yīng)用程序環(huán)境中工作。盡管它可以在Web,EJB和IoC環(huán)境中運(yùn)行,但并不需要它們。Shiro也不要求任何規(guī)范,甚至沒(méi)有很多依賴性。
- 具有Web功能 -Apache Shiro具有出色的Web應(yīng)用程序支持,使您可以基于應(yīng)用程序URL和Web協(xié)議(例如REST)創(chuàng)建靈活的安全策略,同時(shí)還提供一組JSP庫(kù)來(lái)控制頁(yè)面輸出。
- 可插拔 -Shiro干凈的API和設(shè)計(jì)模式使它易于與許多其他框架和應(yīng)用程序集成。您會(huì)看到Shiro與Spring,Grails,Wicket,Tapestry,Mule,Apache Camel,Vaadin等框架無(wú)縫集成。
- 受支持 -Apache Shiro是Apache Software Foundation(Apache軟件基金會(huì))的一部分,該組織被證明以其社區(qū)的最大利益行事。項(xiàng)目開(kāi)發(fā)和用戶群體友好的公民隨時(shí)可以提供幫助。如果需要,像Katasoft這樣的商業(yè)公司也可以提供專業(yè)的支持和服務(wù)。
Shiro 核心概念
Apache Shiro 是一個(gè)全面的、蘊(yùn)含豐富功能的安全框架。
下圖為描述 Shiro 功能的框架圖:
如圖所示,功能包括:
- Authentication(認(rèn)證):用戶身份識(shí)別,通常被稱為用戶“登錄”
- Authorization(授權(quán)):訪問(wèn)控制。比如某個(gè)用戶是否具有某個(gè)操作的使用權(quán)限。
- Session Management(會(huì)話管理):特定于用戶的會(huì)話管理,甚至在非web 或 EJB 應(yīng)用程序。
- Cryptography(加密):在對(duì)數(shù)據(jù)源使用加密算法加密的同時(shí),保證易于使用。
并且 Shiro 還有通過(guò)增加其他的功能來(lái)支持和加強(qiáng)這些不同應(yīng)用環(huán)境下安全領(lǐng)域的關(guān)注點(diǎn)。
特別是對(duì)以下的功能支持:
- Web支持:Shiro 提供的 Web 支持 api ,可以很輕松的保護(hù) Web 應(yīng)用程序的安全。
- 緩存:緩存是 Apache Shiro 保證安全操作快速、高效的重要手段。
- 并發(fā):Apache Shiro 支持多線程應(yīng)用程序的并發(fā)特性。
- 測(cè)試:支持單元測(cè)試和集成測(cè)試,確保代碼和預(yù)想的一樣安全。
- "Run As":這個(gè)功能允許用戶假設(shè)另一個(gè)用戶的身份(在許可的前提下)。
- "Remember Me":跨 session 記錄用戶的身份,只有在強(qiáng)制需要時(shí)才需要登錄。
注意: Shiro 不會(huì)去維護(hù)用戶、維護(hù)權(quán)限,這些需要我們自己去設(shè)計(jì)/提供,然后通過(guò)相應(yīng)的接口注入給 Shiro
使用案例 Demo
1.新建 maven 項(xiàng)目
為方便我們初始化項(xiàng)目,Spring Boot給我們提供一個(gè)項(xiàng)目模板生成網(wǎng)站。
- 1、打開(kāi)瀏覽器,訪問(wèn):start.spring.io/
- 2、根據(jù)頁(yè)面提示,選擇構(gòu)建工具,開(kāi)發(fā)語(yǔ)言,項(xiàng)目信息等。
2.導(dǎo)入 springboot 父依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
復(fù)制代碼
3.相關(guān) jar 包
web 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
復(fù)制代碼
shiro-spring 包就是此篇文章的核心
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
shiro 注解會(huì)用到 aop<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
數(shù)據(jù)庫(kù)相關(guān)包使用的是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>
復(fù)制代碼
4.數(shù)據(jù)庫(kù)
建表語(yǔ)句在項(xiàng)目中有,項(xiàng)目地址: github.com/mmzsblog/mm…
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();//這個(gè)可以用來(lái)獲取在登錄的時(shí)候提交的其他額外的參數(shù)信息
String username = (String) principals.getPrimaryPrincipal();
// 受理權(quán)限
// 角色
Set<String> roles = new HashSet<String>();
Role role = roleService.getRoleByUserName(username);
System.out.println(role.getRoleName());
roles.add(role.getRoleName());
authorizationInfo.setRoles(roles);
// 權(quán)限
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) {
// 沒(méi)找到帳號(hào)
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進(jìn)行密碼匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginName, user.getPassword(),
ByteSource.Util.bytes(loginName), getName());
return authenticationInfo;
}
}
復(fù)制代碼
6.shiro 配置類
@Configuration
public class ShiroConfiguration { private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class); /** * Shiro的Web過(guò)濾器Factory 命名:shiroFilter */ @Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,這個(gè)屬性是必須的 shiroFilterFactoryBean.setSecurityManager(securityManager); //需要權(quán)限的請(qǐng)求,如果沒(méi)有登錄則會(huì)跳轉(zhuǎn)到這里設(shè)置的url shiroFilterFactoryBean.setLoginUrl("/login.html");
//設(shè)置登錄成功跳轉(zhuǎn)url,一般在登錄成功后自己代碼設(shè)置跳轉(zhuǎn)url,此處基本沒(méi)用 shiroFilterFactoryBean.setSuccessUrl("/main.html");
//設(shè)置無(wú)權(quán)限跳轉(zhuǎn)界面,此處一般不生效,一般自定義異常 shiroFilterFactoryBean.setUnauthorizedUrl("/error.html");
Map<String, Filter> filterMap = new LinkedHashMap<>(); // filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap); /* * 定義shiro過(guò)濾鏈 Map結(jié)構(gòu) * Map中key(xml中是指value值)的第一個(gè)'/'代表的路徑是相對(duì)于HttpServletRequest.getContextPath()的值來(lái)的
* anon:它對(duì)應(yīng)的過(guò)濾器里面是空的,什么都沒(méi)做,這里.do和.jsp后面的*表示參數(shù),比方說(shuō)login.jsp?main這種
* authc:該過(guò)濾器下的頁(yè)面必須驗(yàn)證后才能訪問(wèn),它是Shiro內(nèi)置的一個(gè)攔截器org.apache.shiro.web.filter.authc. * FormAuthenticationFilter */ Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); /* * 過(guò)濾鏈定義,從上向下順序執(zhí)行,一般將 /**放在最為下邊; authc:所有url都必須認(rèn)證通過(guò)才可以訪問(wèn); * anon:所有url都都可以匿名訪問(wèn) */ 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;
} /** * 權(quán)限管理 */ @Bean public SecurityManager securityManager() {
logger.info("=======================shiro=======================");
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(MyShiroRealm()); // securityManager.setRememberMeManager(rememberMeManager); return securityManager;
} /** * Shiro Realm 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗(yàn)證用戶登錄的類為自定義的 */ @Bean public MyShiroRealm MyShiroRealm() {
MyShiroRealm userRealm = new MyShiroRealm(); userRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return userRealm;
} /** * 憑證匹配器 密碼驗(yàn)證 */ @Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 散列算法:這里使用MD5算法; hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次數(shù),比如散列兩次,相當(dāng)于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(1); // storedCredentialsHexEncoded默認(rèn)是true,此時(shí)用的是密碼加密用的是Hex編碼;false時(shí)用Base64編碼
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
} /** * 開(kāi)啟Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類,并在必要時(shí)進(jìn)行安全邏輯驗(yàn)證 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor;
}}復(fù)制代碼
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);// 會(huì)觸發(fā)com.shiro.config.MyShiroRealm的doGetAuthenticationInfo方法
result = "登錄成功";
} catch (UnknownAccountException e) { result = "用戶名錯(cuò)誤";
} catch (IncorrectCredentialsException e) { result = "密碼錯(cuò)誤";
} } 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";
}}復(fù)制代碼
7.1 登錄測(cè)試
數(shù)據(jù)庫(kù)賬號(hào)(密碼經(jīng)過(guò)md5加鹽加密)
7.2 權(quán)限測(cè)試
8.說(shuō)明
8.1 無(wú)權(quán)限時(shí)的處理
無(wú)權(quán)限時(shí)自定義了一個(gè)異常。所以,權(quán)限測(cè)試的時(shí)候沒(méi)有權(quán)限就會(huì)提示配置的提示語(yǔ) “沒(méi)有權(quán)限”。
@ControllerAdvice
public class ShiroException {
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public String name() {
return "沒(méi)有權(quán)限";
}}復(fù)制代碼
8.2 角色權(quán)限測(cè)試與權(quán)限測(cè)試相同
權(quán)限設(shè)置可在shiro配置類中shiro過(guò)濾鏈設(shè)置,也可用注解方式設(shè)置,本文使用注解方式。
8.3 shiro 的 session 和 cache
shiro 的 session 和 cache 管理可以自定義,本文用的是默認(rèn)的,推薦自定義,方便管理。
小結(jié)
- Apache Shiro是Java的一個(gè)安全框架
- Shiro是一個(gè)強(qiáng)大的簡(jiǎn)單易用的Java安全框架,主要用來(lái)更便捷的認(rèn)證、授權(quán)、加密、會(huì)話管理、與Web集成、緩存等
- Shiro使用起來(lái)小而簡(jiǎn)單
- spring中有spring security ,是一個(gè)權(quán)限框架,它和spring依賴過(guò)于緊密,沒(méi)有shiro使用簡(jiǎn)單。
- shiro不依賴于spring,shiro不僅可以實(shí)現(xiàn)web應(yīng)用的權(quán)限管理,還可以實(shí)現(xiàn)c/s系統(tǒng),分布式系統(tǒng)權(quán)限管理,
- shiro屬于輕量框架,越來(lái)越多企業(yè)項(xiàng)目開(kāi)始使用shiro.