[spring] Spring MVC - security(下)
callback 一下,当前项目结构如下:
这里实现的功能是连接数据库,大范围和 [spring] rest api security 重合
数据库连接 - 明文密码
第一部分使用明文密码
设置数据库
主要就是运行 SQL:
sql
USE `employee_directory`;
DROP TABLE IF EXISTS `authorities`;
DROP TABLE IF EXISTS `users`;
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`enabled` tinyint NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `users`
--
INSERT INTO `users`
VALUES
('john','{noop}test123',1),
('mary','{noop}test123',1),
('susan','{noop}test123',1);
--
-- Table structure for table `authorities`
--
CREATE TABLE `authorities` (
`username` varchar(50) NOT NULL,
`authority` varchar(50) NOT NULL,
UNIQUE KEY `authorities_idx_1` (`username`,`authority`),
CONSTRAINT `authorities_ibfk_1` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `authorities`
--
INSERT INTO `authorities`
VALUES
('john','ROLE_EMPLOYEE'),
('mary','ROLE_EMPLOYEE'),
('mary','ROLE_MANAGER'),
('susan','ROLE_EMPLOYEE'),
('susan','ROLE_MANAGER'),
('susan','ROLE_ADMIN');
运行结果如下:
以及目前的 ER 图:
添加 maven 依赖
新增的依赖如下:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
修改 properties 文件
这里具体的数据库/用户名/密码和本地设置有关,除此之外新增了一个 logging.level.org.springframework.jdbc.core=TRACE
,这个设置是为了增加连接数据库的 logging 的,主要作用是 demo/debug
properties
spring.application.name=demo
# JDBC properties
spring.datasource.url=jdbc:mysql://localhost:3306/employee_directory
spring.datasource.username=springstudent
spring.datasource.password=springstudent
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
# log JDBC SQL statements
# only use this for dev/testing purpose
logging.level.org.springframework.jdbc.core=TRACE
更新 security config
之前用的是 InMemoryUserDetailsManager
,代表用的是内存中的用户名和密码。这里需要吧 InMemoryUserDetailsManager
注释掉,更换成 UserDetailsManager
具体实现如下:
java
@Bean
public UserDetailsManager userDetailsManager(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource);
}
执行的 sql 指令如下:
这代表着现在已经使用数据库连接而非内存中写死的用户名和密码
数据库连接 - bcrypt 密码
这里的配置其实和明文密码基本一样,最大的差别在于写入数据库的密码
明文中存入的数据为 {noop}test123
, 而 bcrypt 中写入的数据为加密后的哈希值: bcrypt}$2a$10$qeS0HEh7urweMojsnwNAR.vcXJeXR1UcMRZ2WcGQl9YeuspUdgF.q
bcrypt 是一种密码加密方式,其主要的优点在于,因为 salt 不是固定的,因此同样的密码会生成不同的哈希值
具体的实现这里不过多涉及
设置数据库
sql
USE `employee_directory`;
DROP TABLE IF EXISTS `authorities`;
DROP TABLE IF EXISTS `users`;
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`username` varchar(50) NOT NULL,
`password` char(68) NOT NULL,
`enabled` tinyint NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `users`
--
-- NOTE: The passwords are encrypted using BCrypt
--
-- A generation tool is avail at: https://www.luv2code.com/generate-bcrypt-password
--
-- Default passwords here are: fun123
--
INSERT INTO `users`
VALUES
('john','{bcrypt}$2a$10$qeS0HEh7urweMojsnwNAR.vcXJeXR1UcMRZ2WcGQl9YeuspUdgF.q',1),
('mary','{bcrypt}$2a$10$qeS0HEh7urweMojsnwNAR.vcXJeXR1UcMRZ2WcGQl9YeuspUdgF.q',1),
('susan','{bcrypt}$2a$10$qeS0HEh7urweMojsnwNAR.vcXJeXR1UcMRZ2WcGQl9YeuspUdgF.q',1);
--
-- Table structure for table `authorities`
--
CREATE TABLE `authorities` (
`username` varchar(50) NOT NULL,
`authority` varchar(50) NOT NULL,
UNIQUE KEY `authorities4_idx_1` (`username`,`authority`),
CONSTRAINT `authorities4_ibfk_1` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `authorities`
--
INSERT INTO `authorities`
VALUES
('john','ROLE_EMPLOYEE'),
('mary','ROLE_EMPLOYEE'),
('mary','ROLE_MANAGER'),
('susan','ROLE_EMPLOYEE'),
('susan','ROLE_MANAGER'),
('susan','ROLE_ADMIN');
代码方面同样不需要任何的修改,效果如下:
⚠️:这里数据库中的密码换成了 bcrypt 加密后的密码
⚠️:这里是用原本的密码登录会报错------?error
证明了用户名和密码不符合数据库中数据,下面使用更新过的用户名和密码则可以登录
使用自定义表
前面的实现都是用默认的 spring boot 实现,即用户的表为 users
,而权限的表单为 authorities
。但是在实际的应用场景中,更多的业务情况是需要使用更特定的表名。spring 同样也有对这个需求的支持,其实现方式则需要写少量的 sql 语句
更新数据库
这里主要新建两个平行的数据库分别取代 users
和 authorities
:
sql
USE `employee_directory`;
DROP TABLE IF EXISTS `roles`;
DROP TABLE IF EXISTS `members`;
--
-- Table structure for table `members`
--
CREATE TABLE `members` (
`user_id` varchar(50) NOT NULL,
`pw` char(68) NOT NULL,
`active` tinyint NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `members`
--
-- NOTE: The passwords are encrypted using BCrypt
--
-- A generation tool is avail at: https://www.luv2code.com/generate-bcrypt-password
--
-- Default passwords here are: fun123
--
INSERT INTO `members`
VALUES
('john','{bcrypt}$2a$10$qeS0HEh7urweMojsnwNAR.vcXJeXR1UcMRZ2WcGQl9YeuspUdgF.q',1),
('mary','{bcrypt}$2a$10$qeS0HEh7urweMojsnwNAR.vcXJeXR1UcMRZ2WcGQl9YeuspUdgF.q',1),
('susan','{bcrypt}$2a$10$qeS0HEh7urweMojsnwNAR.vcXJeXR1UcMRZ2WcGQl9YeuspUdgF.q',1);
--
-- Table structure for table `authorities`
--
CREATE TABLE `roles` (
`user_id` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE KEY `authorities5_idx_1` (`user_id`,`role`),
CONSTRAINT `authorities5_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `members` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `roles`
--
INSERT INTO `roles`
VALUES
('john','ROLE_EMPLOYEE'),
('mary','ROLE_EMPLOYEE'),
('mary','ROLE_MANAGER'),
('susan','ROLE_EMPLOYEE'),
('susan','ROLE_MANAGER'),
('susan','ROLE_ADMIN');
实现后的 ER 图如下:
配置 security config 使用自定义表单
这里需要更新的也是 userDetailsManager
,之前是直接返回 jdbcUserDetailsManager
,让其运行默认的 sql 语句,这里则是写一点 sql 语句,重写默认的执行语句:
java
@Bean
public UserDetailsManager userDetailsManager(DataSource dataSource) {
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
// define query to retrieve a user by username
jdbcUserDetailsManager.setUsersByUsernameQuery(
"select user_id, pw, active from members where user_id=?"
);
// define query to retrieve the authorities/roles by username
jdbcUserDetailsManager.setAuthoritiesByUsernameQuery(
"select user_id, role from roles where user_id=?"
);
return jdbcUserDetailsManager;
}