用户注册、登录、鉴权场景-后端
技术选型
- java
- spring(依赖注入框架,管理java对象,集成一些其他的内容)
- springmvc(web框架,提供接口访问,restful接口等能力)
- mybatis(Java 操作数据库的框架,持久层框架,对jdbc的封装)
- mybatis-plus(对mybatis的增强,不用写sql也能实现增删改查)
- spring-boot(快速启动/快速集成项目,不需要自己管理spring配置,不用自己整合各种框架)
- junit 单元测试库
- mysql 数据库
1.数据库设计
用户表结构设计
id(主键)bigint
username 昵称 varchar
userAccount 登录账号
avatarUrl 头像 varchar
gender 性别 tinyint
userPassword 密码 varchar
phone 电话 varchar
email 邮箱 varchar
userStatus 用户状态 int 0 - 正常
userRole 用户角色 int 0-普通角色 1-管理员
createTime 创建时间(数据插入时间)datetime
updateTime 更新时间(数据更新时间)datetime
isDelete 是否删除 0 1(逻辑删除)tinyint
2.使用MybatisX插件自动生成代码
MyBatisX 插件,自动根据数据库生成 domain 实体对象、mapper(操作数据库的对象)、mapper.xml(定义了 mapper对象和数据库的关联,可以在里面自己写 SQL)、service(包含常用的增删改查)、serviceImpl(具体实现 service)
3.注册
- 用户在前端输入账户和密码、以及校验码(todo)
- 校验用户的账户、密码、校验密码,是否符合要求
- 非空
- 账户长度 不小于 4 位
- 密码就 不小于 8 位
- 账户不能重复
- 账户不包含特殊字符
- 密码和校验密码相同
- 对密码进行加密(密码千万不要直接以明文存储到数据库中)
- 向数据库插入用户数据
Controller层
@PostMapping("/register")
public Long userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
if (userRegisterRequest == null) {
return null;
}
String userAccount = userRegisterRequest.getUserAccount();
String userPassword = userRegisterRequest.getUserPassword();
String checkPassword = userRegisterRequest.getCheckPassword();
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
return null;
}
return userService.userRegister(userAccount, userPassword, checkPassword);
}
这里,调用service层前进行一次数据校验,减少一丢丢数据库压力
但请注意
controller 层倾向于对请求参数本身的校验,不涉及业务逻辑本身(越少越好)
service 层是对业务逻辑的校验(有可能被 controller 之外的类调用,比如service层的其他类)
Service层
@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
// 1.校验
if(StringUtils.isAnyBlank(userAccount,userPassword,checkPassword)){
return -1;
}
if(userAccount.length() < 4){
return -1;
}
if(userPassword.length() < 8 || checkPassword.length() < 8){
return -1;
}
// 密码和校验密码相同
if(!userPassword.equals(checkPassword)){
return -1;
}
//账户不能包含特殊字符
String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
if(matcher.find()){
return -1;
}
// 账户不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount",userAccount);
Long count = userMapper.selectCount(queryWrapper);
if(count > 0){
return -1;
}
// 2.加密
final String SALT = "xrp";
DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// 3.插入数据
User user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(userPassword);
boolean saveResult = this.save(user);
if(!saveResult){
return -1;
}
return user.getId();
}
4.登录
接口
接受参数:用户账户、密码
请求类型:POST
请求体:JSON 格式的数据
请求参数很长时不建议用 get
返回值:用户信息( 脱敏 )
逻辑
-
校验用户账户和密码是否合法
- 非空
- 账户长度 不小于 4 位
- 密码就 不小于 8 位
- 账户不包含特殊字符
-
校验密码是否输入正确,要和数据库中的密文密码去对比
-
用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露
-
我们要记录用户的登录态(session),将其存到服务器上(用后端 SpringBoot 框架封装的服务器 tomcat 去记录)
cookie
-
返回脱敏后的用户信息
Controller
@PostMapping("/login")
public User userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
if (userLoginRequest == null) {
return null;
}
String userAccount = userLoginRequest.getUserAccount();
String userPassword = userLoginRequest.getUserPassword();
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
return null;
}
return userService.userLogin(userAccount, userPassword, request);
}
Service
/**
* 用户登录实现
* @param userAccount 用户账户
* @param userPassword 用户密码
* @return
*/
@Override
public User userLogin(String userAccount, String userPassword, HttpServletRequest request){
// 1.校验
if(StringUtils.isAnyBlank(userAccount,userPassword)){
return null;
}
if(userAccount.length() < 4){
return null;
}
if(userPassword.length() < 8 ){
return null;
}
//账户不能包含特殊字符
String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
if(matcher.find()){
return null;
}
// 对密码进行加密
DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// 查询用户是否存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount",userAccount);
queryWrapper.eq("userPassword",userPassword);
User user = userMapper.selectOne(queryWrapper);
if(user == null){
log.info("user login failed, userAccount cannot match userPassword");
return null;
}
//用户信息脱敏
User safetyUser = getSafetyUser(user);
//用户登录成功,返回用户信息
request.getSession().setAttribute(USER_LOGIN_STATE,safetyUser);
return safetyUser;
}
5.鉴权
用户登录信息保存在session中,后续改为redis实现(todo)
private boolean isAdmin(HttpServletRequest request) {
//仅管理员可查询
Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
User user = (User) userObj;
return user != null && user.getUserRole() == ADMIN_ROLE;
}