Spring Security实现用户认证三:结合MySql数据库对用户进行认证

1 原理

Spring Security实现用户认证一中说到,请求被过滤器UsernamePasswordAuthenticationFilter处理生成UsernamePasswordAuthenticationToken,实际上这里的token只是临时的,并没有进行认证,需要一个AuthenticationProvider提供认证方式。

如下官方所提供的一张原图清楚说明了后续的认证过程。
在这里插入图片描述
从上图可以看出,UsernamePasswordAuthenticationTokenProviderManager类对在Token匹配AuthenticationProvider

对于采用用户名和密码的认证方式,匹配到的是DaoAuthenticationProvider。进入到DaoAuthenticationProvider,这个类需要UserDetailsServicePasswordEncoder

UserDetailsService里面存储着用户的细节UserDetails,包括用户名、密文密码、权限等信息。PasswordEncoder是用来对密码进行加密的,默认的加密算法是BCryptPasswordEncoder

DaoAuthenticationProvider拿到用户请求中的username和明文password,也就是包裹在UsernamePasswordAuthenticationToken中的字段principalcredentials。如下图所示:
在这里插入图片描述
UserDetailsService里面是密文密码,所以需要应用对应的加密算法将用户的明文密码映射成密文密码。

UserDetailsService首先通过username查找UserDetails,将找到的UserDetails取出密文密码,再对比两个密文密码的一致性去认证用户信息。

下图展示了认证成功后的UsernamePasswordAuthenticationToken,认证成功的Token将会拿到用户的角色信息和授权信息。最终,返回的 UsernamePasswordAuthenticationToken 被设置在 SecurityContextHolder 上。
在这里插入图片描述
讲到这里我们大致知道怎么修改了。我们需要一个读取UserDetails的一个UserDetailsService,以及一个密码编码方法PasswordEncoder

2 基于内存的认证(默认方式)

其他配置请参考往期内容。

在不进行任何配置条件下,系统会默认生成一个username为user,密码随机的用户(在控制台打印)。
如果想要修改该默认用户的用户名和密码。可以在application.yml文件中添加如下内容:

spring:
  security:
    user:
      name: user
      password: 1234

这是就可以用user和1234进行登录。

下面请看如何添加新的用户到内存中。

2.1 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.2 WebSecurityConfig配置类添加配置

下面配置了一个基于内存的UserDetailsService,并且向其中添加了一个用户(root,root),该用户角色定义为USER。重新启动服务器可以使用该用户登录。这时系统默认的用户将会失效。

这里拥有我们所需要的两个条件UserDetailsServicePasswordEncoderwithDefaultPasswordEncoder会采用系统默认的密码编码器。

package com.song.cloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity  //开启SpringSecurity自动配置(springboot中可以省略)
public class WebSecurityConfig {
    //为存储在内存中的基于用户名/密码的认证提供支持。
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withDefaultPasswordEncoder().username("root").password("root").roles("USER").build());
        return manager;
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // ...
    }
}

上面的配置变可以启动服务器测试,输入(root,root)可以进行登录。

3 为下一步准备数据源

MySql 8.0.35,druid, mybatis

3.1 依赖

<!-- 数据库依赖 -->
<dependency>
    <groupId>javax.persistence</groupId>
    <artifactId>persistence-api</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

3.2 创建表users和authorities

分别用于存放用户和权限。

create table users
(
    username varchar(50) not null primary key,
    password varchar(500) not null,
    enabled boolean not null
);

create table authorities
(
    username varchar(50) not null,
    authority varchar(50) not null,
    constraint fk_authorities_users foreign key (username) references users (username)
);
create unique index ix_auth_username on authorities (username, authority);

3.3 配置DruidDataSource数据源

application.yml配置

spring:
  application:
    name: spring-security
  security:
    user:
      name: user
      password: 1234

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    username: root  # 修改成自己的数据库username和password
    password: root
    # 请把test_db修改成自己数据库名字
    url: jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8&useSSL=false&serverTimeZone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true

server:
  port: 4555

logging:
  level:
    web: debug

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.song.cloud.entities
  configuration:
    map-underscore-to-camel-case: true

下面请根据实际情况修改,会用mybatis-generator插件的请自行生成。不会请查阅其他教程。

3.4 创建实体类User

