6-SpringSecurity:数据库存储用户信息

背景

本系列教程,是作为团队内部的培训资料准备的。主要以实验的方式来体验 SpringSecurity 的各项Feature。

之前涉及到的用户信息都是存在内存中的,显然,这种方法用于测试或演示还可以,实际中的应用场景肯定要求从数据库中读取的。

新建一个 SpringBoot 项目,起名 springboot-security-db ,核心依赖为 Web , SpringSecurity , ThymeleafMyBatis

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
            <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.1</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

实验0:官方默认的用户权限表

SpringSecurity 官方文档提供了默认的 User Schema ,比较简单,直接是用户及其权限关系,这里不作演示;

2020-12-24-ddl.png

实验1:自定义用户权限表

  • 首先看下表结构,是实际应用中相对比较通用的,共5张表,遵循了RBAC的模式:三个抽象实体(用户、角色、权限)表,两张关系表(用户-角色,角色-权限),即:

user, user-role, role, role-permission, permission

CREATE DATABASE IF NOT EXISTS `spring_security` ;
USE `spring_security` ;

CREATE TABLE IF NOT EXISTS `t_user` (
`id` bigint(20) NOT NULL COMMENT '用户id',
`username` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`realname` varchar(255) NOT NULL COMMENT '真实姓名',
`mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
`enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
`accountNonExpired` tinyint(1) NOT NULL DEFAULT '1',
`accountNonLocked` tinyint(1) NOT NULL DEFAULT '1',
`credentialsNonExpired` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY ( `id` ) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

CREATE TABLE IF NOT EXISTS `t_user_role` (
`user_id` varchar(32) NOT NULL,
`role_id` varchar(32) NOT NULL,
`create_time` datetime DEFAULT NULL,
`creator` varchar(255) DEFAULT NULL,
  PRIMARY KEY ( `user_id` , `role_id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `t_role` (
`id` varchar(32) NOT NULL,
`role_name` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY ( `id` ),
  UNIQUE KEY `unique_role_name` ( `role_name` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `t_role_permission` (
`role_id` varchar(32) NOT NULL,
`permission_id` varchar(32) NOT NULL,
  PRIMARY KEY ( `role_id` , `permission_id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `t_permission` (
`id` varchar(32) NOT NULL,
`code` varchar(32) NOT NULL COMMENT '权限标识',
`description` varchar(64) DEFAULT NULL COMMENT '描述',
`url` varchar(128) DEFAULT NULL COMMENT '请求地址',
  PRIMARY KEY ( `id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 基本配置
server:
  port: 8080
spring:
  thymeleaf:
    cache: false
  datasource:
    url: jdbc:mysql://localhost:3306/spring_security?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  • 从以前的内存用户换为从数据库中读取用户

这里我们实现 UserDetailsService 接口,重写 loadUserByUsername(String username) 方法,基本逻辑:查询用户,及联表查询对应的权限。

@Component
public class CustomUserDetailsService implements UserDetailsService {
  @Autowired
  private UserMapper userMapper;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    UserDto user = userMapper.getUserByUsername(username);
    if (user != null) {
      List<PermissionDto> permissions = userMapper.getPermissionsByUsername(username);
      if (permissions != null) {
        System.out.println(user.getUsername() + " has these permissions: " + permissions);
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        permissions.stream().forEach(p -> authorities.add(new SimpleGrantedAuthority(p.getCode())));
        // user.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("p1"))); // hard-coded permission
        user.setAuthorities(authorities);
      }
    }
    return user;
  }
}

相关查询接口:

public interface PermissionMapper {
  @Select("SELECT * FROM t_permission")
  List<PermissionDto> getAllPermissions();
}

public interface UserMapper {
    @Select("SELECT * FROM t_user WHERE username = #{username}")
    UserDto getUserByUsername(@Param("username") String username);

    /*
    - SELECT p.* FROM t_permission p LEFT JOIN t_role_permission rp ON p.id = rp.permission_id
    LEFT JOIN t_user_role ur ON rp.role_id = ur.role_id
    LEFT JOIN t_user u ON ur.user_id = u.id
    WHERE u.username = "test";
    - */

    @Select("SELECT p.* FROM t_permission p LEFT JOIN t_role_permission rp ON p.id = rp.permission_id LEFT JOIN t_user_role ur ON rp.role_id = ur.role_id LEFT JOIN t_user u ON ur.user_id = u.id WHERE u.username = #{username};")
    List<PermissionDto> getPermissionsByUsername(@Param("username") String username);
}

将用户数据源配置为从数据库中查询:

@Autowired
CustomUserDetailsService customUserDetailsService;

@Bean
public PasswordEncoder passwordEncoder () {
    return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // Method1:
    // There is no PasswordEncoder mapped for the id "null"
    // PasswordEncoder encoder = new BCryptPasswordEncoder();        
    // String yourPassword = "123";
    // System.out.println("Encoded password: " + encoder.encode(yourPassword));
    // auth.userDetailsService(customUserDetailsService).passwordEncoder(encoder);

    auth.userDetailsService(customUserDetailsService);
}
  • 从以前的硬编码的权限控制换为动态配置每个资源的权限
@Override
protected void configure(HttpSecurity http) throws Exception {
    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http
    .authorizeRequests();
    
    List<PermissionDto> permissions = permissionMapper.getAllPermissions();

    for (PermissionDto permission : permissions) {
        authorizeRequests.antMatchers(permission.getUrl()).hasAuthority(permission.getCode());
    }
    authorizeRequests
            .antMatchers("/user/**").authenticated()
            .anyRequest().permitAll() // Let other request pass
            .and()
            .csrf().disable() // turn off csrf, or will be 403 forbidden
            .formLogin() // Support form and HTTPBasic
            .loginPage("/login")
            .successForwardUrl("/greeting")// custom login success page, a POST request
            .failureHandler(failureHandler)
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login?logout");
}

关键的改动:

List<PermissionDto> permissions = permissionMapper.getAllPermissions();

for (PermissionDto permission : permissions) {
    authorizeRequests.antMatchers(permission.getUrl()).hasAuthority(permission.getCode());
}

至此,便实现了从数据库中读取用户权限信息,符合实际的应用场景,并借助SpringSecurity实现动态的权限拦截配置,之后可进行以前的实验:

  • 在数据库中添加两个用户:

    • dev用户具有dev与test角色;
    • test用户仅具有test角色;
  • 配置资源授权:

    • /user/add 需要有dev角色才可访问;
    • /user/query 需要有test角色才可访问;

Reference


If you have any questions or any bugs are found, please feel free to contact me.
Your comments and suggestions are welcome!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Security基于数据库认证是通过将用户信息存储数据库中进行认证的一种方式。首先,我们需要根据Spring Security框架的定义要求,创建相关的数据表来存储用户信息[2]。接下来,我们可以使用org.springframework.security.core.userdetails.UserDetailsService包路径下的接口进行用户信息的获取和认证。通过实现这个接口,并在其中编写逻辑代码,我们可以从数据库中获取用户信息,并进行相应的认证操作。在数据库存储用户信息的方式可以是明文存储、加密存储或者使用其他安全方式进行存储。通过这种基于数据库的认证方式,Spring Security可以实现灵活可靠的用户认证功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Spring Security——基于数据库用户信息认证](https://blog.csdn.net/qq_40151840/article/details/104620157)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [SpringSecurity 基于数据库的验证](https://blog.csdn.net/qq_41723615/article/details/89512333)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Heartsuit

别说什么鼓励,这就是互联网乞讨

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

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

打赏作者

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

抵扣说明:

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

余额充值