「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」。
一、基于配置的认证与授权
- 新建
controller
包 - 在该包下新建三个控制器,AdminController,AppController,UserController
- 分别创建测试API
1 | java复制代码/** |
1 | java复制代码/** |
1 | java复制代码/** |
- 配置资源授权
- 配置
configure
- 修改之前的配置
1 | java复制代码 @Override |
antMatchers()
一个采用ANT模式的URL匹配器?
表示匹配任意单个字符*
表示匹配0或任意数量字符**
表示匹配0或更多的目录- 重启服务
- 访问api
http://localhost:8080/app/api/hi
- 访问成功 页面显示
hi,app.
- 访问api
http://localhost:8080/user/api/hi
- 跳转到登录页面
- 输入自定义的用户名密码
- 登录成功,页面却报403错误,表示授权失败
- 认证已经通过,授权失败
因为我们配置的
.antMatchers("/user/api/**").hasRole("USER")
,需要用户具有USER角色权限
- 修改配置文件
application.yml
1 | yml复制代码spring: |
- 给用户添加USER权限
- 重启项目
- 访问api
http://localhost:8080/user/api/hi
- 登录成功后,页面显示
hi,user.
访问api http://localhost:8080/admin/api/hi
出现同样情况
修改配置文件application.yml
给用户添加上ADMIN权限
重启项目
访问正常,页面显示hi,admin.
二、基于内存的多用户设置
1. 实现自定义的UserDetailsService
1 | java复制代码@Bean |
注意: SpringSecurity5.x 以上版本需要配置加密否则会出现以下异常
1 | java复制代码There is no PasswordEncoder mapped for the id "null" |
SpringSecurity5.x 加密方式采用
{Id}password
的格式配置
我们可以看一下PasswordEncoderFactories自带的加密方式
1 | java复制代码public class PasswordEncoderFactories { |
- 重新启动
- 输入账号密码
- 登录成功
- 此配置会覆盖原先
application.yml
中的配置
2. 通过congfigure
1 | java复制代码@Override |
同实现自定义UserDetailsService大同小异,此配置会覆盖原先
application.yml
中的配置和自定义UserDetailsService中配置,选其中之一就可以
三、 基于默认数据库模型的授权与认证
- 查看InMemoryUserDetailsManager源码
- 实现了UserDetailsManager接口
- 选中UserDetailsManager接口,Ctrl+H
发现实现该接口的还有另一个实现类JdbcUserDetailsManager
从命名应该能猜到该实现类通过JDBC方式连接数据库
- 为工程引入JDBC和MYSQL依赖
1 | xml复制代码<dependency> |
application.yml
配置数据连接参数
1 | yml复制代码spring: |
- 创建数据库springSecurityDemo
SpringSecurity提供了默认的数据库模型
1 | java复制代码public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema() { |
地址在org/springframework/security/core/userdetails/jdbc/users.ddl
下
1 | mysql复制代码create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null); |
注意: MySql不支持varchar_ignorecase这种类型,将其改为varchar
1 | mysql复制代码create table users(username VARCHAR(50) not null primary key,password VARCHAR(500) not null,enabled boolean not null); |
- 执行建表语句
- 创建两张表
authorities表
users表
- 构建JdbcUserDetailsManager实例,让SpringSecurity使用数据库来管理用户,和基于内存类似,只是用户信息来源于数据库
- 引入DataSource
1 | java复制代码@EnableWebSecurity |
- 重启项目
- 访问api
http://localhost:8080/user/api/hi
- 输入用户名
aa
密码111
- 访问成功
发现数据库存储了这些信息
并且注意到在我们设置的权限前加了ROLE_前缀
- 查看JdbcUserDetailsManager源码
发现定义了大量的sql执行语句
createUser()
其实就相当与执行下面SQL语句
1 | java复制代码insert into users (username, password, enabled) values (?,?,?) |
上述代码中存在一个问题,每当我们重启项目时都会去创建用户,但是username是主键,会出现主键冲突异常
1 | java复制代码nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'aa' for key 'PRIMARY' |
- 稍作修改
1 | java复制代码/** |
- 重启项目
- 正常运行
- 通过修改数据库数据添加管理员用户
- 访问api
http://localhost:8080/admin/api/hi
输入自己定义的管理员用户名密码,访问成功
四、 基于自定义数据库模型的授权与认证
在项目开发中,默认的数据库模型太过于简单,往往不能满足我们业务的需求,SpringSecurity同样支持,自定义数据库模型的授权与认证。
- 下面接入自定义的数据库模型
- 持久层框架使用MyBatis-Plus
- 使用lombok插件简化代码
- 为工程引入相关依赖
1 | xml复制代码<dependency> |
1. 实现UserDetails
之前的案例中通过实现UserDetailsService,并加上注解注入spring容器,Spring Security会自动发现并使用, UserDetailsService也仅仅实现了一个
loadUserByUsername()
方法,用于获取UserDetails对象 ,UserDetails包含验证所需的一系列信息
1 | java复制代码public interface UserDetails extends Serializable { |
所以无论数据来源是什么,或者数据库结构如何变化,我们只需要构造一个UserDetails即可。
1.1 实现自己的用户表
1 | mysql复制代码CREATE TABLE `t_user` ( |
- 在username字段上添加索引,提高搜索速度
- 手动插入两条数据
1.2 编写我们的User实体
- 创建
entity
包存放实体 - 新建User实体类
1 | java复制代码@Data |
- 实现UserDetails
1 | java复制代码@Data |
重写方法
- isAccountNonExpired()、isAccountNonLocked()、isCredentialsNonExpired()暂时用不到全部返回true
- isEnabled()对应enable字段
- getAuthorities()原本对应的是roles字段,但是自己定义的结构变化,所以我们先新建一个authorities,后期进行填充。
1.3 持久层准备
- 创建
mapper
包 - 创建UserMapper
1 | java复制代码@Component |
- 启动类添加包扫描注解
1 | java复制代码@SpringBootApplication |
- 编写业务代码
- 创建
service
包 - 创建MyUserDetailsService实现UserDetailsService
1 | java复制代码@Service |
注意: SpringSecurity5.x 以上版本需要配置加密否则会出现以下异常
1 | java复制代码There is no PasswordEncoder mapped for the id "null" |
- 配置默认加密方式
1 | java复制代码@EnableWebSecurity |
- 重启项目
- 访问api
http://localhost:8080/admin/api/hi
- 输入用户名密码
- 访问成功
到此我们已经实现了自定义的数据库模型的授权与认证,后期可以根据项目需要丰富验证逻辑,加强安全性
这里一直有个问题,为什么我们的数据库里权限需要加上
ROLE_
前缀?
查看 hasRole() 方法源码就很容易理解了
1 | java复制代码private static String hasRole(String role) { |
如果不想要匹配这个前缀可换成**hasAuthority()**方法
本文转载自: 掘金