其实是自己有点遗忘,没啥事,整理下
关于Springboot的配置文件问题.见过的有三种 .factorys .yml .properties给自己以后查看留个笔记
直接上手
先创建个工程,想省去这个环节,算了,一步一步踏踏实实来。当然可以用Spring插件,为了方便直接用了mybatis-plus
父类工程的pom文件,先将Shiro的依赖注释了,不然启动报错 提示realmsXXX什么什么玩意错误的什么玩意的,一会就知道了
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tiantian</groupId>
<artifactId>SpringBootShiro</artifactId>
<version>1.0-SNAPSHOT</version>
<!--jar/war/pom聚合工程-->
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<mybatis.starter>2.1.3</mybatis.starter>
<pagehelper.starter>1.3.0</pagehelper.starter>
<!-- <spring.shiro>1.7.1</spring.shiro>-->
</properties>
<dependencies>
<!--Spring DataSource-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!--Spring mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.starter}</version>
</dependency>
<!--PageHelper (基于 mybatis 框架实现的分页插件)-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.starter}</version>
</dependency>
<!--Spring Web (提供了 web 请求的分层设计)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring AOP (提供面向切面的实现)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Lombok (提供对类的字节码功能增强(类中添加
set,get,toString,...))-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--热部署依赖(修改完业务代码和配置文件时自动重启服务)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--Spring Shiro (权限管理框架)-->
<!-- <dependency>-->
<!-- <groupId>org.apache.shiro</groupId>-->
<!-- <artifactId>shiro-spring-boot-web-starter</artifactId>-->
<!-- <version>${spring.shiro}</version>-->
<!-- </dependency>-->
<!--单元测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<!--排除一些不需要的依赖-->
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
admin工程依赖common工程且这里对dev,prod,test进行了配置
可以配置不同端口自己试试
感觉发了一堆废话
Shiro是Apache旗下一个开源的安全框架,主要实现了用户身份认证,权限授权,加密,会话管理功能
比如我们写完用户登入的控制器,我们可以访问系统,
但是真实的系统不会让你这样肆无忌惮的访问,肯定是需要认证拦截的
而Shiro框架就是用户认证拦截的实现
通常就是登录时候对用户的一些认证
Shiro有三个核心组件:Subject, SecurityManager 和 Realms.
这里为了方便用户登录已经写好了,
问个问题.控制层提交的数据是提交到了哪里????
比如说
提交到了SpringMVC公用的Servlet ------>DispatcherServlet
现在将依赖里的Shiro框架依赖的注释去掉
首先我们得写个Realm,如果一开始Shiro依赖没有被注释,而项目里没有Realm会报错
如果引入shiro而没写Realm
启动发现
看样子在ShiroRealm上加了@Compent没有用 查看了官网发现
加个@Bean配置,好的启动OK了 也就是说上面@Compent可以去掉
临时想起一个事,关于配置一些文件给对象赋值的方式
当然,你也可以自定义一个配置类,效果是一样的 注意Realm导入的是shiro的包
这里也说一下 比如说Spring的bean池中 这个bean的引用名为abc 怎样去获得这个bean的引用呢???????????????????
@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false)
@Resource(这个注解属于J2EE的),默认安装名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
是自己有些遗忘了,回忆下哈哈哈哈
就不发图了
如上,这么一引入shiro的话,也就是说,所有的请求现在都访问不了
需要在配置类(或者启动类)定义过滤规则,指明哪些访问路径需要认证哪些放行
就是一个过滤器,在SpringMVC 请求进入DispatcherServlet之前就进行过滤
对过滤器不熟悉的参见Vue,SpringBoot基础上自己搭建前后端分离应用平台_fhrui的博客-CSDN博客
也就两张图,系统学习过滤器的话.....嗯。。。。。网上找吧#24
其实是shiro给的规则设计
我们需要指明哪些可以放行,哪些要通过认证
这样就设置了所有接口都可以匿名访问
这是个过滤链
ShiroFilterChainDefinition就是个过滤器定义过滤规则
Shiro官方定义了很多过滤器名字
翻译下
定义哪些需要过滤,哪些需要认证,还可以定义自己的过滤器
比如说 authc是需要认证访问的, anon是可以匿名访问的
注意注意注意:这里的过滤器链是有先后顺序的 比如说登录接口,肯定要设置匿名的, 登出接口Shiro有默认定义的logout过滤器
上面的logout过滤器在没有配置的情况下会默认跳转到login.jsp页面
可以在yml文件里进行登录页面的设计
这样当访问比如说localhost:8080/user/logout接口时就会跳到某度上去
Subject的作用是将用户提交信息交给Security Manager 是个接口 ,Security Manager交给Authenticator也是个接口,再由Authenticator负责查询数据库完成认证授权
一般Subject获取用户信息是在控制层调用Subject去提交用户信息,账密,那么就验证账密,刷脸就验证刷脸
其中认证的流程:
1.用户信息进入过滤器,过滤器对比如登录接口放开检查,到达控制器,控制器调用Subject的login方法将用户信息提交给SecurityManager
2.SecurityManage将认证操作委托给认证其对象Authenticator
3.Authenticator将用户输入的身份信息传递给Realm
4.Realm访问数据库获取用户信息然后对信息进行封装并返回
5.Authenticator对realm返回的信息进行身份认证
其实我们只是对 111111111111111111111111和444444444444444444444进行了操作,其他都由Shiro框架完成
感觉现在才开始
数据库表.就是个登录接口
CREATE TABLE `sys_users` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '密码',
`salt` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '盐 密码加密时前缀,使加密后的值不同',
`email` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '手机号',
`valid` tinyint DEFAULT NULL COMMENT '状态 0:禁用 1:正常 默认值 :1',
`deptId` int DEFAULT NULL,
`createdTime` datetime DEFAULT NULL COMMENT '创建时间',
`modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',
`createdUser` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '创建用户',
`modifiedUser` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '修改用户',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='系统用户';
持久层
下面方法里的 AuthenticationToken token我会在控制层通过Subject对象的方法传进来
public class ShiroRealm extends AuthorizingRealm{
@Autowired
SysUserMapper sysUserMapper;//其实通过构造函数或者在给ShiroRealm初始化就是@Bean时候传入这个类型也可以初始化这样不需要@Autowired注解
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**获取并封装认证信息
* @param authenticationToken 为封装了客户端认证信息的一个令牌对象
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//控制层将token传过来强转一下 你不强转idea也会给你强转 控制层我会通过UsernamePasswordToken进行封装
UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken)token;
//获取用户名
String username=usernamePasswordToken.getUsername();
//查询数据库
SysUser sysUser=sysUserMapper.selectUserByusername(username);
//下面两个异常是shiro封装好的异常,查询更多封装好的异常查官网
if(sysUser==null){
throw new UnknownAccountException("用户名不存在");
}
if(sysUser.getValid()==0){
throw new LockedAccountException("账户被禁用");
}
//封装用户信息SimpleAuthenticationInfo是shiro封装的一个认证返回对象
//那个通过盐值获取的乱七八糟对象
String reamlName=getName();
System.out.println("reamlName="+reamlName);
ByteSource byteSource=ByteSource.Util.bytes(sysUser.getSalt());
//因为返回的是AuthenticationInfo或子孙类 直接进去按ctrl+H 英语号的看官网文档或者猜,去猜
return new SimpleAuthenticationInfo(sysUser,//用户身份(基于实际业务设置,放个什么随自己)
sysUser.getPassword(),//已加密的凭证(密码,指纹,脸部信息等等)
byteSource,//这里需要用户密码的盐值的乱七八糟对象(做了编码处理的盐值对象)
reamlName//我也不知道this.getName()就是bean的名字"shiroRealm" 猜的
);
}
}
控制层,这里单纯为演示别讨论get请求 post请求,其实现在控制器因为设置了过滤器拦截,也就看你过滤器里哪些接口可以匿名访问
注意注意请求用的是restful
顺便演示下逮异常哈哈哈哈
看样子异常我们也截获了
好累休息下
这里注意下,这里注意下这里注意下这里注意下这里注意下这里注意下这里注意下这里注意下这里注意下这里注意下这里注意下
在用户注册时候
所使用的加密
因此在我们用Shiro框架进行认证时候,由于客户传进来的是明文
我们也需要给Shiro提供解密方式,至于调用方面由shiro去调用,没有深入研究
也就是说,你是如何加密的,就要告诉Shiro加密方式
理一下流程:
注册用户是已经做好了.使用Shiro进行认证,授权,这里认证是做完了
1.首先创建个Reaml类继承AuthenticatingRealm,是只做认证,继承AuthenticatingRealm的子类
AuthorizingRealm是即做认证也做授权,重写认证方法和授权方法
2.在配置类中进行配置
2.1.@Bean 随意取名 这里是return new ShiroRealm()
2.2.@Bean 创建了一个过滤链.且对登录接口开发访问权限
3.在登录接口通过Subject进行用户数据提交,到达Realm的认证方法.进行认证,且提供了加密算法方式
4.返回控制器,返回Json数据
上面也就是说Shiro的三大核心组件:Subject,SecurityManage,Realm中的Subject和Realm已经用到看到了
Subject就是用来提交用户信息的
SecurityManage进行认证与授权
Realm是负责查询数据库认证与授权对象
当然这只是Shiro在我写的程序理的部分运用而已,其实Shiro还有SessionManager,CacheManage这里不做讨论(主要没那水平).画张图,真正Shiro的应用图比此复杂,分支也多
大概意思
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
其实是AuthenticationToken的子孙类
AuthenticationToken token=new UsernamePasswordToken(username,password);
Subject subject=SecurityUtils.getSubject();
subject.login(token);就是将用户数据提交给shiro的SecurityManager
就是传给了shiroRealm中重写的认证方法的那个参数
shiro的Realm其实是可以有多个的,但是具体的没有研究过
在shiro调用Realm里的认证方法doGetAuthenticationInfo()
return 用户信息,加密凭证,盐值,reamName之后再去
调用getCredentialsMaycher去验证密码合法性验证
重来下
当我们访问/menu菜单页面,跳到了某度,原因yml里做了配置没有登录就会自动跳转登录页
然后我们先登录看下
session
关于cookie和session自己也快忘记了,改天专门写下
只记得cookie保存在客户端,session以key value形式,key带到了客户端 而再次访问时,浏览器将session的key带过来 找服务端的value进行匹配,匹配成功OK。......乱七八糟
原先访问因为没有认证跳转到了某度,然后登入之后再次再访问localhost:8090/inspect/menu
既然刚才那个密码匹配问题暂时没有解决,其实,可以在SpringMVC的全局异常处理来捕获处理
注意将原先的控制器里的异常捕获注释掉,不然先进入控制器的异常捕获,再进入全局异常捕获
而控制器的异常捕获return 了一个返回类对象
这里对Cookie,Session画张图了解下
大概就这么个意思吧,自己也快忘了.
Cookie又好像分:会话Cookie 和 持久Cookie算了
反正Session是通过Cookie携带到客户端的,Session保存在服务端,Cookie保存在客户端这应该没错
现在认证基本算做完了
接着做授权,就是Reaml里的两个重写方法的另外一个doGetAuthorizationInfo
还是一样,由Subject提交用户信息,但是我们现在要做授权,授权信息在数据库。
就是登录用户允许访问哪些菜单,关于用户---->角色------>菜单的关系就不理了
先说下两个注解:
@Transactional描述方法时,此方法为一个事务AOP切入点方法(这个和Shiro无关....给自己留点记忆哈哈哈)
针对于Shiro授权的注解:
@RequiresPermissions("xxxxxxxx")比如说@RequiresPermissions("sys:user:update")
描述方法时,此方法为一个AOP授权切入点方法,我们在访问此方法时就需要授权,有权限则可以访问,没有权限则抛出异常.
那么如何判定用户有没有访问此方法的权限??????????????
Shrio会获取此方法上的@RequiresPermissions("xxxxxxxx")中XXXXX的内容,然后调用Subject的checkPermissions("xxxx")方法比对检测用户是否有对XXXX接口方法访问的权限
其实还是通过Subject提交用户数据 但是Subject提交的数据已经给认证了,那么授权那边难道再提交一次?????????????????????????????
其实授权方法里的参数即身份,已经从认证里传过去了
那么我们现在要找的就是该用户对应角色可以访问哪些菜单
既然用户身份信息已经从认证中传给了授权,那么我们可以直接转一下
那么在需要授权管理的接口上对应添加@RequiresPermissions("xxxxxxxx")
shiro就可以帮我们进行管理
因为没写太多接口,就以菜单接口做个授权实验
然后做个实验
发现将用已经登录的账号@RequiresPermissions("xxxxxxxx")放在控制层居然报错
这个问题暂时没法解答
补充:::::原因找到了:AOP需要代理对象,而在控制层Shiro无法产生代理对象的原因.
当控制器加了@RequiresPermissions会导致控制器上比如@GetMapping @PostMapping等全部失效.映射关系也就不成立,导致404.我们需要在配置类加个配置
这样@RequiresPermissions("xxxxxxxx")可以放在控制层了
在控制层@RequiresPermissions("xxxxxxxx")产生的代理对象是CGLIB代理因为控制层没有实现接口,即使你设置了JDK代理,他找不到JDK代理默认也会产生CGLIB代理。
然后将@RequiresPermissions("xxxxxxxx")放在业务层
用户登录后是可以访问的
其实@RequiresPermissions的内部AOP逻辑也很好理解
自己写的话逻辑大概.
就是在AOP中:
1.获取用户的权限
2获取注解内的标识符
3.判断用户权限是否包含该标识符
4.判断是否包含然后到底是执行还是抛异常即可.执行就AOP环绕通知的jointPoint.proceed;//执行目标方法即可 或者throw new Exception咯
关于控制层用该注解报错的问题,如果找到原因,想起来了会回来解决
注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意
这个@RequiresPermissions("XXXXXX")存在一个问题
就是当分页时候有可能将你本想查询的数据代替的问题.是由于分页时跳过了你的查询语句
关于会话Session的配置
使用Shiro框架做认证和授权时,用户的登录信息会写道Session中,这个Session默认为30分钟,
然后会将这个Session的id以会话Cookie的方式写道客户端,假如需要对Session相关进行配置.
由于启动类配置了太多
单独建了个配置类
这里提一下,当用户登入成功,如何在控制器,或者业务层 获取当前登录用户信息呢??????
登录断点试下
访问断点接口
其实在认证方法可以可以设置Session的值,应现在不是太了解,不敢乱讲
比如说
那么获取用户信息,我们也可以单独做个方法,不然每次这样调用太麻烦
那么我们调用就方便了
咱风骚一点来个高端的,让人看着晕的,得装一下
很成功的装到了,登录后访问
关于Shiro记住我功能
记住我功能是用户登录成功,假如关闭浏览器,下次再访问系统资源时,无需再执行登录操作
而现在我们做不到原因:浏览器关闭后.客户端的SessionID没有了.虽然服务端保留着Session的value
但是没有key找不到Value,服务器的Value其实也就没用了
那么我们需要将服务端创建的会话Cookie转为持久Cookie
这样SessionID会跟着Cookie保存在客户端
具体做法
在登录时候配置下
这个在实际工作中有可能在页面打个勾将值传进来进行设置
然后在配置类中配置
再将原先需要认证访问的改成"user",Cookie会携带SessionId以文件形式保存的客户端这个猜的
至少关闭浏览器不影响
对比原来的SessionID
帅了很多
看下自定义的什么玩意的生命周期
临时会话的生命周期
自定义的持续会话生命周期
那么每次访问时request请求会携带这个Cookie包含SessionID的值作为KEY去服务器找到对应的Session的value.Session的value保存在服务器内存中,定时清理.
至于这个token里要不要保留用户密码这个问题.个人认为服务器在给SessionID时候好比也用了盐值进行了加密.这个问题以后学习后再说吧........
而当持续会话或者临时会话期间,服务器重启的话,会导致key找不到value的情况,那肯定也需要重更新登录.
这个其实也可以通过配置解决.但是个人安全不安全-----------不知道
由该方式得到一个key
启动服务器登入访问都没问题
重启下服务器 没有登录直接可以访问
是因为前面登录过,客户端保留着Cookie,在指定时间内就不需要再次登录
换个浏览器登录
直接跳转到了某度.......
发现这个也蛮好用的.那么即使服务器重启.客户端那边也不受影响
关于配置Shiro授权缓存
配置Shiro中自带的CacheManager对象,此对象可以通过Cache缓存授权时获取到的权限信息.
就是
下次再访问授权方法时,不需要再查询用户权限,这样就减少数据库的访问压力,
同时提高授权性能,意思就是说比如
用户登录后,我们每次访问菜单页面.
因为菜单接口设置了权限匹配
而每次访问该接口,都会去匹配权限
这样性能不高
那么进行配置一下
这个是配置授权缓存管理器,默认应用的缓存是shiro框架内置的缓存对象,缓存实现就是一个map
软引用的特点就是内存不足时,GC会清楚引用引用的对象,释放内存
重启服务器,因为设置了指定时间即使服务器重启也无需登录的配置,直接访问菜单接口
就第一次会去匹配权限,后面指定登录时间内都不会再去检查权限
Shiro的主线就是认证与授权其他都可以当扩展
其实Session中的数据可做持久化
最后强调下.
这里注意,用户信息其实是从本地缓存中取出来的.那么原先用户没有被禁用.而我将用户禁用了呢.缓存里的数据没有及时更新怎么办??????????
注意这里前后端分离是存在跨域问题的.在控制层加@CrossOrigin解决不了跨域问题