SpringSecurity multi-table, multi-terminal account login

This article corresponds to the video SpringSecurity6 multi-terminal account login, which can be extended infinitely. Remember to connect three times, this is very important to me!
Warm reminder:Videos and articles complement each other, and the learning effect is stronger when combined! For more video tutorials, please visit Station B [Shi Tian’s Programming Philosophy]

SpringSecurity implements multi-table account login

Requirements: For company employees, ordinary users and other types of users, store them in different user tables respectively, and implement user authentication based on SpeedSecurity, that is, the login function

process

  • First do the database design
  • Create a project based on SpringBoot
  • Do related implementations in the project
  • Test through apifox interface testing tool
  • Test the login methods of different users separately to see whether the corresponding login logic is called [Login is also called authentication]

Note: Permissions are not involved, only user data.

Data table design

This article does not involve permissions. The table design is two user tables.

Employee List

CREATE TABLE `ums_sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'userID',
  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'user account',
  `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'User's Nickname',
  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'User email',
  `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'phone number',
  `sex` int DEFAULT '0' COMMENT 'User gender(0male 1female 2unknown)',
  `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'Avatar address',
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'password',
  `status` int DEFAULT '0' COMMENT 'Account status(0normal 1deactivate)',
  `creator` bigint DEFAULT '1' COMMENT 'creator',
  `create_time` datetime DEFAULT NULL COMMENT 'creation time',
  `updater` bigint DEFAULT '1' COMMENT 'updater',
  `update_time` datetime DEFAULT NULL COMMENT 'Update time',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'Remark',
  `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='Backend user table'; 

Customer table

CREATE TABLE `ums_site_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'userID',
  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'user account',
  `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'User's Nickname',
  `openid` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'WeChatopenid',
  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'User email',
  `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'phone number',
  `sex` int DEFAULT '0' COMMENT 'User gender(0male 1female 2unknown)',
  `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'Avatar address',
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'password',
  `status` int DEFAULT '0' COMMENT 'Account status(0normal 1deactivate)',
  `create_time` datetime DEFAULT NULL COMMENT 'creation time',
  `updater` bigint DEFAULT '1' COMMENT 'updater',
  `update_time` datetime DEFAULT NULL COMMENT 'Update time',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'Remark',
  `deleted` tinyint DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='external user table'; 

Create project

The login function uses a very simple three-tier architecture. The technology options are:

  • SpringBoot 3.1.X
  • SpringSecurity 6.1.X
  • Mybatis Plus
  • lombok [simplified entity class] can generate getter, setter methods, construction methods, toString methods, etc. through annotations
  • maven

pom file

<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 file configuration

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 

Create a three-tier architecture

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;
    }

    /**
     * Backend management system login
     * return value:token
     */
    @PostMapping("sys_login")
    public String sysLogin(@RequestBody LoginParam loginParam) {

        return "Backend user login======》" +sysUserService.sysLogin(loginParam);
    }

    @PostMapping("site_login")
    public String siteLogin(@RequestBody LoginParam loginParam) {

        return "APPUser login======》" + siteUserService.siteLogin(loginParam);
    }

} 

SysUserServiceImpl

@Service
@Slf4j
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    @Autowired
    @Qualifier("sysUserAuthenticationManager")
    private AuthenticationManager authenticationManager;

    /**
     * Login isSpringSecurityrealized,We just go tellSpringSecurityLog in now
     * SpringSecirityLogin is via AuthticationManager realized
     * WillAuthticationManagerintroduced toservicemiddle,Just call his authentication method
     * @param loginParam
     * @return
     */
    @Override
    public String sysLogin(LoginParam loginParam) {
        // passauthenticationManager Authentication method to achieve login,This method requires passing in Authentication object It is an authentication object
        // AuthenticationlWhat is stored inside is the user’s authentication information.,Permissions,username,Password and other information,Actually it isloadUserByUsernamemethod returnsUserDetails
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginParam.getUsername(), loginParam.getPassword());

        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        // Get user information
        SysUser sysUser = (SysUser) authenticate.getPrincipal();
        log.info("sysUser==========》{}",sysUser);
        // What is returned istoken
        return sysUser.getUsername();
    }
} 

SiteUserServiceImpl

@Service
@Slf4j
public class SiteUserServiceImpl extends ServiceImpl<SiteUserMapper, SiteUser> implements ISiteUserService {

    /**
     * WillAuthenticationManagerinjection
     */
    @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);
        // Forced to user type
        SiteUser siteUser = (SiteUser) authenticate.getPrincipal();
        log.info("siteUser===========>{}",siteUser);
        return siteUser.getUsername();
    }
} 

Implement login function

SpringSecurity is introduced into the project. SpringSecurity needs to use two interfaces when implementing user login [authentication].

  • UserDetailsService: is an interface that provides a method loadUserByUsername();
  • UserDetails: is an interface used to store user permissions, status [whether disabled, timeout, etc.]

Query the user through UserDetailsService, put the user information into UserDetails, and leave the rest to SpringSecurity’s AuthenticationManager to determine whether the user is allowed to log in.

Take two steps

Create UserDetailsService interface implementation class

To query users, create corresponding query user implementation classes for customers and backend system users.

// System user&#39;sDetailsService
@Service
public class SysUserDetailsService implements UserDetailsService {

    private final SysUserMapper sysUserMapper;

    public SysUserDetailsService(SysUserMapper sysUserMapper) {
        this.sysUserMapper = sysUserMapper;
    }

    /**
     * This method queries the user from the database
     * Return a UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("Backend system user login=============》");
        // Query users based on username
        SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        // If you have permission,Need to query the corresponding permissions of the user
        if(sysUser == null) {
            throw new UsernameNotFoundException("Incorrect user or password");
        }
        return sysUser;
    }
}
// APPusersDetailsService
@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("APPUser login===================》");
        SiteUser siteUser = siteUserMapper.selectOne(new LambdaQueryWrapper<SiteUser>().eq(SiteUser::getMobile, mobile));
        if(siteUser == null) {
            throw new UsernameNotFoundException("wrong user name or password!");
        }
        return siteUser;
    }
} 

Create UserDetails interface implementation class

To store user information, create two implementation classes in the same way, store different user information, and modify them directly on the entity class.

// Backend management system user class
@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;
    }
}
// APPUser entity class
@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;

    /**
     * Permissions。There is no query permission now
     * @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;
    }
} 

association

Associate SpringSecurity’s AuthenticationManager [authentication manager, component that manages login] with the login logic we wrote [loadUserByUsername method], and the implementation is to implement it in the SpringSecurity configuration class

/**
 * Currently usingSpringSecurity 6.1.5Version,turn onSpringSecurityCustom configuration of,
 * Need to use @EnableWebSecurityannotation,instead of inheritanceAdpater
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private SysUserDetailsService sysUserDetailsService;

    @Autowired
    private SiteUserDetailsService siteUserDetailsService;

    // ConfigurationSpringSecurityfilter chain
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // Set login interface permission
        http.authorizeHttpRequests(auth -> auth.requestMatchers("/auth/sys_login","/auth/site_login").permitAll().anyRequest().authenticated());
        // closurecsrf
        http.csrf(csrf -> csrf.disable());
        return http.build();
    }

    // ConfigurationAuthenticationManager,Configure two。An administrative backend user
    @Primary
    @Bean("sysUserAuthenticationManager")
    public AuthenticationManager sysUserAuthenticationManager(PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        // associationUserDetailsService
        authenticationProvider.setUserDetailsService(sysUserDetailsService);
        // Associate a password manager
        authenticationProvider.setPasswordEncoder(passwordEncoder);
        return new ProviderManager(authenticationProvider);
    }

    // ConfigurationAuthenticationManager,manageAPPuser
    @Bean("siteUserAuthenticationManager")
    public AuthenticationManager siteUserAuthenticationManager(PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        // associationUserDetailsService
        authenticationProvider.setUserDetailsService(siteUserDetailsService);
        // Associate a password manager
        authenticationProvider.setPasswordEncoder(passwordEncoder);
        return new ProviderManager(authenticationProvider);
    }

    /**
     * Password manager,Convert plain text password to cipher text,encryption,and cannot be decoded
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
} 

password

The password stored in the database needs to be encoded, because when Spring Security performs password matching, it will first encode the password entered by the user, then verify it, and first generate the encrypted password through PasswordEncoder.

@SpringBootTest
public class MyTestApplication {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Test
    public void test() {
        // Password encryption
        String encode = passwordEncoder.encode("123456");
        System.out.println(encode);

    }
} 

SpringSecurity configuration

Create two AuthenticationManagers. Be sure to set a main AuthenticationManager otherwise an error will be reported. That is, add the @Primary annotation mark to any Bean.

@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;
}

/**
 * External user authentication manager
 * @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;
} 

Database configuration

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 is used to define how SpringSecurity performs identity authentication, and then encapsulates the authentication information in the Authentication object and sets it on the SecurityContextHolder. The commonly used implementation of AuthenticationManager is ProviderManager, and you can also customize it.