postman测试结果(示例):
注册失败返回值:
后端可以判断账号是否被注,并返回出结果
登录错误返回值:
登录错误会返回用户名错误还是其他问题导致用户登录不上去,用于精准的提示用户注册
用户登录账号,密码正确但是跨游览器导致token缺失会返回验证码(让用户选择或者填:前端的事情),用于精准的帮助用户找回token从而登录
用户登录账号,密码正确但是跨游览器导致token缺失会返回验证码用户输入验证码后,后端可以判断验证码是否正确。正确则返回token,前端收到toke保存本地即可:
登录成功返回值:
登录成功会返回一系列的值给予前端一系列的操作。
开发工具:
idel.vsCode,postman,mysql(navicat)
用到的包:
Lombok,BCrypt.druid,jwt,jbcrypt,
基于此后端登录注册业务逻辑:
1.注册流程
前端注册时不携带token,后端springboot接收数据对用户名进行加jwt加密返回token操作,密码则进行加密操作存储到数据库(使用BCrypt.hashpw进行加密),
2.登录流程
前端登录发送用户名,密码等数据到后端,使用mp查询数据库中加密的密码和用户名,判断用户名是否一致,后端接收数据然后把前端发来的密码和数据库中的密码用BCrypt.checkpw解密后,判断密码是否一致,确认一致后然后返回token到前端,前端把token存储在本地。基于以上的业务逻辑,主要介绍注册和登录时密码和用户名的前后端校验。
3.注意
1.前端收到token需要把token存储在游览器本地(localstorage),下次请求时,携带token:才能请求成功。
2.前端用户登录和注册校验数据时,用户名不能超过12位,密码不能超过20位,网名不能超过8位最好!否则会引起后端程序崩溃!!!!!!!!!!!!!!
3.
当用户注册过后又换了游览器登录,就会导致token在新的游览器上丢失,这时只要用户输入的账号密码正确,后端会使用mp查询数据库,判断token是否正确,错误返回验证码("由于设备更改:返回验证数字"+loginRandon):前端收到数据,让用户选择哪个数字或者让用户填数字,填完后请求即可。然后端判断验证码是否正确,正确则返回数据库中的token和字符串做拼接来传递给前端。(拼接示例:"检测令牌为空:返回令牌"+tokenOne),前端收到数据,把字符串拆掉存储在本地即可(前端需要做非空验证)。不正确则会返回("数字令牌错误!")
4.前端请求不成功解决方案:
确认环境是否是vue?
是vue:代理才行能请求成功(安全)
原生js:创建控制层类下的可跨域配置类修改成另一种类型(下面会发)不做代理也可以请求成 功
开始编码:
创建mysql数据库和表字符集utf-8
文件目录:
1.创建spting-boot工程,
勾选MybatisDriver,spring-boot(3以下版本),lombok
2.配置坐标(pom.xml):
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3.配置数据库驱动
server:
port: 81
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shopping_mall_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: assign_id
table-prefix: tab_
logic-delete-field: delete
logic-delete-value: 1
logic-not-delete-value: 0
4.创建实体类entity(User)
package com.sms.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class User {
private Long id;
private String username;
private String password;
@TableField(value = "screen_name")
private String screenName;
private String token;
@TableField(exist = false)
private Integer loginRandom;
}
5.创建Entity 接口继承BaseMapper类
package com.sms.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sms.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao extends BaseMapper<User> {
}
5.创建service层接口SysUserservice,QueryTokenService:
SysUserservice类:
public interface SysUserService extends IService<User> {
/**
* 用户注册
* enroll
* @return
*/
String enroll(User user);
/**
* 用户登录
* @param
* user
* @return
*/
// String login(String userName, String password,String token);
String login(User user);
}
QueryTokenService类:
package com.sms.server;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sms.domain.User;
public interface QueryTokenService extends IService<User> {
Boolean returnQueryToken(String token);
}
6.创建service层接口实现类SysUserserviceImpl,QueryTokenServiceImpl:
SysUserserviceImpl 类:
@Service
@Transactional
public class SysUserserviceImpl extends ServiceImpl<UserDao, User> implements SysUserService {
@Autowired
private UserDao userDao;
//写sql语句的类
//注册的类,注解可以校验是不是父接口的子类
Integer returnLoginRandom=null;
@Override
public String enroll(User user) {
String username = user.getUsername();
String password = user.getPassword();
String screenName = user.getScreenName();
try {
if (username == null || password == null) {
return "账号密码中不能为空";
}
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("userName", username);
User userEnrollEnroll1 = userDao.selectOne(qw);
if (userEnrollEnroll1 != null) {
return "已有账号";
}
} catch (NullPointerException e) {
throw new ComFoundException();
}
//使用jwt加密用户名来生成随机token
LoginToken loginToken = new LoginToken();
String userUserNameJwt = loginToken.returnLogin(username);
//使用BCrypt对密码进行加密
String BCryptPassword = BCrypt.hashpw(password, BCrypt.gensalt());
//开始存储数据
User userEnrollEnrollSave = new User();
userEnrollEnrollSave.setUsername(username);
userEnrollEnrollSave.setScreenName(screenName);
userEnrollEnrollSave.setPassword(BCryptPassword);
userEnrollEnrollSave.setToken(userUserNameJwt);
int insert = userDao.insert(userEnrollEnrollSave);
if (insert != 1) {
return "插入失败";
}
return userUserNameJwt;
}
//登录的类,注解可以校验是不是父接口的子类
@Override
public String login(User user) {
//获取前端的数据
String username = user.getUsername();
String password = user.getPassword();
String token = user.getToken();
Integer loginRandom=user.getLoginRandom();
if (username == null || password == null) {
return "账号不为空";
}
//前端无数字验证值走这些代码(默认无值)
if (loginRandom==null) {
//判断数据是否无误
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getUsername,username);
lqw.select(User::getId, User::getPassword,User::getToken);
List<User> userList = userDao.selectList(lqw);
if (userList == null || userList.size() == 0) {
return "用户名或密码错误";
}
//对密码进行解密
String passwordBCryptCheckpw = null;
for (User userPassword : userList) {
passwordBCryptCheckpw = userPassword.getPassword();
}
//使用BCrypt.checkpw(password,passwordBCryptCheckpw)来确认数据是否正确
Boolean flag = BCrypt.checkpw(password, passwordBCryptCheckpw);
if (flag != true) {
return "用户名或密码错误";
}
//用户跨游览器导致token缺失解决
String tokenOne=null;
for (User userToken:userList){
tokenOne= userToken.getToken();
}
if (tokenOne==null||!tokenOne.equals(token)) {
if (loginRandom==null){
RandomNumber randomNumber=new RandomNumber();
returnLoginRandom = randomNumber.returnLoginRandom();
//如果数据库中查不到token则证明这个用户要么是黑户,要么是工作人员的失误
return "由于您的设备更改:返回验证数字"+ returnLoginRandom;
}
}
//使用jwt加密用户名来生成随机token
LoginToken loginToken = new LoginToken();
String ReturnUserNameToken = loginToken.returnLogin(password);
//查询登录的是哪一个人的id;
String idStr = null;
for (User userId : userList) {
idStr = String.valueOf(userId.getId()) + "L";
}
long id = Long.parseLong(idStr.replace("L", ""));
//更新token
User userPutToken = userDao.selectById(id);
userPutToken.setToken(ReturnUserNameToken);
int flags = userDao.updateById(userPutToken);
if (flags != 1) {
return "添加失败";
}
return ReturnUserNameToken;
}
//前端传值loginRandom走这些代码(默认无值)
if(loginRandom!=null) {
//判断数据是否无误
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getUsername,username);
lqw.select(User::getId, User::getPassword,User::getToken);
List<User> userList = userDao.selectList(lqw);
if (userList == null || userList.size() == 0) {
return "用户名或密码错误";
}
//对密码进行解密
String passwordBCryptCheckpw = null;
for (User userPassword : userList) {
passwordBCryptCheckpw = userPassword.getPassword();
}
//使用BCrypt.checkpw(password,passwordBCryptCheckpw)来确认数据是否正确
Boolean flag = BCrypt.checkpw(password, passwordBCryptCheckpw);
if (flag != true) {
return "用户名或密码错误";
}
//用户跨游览器导致token缺失解决
// 前端拿到token在进行登录,则能登录成功
String tokenOne=null;
for (User userToken:userList){
tokenOne= userToken.getToken();
}
if (tokenOne==null||!tokenOne.equals(token)) {
if (loginRandom.equals(returnLoginRandom)){
returnLoginRandom=null;
return "检测令牌为空:返回令牌"+tokenOne;
}
if (!loginRandom.equals(returnLoginRandom)){
return "数字令牌错误!";
}
}
//使用jwt加密用户名来生成随机token
LoginToken loginToken = new LoginToken();
String ReturnUserNameToken = loginToken.returnLogin(password);
//查询登录的是哪一个人的id;
String idStr = null;
for (User userId : userList) {
idStr = String.valueOf(userId.getId()) + "L";
}
long id = Long.parseLong(idStr.replace("L", ""));
//更新token
User userPutToken = userDao.selectById(id);
userPutToken.setToken(ReturnUserNameToken);
int flags = userDao.updateById(userPutToken);
if (flags != 1) {
return "添加失败";
}
return ReturnUserNameToken;
}
return "错误";
}
QueryTokenServiceImpl类:
@Service
@Transactional
public class QueryTokenServiceImpl extends ServiceImpl<UserDao,User> implements QueryTokenService {
@Autowired
private UserDao userDao;
@Override
public Boolean returnQueryToken(String token) {
QueryWrapper<User> qw=new QueryWrapper<>();
qw.eq("token",token);
List<User> userList=userDao.selectList(qw);
try{
if (userList==null||userList.size()==0){
return false;
}
}catch (NullPointerException e){
throw new ComFoundException();
}
return true;
}
}
7.创建统一错误处理类ComFoundException:
package com.sms.server.ex;
public class ComFoundException extends RuntimeException{
public ComFoundException() {
super();
}
public ComFoundException(String message) {
super(message);
}
public ComFoundException(String message, Throwable cause) {
super(message, cause);
}
public ComFoundException(Throwable cause) {
super(cause);
}
public ComFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
8.创建工具类来实现返回数据给前端的类和校验类:
RandomNumber:(生成验证码)
public class RandomNumber {
public int returnLoginRandom(){
Random random=new Random();
int loginRandom = random.nextInt(9000) + 1000;
//生成四位随机数
return loginRandom;
}
}
LoginToken:
package com.sms.util.jwttoken;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class LoginToken {
//登录会用到的工具
public String returnLogin(String password){
Date now=new Date();
//HashMap可以存储键值对
Map<String,Object> claims=new HashMap<>();
claims.put("password",password);
//使用jwt生成器生成Token
String token= Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256,"secret")
.compact();
return token;
}
}
Result:
@Data
public class Result {
private Integer code;
private boolean success;
private String message;
private Object data;
private Date date;
public static Result loginBody(Integer code,
Boolean success,
String message,
Object data,
Date date
){
Result result = new Result();
result.setCode(code);
result.setSuccess(success);
result.setData(data);
result.setMessage(message);
result.setDate(date);
return result;
}
public static Result enroll(Integer code,
Boolean success,
String message,
String data,
Date date){
Result result = new Result();
result.setCode(code);
result.setSuccess(success);
result.setData(data);
result.setMessage(message);
result.setDate(date);
return result;
}
}
ResultCode
package com.sms.util.ResultLoginCode;
public class ResultCode{
public static final Integer POST_OK=20011;
public static final Integer POST_ERR=20010;
public static final Integer DELETE_OK=20021;
public static final Integer DELETE_ERR=20020;
public static final Integer UPDATE_OK=20031;
public static final Integer UPDATE_ERR=20030;
public static final Integer GET_OK=20041;
public static final Integer GET_ERR=20010;
}
9.创建控制层类
1.可跨域配置类(上面提到的请求不成功解决方案!二选一!)
使用:vue做代理的(安全):
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/shop/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET","POST","PUT","DELETE")
.allowedHeaders("Content-Type")
.allowCredentials(true).maxAge(3600);
}
}
原生js的,不需要做代理的:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET","POST","PUT","DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
2.接口类:
@RestController
@RequestMapping("/shop")
public class Controller {
@Autowired
private SysUserService sysUserservice;
@Autowired
private QueryTokenService queryTokenService;
@PostMapping("/login")
public Result getPersonInfo(@RequestBody User user){
String data = sysUserservice.login(user);
String newData=null;
Boolean flag= queryTokenService.returnQueryToken(data);
Integer code=flag!=true?ResultCode.POST_ERR:ResultCode.POST_OK;
//解决用户跨游览器导致token缺失;
String message = null;
message=flag!=true?data:"恭喜你,登录成功!请稍等,正在进入页面。";
Boolean flagMessage=data.contains("检测令牌为空:返回令牌");
Boolean flagMessageNumber=data.contains("由于您的设备更改:返回验证数字");
if (flagMessageNumber==true){message=data;code=ResultCode.POST_OK;data=null;flag=true;}
if (flagMessage==true){message=data;code=ResultCode.POST_OK;data=null;flag=true;}
Date date=new Date();
Result result = Result.loginBody(code, flag, message,data,date);
return result;
}
//@RequestBody把前端的值转化为json类型
@PostMapping("/enroll")
public Result postPersonInfo(@RequestBody User user){
Integer code;
String message=null;
//返回的是token
String data = sysUserservice.enroll(user);
code=data!=null? ResultCode.POST_OK:ResultCode.POST_ERR;
message=code!=ResultCode.POST_OK?data=null:"注册成功!";
if (data=="已有账号"){message="注册失败:已有账号!";code=ResultCode.POST_ERR;data=null;}
Boolean success=code!=ResultCode.POST_ERR?true:false;
Date date=new Date();
Result result = Result.enroll(code,success,message,data,date);
return result;
}
}
恭喜你!!!已成功学完。
最难不过坚持!!!
后续前端会出,敬请关注!^_^