07 - spring security基于数据库的账号密码

spring security基于数据库的账号密码

文档

  1. 00 - spring security框架使用
  2. 01 - spring security自定义登录页面
  3. 02 - spring security基于配置文件及内存的账号密码
  4. 03 - spring security自定义登出页面
  5. 04 - spring security关闭csrf攻击防御
  6. 05 - spring security权限控制
  7. 06 - spring security角色和权限设置

基于数据库的账号密码

调整配置
  1. pom.xml文件添加依赖,这里使用mybatis-plus

    xml 复制代码
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>3.5.12</version>
    </dependency>
  2. 调整applicaiton.yml

    yaml 复制代码
    server:
      port: 8080
      servlet:
        context-path: /springsecurity03
    
    spring:
      security:
        user:
          name: admin
          password: 123456
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/important?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
      jackson:
        time-zone: GMT+8
    
    mybatis-plus:
      mapper-locations: classpath:mybatis/mapper/*.xml
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  3. 添加实体类SpringSecurityUser.class

    java 复制代码
    package xin.yangshuai.springsecurity03.entity;
    
    import com.baomidou.mybatisplus.annotation.TableName;
    
    import java.io.Serializable;
    
    @TableName("`spring_security_user`")
    public class SpringSecurityUser implements Serializable {
        /**
         * 主键
         */
        private Integer id;
    
        /**
         * 用户名
         */
        private String username;
    
        /**
         * 密码
         */
        private String password;
    
        private static final long serialVersionUID = 1L;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username == null ? null : username.trim();
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password == null ? null : password.trim();
        }
    }
  4. 创建mapperSpringSecurityUserMapper.java

    java 复制代码
    package xin.yangshuai.springsecurity03.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import xin.yangshuai.springsecurity03.entity.SpringSecurityUser;
    
    public interface SpringSecurityUserMapper extends BaseMapper<SpringSecurityUser> {
    }
  5. 启动类添加mybatis-plus包扫描

    java 复制代码
    package xin.yangshuai.springsecurity03;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan(basePackages = {"xin.yangshuai.springsecurity03.mapper"})
    public class Springsecurity03Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Springsecurity03Application.class, args);
        }
    
    }
配置基于数据库的账号密码
  1. 在基于内存的账号密码时,我们定义了一个UserDetailsService类型的Bean

    java 复制代码
    @Configuration
    // @EnableWebSecurity
    @EnableMethodSecurity //开启基于方法的授权
    public class WebSecurityConfig {
    
        @Bean
        public UserDetailsService userDetailsService() {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            // 此时配置文件中的用户名和密码将不可用
            manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").authorities("USER_LIST").build());
            return manager;
        }
        
        // ...
    }
  2. spring security框架在认证的时候就会调用该类的loadUserByUsername方法

  3. 使用基于数据库的账号密码时,需要要将上面的Bean注释掉

  4. 自定义一个类DatabaseUserDetailsManager.java,实现UserDetailsManager接口,并接受Spring管理,同样的,spring security框架在认证的时候就会调用该类的loadUserByUsername方法

    复制代码
    package xin.yangshuai.springsecurity03.config;
    
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.provisioning.UserDetailsManager;
    import org.springframework.stereotype.Component;
    import xin.yangshuai.springsecurity03.entity.SpringSecurityUser;
    import xin.yangshuai.springsecurity03.mapper.SpringSecurityUserMapper;
    
    @Component
    public class DatabaseUserDetailsManager implements UserDetailsManager {
    
        @Autowired
        private SpringSecurityUserMapper springSecurityUserMapper;
    
        @Override
        public void createUser(UserDetails user) {
    
        }
    
        @Override
        public void updateUser(UserDetails user) {
    
        }
    
        @Override
        public void deleteUser(String username) {
    
        }
    
        @Override
        public void changePassword(String oldPassword, String newPassword) {
    
        }
    
        @Override
        public boolean userExists(String username) {
            return false;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            SpringSecurityUser springSecurityUser1 = springSecurityUserMapper.selectById("123");
            System.out.println(springSecurityUser1);
    
            LambdaQueryWrapper<SpringSecurityUser> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SpringSecurityUser::getUsername, username);
            SpringSecurityUser springSecurityUser = springSecurityUserMapper.selectOne(queryWrapper);
    
            if (springSecurityUser == null) {
                throw new UsernameNotFoundException(username);
            } else {
                UserDetails user = User.builder()
                        .username(springSecurityUser.getUsername())
                        .password(springSecurityUser.getPassword())
                        // 这里示例,权限手动添加
                        .authorities("USER_ADD", "USER_UPDATE", "USER_LIST").build();
                return user;
            }
        }
    }
