一.简介
上一篇文章中讲解了如何基于内存模型来实现授权,在这种模型里,用户的信息是保存在内存中的。但是,保存在内存中的信息,是无法持久化的,也就是程序一旦关闭,或者断电等情况发生,内存中的信息就丢失了,所以这种方式并不适用于生产环境。
那么肯定就要把用户信息持久化, 持久化到数据库,这边文章中采用的是MySQL数据库。
二.JdbcUserDetailsManager类
在数据库中创建并保存用户及角色信息,Spring Security给我们提供了另一个UserDetailsService的实现子类,就是JdbcUserDetailsManager。其中JdbcUserDetailsManager类的关系结构如下图所示:
从上图中,可以看到JdbcUserDetailsManager的直接父类是JDBCDaoSuport,利用JdbcUserDetailsManager可以帮助我们以JDBC的方式对接数据库进行增删改查等操作。它内部设定了一个默认的数据库模型,只要遵从这个模型,我们就可以很方便的实现在数据库中创建用户名和密码、角色等信息,但是灵活性不足。
三. 创建SpringSecurity项目
参考之前的文章,这边不做叙述。
四. 基于默认数据库模型实现授权
3.1创建测试接口
在开始授权代码之前,先创建3个用于测试的Web接口,分别供3个不同的用户角色来进行操作。
3.1.1创建/admin/hello接口:
AdminController 类代码如下:
@RestController
@RequestMapping("/admin")
public class AdminController {@GetMapping("/hello")public String hello() {return "hello, admin";}
}
3.1.2创建/user/hello接口:
UserController 类代码如下:
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("hello")public String hello() {return "hello, user";}
}
3.1.3创建/visitor/hello接口:
VisitorController 类代码如下:
@RestController
@RequestMapping("/visitor")
public class VisitorController {@GetMapping("/hello")public String hello() {return "hello, visitor";}
}
对于以上三个接口,做如下解释:
- /visitor/hello 任何人都可以访问;
- /admin/hello 具有 admin 角色的人才能访问;
- /user/hello 具有 user 角色的人才能访问;
- 所有 user 角色能够访问的接口资源,admin 角色也都能够访问。
4.2准备数据库
既然要操作数据库,那肯定得先建库建表,JdbcUserDetailsManager本身就给我们提供了对应的数据库脚本模型,这个数据库脚本模型保存在如下位置:
org/springframework/security/core/userdetails/jdbc/users.ddl
截图如下:
直接去这个对应的位置下,找到这个数据库脚本文件打开即可。
当打开这个users.ddl文件,可以看到如下内容,会发现其中有2个建表语句,分别是创建了users表和authorities表,并且在authorities表中创建了唯一索引。代码如下:
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));create unique index ix_auth_username on authorities (username,authority);
放到navicat中,创建即可。
注意:
上面的建表脚本中,有一种数据类型 varchar_ignorecase,这个是针对 HSQLDB 数据库创建的,但是我们使用的 MySQL 数据库并不支持这种数据类型,所以这里需要手动将这个数据类型改为 varchar。
4.3配置资源访问权限
先定义一个SecurityConfig配置类,在其中配置对资源的访问控制,创建一个UserDetailsService实例,并在其中配置,生成存储用户和角色信息,代码如下:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasRole("USER").antMatchers("/app/**").permitAll().anyRequest().authenticated().and().formLogin().permitAll();}/***************************************在默认的数据库中创建用户和角色****************************************************/@Autowiredprivate DataSource dataSource;@Beanpublic UserDetailsService createUserDetailService() {JdbcUserDetailsManager manager = new JdbcUserDetailsManager();manager.setDataSource(dataSource);if (!manager.userExists("user")) {manager.createUser(User.withUsername("user").password("123").roles("USER").build());}if (!manager.userExists("admin")) {manager.createUser(User.withUsername("admin").password("123").roles("USER", "ADMIN").build());}return manager;}@Beanpublic PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}}
4.4代码释义
要针对createUserDetailService()方法进行解释。
- 在这个方法中,我们首先构建一个 JdbcUserDetailsManager 实例对象,并给 JdbcUserDetailsManager 实例添加一个 DataSource 对象。
- 接下来调用 userExists()方法 判断用户是否存在,如果不存在,就创建一个新的用户出来(因为每次项目启动时这段代码都会执行,所以加一个判断,避免重复创建用户)。
- 用户的创建方法和我们之前 InMemoryUserDetailsManager 中的创建用户的方法基本一致。
以上就是我们基于默认的数据库模型实现的对用户及角色的操作,之所以可以实现,就是因为在UserDetailsManager这个父接口中,定义了如下方法,这些方法在子类中都有具体的实现。截图如下:
可以看到UserDetailsManager类中,提供了创建、修改、删除、判断用户是否存在的方法,和修改用户密码的方法。
在JdbcUserDetailsManager这个实现子类中,已经定义好了users与authorities表对应的CRUD语句,所以我们直接调用相关方法即可实现对用户及角色的管理。另外我们在特殊情况下,也可以自定义这些SQL语句,如有需要,调用对应的setXxxSQL()方法即可。
五.验证
项目启动起来,然后以不同的身份登录进来,分别访问不同的接口,当某个用户在不具备相应角色时,会出现403提示信息。只有具有相应的角色权限时,才可以访问对应的接口。截图如下:
当我们使用Spring Security默认的数据库模型来操作实现用户授权时,存在灵活性不足的问题,因为我们必须按照源码规定的方式去建库建表。而我们真正开发时,用户角色等表肯定是根据自己的项目需求来单独设计的,所以真正开发时,我们有必要进行用户及角色表的自定义设计。