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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 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 ,比较简单,直接是用户及其权限关系,这里不作演示;

image.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角色才可访问;


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
0
0
8
分享
相关文章
农业信息|基于SSM+vue的农业信息管理系统的设计与实现(源码+数据库+文档)
农业信息|基于SSM+vue的农业信息管理系统的设计与实现(源码+数据库+文档)
135 1
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
这篇文章介绍了如何在IDEA和Spring Boot中使用AOP技术实现日志信息的记录到数据库的详细步骤和代码示例。
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
springboot整合springsecurity,从数据库中认证
本文介绍了如何在SpringBoot应用中整合Spring Security,并从数据库中进行用户认证的完整步骤,包括依赖配置、数据库表创建、用户实体和仓库接口、用户详情服务类、安全配置类、控制器类以及数据库初始化器的实现。
363 3
springboot整合springsecurity,从数据库中认证
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
1283 2
PbootCMS提示错误信息“未检测到您服务器环境的sqlite3数据库扩展...”
PbootCMS提示错误信息“未检测到您服务器环境的sqlite3数据库扩展...”
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
本文提供了在数据库中对密码等敏感信息进行加盐加密的详细教程,包括手写MD5加密算法和使用Spring Security的BCryptPasswordEncoder进行加密,并强调了使用BCryptPasswordEncoder时需要注意的Spring Security配置问题。
278 0
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
294 5
jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上
该博客文章介绍了在JSP应用中使用Servlet查询SQL Server数据库的表信息,并通过JavaBean封装图书信息,将查询结果展示在Web页面上的方法。
jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等