在前面 SpringBoot 2.7.2 的系列文章中,已经创建了几个 computer 相关的接口,这些接口直接通过 Spring Doc 或 POSTMAN 就可以访问。例如:
GET http://localhost:9099/computer/1
访问该服务可以获取 id 为 1 的电脑详情。
接下来的文章就使用 Spring Security 实现用户认证和授权。
1 添加 Spring Security
1.1 添加依赖
Spring Boot 对 Spring Security 非常友好,它已经管理了 Spring Security 的依赖版本,并且提供 starter 的方式进行整合:
<!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
1.2 测试服务
添加依赖后重新启动服务,可以在控制台看到输出:
在浏览器中访问该服务,会自动跳转到登录页面,该页面是 Spring Security 默认提供的。浏览器中地址栏自动跳转到:
http://localhost:9099/login
。
在登录页面,用户名填写 user
,密码填写上面控制台中输出的密码,点击“Sign in”,便会进入系统,显示电脑详情。
2 硬编码配置用户名和密码
咱们使用的 Spring Boot 版本是 2.7.2,对应的 Spring Security 版本为 5.7.2,从 5.7 开始,Spring Security 的使用有一些改变,其中之一就是配置 Security 时不推荐继承 WebSecurityConfigurerAdapter 类。
上面的 demo 中使用的用户名是 user,密码是在启动中生成的,这肯定不符合开发的需求。配置用户名密码有三种方式:
- 在配置文件 application.yml 中写死用户名和密码;
- 定义配置类,在该方法中写死用户名密码;
- 实现 UserDetailsService 接口,然后定义配置类进行配置。
前面两者在 demo 中玩玩就行。
2.1 在配置文件中配置用户名密码
这种方式比较简单,直接在 application.yml 中配置即可:
spring: profiles: active: @env@ security: user: name: hero1 password: '111111' roles: 'admin'
2.2 在内存中配置用户名密码
删除上面的 security 节点相关的配置,使用配置文件,配置在内存中生效的用户名密码。
创建配置类 SecurityConfig:
package com.yygnb.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public InMemoryUserDetailsManager userDetailsService() { UserDetails user = User.builder() .username("hero2") .password(passwordEncoder().encode("111111")) .roles("admin") .build(); return new InMemoryUserDetailsManager(user); } }
重启服务,登录界面输入 hero2/111111 进行测试。
这种方式也是写死的用户名密码,在实际开发中不会使用的。所以,先删除这个文件,然后再继续后面的学习。
3 数据库配置用户名和密码
在开始这个部分之前,请确保删除了上面编写的 SecurityConfig。
3.1 数据库表
首先需要定义表结构。
由于我这里是基于前面 spring boot 2.7.2 系列文章的demo进行的,我继续沿用 Liquibase 的方式定义表结构。尊敬的读者们可以从 GitHub 上获取 demo 基础工程,也可以联系我获取,还可以脱离 demo 基础工程,自己用习惯的方式创建数据库表结构,如 SQL 语句、图形化界面等。
在 resources/db/demo/
目录中创建文件 demo-changelog-v2.xml
,该文件用来定义这次数据库的变更。
<?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <changeSet id="T100-20220801-yyg-025" author="yyg"> <createTable remarks="用户表" tableName="user"> <column autoIncrement="true" name="id" type="BIGINT"> <constraints nullable="false" primaryKey="true"/> </column> <column name="username" remarks="用户名" type="VARCHAR(32)"> <constraints nullable="false"/> </column> <column name="password" remarks="密码" type="VARCHAR(128)"> <constraints nullable="false"/> </column> <column name="name" remarks="姓名" type="VARCHAR(32)"> <constraints nullable="false"/> </column> </createTable> <addAutoIncrement tableName="user" columnName="id" columnDataType="BIGINT" incrementBy="1" startWith="1"/> <insert tableName="user"> <column name="id" valueNumeric="1"/> <column name="username" value="hero1"/> <column name="password" value="111111"/> <column name="name" value="HERO 1"/> </insert> <insert tableName="user"> <column name="id" valueNumeric="2"/> <column name="username" value="hero2"/> <column name="password" value="222222"/> <column name="name" value="HERO 2"/> </insert> <insert tableName="user"> <column name="id" valueNumeric="3"/> <column name="username" value="hero3"/> <column name="password" value="333333"/> <column name="name" value="HERO 3"/> </insert> </changeSet> </databaseChangeLog>
该文件定义了 user 表并插入了三条数据,user 表只有四个字段:id 主键、用户名、密码、姓名,主键自增长。
重启服务,会自动生成表结构。
3.2 生成实体类
可以通过逆向工程生成,可以复制下面的代码。
com.yygnb.demo.entity.User
/** * 用户表 */ @Schema(title = "用户") @Data @NoArgsConstructor @AllArgsConstructor @Builder public class User implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; @Schema(title = "用户名") @NotNull(message = "不能为空") private String username; @Schema(title = "密码") @NotNull(message = "不能为空") private String password; @Schema(title = "姓名") private String name; }
3.3 Mapper
com.yygnb.demo.mapper.UserMapper
:
public interface UserMapper extends BaseMapper<User> { }
resources/mapper/UserMapper.xml
:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.yygnb.demo.mapper.UserMapper"> </mapper>
到这一步,准备工作就完成了,接下来继续回到 Spring Security。
3.4 PasswordEncoder
Spring Security 中在认证授权时,需要 PasswordEncoder 对象,该对象可以对密码加密。
com.yygnb.demo.config.PasswordEncoderConfig
:
@Configuration public class PasswordEncoderConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
3.5 实现 UserDetailsService 接口
Spring Security 提供了一个接口:UserDetailsService 。该接口只有一个方法 loadUserByUsername,在该方法中就可以查询数据库,根据用户名查询用户信息了。该方法返回 UserDetails。UserDetails 是一个接口,Spring Security 提供了一个实现类 User。
创建类:UserDetailsServiceImpl,实现 UserDetailsService 接口,并添加 @Service
注解。
com.yygnb.demo.service.impl.UserDetailsServiceImpl
:
package com.yygnb.demo.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yygnb.demo.entity.User; import com.yygnb.demo.mapper.UserMapper; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.List; @RequiredArgsConstructor @Service public class UserDetailsServiceImpl implements UserDetailsService { // @Autowired private final PasswordEncoder passwordEncoder; private final UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("username", username); User user = this.userMapper.selectOne(wrapper); if (user == null) { throw new UsernameNotFoundException("根据用户名未查询到用户"); } // 模拟查询权限 List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); return new org.springframework.security.core.userdetails.User( user.getUsername(), passwordEncoder.encode(user.getPassword()), authorities); } }
如果是以前版本的 Spring Security,还需要定义配置继承自 WebSecurityConfigurerAdapter 类,重写 config 方法,并在该方法中设置 UserDetailsService 等。现在的版本不需要这些无聊的操作了。
重启服务,访问测试。
感谢你阅读本文,如果本文给了你一点点帮助或者启发,还请三连支持一下,点赞、关注、收藏,作者会持续与大家分享更多干货