环境用户注册
思路数据库项目结构配置编码测试 参考
环境操作系统:
Windows 10 x64
集成开发环境:
Spring Tool Suite 4 Version: 4.14.0.RELEASE Build Id: 202203131612
Postman(客户端):
Postman for Windows Version 9.0.9 win32 10.0.19044 / x64用户注册 思路
要实现用户注册功能,首先要准备一个数据库,此处我选用 MySQL,然后使用 MyBatis-Plus 框架完成添加用户…
数据库MySQL 数据库表结构:
mysql> describe user; +-------------------------+--------------+------+-----+-------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +-------------------------+--------------+------+-----+-------------------+-----------------------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | username | varchar(256) | NO | | NULL | | | password | varchar(256) | NO | | NULL | | | account_non_expired | tinyint(1) | YES | | 1 | | | account_non_locked | tinyint(1) | YES | | 1 | | | credentials_non_expired | tinyint(1) | YES | | 1 | | | enabled | tinyint(1) | YES | | 1 | | | deleted | tinyint(1) | YES | | 0 | | | create_time | timestamp | YES | | CURRENT_TIMESTAMP | | | update_time | timestamp | YES | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | +-------------------------+--------------+------+-----+-------------------+-----------------------------+ 10 rows in set (0.00 sec)
建表语句:
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, `password` varchar(256) NOT NULL, `account_non_expired` tinyint(1) DEFAULT '1' COMMENT 'true (1, default) if the user''s account is valid (ie non-expired), false (0) if no longer valid (ie expired).', `account_non_locked` tinyint(1) DEFAULT '1' COMMENT 'true (1, default) if the user is not locked, false (0) otherwise.', `credentials_non_expired` tinyint(1) DEFAULT '1' COMMENT 'true (1, default) if the user''s credentials are valid (ie non-expired), false (0) if no longer valid (ie expired).', `enabled` tinyint(1) DEFAULT '1' COMMENT 'true (1, default) if the user is enabled, false (0) otherwise.', `deleted` tinyint(1) DEFAULT '0' COMMENT 'true (1) if this record is deleted, false (0, default) otherwise.', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;项目结构
参考:Spring Security - 13 - 禁用 CSRF、获取 CSRF 值
配置修改 pom.xml 配置文件,引入 MyBatis-Plus、MySQL Connector、Validation 等依赖(第 10 ~ 25 行):
com.baomidou mybatis-plus-boot-starter 3.5.1 mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-validation
修改 application.yml 配置文件,添加数据源:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.88.158:3307/hello-account?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true
username: root
password: 123456
thymeleaf:
cache: false
logging:
level:
com.mk: debug
编码
用于返回结果的 HttpResult 类:
package com.mk.web.common;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class HttpResult implements Serializable {
private static final long serialVersionUID = 1L;
private String message;
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private Object data;
}
自定义的服务异常 ServiceException 类:
package com.mk.web.exception;
public class ServiceException extends Exception {
private static final long serialVersionUID = 1L;
public ServiceException() {
super();
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
public ServiceException(String message) {
super(message);
}
public ServiceException(Throwable cause) {
super(cause);
}
}
常量 Constants 类:
package com.mk.web.constant;
public class Constants {
private static final String SUCCESS = "成功";
private static final String FAILURE = "失败";
private static final String INSERT = "新增";
private static final String DELETE = "删除";
private static final String UPDATE = "更新";
private static final String SELECT = "查询";
public static final String JDBC_INSERT_SUCCESS = INSERT + SUCCESS;
public static final String JDBC_INSERT_FAILURE = INSERT + FAILURE;
public static final String JDBC_DELETE_SUCCESS = DELETE + SUCCESS;
public static final String JDBC_DELETE_FAILURE = DELETE + FAILURE;
public static final String JDBC_UPDATE_SUCCESS = UPDATE + SUCCESS;
public static final String JDBC_UPDATE_FAILURE = UPDATE + FAILURE;
public static final String JDBC_SELECT_SUCCESS = SELECT + SUCCESS;
public static final String JDBC_SELECT_FAILURE = SELECT + FAILURE;
}
全局异常处理类:
package com.mk.exception.handler;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mk.web.common.HttpResult;
import com.mk.web.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
private static final ObjectMapper objectMapper = new ObjectMapper();
@ExceptionHandler(value = { ServiceException.class })
@ResponseBody
public void serviceExceptionHandler(Exception e, HttpServletResponse response) throws IOException {
log.error("Service Exception: ", e);
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
String message = e.getMessage();
HttpResult result = new HttpResult(message, null);
PrintWriter writer = response.getWriter();
writer.write(objectMapper.writevalueAsString(result));
writer.flush();
writer.close();
}
// https://blog.csdn.net/qq_43409401/article/details/116017177
// https://blog.csdn.net/zmflying8177/article/details/100755223
@ExceptionHandler(value = { BindException.class })
@ResponseBody
public void bindExceptionHandler(BindException e, HttpServletResponse response) throws IOException {
log.error("Bind Exception: ", e);
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
String message = e.getBindingResult().getFieldError().getDefaultMessage();
HttpResult result = new HttpResult(message, null);
PrintWriter writer = response.getWriter();
writer.write(objectMapper.writevalueAsString(result));
writer.flush();
writer.close();
}
}
实体类 User:
package com.mk.web.console.account.pojo; import java.io.Serializable; import java.time.LocalDateTime; import javax.validation.constraints.NotBlank; import org.hibernate.validator.constraints.Length; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.extension.activerecord.Model; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class User extends Model{ private static final long serialVersionUID = 1L; public static final String TABLE_NAME = "user"; public static final String ID = "id"; public static final String USERNAME = "username"; public static final String PASSWORD = "password"; public static final String ACCOUNT_NON_EXPIRED = "account_non_expired"; public static final String ACCOUNT_NON_LOCKED = "account_non_locked"; public static final String CREDENTIALS_NON_EXPIRED = "credentials_non_expired"; public static final String ENABLED = "enabled"; public static final String DELETED = "deleted"; @TableId(value = "id", type = IdType.AUTO) private Long id; @NotBlank(message = "【用户名】不能为空") @Length(min = 2, max = 20, message = "【用户名】长度不满足要求") private String username; @NotBlank(message = "【密码】不能为空") @Length(min = 8, max = 20, message = "【密码】长度不满足要求") private String password; private Boolean accountNonExpired; private Boolean accountNonLocked; private Boolean credentialsNonExpired; private Boolean enabled; private Boolean deleted; private LocalDateTime createTime; private LocalDateTime updateTime; @Override public Serializable pkVal() { return this.id; } }
UserMapper 接口:
package com.mk.web.console.account.mapper; import com.mk.web.console.account.pojo.User; import com.baomidou.mybatisplus.core.mapper.baseMapper; public interface UserMapper extends baseMapper{ }
UserService 服务接口:
package com.mk.web.console.account.service; import com.baomidou.mybatisplus.extension.service.IService; import com.mk.web.console.account.pojo.User; import com.mk.web.exception.ServiceException; public interface UserService extends IService{ void register(User entity) throws ServiceException; }
UserService 服务接口的实现类 UserServiceImpl:
package com.mk.web.console.account.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mk.web.console.account.mapper.UserMapper;
import com.mk.web.console.account.pojo.User;
import com.mk.web.console.account.service.UserService;
import com.mk.web.constant.Constants;
import com.mk.web.exception.ServiceException;
@Service("CONSOLE_ACCOUNT_USER_SERVICE")
public class UserServiceImpl extends ServiceImpl implements UserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
@Transactional(rollbackFor = Exception.class)
public void register(User entity) throws ServiceException {
entity.setId(null);
entity.setPassword(encodedPassword(entity.getPassword()));
entity.setAccountNonExpired(null);
entity.setAccountNonLocked(null);
entity.setCredentialsNonExpired(null);
entity.setEnabled(null);
entity.setDeleted(null);
entity.setCreateTime(null);
entity.setUpdateTime(null);
if (!super.save(entity)) {
throw new ServiceException(Constants.JDBC_INSERT_FAILURE);
}
}
private String encodedPassword(String rawPassword) {
return passwordEncoder.encode(rawPassword);
}
}
控制器类 UserController:
package com.mk.web.console.account.controller;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.mk.web.common.HttpResult;
import com.mk.web.console.account.pojo.User;
import com.mk.web.console.account.service.UserService;
import com.mk.web.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
@RestController("ACCOUNT_USER_CONTROLLER")
@RequestMapping("/console/account/user")
@Slf4j
public class UserController {
private static final boolean debug = log.isDebugEnabled();
@Autowired
@Qualifier("CONSOLE_ACCOUNT_USER_SERVICE")
private UserService userService;
@PostMapping(path = "register")
public HttpResult register(@Valid User entity) throws ServiceException {
if (debug)
log.debug("Register: {}", entity);
userService.register(entity);
return new HttpResult("ok", null);
}
}
修改 WebSecurityConfigurer 配置类,添加允许匿名访问的路径(第 28 ~ 31 行):
package com.mk.security.config.annotation.web.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
//@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(); // Specifies to support form based authentication.
http.authorizeRequests(customizer -> {
String anonymous[] = { "/csrf", "/console/account/user/register" };
// Specify that URLs are allowed by anyone.
customizer.antMatchers(anonymous).permitAll();
// Any request are allowed by any authenticated user.
customizer.anyRequest().authenticated();
});
// http.csrf(customizer -> {
// customizer.disable();
// });
}
}
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 mapper 包(第 8 行):
package com.mk;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.mk.web.console.account.mapper")
public class SpringSecurityHelloApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityHelloApplication.class, args);
}
}
测试
启动应用,使用 Postman 访问 http://127.0.0.1:8080/console/account/user/register,提交必要的注册信息。成功之后,可以看到返回的信息:
查询数据库,得到:
mysql> select * from user; +----+----------+--------------------------------------------------------------+---------------------+--------------------+-------------------------+---------+---------+---------------------+---------------------+ | id | username | password | account_non_expired | account_non_locked | credentials_non_expired | enabled | deleted | create_time | update_time | +----+----------+--------------------------------------------------------------+---------------------+--------------------+-------------------------+---------+---------+---------------------+---------------------+ | 1 | zs | $2a$10$WDVUU5l3TyjvEiuzeWAIAeizQm4bp3T3J05tIplcB8Rndrj0h5r2i | 1 | 1 | 1 | 1 | 0 | 2022-03-27 07:31:20 | 2022-03-27 07:31:20 | | 2 | ls | $2a$10$b1BL/sjoFNxbzs2/Tgcliur5biTd51qB229YT8Uk1m2gPhAePu/wy | 1 | 1 | 1 | 1 | 0 | 2022-03-27 08:37:52 | 2022-03-27 08:37:52 | +----+----------+--------------------------------------------------------------+---------------------+--------------------+-------------------------+---------+---------+---------------------+---------------------+ 2 rows in set (0.00 sec)
如果提交的信息不满足 User 类中 username 和 password 字段的验证要求:
package com.mk.web.console.account.pojo; // 其他... @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class User extends Model{ // 其他... @NotBlank(message = "【用户名】不能为空") @Length(min = 2, max = 20, message = "【用户名】长度不满足要求") private String username; @NotBlank(message = "【密码】不能为空") @Length(min = 8, max = 20, message = "【密码】长度不满足要求") private String password; // 其他... }
那么,你将得到如下的提示:
参考Spring Boot - 简单地使用 MyBatis-Plus



