Shiro 三大核心组件
Shiro 有三大核心组件,即 Subject、SecurityManager 和 Realm。先来看一下它们之间的关系。
1. Subject 为认证主体
包含 Principals 和 Credentials 两个信息。我们看下两者的具体含义。
Principals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登录主体的身份。
Credentials:代表凭证。常见的有密码,数字证书等等。
说白了,两者代表了需要认证的内容,最常见的便是用户名、密码了。比如用户登录时,通过 Shiro 进行身份认证,其中就包括主体认证。
2. SecurityManager 为安全管理员
这是 Shiro 架构的核心,是 Shiro 内部所有原件的保护伞。项目中一般都会配置 SecurityManager,开发人员将大部分精力放在了 Subject 认证主体上,与 Subject 交互背后的安全操作,则由 SecurityManager 来完成。
3. Realm 是一个域
它是连接 Shiro 和具体应用的桥梁。当需要与安全数据交互时,比如用户账户、访问控制等,Shiro 将会在一个或多个 Realm 中查找。我们可以把 Realm 看作 DataSource,即安全数据源。一般,我们会自己定制 Realm,下文会详细说明。
Shiro 身份和权限认证
1. Shiro 身份认证
我们分析下 Shiro 身份认证的过程,首先看一下官方给出的认证图。
从图中可以看到,这个过程包括五步
Step1:应用程序代码调用 Subject.login(token) 方法后,传入代表最终用户身份的 AuthenticationToken 实例 Token。
Step2:将 Subject 实例委托给应用程序的 SecurityManager(Shiro 的安全管理)并开始实际的认证工作。这里开始了真正的认证工作。
Step3、4、5:SecurityManager 根据具体的 Realm 进行安全认证。从图中可以看出,Realm 可进行自定义(Custom Realm)。
2. Shiro 权限认证
权限认证,也就是访问控制,即在应用中控制谁能访问哪些资源。在权限认证中,最核心的三个要素是:权限、角色和用户。它们之间的的关系可以用下图来表示:
一个用户可以有多个角色,而不同的角色可以有不同的权限,也可有相同的权限。比如说现在有三个角色,1 是普通角色,2 也是普通角色,3 是管理员,角色 1 只能查看信息,角色 2 只能添加信息,管理员对两者皆有权限,而且还可以删除信息。
Spring Boot 集成 Shiro
1. 依赖导入
Spring Boot 2.0.3 集成 Shiro 需要导入如下 starter 依赖:
2. 数据库表的建立及初始化
这里主要涉及到三张表:用户表、角色表和权限表。其实在 Demo 中,我们完全可以自己来模拟数据库操作,不用建表,但为了更加接近实际情况,我们还是引入了 MyBatis 来操作数据库。下面是数据库各表的创建脚本。
CREATE TABLE `t_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`rolename` varchar(20) DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
`username` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(20) NOT NULL COMMENT '密码',
`role_id` int(11) DEFAULT NULL COMMENT '外键关联role表',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_user_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
CREATE TABLE `t_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`permissionname` varchar(50) NOT NULL COMMENT '权限名',
`role_id` int(11) DEFAULT NULL COMMENT '外键关联role',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
其中,t_user、t_role 和 t_permission 分别存储了用户信息、角色信息和权限信息,表建立好后,我们往表里插入一些测试数据,比如下面这些数据。
t_user 表:
t_role 表:
t_permission 表:
解释一下这里的权限:user:* 表示权限可以是 user:create 或其他,* 表示一个占位符,可以自己定义,下文介绍 Shiro 配置时会对其再做详细说明。
3. 自定义 Realm
有了数据库表和数据,我们开始自定义 Realm。自定义 Realm 需要继承 AuthorizingRealm 类,该类封装了很多方法,且继承自 Realm 类。
继承 AuthorizingRealm 类后,我们需要重写以下两个方法。
1)doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
2)doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。
具体实现如下,相关注解请见代码注释:
public class MyRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取用户名
String username = (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 给该用户设置角色,角色信息存在 t_role 表中取
authorizationInfo.setRoles(userService.getRoles(username));
// 给该用户设置权限,权限信息存在 t_permission 表中取
authorizationInfo.setStringPermissions(userService.getPermissions(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 根据 Token 获取用户名,如果您不知道该 Token 怎么来的,先可以不管,下文会解释
String username = (String) authenticationToken.getPrincipal();
// 根据用户名从数据库中查询该用户
User user = userService.getByUsername(username);
if(user != null) {
// 把当前用户存到 Session 中
SecurityUtils.getSubject().getSession().setAttribute("user", user);
// 传入用户名和密码进行身份认证,并返回认证信息
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
return authcInfo;
} else {
return null;
}
}
}
从上面两个方法中可以看出,验证身份时需先根据用户输入的用户名从数据库中查出对应的用户,这时还未涉及到密码,也就是说即使用户输入的密码不正确,照样可以查询出该用户。
然后,将该用户的相关信息封装到 authcInfo 中并返回给 Shiro。接下来就该 Shiro 上场了,将封装的用户信息与用户的输入信息(用户名、密码)进行对比、校验(注意,这里对密码也要进行校验)。校验通过则允许用户登录,否则跳转到指定页面。
同理,权限验证时,也需先根据用户名从数据库中获取其对应的角色和权限,将其封装到 authorizationInfo 并返回给 Shiro。
4. Shiro 配置
自定义 Realm 写好了,接下来需要配置 Shiro。我们主要配置三个东西:自定义 Realm、安全管理器 SecurityManager 和 Shiro 过滤器。
首先,配置自定义的 Realm,代码如下:
接着,配置安全管理器 SecurityManager:
@Configuration
public class ShiroConfig {
@Bean
public SecurityManager securityManager() {
// 将自定义 Realm 加进来
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
logger.info("====securityManager注册完成====");
return securityManager;
}
}
配置 SecurityManager 时,需要将上面自定义 Realm 添加进来,这样 Shiro 才可访问该 Realm。最后,配置 Shiro 过滤器:
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// 定义 shiroFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
// 设置自定义的 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置默认登录的 URL,身份认证失败会访问该 URL
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置成功之后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/success");
// 设置未授权界面,权限认证失败会访问该 URL
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
// LinkedHashMap 是有序的,进行顺序拦截器配置
Map filterChainMap = new LinkedHashMap<>();
// 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
filterChainMap.put("/css/**", "anon");
filterChainMap.put("/imgs/**", "anon");
filterChainMap.put("/js/**", "anon");
filterChainMap.put("/swagger-*/**", "anon");
filterChainMap.put("/swagger-ui.html/**", "anon");
// 登录 URL 放行
filterChainMap.put("/login", "anon");
// 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
filterChainMap.put("/user/admin*", "authc");
// “/user/student” 开头的用户需要角色认证,是“admin”才允许
filterChainMap.put("/user/student*/**", "roles[admin]");
// “/user/teacher” 开头的用户需要权限认证,是“user:create”才允许
filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");
// 配置 logout 过滤器
filterChainMap.put("/logout", "logout");
// 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
}
配置 Shiro 过滤器时,我们引入了安全管理器。至此,我们可以看出,Shiro 配置一环套一环,遵循从 Reaml 到 SecurityManager 再到 Filter 的过程。在过滤器中,我们需要定义一个 shiroFactoryBean,然后将 SecurityManager 引入其中,需要配置的内容主要有以下几项。
今天就分享到这,Shiro不太好懂,大家好好消化一下:关于Springboot中集成Shiro大家有没有什么问题?
网友评论