package com.song.cloud.entities;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 表名:t_users_test
*/
@Table(name = "t_users_test")
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    /**
     * id
     */
    @Id
    @GeneratedValue(generator = "JDBC")
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码hash
     */
    @Column(name = "password_hash")
    private String passwordHash;

    /**
     * 是否启用
     */
    private Boolean enable;
}

4 基于JDBC的认证

4.1 使用系统自带的JdbcUserDetailsManager

实现的JdbcUserDetailsManager类已经封装了默认的增删改查的sql语句,但是事实上这样做非常麻烦,也不符合编程习惯,虽然官方提供了对语句的修改功能。

依赖

<!-- spring security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

对WebSecurityConfig添加配置UserDetailsService

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity  //开启SpringSecurity自动配置(springboot中可以省略)
public class WebSecurityConfig {
	@Bean
	public JdbcUserDetailsManager jdbcUserDetailsManager(DataSource dataSource){
	    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
		// ----------------------- 向数据库添加用户和权限 ----------------
	    UserDetails user = User.withDefaultPasswordEncoder().username("user").password("user").roles("USER").build();
	    UserDetails admin = User.withDefaultPasswordEncoder().username("admin").password("admin").roles("ADMIN", "USER").build();
	
	    manager.createUser(user);
	    manager.createUser(admin);
	    // -----------------------结束向数据库添加用户和权限 ----------------
	    return manager;
	}
	
	// ...
}

现在,可以使用这两个用户进行登录。

在服务器启动时,这两个用户便被写进数据库中。下次启动需要注释向数据库添加用户和权限的这些,或者将数据库中的users和authorities两个表数据删掉,否则会重复键值而报错, 先删authorities的数据

定义UserController

@PostMapping("/user/add")
public UserDetails addUser(@RequestBody User user) {
    System.out.println(user);

    PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String pd_encode = delegatingPasswordEncoder.encode(user.getPasswordHash());
    UserDetails userDetails = org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(pd_encode)
            .roles("USER") //自己定义
            .disabled(!user.getEnable())
            .build();

    jdbcUserDetailsManager.createUser(userDetails);
    return userDetails;
}

使用swagger3测试

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>

测试地址:http://localhost:port/swagger-ui/index.html 改成项目端口号

进入测试地址前,需要使用之前添加进入db的账户登录。
在这里插入图片描述
测试数据:

{
  "username": "test",
  "passwordHash": "test",
  "enable": true
}

在这里插入图片描述

在这里插入图片描述
测试登录:
在这里插入图片描述

4.2 自定义MyJdbcUserDetailsManager

怎么更加随心所欲的定制一下。

建议使用@Service 注解形式注册,由于需要使用dao层的服务,采用自动注入的形式必须使用@Service 标识为bean,交给spring IoC管理,才能实现自动注入dao层服务。

import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import tk.mybatis.mapper.entity.Example;

@Service  //建议使用@Service 注解形式注册
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {

    @Resource
    private UserMapper userMapper;  // 自定义的dao层

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        return null;
    }

    @Override
    public void createUser(UserDetails userDetails) {
        User user = new User();
        user.setUsername(userDetails.getUsername());
        user.setPasswordHash(userDetails.getPassword());
        userMapper.insertSelective(user);
    }

    @Override
    public void updateUser(UserDetails userDetails) {
		// 自己定义
    }

    @Override
    public void deleteUser(String username) {
		// 自己定义
    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {
		// 自己定义
    }

    @Override
    public boolean userExists(String username) {
	    // 自己定义
        return false;
    }

    /**
     * 从数据库中获取用户信息,继续引入持久层,UserMapper
     *
     * @param username the username identifying the user whose data is required.
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Example selectByUserId = new Example(User.class);
        Example.Criteria criteria = selectByUserId.createCriteria();
        criteria.andEqualTo("username", username);

        User user = userMapper.selectOneByExample(selectByUserId);

        if (user == null) {
            throw new UsernameNotFoundException(username);
        }

        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(() -> "USER_LIST");
        authorities.add(() -> "USER_ADD");

        return org.springframework.security.core.userdetails.User
                .withUsername(user.getUsername())
                .password(user.getPasswordHash())
                .disabled(false)
                .credentialsExpired(false)
                .accountLocked(false)
                .roles("ADMIN")
                .build();
    }
}
  • 31
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不当菜虚困

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值