Spring Security 6
框架基础
SpringSecurity是一个功能强大且高度可定制的身份认证和访问控制框架。它是保护基于
Spring的应用程序的事实上的标准。(Shiro 框架)
SpringSecurity是一个致力于为Java应用程序提供身份认证和授权的框架。像所有Spring
项目一样,SpringSecurity的真正强大之处在于它可以非常轻松地扩展来满足自定义需求;
Spring Security快速上手
spring security现在开发时不会采用spring 进行开发,而是采用spring boot进行开发;
依赖:
1 | <dependency> |
建立项目的时候要勾选Spring Security
创建好后会发现多出这三个文件,下面两个一个是win脚本,一个是Linux脚本,还有个上面的是配置文件,里面的链接下载一个maven什么的rar压缩文件,是用于打包的,这三个删掉就行,因为可以用maven的package打包,还有个help.md也可以删掉
先随便创建一个controller
1 | //这个注解,返回字符串或者json,@Controller注解跳转到页面 |
启动项目,控制台会打印下面信息,这个密码是临时的,格式是uuid,这个密码仅限于开发者使用,不能用于产品
然后访问localhost:8080//hello,然后发现页面进行了跳转到localhost:8080/login,这个也是重定向,输入用户名user密码uuid,然后就会跳转到字符串输出页面,打开网页控制台的cookie里发现有了session,
注意登录成功后默认是跳转回上一个页面
SpringSecurity基本原理分析
SpringSecurity采用16个Filter进行过滤拦截;(基于session),不同的版本filter数量可能有变化
入口:FilterChainProxy
DefaultLoginPageGeneratingFilter生成登录的页面;
DefaultLogoutPageGeneratingFilter生成退出的页面;
登录跳转地址是:/login(这是SpringSecurity框架提供的,不是我们写的)
退出跳转地址是:/logout(这是SpringSecurity框架提供的,不是我们写的)
默认情况下,用户名是user,密码是临时生成的uuid;(来自SecurityProperties类),点进去可以看到代码
1 | String name = "user"; |
可以修改默认的用户名和密码,在配置文件application.properties中配置:
1 | #自己指定登录的用户名和密码 |
如果是yml就是如下
1 | spring: |
SpringSecurity框架登录认证
具体步骤是先建项目,然后加依赖,配文件再写代码
配置文件yml里数据库链接词(type)如果不写的话默认是com.zaxxer:HikariCP这个
1 | spring: |
然后把数据库导入,然后新建个controller,这个是控制登录的
1 | //返回字符串或json,这样我们测试更便捷 |
这里我们想用数据库里的数据登,这里统一密码是aaa111,但直接登录是一定失败的,因为没写代码实现,直接登录默认用户名user密码是uuid,这里的controller不用动,登录会提交给Spring Security,只需要写Service就行
1 | //我们的处理登录的service接口,需要继承springsecurity框架的UserDetailsService接口 |
这个UserDetailsService点进去可以看到有个抽象方法,这个抽象方法被继承了就相当于也有了这个方法,需要去实现
1 | public interface UserDetailsService { |
当前端输完点击登录,这个请求就交给Spring Security,框架会调用下面这个方法,并且把前端的用户名传过来,然后使用逆向工程根据数据表生成接口,实现这步要在idea下载free mybatis tool这个插件,然后在idea里连接数据库并生成,具体的看https://www.bilibili.com/video/BV1RSwXeTE5w?spm_id_from=333.788.player.switch&vd_source=b0128143be22d1783a42f5fbf34d9d25&p=10
生成好了后需要把dao变成bean,有两种方法,一种是在具体的mapper里加上@Mapper注解,这样可以动态代理,如果mapper比较多的话就在Application那个类里加上@MapperScan(value = {‘com.bjpowernode.mapper’})这样可以扫描整个包
@Resource和@Autowired的区别:
- @Resource是javaee提供的标准,@Autowired是Spring提供的
- Resource先名称再字段名再类型注入,Autowired则顺序相反
注入后写查询语句,写完后还要抛出异常
返回的类型不能是单纯的tUser,得是框架里的,而UserDetails是个接口,点进去可以查看,光标点UserDetails这个接口名按ctrl+h就可以看到实现类,有个User,所以要用这个来构建,构建好后赋给userDetails(因为最后的build的返回值是userDetails,要用这个接口去接收)但实际上还是User类型
1 |
|
1 | public interface TUserMapper{ |
1 | <select id="selectByLoginAct" parameterType="java.lang.String" resultMap="BaseResultMap"> |
运行时如果报错Invalid bound statement (not found): com.bjpowernode.mapper.TUserMapper.selectByLoginAct这个的原因是没有帮我们编译,需要在pom里加个resource标签,或者手动使用maven的compile的编译
1 | <resources> |
配置密码加密器
现在还有个问题是现在只是查询用户,密码的比对怎么办呢,框架提醒我们还需要一个密码加密器,如果没有密码加密器会报以下错误
老版本报错:java.lang.llegalArgumentException:You have entered a password with no PasswordEncoder.
新版本报错:You have entered a password with no PasswordEncoder. If that is your intent, it should be prefixed with ‘(noop}’.
1 |
|
新建一个配置类,这个@Beam的效果等同于注释的标签
1 | //配置spring的容器,类似spring.xml文件一样 |
SpringSecurity数据库登录流程分析
- 访问 http://localhost:8080/
- 被spring security 的 filter过滤器拦截(里面有 15个Filter);
- 由于没有登录过,所以springsecurity就跳转到登录页(登录页是框架生成的)
- 我们在登录页输入账号和密码去登录提交;(账号密码来自数据库)
- spring security里面的
UsernamePasswordAuthenticationFilter接收账号和密码; - 第 5步的这个 filter会调用
loadUserByUsername(String username)方法去DB查询用户; - 从数据库查询到用户后,把用户组装成UserDetail对象,然后返回给SpringSecurity框架;
- 第7步返回后,再回到框架的filter里面进行用户状态的判断,用户对象中默认有4个状态字段,如果这4个状态字段的值都是true,该用户才能登录,否则就是提示用户状态不正常,不能登录的(框架中实际上只判断3个状态值,那个密码是否过期没有做判断);
- 第7步返回后,再回到框架的filter里面进行密码的匹配,如果密码匹配上了,就登录成
功,否则失败;
具体理论知识看视频
SpringSecurity自定义登录页面
在Securityconfig里配置自己的登录页,这个.build返回的对象就是DefaultSecurityFilterChain
1 | //配置springsecurity框架的一些行为(配置我们自己的登录页,不使用框架默认的登录页) |
这里视频先介绍的Thymeleaf页面,需要学习的话直接看视频再整理笔记,这里直接跳到前后端分离
前后端分离
在前面的例子中,我们是返回到Thymeleaf页面,但如果是前后端分离开发,是不能返回一
个页面的,而应该是返回一个JSON;
Vue + springboot/spring security/mybatis (Nginx +Tomcat)无法共享sesssion
Thymeleaf+ springboot/spring security/mybatis(Tomcat)共享sesssion
这里提供了代码,登录不需要写controller,是框架提供的,只需要指定一个地址,到时候登录往这里提交就行,这里是/user/login
前后端分离的前端没有csrf,所以获取不到csrf的token,这个值只有后端跳到前端才可以拿到,所以要把他禁用
然后使用ajax会出现跨域问题,这里的跨域如果使用disaple是无法解决的,需要配置一些东西,使用configurationSource方法里面需要传个参数,这个对象可以通过方法参数注入,配置一个bean,在里面new这个对象,但这个对象是个接口,需要new他的实现类
跨域问题解决后还有个返回信息问题,当登录成功后不是跳转页面,而是跳一个handler,这个handler里的参数是个接口,需要写一个handler实现接口覆盖里面的抽象方法,就要新建一个类,然后注入进去
1 | //注入handler |
新建一个handler类
1 |
|
Spring Security密码加密和密码匹配
对于用户密码的保护,通常都会进行加密然后存放在数据库中;(基本常识)
目前密码加密MD5 和BCrypt比较流行,Spring Security默认是采用BCrypt;
SpringSecurity密码加密接口:PasswordEncoder;
准备一个配置类
1 |
|
我们只需要学习两个方法就可以
1 |
|
如果循环加密,查看控制台发现特点是:相同的字符串加密之后的结果都不一样,但是比较的时候是一样的
BCrypt加密原理
输入的明文密码比如aaa111,通过随机加盐salt(abcxyz….22位字符串)后再进行加密得到密文密码xxx
(version+salt+hash)版本号+盐值+哈希加密,然后存入数据库;
bcrypt(aaa111+22位随机的盐abcxyz)=密文
BCrypt匹配原理
系统在验证用户的密码时,需要从密文密码xXx中取出盐salt(22位),然后与用户页面输
入的password(aaa111)进行加密,把得到的结果与保存在数据库中的密文xxx进行比对,
如果一致才算验证通过;
明文:aa111
密文:$2a$10$ 9z08lUjY.Htp4xdWLT7TzO wrz4MGz4V7tt1m/61HdebDqR2m7Oj52
比较:bcrypt(aaa111+9z08lUjY.Htp4xdWLT7TzO)–>密文==上面这个密文;
其实最终还是比较密文
这个不能解密







