背景
目前比较流行的权限框架有Apache Shiro和 Spring Security。相比Spring Secrity, Shiro更加简单,概念相对好理解。同时,原有项目对于用户登录态,用户权限的管理都使用到了Shiro,因此选择Shiro作为一个权限框架的学习。Shiro读音为[shee-roh],为日语的“城堡,堡垒”。本文将从两个方面介绍。首先是Shiro的一些设计概念和原理,然后以一个比较简单的Demo为例,介绍基于其实现的登录和权限控制。阅读时长大致45分钟左右。
框架概念和结构介绍
框架
参考:https://shiro.apache.org/architecture.html
简单结构
完整结构
核心概念
Subject
表示为当前和软件交互的主体。
SecurityManager
是整个框架的核心,协调多个功能组件一起发挥各自的作用,用于控制所有用户的权限功能。
Realms
realms是将Shiro框架和应用数据连接起来的桥梁,类似数据源。一般情况下,这些数据源存储了用户和权限定义。然后通过不同的realm实现,和Shiro权限框架结合起来。Realms有多种提供的实现,包括JDBC,LDAP,Text(INI)等,当然也可以实现自己的Realm,比如和自有的权限平台连接起来。另外,Realms可以多个组合起来,实现多层次的授权处理。
其他重要概念
Authentication(认证)
用来处理用户的认证过程,即登录过程。主要是通过对用户提供的账号和密码(或凭据)校验,判断是否和保存的凭据匹配,匹配即认证通过,否则是认证失败。
Authorization(授权)
用来处理用户权限的控制,用户权限一般来说使用“角色-权限”的方式,即Role, Permission。不同用户绑定了不同的角色,进而绑定了不同的权限。当然用户也可以直接绑定到权限,但是一般不这么操作。然后权限和应用程序的资源做了绑定,这样实现了用户所属程序资源的控制。这里的资源,一般只某个接口,某个页面,或某个方法。官方的文档提供了如下的几种权限样例。
- 角色判断
if ( subject.hasRole(“administrator”) ) {
//show the ‘Create User’ button
} else {
//grey-out the button?
}
- 权限判断
注意这里使用A:B:C
这种结构,其中的user表示角色,create表示一个操作
if ( subject.isPermitted(“user:create”) ) {
//show the ‘Create User’ button
} else {
//grey-out the button?
}
- 资源实例判断
if ( subject.isPermitted(“user:delete:jsmith”) ) {
//delete the ‘jsmith’ user
} else {
//don’t delete ‘jsmith’
}
Session Management
Shiro框架提供Session管理,也支持使用数据库等做持久化。容易理解,之前的Subject包括Authentication等概念中,用户的判断其实一定程度是依赖Session的。但是,当我们使用前后端分离的方式时或者某些不需要Session的场景,也可以关闭Shiro的HttpSession功能,更详细的可以参考文后的资料。
Cryptography
这个模块用于一些加密解密和哈希处理,Shiro对相关的算法做了封装,能够比较容易上手使用。
使用样例
代码介绍
代码主体基于Springboot,然后集成Shiro,使用的依赖如下。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.1</version>
</dependency>
用户凭据和权限定义文件
为了简单起见,这里使用ini文件的方式来定义凭据和权限关系,实际也是一种Realms,是Shiro本身支持的方式,具体的实现逻辑在 org.apache.shiro.realm.text.IniRealm
其中[users]区域,定义用户凭据和角色;[roles]区域定义了角色对应的资源标记。其中“*”通配符可以实现权限的层次控制。如文件中描述,admin角色具有所有资源的权限,而author角色只有文章的创作和保存权限。
#shiro.ini
[users]
user = password, admin
user2 = password2, editor
user3 = password3, author
[roles]
admin = *
editor = articles:*
author = articles:compose,articles:save
接口附加权限的Controller
//authController.java
package com.auth.demo.controller;
import com.auth.demo.entity.AuthUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/login")
public Object login(@RequestBody AuthUser user) {
try {
AuthenticationToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
SecurityUtils.getSubject().login(token);
return "login";
} catch (Exception e) {
log.error("login failed:{}", user, e);
throw e;
}
}
@RequiresRoles("admin")
@GetMapping("/admin/op")
public Object adminOp() {
return "adminOp";
}
@RequiresPermissions("articles:*")
@GetMapping("/editor/op")
public Object editorOp() {
return "editorOp";
}
@RequiresPermissions("articles:compose")
@GetMapping("/author/compose")
public Object authorCompose() {
return "authorCompose";
}
@RequiresPermissions("articles:save")
@GetMapping("/author/save")
public Object authorSave() {
return "authorSave";
}
}
Shiro的相关配置
# used in develop and test
shiro:
web:
enabled: true
loginUrl: /auth/login
unauthorizedUrl:
登录
通过调用login接口,执行对应的登录操作。登录成功返回对应的数据。
失败的情况