Back

SpringBoot实现注册和登录

SpringBoot实现注册和登录

SpringBoot3 实现登录注册功能-1

仅仅是一枚新手实现功能的记录。仅是一个参考,还有很多不足,后续再完善。

该文主要是学习了简单的登录注册功能、邮箱验证、拦截器的设置。

后续还得学习上验证码的生成以及时效性、设置前端header、springBoot security(本来是有用到的,但是频繁出现小问题,决定先不用后续再学习。)还有邮箱验证登录等等。

也会有一些不同的注册思路需要学习。

流程、接口

简单提一嘴:dto\do : do也就是实体类,和数据库一一对应,dto是层与层之间交互的实体类,用来封装前端传来的数据。

由于没有前端,简单了解一下流程:

注册:

用户输入用户、密码、邮件进行注册,会发送验证码进行注册,验证码验证成功后将数据存储到数据库中。

image-20241120211610319
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
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
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-20241121003214150

对应的邮箱接收到了验证码,也就证明注册成功。

image-20241121003530208
image-20241121003530208

查看数据库:证明注册成功。

image-20241121003621821
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
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进行密码加密存储以及邮箱验证。

author: rose
Built with Hugo
Theme Stack designed by Jimmy
© Licensed Under CC BY-NC-SA 4.0