目录
  • 流程
  • 数据表设计
  • 创建项目
    • application.yml文件配置
    • 创建三层架构
    • 实现login功能
      • 创建UserDetailsService接口实现类
      • 创建UserDetails接口实现类
    • 关联
      • 密码
      • SpringSecurity配置
        • AuthenticationManager

          需求:针对公司员工,普通用户等各类型用户,将其分别存储在不同的用户表中,基于SpeingSecurity实现用户认证,也就是登录功能

          流程

          • 首先做数据库设计
          • 基于SpringBoot创建一个项目
          • 项目中做相关的实现
          • 通过apifox接口测试工具进行测试
          • 分别测试不同用户的登录方法,是否调用了对应的登录逻辑【登录也称为认证】

          注意:权限这块并没有涉及,仅仅是用户数据这块

          数据表设计

          本文先不涉及权限,表设计就是两张用户表

          员工表

          CREATE TABLE `ums_sys_user` (
            `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
            `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户账号',
            `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称',
            `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '用户邮箱',
            `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '手机号码',
            `sex` int DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
            `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '头像地址',
            `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '密码',
            `status` int DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
            `creator` bigint DEFAULT '1' COMMENT '创建者',
            `create_time` datetime DEFAULT NULL COMMENT '创建时间',
            `updater` bigint DEFAULT '1' COMMENT '更新者',
            `update_time` datetime DEFAULT NULL COMMENT '更新时间',
            `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
            `deleted` tinyint DEFAULT '0',
            PRIMARY KEY (`id`) USING BTREE
          ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='后台用户表';
          

          客户表

          CREATE TABLE `ums_site_user` (
            `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
            `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户账号',
            `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称',
            `openid` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '微信openid',
            `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '用户邮箱',
            `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '手机号码',
            `sex` int DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
            `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '头像地址',
            `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '密码',
            `status` int DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
            `create_time` datetime DEFAULT NULL COMMENT '创建时间',
            `updater` bigint DEFAULT '1' COMMENT '更新者',
            `update_time` datetime DEFAULT NULL COMMENT '更新时间',
            `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
            `deleted` tinyint DEFAULT '0',
            PRIMARY KEY (`id`) USING BTREE
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='外部用户表';
          

          创建项目

          登录功能,使用非常简单的三层架构,技术选型有:

          • SpringBoot 3.1.X
          • SpringSecurity 6.1.X
          • Mybatis Plus
          • lombok【简化实体类】,可以通过注解生成getter、setter方法,构造方法,toString方法等
          • maven

          pom文件

          <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>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>8.0.33</version>
              </dependency>
              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
                  <version>3.5.3.2</version>
              </dependency>
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
              </dependency>
          </dependencies>
          

          application.yml文件配置

          spring:
            datasource:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
              username: root
              password: stt123456
          

          创建三层架构

          Controller

          @RestController
          @RequestMapping("/auth")
          public class AuthController {
          
              private final ISysUserService sysUserService;
              private final ISiteUserService siteUserService;
          
              public AuthController(ISysUserService sysUserService, ISiteUserService siteUserService) {
                  this.sysUserService = sysUserService;
                  this.siteUserService = siteUserService;
              }
          
              /**
               * 后端管理系统登录
               * 返回值:token
               */
              @PostMapping("sys_login")
              public String sysLogin(@RequestBody LoginParam loginParam) {
          
                  return "后台用户登录======》" +sysUserService.sysLogin(loginParam);
              }
          
              @PostMapping("site_login")
              public String siteLogin(@RequestBody LoginParam loginParam) {
          
                  return "APP用户登录======》" + siteUserService.siteLogin(loginParam);
              }
          
          }
          

          SysUserServiceImpl

          @Service
          @Slf4j
          public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
          
              @Autowired
              @Qualifier("sysUserAuthenticationManager")
              private AuthenticationManager authenticationManager;
          
              /**
               * 登录是SpringSecurity实现的,我们就是去告诉SpringSecurity现在要登录
               * SpringSecirity登录是通过 AuthticationManager 实现的
               * 将AuthticationManager引入到service中,调用他的认证方法就可以了
               * @param loginParam
               * @return
               */
              @Override
              public String sysLogin(LoginParam loginParam) {
                  // 通过authenticationManager 的认证方法实现登录,该方法需要传入 Authentication 对象 就是一个认证对象
                  // Authenticationl里边存储的就是用户的认证信息,权限,用户名,密码的等信息,其实就是loadUserByUsername方法返回的UserDetails
                  UsernamePasswordAuthenticationToken authenticationToken =
                          new UsernamePasswordAuthenticationToken(loginParam.getUsername(), loginParam.getPassword());
          
                  Authentication authenticate = authenticationManager.authenticate(authenticationToken);
                  // 获取用户信息
                  SysUser sysUser = (SysUser) authenticate.getPrincipal();
                  log.info("sysUser==========》{}",sysUser);
                  // 返回的是token
                  return sysUser.getUsername();
              }
          }
          

          SiteUserServiceImpl

          @Service
          @Slf4j
          public class SiteUserServiceImpl extends ServiceImpl<SiteUserMapper, SiteUser> implements ISiteUserService {
          
              /**
               * 将AuthenticationManager注入
               */
              @Autowired
              @Qualifier("siteUserAuthenticationManager")
              private AuthenticationManager authenticationManager;
          
              @Override
              public String siteLogin(LoginParam loginParam) {
                  UsernamePasswordAuthenticationToken authenticationToken =
                          new UsernamePasswordAuthenticationToken(loginParam.getMobile(), loginParam.getPassword());
                  Authentication authenticate = authenticationManager.authenticate(authenticationToken);
                  // 强转为用户类型
                  SiteUser siteUser = (SiteUser) authenticate.getPrincipal();
                  log.info("siteUser===========>{}",siteUser);
                  return siteUser.getUsername();
              }
          }
          

          实现login功能

          项目中引入SpringSecurity,SpringSecurity在实现用户登录【认证】时需要使用到两个接口

          • UserDetailsService:是一个接口 ,提供了一个方法loadUserByUsername();
          • UserDetails:是一个接口,用来存储用户权限,状态【是否禁用,超时等】

          通过UserDetailsService查询用户,将用户信息放到UserDetails中,剩下的就交给SpringSecurity的AuthenticationManager做判断,判断用户是否允许登录

          分两步走

          创建UserDetailsService接口实现类

          查询用户,分别为客户和用后台系统用户创建对应的查询用户的实现类

          // 系统用户的DetailsService
          @Service
          public class SysUserDetailsService implements UserDetailsService {
          
              private final SysUserMapper sysUserMapper;
          
              public SysUserDetailsService(SysUserMapper sysUserMapper) {
                  this.sysUserMapper = sysUserMapper;
              }
          
              /**
               * 此方法从数据库中查询用户
               * 返回一个 UserDetails
               */
              @Override
              public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                  log.info("后台系统用户登录=============》");
                  // 根据用户名查询用户
                  SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
                  // 有权限的话,需要查询该用户对应的权限
                  if(sysUser == null) {
                      throw new UsernameNotFoundException("用户或密码不正确");
                  }
                  return sysUser;
              }
          }
          // APP用户的DetailsService
          @Slf4j
          public class SiteUserDetailsService implements UserDetailsService {
          
              private final SiteUserMapper siteUserMapper;
          
              public SiteUserDetailsService(SiteUserMapper siteUserMapper) {
                  this.siteUserMapper = siteUserMapper;
              }
          
              @Override
              public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
                  log.info("APP用户登录===================》");
                  SiteUser siteUser = siteUserMapper.selectOne(new LambdaQueryWrapper<SiteUser>().eq(SiteUser::getMobile, mobile));
                  if(siteUser == null) {
                      throw new UsernameNotFoundException("用户名或密码错误!");
                  }
                  return siteUser;
              }
          }
          

          创建UserDetails接口实现类

          存储用户信息,同样的创建两个实现类,存储不同的用户信息,再实体类上直接修改

          // 后台管理系统用户类
          @TableName("ums_sys_user")
          @Data
          public class SysUser implements Serializable, UserDetails {
          
              private Long id;
              private String username;
              private String nickname;
              private String email;
              private String mobile;
              private Integer sex;
              private String avatar;
              @JsonIgnore
              private String password;
              private Integer status;
              private Long creator;
              private Long updater;
              private String remark;
              @TableLogic
              private Integer deleted;
          
              private LocalDateTime createTime;
              private LocalDateTime updateTime;
          
              @Override
              public Collection<? extends GrantedAuthority> getAuthorities() {
                  return null;
              }
          
              @Override
              public boolean isAccountNonExpired() {
                  return true;
              }
          
              @Override
              public boolean isAccountNonLocked() {
                  return true;
              }
          
              @Override
              public boolean isCredentialsNonExpired() {
                  return true;
              }
          
              @Override
              public boolean isEnabled() {
                  return true;
              }
          }
          // APP用户实体类
          @Data
          @TableName("ums_site_user")
          public class SiteUser implements Serializable, UserDetails {
          
              private Long id;
              private String username;
              private String nickname;
              private String openid;
              private String email;
              private String mobile;
              private Integer sex;
              private String avatar;
              @JsonIgnore
              private String password;
              private Integer status;
              private Long updater;
              private String remark;
              @TableLogic
              private Integer deleted;
          
              private LocalDateTime createTime;
              private LocalDateTime updateTime;
          
              /**
               * 权限。现在并没有查询权限
               * @return
               */
              @Override
              public Collection<? extends GrantedAuthority> getAuthorities() {
                  return null;
              }
          
              @Override
              public boolean isAccountNonExpired() {
                  return true;
              }
          
              @Override
              public boolean isAccountNonLocked() {
                  return true;
              }
          
              @Override
              public boolean isCredentialsNonExpired() {
                  return true;
              }
          
              @Override
              public boolean isEnabled() {
                  return true;
              }
          }
          

          关联

          将SpringSecurity的AuthenticationManager 【认证管理器,管登录的组件】,与我们写的登录逻辑关联起来【loadUserByUsername方法】,实现方式就是在SpringSecurity的配置类中实现

          /**
           * 现在使用的是SpringSecurity 6.1.5版本,开启SpringSecurity的自定义配置,
           * 需要使用 @EnableWebSecurity注解,而不再是继承Adpater
           */
          @Configuration
          @EnableWebSecurity
          public class SecurityConfig {
          
              @Autowired
              private SysUserDetailsService sysUserDetailsService;
          
              @Autowired
              private SiteUserDetailsService siteUserDetailsService;
          
              // 配置SpringSecurity的过滤器链
              @Bean
              public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                  // 设置登录接口放行
                  http.authorizeHttpRequests(auth -> auth.requestMatchers("/auth/sys_login","/auth/site_login").permitAll().anyRequest().authenticated());
                  // 关闭csrf
                  http.csrf(csrf -> csrf.disable());
                  return http.build();
              }
          
          
              // 配置AuthenticationManager,配置两个。一个管理后台用户
              @Primary
              @Bean("sysUserAuthenticationManager")
              public AuthenticationManager sysUserAuthenticationManager(PasswordEncoder passwordEncoder) {
                  DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
                  // 关联UserDetailsService
                  authenticationProvider.setUserDetailsService(sysUserDetailsService);
                  // 关联密码管理器
                  authenticationProvider.setPasswordEncoder(passwordEncoder);
                  return new ProviderManager(authenticationProvider);
              }
          
          
              // 配置AuthenticationManager,管理APP用户
              @Bean("siteUserAuthenticationManager")
              public AuthenticationManager siteUserAuthenticationManager(PasswordEncoder passwordEncoder) {
                  DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
                  // 关联UserDetailsService
                  authenticationProvider.setUserDetailsService(siteUserDetailsService);
                  // 关联密码管理器
                  authenticationProvider.setPasswordEncoder(passwordEncoder);
                  return new ProviderManager(authenticationProvider);
              }
          
              /**
               * 密码管理器,会将明文密码转换成密文,加密,而且不能解码
               * @return
               */
              @Bean
              public PasswordEncoder passwordEncoder() {
                  return new BCryptPasswordEncoder();
              }
          }
          

          密码

          需要对数据库中存储的密码进行编码,因为SpringSecurity进行密码匹配时,会对用户输入的密码先编码,再验证,先通过PasswordEncoder生成加密后的密码

          @SpringBootTest
          public class MyTestApplication {
          
              @Autowired
              private PasswordEncoder passwordEncoder;
          
              @Test
              public void test() {
          		// 密码加密
                  String encode = passwordEncoder.encode("123456");
                  System.out.println(encode);
          
              }
          }
          

          SpringSecurity配置

          创建两个AuthenticationManager,一定要设置一个主AuthenticationManager否则将会报错,即在任意一个Bean上添加@Primary注解标记

          @Primary
          @Bean("sysAuthenticationManager")
          public AuthenticationManager sysAuthenticationManager(PasswordEncoder passwordEncoder) {
              DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
              authenticationProvider.setUserDetailsService(sysUserDetailsService);
              authenticationProvider.setPasswordEncoder(passwordEncoder);
              ProviderManager providerManager = new ProviderManager(authenticationProvider);
              providerManager.setEraseCredentialsAfterAuthentication(false);
              return providerManager;
          }
          
          /**
           * 外部用户验证管理器
           * @param passwordEncoder
           * @return
           */
          @Bean("siteAuthenticationManager")
          public AuthenticationManager siteAuthenticationManager(PasswordEncoder passwordEncoder) {
              DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
              authenticationProvider.setUserDetailsService(siteUserDetailsService);
              authenticationProvider.setPasswordEncoder(passwordEncoder);
              ProviderManager providerManager = new ProviderManager(authenticationProvider);
              providerManager.setEraseCredentialsAfterAuthentication(false);
              return providerManager;
          }
          

          数据库配置

          spring:
            datasource:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: jdbc:mysql://localhost:3306/springsecurity?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
              username: root
              password: stt123456
          

          AuthenticationManager

          AuthenticationManager用于定义SpringSecurity如何进行身份认证,之后将认证信息封装在Authentication对象上,设置到SecurityContextHolder上,AuthenticationManager常用的实现是ProviderManager,你也可以对其做自定义实现。

          声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。