web后端-Shiro
参考资料
什么是权限管理?
不同身份的用户进入到系统所能够完成的操作是不相同的,我们对不同用户进行的可执行的操作的管理称之为权限管理。
例如下图职员的权限存在一定交集。
如何实现权限管理?
权限管理设计
- 基于主页的权限管理(不同用户使用不同的主页,权限通过主页功能菜单进行限制)适用于权限管理比较单一、用户少、每类用户权限固定。
- 基于用户和权限的权限管理,但是不够灵活,如果新增了100个用户,每个用户都去手动分配权限,太麻烦了。
- 添加角色表,新增用户时,只需要为其设置角色即可,无需依次设置权限,而对应角色的权限统一设置。如果想为某些用户设置额外的权限,就需要添加新的表。
- 添加一张额外授权表,即拥有相同角色的某些用户拥有额外的权限
RABC权限管理
为了达成不同账号(员工、总裁)登录系统后看到不同页面,执行不同功能,得出了RBAC(Role-Based Access control)权限模型,就是根据角色的权限,分配可视页面。
三个关键点:
- 用户 :使用系统的人
- 角色:使用系统的人是什么职位(员工、经理、总裁)
- 权限点:职位可以做的事情(左侧菜单栏中的功能模块——>增删改查)
认证权限流程
传统方法
- 认证:对用户的身份进行检查(登录验证)。
- 授权:对用户的权限进行检查(是否有对应的操作权限)
流程示意图:
核心思想通过过滤器+session实现
安全框架
帮助我们完成用户身份认证及权限检查功能框架
常用的安全框架:
- Shiro: Apache Shiro是一个功能强大并且易用的Java安全框架(小而简单)。
- Spring Security:基于Spring的一个安全框架,依赖Spring
- OAuth2:第三方授权登录
- 自定义安全认证中心
Shiro
Apache Shiro是一个功能强大并且易用的Java安全框架,适用于非分布式应用,因为Shiro是基于Session的,而多台服务器Session无法共享。
Shiro可以完成用户认证、授权、密码及会话管理。
Shiro工作原理
Shiro的核心功能
- Anthentication认证,验证用户是否有相应的身份-登录认证;
- Authorization授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作;
- Session Managment会话管理,用户在认证成功之后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中;可以是普通的JavaSE应用,也可以是web应用;
- Cryptography加密,对敏感信息进行加密处理,shiro就提供这种加密机制;
其它特性
- Web Support - Shiro提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制
- Caching缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率
- Concurrency shiro支持多线程应用
- Testing提供测试支持
- Run As 允许一个用户以另一种身份去访问
- Remeber Me
核心组件
- Subject表示待认证和授权的用户,即用户的账号和密码封装到Subject中,代表某个用户,让Shiro使用Subject进行认证。Subject由帮助
- SecurityUtil获得。SecurityUtil由Security Manager管理。
- Security Manager,它是Shiro框架的核心,Shiro就是通过Security Manager来进行内部实例的管理,并通过它来提供安全管理的各种服务。
- Authenticator,认证器
- Anthorizer,授权器
- SessionManager,会话管理器
- CacheManager,缓存管理器
- Realm,相当于Shiro进行认证和授权的数据源,充当了Shiro与安全数据之间的“桥梁”或者"连接器”。也就是说,当对用户进行认证(登录)和授权(访问控制)验证时,Shiro会用应用配置的Realm中查找用户及其权限信息,即如果用户信息在Realm中不存在,则认证,授权失败。
Shiro JavaSE基础开发
前期准备
导包
创建shiro配置文件
在resource目录下创建名为shiro.ini的文件在文件中完成用户、角色及权限的配置
基本使用
初始化
//1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
/ /2.创建realm
IniRealm iniRealm = new IniRealm( resourcePath: "classpath:shiro.ini");
//3.将realm设置给安全管理器
securityManager.setRealm(iniRealm);
//4.将ReaLm设置给Securityutils工具
SecurityUtils.setSecurityManager(securityManager);
/ /5.通过SecurityUtils工具类获取subject对象
Subject subject = SecurityUtils.getSubject();
认证流程
//【认证流程】
// a.将认证帐号和密码封装到token对象中
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//b.通过subject对象调用Login方法进行认证申请:
boolean b = false;
try{
subject. login(token);
b = true;
}catch(IncorrectCredentialsException e){
b = false;
}
System.out.println(b?"登录成功":"登录失败");
鉴权流程
//【授权】
//判断是否有某个角色
System.out.println(subject.hasRole( "seller" ));
//判断是否有某个权限
boolean permitted = subject.isPermitted( "order-del" );System. out.println(permitted) ;
整体流程
1.通过subject.login(token)进行登录,就会将token包含的用户信息(帐号和密码)传递给SecurityManager
2.SecurityManager就会调用Anthenticator进行身份认证
3.Anthenticator把token传递给对应的Realm
4.Realm根据得到的token,调用doGetAuthenticationInfo方法进行认证(如果认证失败通过抛出异常提示认证器)
5-7将认证结果一层—层返回到subject(如果subject.login)
SpringBoot整合Shiro
创建springboot应用
添加依赖
添加mybatis,druid依赖
整合Shiro
配置application.yml
创建配置文件
spirngboot不提供shiro的配置,所以要自己配
@Configuration
public class ShrioConfig {
@Bean
public IniRealm getIniRealm() {
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
return iniRealm;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(IniRealm iniRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(iniRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
filter.setSecurityManager(securityManager);
//设置shiro的拦截规则
// anon 匿名用户可访问
// authc 认证用户可访问
// user 使用RemeberMe的用户可访问
// perms 对应权限可访问
// role 对应的角色可访问
Map<String, String> filterMap = new HashMap<>();
filterMap.put(" /", "anon");
filterMap.put(" /1ogin.htm1", "anon");
filterMap.put(" /regist.htm1", "anon");
filterMap.put(" /user/login", "anon");
filterMap.put("/ user/regist", "anon");
filterMap.put(" /static/**", "anon");
filterMap.put("/**", "authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/1ogin.htm1");
//设置未授权访问的页面路径,即找未授权时跳转的页面
filter.setUnauthorizedUrl("/1ogin.htm1");
return filter;
}
}
登出
在Shiro过滤器中进行配置,配置logut对应的路径:filterMap. put( " /exit" , " logout" );
在页面的“退出”按钮上,跳转到logout对应的url:<a href= "exit">退出</ a>
认证测试
主要代码:
Service
@Service
public class CheckServiceImpl {
public void checkLogin(String userName,String userPwd) throws Exception{
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd) ;
subject.login(token);
}
}
Controller
@Resource
private CheckServiceImpl userService;
@RequestMapping("1ogin")
public String login(String userName, String userPwd) {
try {
userService.checkLogin(userName, userPwd);
System.out.println("------登录成功!");
return "index";
} catch (Exception e) {
System.out.println("------登录失败!");
return "login";
}
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title></head>
<body>
login
<hr/>
<form action="user/login">
<p>帐号:<input type="text" name="userName "/></p>
<p>密码:<input type="text" name="userPwd "/></p>
<p><input type="submit" value="登录"/></p>
</form>
</body>
</html>
JDBCRealm
为什么要用?
前面的例子的realm(从shiro.ini加载)都是写死的,现在让Shiro从数据库中读取。
怎么用?
如果使用JdbcRealm,则必须提供JdbcRealm所需的表结构(权限设计)
JdbcRealm规定的表结构(必须遵守,否者报错)。
- 用户信息表:users
create table users(
id int primary key auto_increment,
username varchar ( 68) not null,
password varchar ( 20) not null,
...(按需添加)
);
-
角色信息表:user_roles
create table user_roles(
id int primary key auto_increment,
username varchar ( 60) not null,
role_name varchar ( 100) not null
...
};
-
权限信息表:roles_permission
create table roles_permissions(
id int primary key auto_increment,
role_name varchar( 188) not null,
perssion varchar ( 100) not null,
...
);
例子
设置数据源
//dataSource由配置好的Druid提供
@Bean
public JdbcRealm getJdbcRealm( DataSource dataSource){
JdbcRealm jdbcRealm = new JdbcRealm();
/ / JdbcRealm会自行从数据库查询用户及权限数据(数据库的表结构要符合 JdbcRealm的规范)
jdbcRealm .setDataSource( dataSource) ;
// JdbcRealn黑t认开启认证功能,需要手动开启授权功能
jdbcRealm.setPermissionsLookupEnabled(true) ;
return jdbcRealm;
}
认证测试,同上一章
Shiro的标签使用
当用户认证进入到主页面之后,需要显示用户信息及当前用户的权限信息;Shiro就提供了一套标签用于在页面来进行权限数据的呈现
JSP页面中引用
<%@taglib prefix="shiro" uri="http: / /shiro.apache.org/tags"%>
Thymeleaf模版中引用
在pom.xml文件中导入thymeleaf模版对shiro标签支持的依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.8.0</version>
</dependency>
在ShiroConfig中配置相关依赖注入
@configuration
public class shiroConfig {
@Bean
public shiroDialect getshiroDialect(){
return new shiroDialect();
}
……
}
Thymeleaf模版中引入shiro的命名空间
<html xmlns:th="hLtp : / / www.thymeleaf.org"
xmlns :shiro="http:/ / www .pollix.at/thymeleaf/shiro">
</html>
其它略
自定义Realm
为什么要用?
可以从上节看到jdbcReal对表的结构存在限制,不够灵活
认证流程回顾
- subject调用login方法,将包含用户和密码的token传递给SecurityManager
- SecurityManager就会调用认证器(Authenticator)进行认证
- Authenticator就将token传递给绑定的Realm,在Realm中进行用户的认证检查;如果认证通过则正常执行,如果认证不通过则抛出认证异常
实验环境
表结构:
自定义过程
整体结构
使用jdbcRealm时需要使用固定结构的表,而使用自定义Realm时需要将自定义的表数据转换为Shiro权限验证需要的数据
- Shiro进行认证需要用户信息,根据用户名查询用户信息
根据用户名查询当前用户的角色列表
根据用户名查询当前用户的权限列表
- Shiro进行授权管理需要当前用户的角色和权限
重写认证
重写授权
principalcollection中的内容为认证函数返回的的info对象中的第一个参数。
myRealm注入
加密
基础知识
加密规则可以自定义,在项目开发中我们通常使用BASE64和MD5编码方式。
- BASE64:可反编码的编码方式
-
明文---密文
-
密文----明文
-
- MD5:不可逆的编码方式(非对称)
-
明文----密文
-
加密过程
加密并不绝对安全,因为只要有有充足的时间与算力,再复杂的md5加密方式也会有被穷举法穷举出来的时候,当然如果付出大于回报,也没人这么干
过程1
前端明文传输,在业务层进行加密,加密后保存在数据库.
缺点:如果获取加密规则,则可以通过解密获取密码
过程2
使用md5进行加密,优点是无法通过算法解密获取密码。
缺点:可以建立密码-密文数据库(穷举所有合法的密码,通过md5规则加密,存入数据库以便查询),通过查询密文来获取密码,如果密码较为复杂,则较为安全,但是如果算力足够,或者破解时间久,也会有被解密的一天。
过程3
加盐:即在密码加密前拼接随机字符串(盐),然后再进行md5加密,这样做的好处就是即使被解密,得到的也是密码与盐的混合体,无法获取真实密码,还可以通过多层md5加密,增加破解难度。
如果数据库用户的密码存储的密文,Shiro该如何完成验证呢?
使用Shiro提供的加密功能,对输入的密码进行加密之后再进行认证,加密次数要与组注册时的加密次数一致
加盐+加密*n,当然,需要把盐存入数据库
修改myRealm,返回认证信息时需要同时返回加盐
授权
用户登录成功之后,要进行响应的操作就需要有对应的权限;在进行操作之前对权限进行检查―授权权限控制通常有两类做法:
- 不同身份的用户登录,我们显示不同的操作菜单(没有权限的菜单不显示)
- 对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提示权限不足
HTML授权
在菜单页面只显示当前用户拥有权限操作的菜单. shiro标签
<shiro:hasPermission name= "sys:c :save">
<dd><a href=" javascript: ; ">入库</ a></ dd>< / shiro:hasPermission>
过滤器授权
在shiro过滤器中对请求的url进行权限设置
filterMap . put( " /c_add . html " , " perms[ sys:c :save] " );
//设置未授权访问的页面路径-当权限不足时显示此页面
filter . setUnauthorizedUr1( " / lesspermission. html" );
注解授权
配置spring对shiro注解的支持
再请求的控制器添加注解
通过全局异常处理,指定权限不足时的页面跳转
基于代码的校验
在代码中进行手动权限校验
缓存使用
为什么要用?
使用Shiro进行权限管理过程中,每次授权都会访问realm中的doGetAuthorizationInfo方法查询当前用户的角色及权限信息,如果系统的用户量比较大则会对数据库造成比较大的压力。
Shiro支持缓存以降低对数据库的访问压力(缓存的是授权信息)
怎么用?
导入缓存依赖(使用ehcahe做缓存)
缓存配置文件
加入缓存管理
Session管理
为什么要用?
Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理。在某些场景中,需要对session进行配置,如银行的页面超时时需要设置很短
怎么用?
如果我们需要对session进行管理,首先需要,自定义session管理器,将自定义的session管理器设置给SecurityManager
配置sessionManager
rememberMe
记住密码
为什么要用?
对于一些展示性页面,当用户选择了记住我,即使好久都没有认证,也可以正常访问,但是如果想要修改,则需要重新登录
Shiro将用户对页面访问的权限分为三个级别:
- 未认证―可访问的页面-(陌生人)—问候
- 记住我―可访问的页面-(前女友)-朋友间的拥抱
- 已认证-可访问的页面-(现女友)-牵手
流程
怎么用?
指定哪些页面为记住我下可以访问。
设置Cookie管理器并进行配置
在token中写入记住我
多Realm
为什么要用?
当shiro进行权限管理,数据来自于不同的数据源时,我们可以给SecurityManager配置多个Realm,或者在前端选择用户登录或管理员登录后,可以选择使用指定的Realm
怎么用?
多Realm处理方式
链式处理:多个Realm依次进行认证
分支处理:根据不同的条件从多个Realm中选择—个进行认证处理
认证源码
链式处理
两个Realm
在ShiroConfig.java中为SecurityManager配置多个Realm
分支处理
原理
将选择Realm的参数写入自定义的Token,然后通过继承认证器重写其中的doAuthentication方法,当Shiro进行认证时,就会调用自定义的doAuthentication方法,从而完成自定义Realm选择
流程
自定义token
自定义认证器
配置自定义认证器
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通