文章就使用 Spring Security 实现用户认证和授权。 在前面 SpringBoot 2.7.2 的系列文章中,已经创建了几个 computer 相关的接口,这些接口直接通过 Spring Doc 或 POSTMAN 就可以访问。例如:
GET http://localhost:9099/computer/1访问该服务可以获取 id 为 1 的电脑详情。
接下来的文章就使用 Spring Security 实现用户认证和授权。
Spring Boot 对 Spring Security 非常友好,它已经管理了 Spring Security 的依赖版本,并且提供 starter 的方式进行整合:
<!-- Spring Security --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>添加依赖后重新启动服务,可以在控制台看到输出:

在浏览器中访问该服务,会自动跳转到登录页面,该页面是 Spring Security 默认提供的。浏览器中地址栏自动跳转到:
http://localhost:9099/login。
在登录页面,用户名填写 user,密码填写上面控制台中输出的密码,点击“Sign in”,便会进入系统,显示电脑详情。
咱们使用的 Spring Boot 版本是 2.7.2,对应的 Spring Security 版本为 5.7.2,从 5.7 开始,Spring Security 的使用有一些改变,其中之一就是配置 Security 时不推荐继承 WebSecurityConfigurerAdapter 类。
上面的 demo 中使用的用户名是 user,密码是在启动中生成的,这肯定不符合开发的需求。配置用户名密码有三种方式:
前面两者在 demo 中玩玩就行。
这种方式比较简单,直接在 application.yml 中配置即可:
spring: profiles: active: @env@ security: user: name: hero1 password: '111111' roles: 'admin'删除上面的 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;@Configurationpublic 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 进行测试。
这种方式也是写死的用户名密码,在实际开发中不会使用的。所以,先删除这个文件,然后再继续后面的学习。
在开始这个部分之前,请确保删除了上面编写的 SecurityConfig。
首先需要定义表结构。
由于我这里是基于前面 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 主键、用户名、密码、姓名,主键自增长。
重启服务,会自动生成表结构。
可以通过逆向工程生成,可以复制下面的代码。
com.yygnb.demo.entity.User
/** * 用户表 */@Schema(title = "用户")@Data@NoArgsConstructor@AllArgsConstructor@Builderpublic 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;}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。
Spring Security 中在认证授权时,需要 PasswordEncoder 对象,该对象可以对密码加密。
com.yygnb.demo.config.PasswordEncoderConfig:
@Configurationpublic class PasswordEncoderConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}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@Servicepublic 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 等。现在的版本不需要这些无聊的操作了。
重启服务,访问测试。
感谢你阅读本文,如果本文给了你一点点帮助或者启发,还请三连支持一下,点赞、关注、收藏,作者会持续与大家分享更多干货