SpringBoot3 实现登录注册功能-1
仅仅是一枚新手实现功能的记录。仅是一个参考,还有很多不足,后续再完善。
该文主要是学习了简单的登录注册功能、邮箱验证、拦截器的设置。
后续还得学习上验证码的生成以及时效性、设置前端header、springBoot security(本来是有用到的,但是频繁出现小问题,决定先不用后续再学习。)还有邮箱验证登录等等。
也会有一些不同的注册思路需要学习。
流程、接口
简单提一嘴:dto\do : do也就是实体类,和数据库一一对应,dto是层与层之间交互的实体类,用来封装前端传来的数据。
由于没有前端,简单了解一下流程:
注册:
用户输入用户、密码、邮件进行注册,会发送验证码进行注册,验证码验证成功后将数据存储到数据库中。
image-20241120211610319
数据库表: userid 、username 、password、 email
发送邮件:
要实现发送邮箱验证,使用 spring mail
添加依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
|
用qq邮箱,配置邮箱客户端
image-20241120232744235
开启服务后就会有个授权码,要配置到yml文件中。
1
2
3
4
5
6
7
8
|
spring:
#邮件服务配置
mail:
host: smtp.qq.com #邮件服务器地址
protocol: smtp #协议
username: #发送邮件的邮箱也就是你开通服务的邮箱
password: #开通服务后得到的授权码
default-encoding: utf-8 #邮件内容的编码
|
接着就可以使用了,简单示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**
* 点击发送验证码给对应的邮箱
* @param email 邮箱
*/
@Override
public void sendEmail(String email) {
//生成对应的验证码取uuid的0-6位
String code = UUID.randomUUID().toString().substring(0,6);
// 设置邮箱要发送的信息:
String subject = "健康管理系统的验证码";
String message = "验证码为:"+code;
// 创建一个mailMessage对象
SimpleMailMessage mailMessage = new SimpleMailMessage();
// 设置发送的邮箱
mailMessage.setFrom("1320104286@qq.com");
mailMessage.setTo(email);
// 设置主题和内容
mailMessage.setSubject(subject);
mailMessage.setText(message);
// 发送邮件
mailSender.send(mailMessage);
}
|
注册基础功能实现
流程:
image-20241121001127648
邮箱验证
先完成邮件的验证(暂时是临时存储,以及随机的uuid进行注册)
Controller层
1
2
3
4
5
6
7
8
|
@PostMapping("/verify")
public Result add(@RequestBody EmailDto emailDto)
{
log.info("给邮箱{}发送验证码",emailDto.getEmail());
userService.sendEmail(emailDto.getEmail());
return Result.success("邮件已发送");
}
}
|
Service 层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 临时存储邮箱和对应的验证码
private Map<String,String> verifyCodes = new HashMap<>();
@Override
public void sendEmail(String email) {
//生成对应的验证码取uuid的0-6位
String code = UUID.randomUUID().toString().substring(0,6);
verifyCodes.put(email,code);
// 到时候这部分修改一下
String subject = "健康管理系统的验证码";
String message = "验证码为:"+code;
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom("1320104286@qq.com");
mailMessage.setTo(email);
mailMessage.setSubject(subject);
mailMessage.setText(message);
mailSender.send(mailMessage);
}
|
实现注册
实现注册:需要对密码进行处理,以及对数据库进行查询是否存在。
Controller
1
2
3
4
5
6
7
8
9
|
@PostMapping("/register")
public Result add(@RequestBody RegisterDto registerDto)
{ Integer Id = userService.register(registerDto);
if( Id== null)
{
return Result.error("用户名或邮箱已经存在!注册失败!");
}
return Result.success("用户创建成功,id为:"+Id);
}
|
service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/**
* 对包含 验证码的请求进行处理。
* @param registerDto web端传进来的参数。
* @return 返回 用户的id,感觉有点多余
*/
@Transactional(rollbackFor = Exception.class)
@Override
public Integer register(RegisterDto registerDto) {
// 如果已经存在用户了,则无法注册
if(userMapper.getByUserNameOrEmail(registerDto.getUsername()) != null)
{
return null;
}else{ // 没有找到用户
log.info("用户不存在,可以注册!");
//这里去获取user里的临时存储的code
String code = verifyCodes.get(registerDto.getEmail());
// 验证code是否正确且验证对应的email(上面获取code的过程就判断了)
if(code != null && code.equals(registerDto.getCode()))
{
// 这里要处理user的password !
String secretPass = PassUtils.encryptPassword(registerDto.getPassword());
registerDto.setPassword(secretPass);
userMapper.add(registerDto);
return userMapper.getIdByUserName(registerDto);
}
// 注册失败
return null;
}
}
|
这里的密码加密采用的Bcrypt 算法,会随机生成盐对密码进行加密,且不可逆,是最主流的密码加密算法。
密码加密:
需要导入依赖:
1
2
3
4
5
6
|
<!-- https://mvnrepository.com/artifact/org.mindrot/jbcrypt -->
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
|
编写PassUtils类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class PassUtils {
/**
* 加密密码
* @param plainPassword 明文密码
* @return 加密后的密码*/
public static String encryptPassword(String plainPassword) {
return BCrypt.hashpw(plainPassword,BCrypt.gensalt());
}
/**
* 验证密码
* @param plainPassword 明文密码
* @param encryptedPassword 加密后的密码
* @return 验证结果*/
public static boolean verifyPassword(String plainPassword, String encryptedPassword) {
return BCrypt.checkpw(plainPassword,encryptedPassword);
}
}
|
注册测试:
根据流程,需要接收前端的Email,封装到EmailDto类中,会发送验证码。
image-20241121003214150
对应的邮箱接收到了验证码,也就证明注册成功。
image-20241121003530208
查看数据库:证明注册成功。
image-20241121003621821
这里说明一下:BCrypt 加密后长度固定为60个字符。
登录
对应的登录流程也需要对密码进行校验,这里要学习的关键是jwt身份认证
Controller (这里应该为前端访问的用户设置header token = jwt,先将jwt返回给前端,由前端setheaders )
1
2
3
4
5
6
7
8
9
10
11
12
|
@PostMapping("/login")
public Result login(@RequestBody LoginDto loginDto)
{
log.info("登录的账号:{},{}",loginDto.getUsername(),loginDto.getPassword());
String jwt = userService.login(loginDto);
if(jwt != null)
{
return Result.success(jwt);
}
return Result.error("登录失败");
}
|
service (关键在于密码的验证)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* 登录接口
* @param loginDto 控制层传输过来的信息
* @return 返回jwt
*/
@Override
public String login(LoginDto loginDto) {
/*
验证账号和密码,并生成jwt进行返回
*/
// 这里应该换成dto,后面再修改,还有VO
// 密码加密再进行查询
UserDo userDo = userMapper.checkLogin(loginDto);
log.info("要登录的用户:{}",userDo);
if(PassUtils.verifyPassword(loginDto.getPassword(),userDo.getPassword()))
{
log.info("登录成功设置返回jwt");
// 并设置为普通用户
return JwtUtils.generateJwt(loginDto.getUsername(),"user");
}
log.info("账号或密码错误!");
return null;
}
|
Mapper层很简单:就是根据用户用户名查询对应的数据userDo.
jwt
简单来说 jwt令牌 就是一种身份认证,你只有带有对应的令牌才能有对应的权限。(结合拦截器使用,进行登录验证)
关于jwt的相关知识,可以看https://blog.csdn.net/shenyunmomie/article/details/139805325
和https://blog.csdn.net/weixin_43443913/article/details/140479283 进行了解,这里就不赘述了。
快速入门:
1
2
3
4
5
6
7
8
9
10
11
12
|
<!--jwt相关依赖 jdk1.8 以前就只需要导第一个-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
|
编写jwtUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@Data
@Component
@ConfigurationProperties(prefix = "jwt") // 从配置文件中获取相关的属性值
public class JwtUtils {
// 签名算法需要的密钥
private static long expiration = 12*60*60*1000; //有效期是12小时
private static SecretKey key = Jwts.SIG.HS256.key().build(); //密钥是随机生成的。
/**
* 生成token
* @param name 用户名
* @param subject 用户类型
* @return 返回token
*/
public static String generateJwt(String name, String subject)
{
String jwt = Jwts.builder()
.subject(subject) // 设置主题一般是用户类型
.issuedAt(new Date())
.claim("name",name)
.signWith(key)
.expiration(new Date(System.currentTimeMillis()+expiration)) // 设置有效期
.compact();
return jwt;
}
/**
*
* @param jws jws字符串
* @return 返回一个对象
*/
public static Object parseJwt(String jws)
{
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(jws);
}
}
|
然后再登录后根据其用户名生成一个jwt.
登录测试:
image-20241121005845573
拦截器
就是当没有带合法jwt的用户都会被拦截,只能访问登录注册页面。
编写登录拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
Gson gson = new Gson();
String jwt = req.getHeader("token");
// 1 判断是否存在 jwt 不存在则返回错误结果
if(StringUtil.isNullOrEmpty(jwt))
{
log.info("token为空,返回未登录信息!");
Result error = Result.error("NOT_LOGIN");
// 手动转成json对象 -- 阿里巴巴 的fastJson
String notLogin = gson.toJson(error);
resp.getWriter().write(notLogin);
return false;
}
// 5 判断jwt是否被篡改或者失效
try{
JwtUtils.parseJwt(jwt);
}catch (Exception e)
{
log.info("jwt被篡改或失效,返回登录信息");
Result error = Result.error("NOT_LOGIN");
// 手动转成json对象 -- 阿里巴巴 的fastJson
String notLogin = gson.toJson(error);
resp.getWriter().write(notLogin);
return false;
}
// 6 放行
log.info("令牌合法!放行!");
return true;
}
}
|
配置拦截器
1
2
3
4
5
6
7
8
9
10
|
@Configuration
public class LoginConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login","/register","/verify","/swagger-ui/index.html");
}
}
|
全局异常处理器
由于开发过程中多多少少会出现一些异常。因此可以设置一个全局异常管理器去管理没有被处理的异常,保证程序可以正常使用。
很简单就不赘述了。
1
2
3
4
5
6
7
8
9
10
11
12
|
@Slf4j
@RestControllerAdvice
public class GlobalException {
// 处理所有异常
@ExceptionHandler(Exception.class)
public Result ex(Exception e)
{
e.printStackTrace();
return Result.error("对不起,操作失败,请联系管理员!");
}
}
|
总结:
到此我们就完成了一个相对完整且基础的登录注册流程,使用了jwt\拦截器、异常处理、BCrypt进行密码加密存储以及邮箱验证。