添加用户
  1. 创建serviceSpringSecurityUserService.java

    java 复制代码
    package xin.yangshuai.springsecurity03.service;
    
    import xin.yangshuai.springsecurity03.entity.SpringSecurityUser;
    
    import java.util.List;
    
    public interface SpringSecurityUserService {
    
        List<SpringSecurityUser> list();
    
        int add(SpringSecurityUser springSecurityUser);
    }
  2. 创建service实现类SpringSecurityUserServiceImpl.java

    java 复制代码
    package xin.yangshuai.springsecurity03.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
    import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    import xin.yangshuai.springsecurity03.entity.SpringSecurityUser;
    import xin.yangshuai.springsecurity03.mapper.SpringSecurityUserMapper;
    import xin.yangshuai.springsecurity03.service.SpringSecurityUserService;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Service
    public class SpringSecurityUserServiceImpl implements SpringSecurityUserService {
    
        @Autowired
        private SpringSecurityUserMapper springSecurityUserMapper;
    
        @Override
        public List<SpringSecurityUser> list() {
            return springSecurityUserMapper.selectList(null);
        }
    
        @Override
        public int add(SpringSecurityUser springSecurityUser) {
    
            if (springSecurityUser == null) {
                return 0;
            }
    
            // org.springframework.security.crypto.factory.PasswordEncoderFactories
    
            String encodingId = "bcrypt";
            Map<String, PasswordEncoder> encoders = new HashMap();
            encoders.put(encodingId, new BCryptPasswordEncoder());
            encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
            // ...
    
            encodingId = "MD5";
            PasswordEncoder encoder = new DelegatingPasswordEncoder(encodingId, encoders);
    
    
            String password = springSecurityUser.getPassword();
            String encode = encoder.encode(password);
            springSecurityUser.setPassword(encode);
    
            return springSecurityUserMapper.insert(springSecurityUser);
        }
    }
  3. 修改SpringSecurityUserController.java

    java 复制代码
    package xin.yangshuai.springsecurity03.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.web.bind.annotation.*;
    import xin.yangshuai.common01.entity.BaseResult;
    import xin.yangshuai.springsecurity03.entity.SpringSecurityUser;
    import xin.yangshuai.springsecurity03.service.SpringSecurityUserService;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("user")
    public class SpringSecurityUserController {
    
        @Autowired
        private SpringSecurityUserService springSecurityUserService;
    
        @GetMapping("list")
        @PreAuthorize("hasAuthority('USER_LIST')")
        public BaseResult<List<SpringSecurityUser>> list() {
    
            List<SpringSecurityUser> list = springSecurityUserService.list();
    
            BaseResult<List<SpringSecurityUser>> result = new BaseResult<>();
            result.setCode("200");
            result.setData(list);
            return result;
        }
    
        @PostMapping("add")
        public BaseResult<Integer> add(@RequestBody SpringSecurityUser springSecurityUser) {
    
            int rows = springSecurityUserService.add(springSecurityUser);
    
            BaseResult<Integer> result = new BaseResult<>();
            result.setCode("200");
            result.setData(rows);
            return result;
        }
    }
  4. 为避免麻烦,关闭csrf攻击防御,对user/add接口设置免认证,编辑WebSecurityConfig.java

    java 复制代码
    package xin.yangshuai.springsecurity03.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.Customizer;
    import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configurers.*;
    import org.springframework.security.web.SecurityFilterChain;
    
    @Configuration
    // @EnableWebSecurity
    @EnableMethodSecurity //开启基于方法的授权
    public class WebSecurityConfig {
    
    //    @Bean
    //    public UserDetailsService userDetailsService() {
    //        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    //        // 此时配置文件中的用户名和密码将不可用
    //        manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").authorities("USER_LIST").build());
    //        return manager;
    //    }
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    
            // 开启授权保护
            http.authorizeRequests(new Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>() {
                @Override
                public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry) {
    
                    // 具有USER_LIST权限的用户可以访问/user/list
                    // expressionInterceptUrlRegistry.requestMatchers("/user/list").hasAuthority("USER_LIST");
    
                    expressionInterceptUrlRegistry
                            .requestMatchers("/user/add").permitAll()
                            // 对所有请求开启授权保护
                            .anyRequest()
                            // 已经认证的请求会被自动授权
                            .authenticated();
                }
            });
    
            // 自定义登录页面
    
            // 自定义登出页面
    
            // 自定义拒绝访问页面
    
            // 关闭csrf攻击防御
            http.csrf(new Customizer<CsrfConfigurer<HttpSecurity>>() {
                @Override
                public void customize(CsrfConfigurer<HttpSecurity> httpSecurityCsrfConfigurer) {
                    httpSecurityCsrfConfigurer.disable();
                }
            });
    
            return http.build();
        }
    }
  5. 这里有个细节,spring security进行账号认证的时候,系统内的用户信息,也就是UserDetails对象的密码需要传入经过某种算法加密后的值,比如:bcrypt算法,具体参考org.springframework.security.crypto.password.DelegatingPasswordEncoder#matches

  6. org.springframework.security.crypto.factory.PasswordEncoderFactories类中,列举了加密算法

  7. 在使用基于内存的账号密码时,构造UserDetails对象时,使用的.withDefaultPasswordEncoder()方法,会将密码进行加密,使用的算法就是bcrypt

  8. 在使用基于数据库的账号密码时,构造UserDetails对象时,并没有使用.withDefaultPasswordEncoder()方法,使用的是.builder()方法,这个方法并不会对原始密码加密

  9. 基于以上,可以将加密后的密码保存在数据库里,这样也能避免数据库中存储用户的原始密码

相关推荐
你我约定有三3 小时前
RabbitMQ--消费端异常处理与 Spring Retry
spring·rabbitmq·java-rabbitmq
Java水解5 小时前
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
后端·spring
杨杨杨大侠6 小时前
第3篇:配置管理的艺术 - 让框架更灵活
java·spring·log4j
Java码农田7 小时前
springmvc源码分析全体流程图
spring·源码
做一位快乐的码农9 小时前
房屋装修设计管理系统的设计与实现/房屋装修管理系统
java·struts·spring·eclipse·tomcat·maven
麦兜*10 小时前
【Prometheus】 + Grafana构建【Redis】智能监控告警体系
java·spring boot·redis·spring·spring cloud·grafana·prometheus
孟婆来包棒棒糖~16 小时前
Maven快速入门
java·spring boot·spring·maven·intellij-idea
知其然亦知其所以然1 天前
SpringAI:Mistral AI 聊天?一文带你跑通!
后端·spring·openai
Warren981 天前
Spring Boot 整合网易163邮箱发送邮件实现找回密码功能
数据库·vue.js·spring boot·redis·后端·python·spring