使用 Spring Email 和 Thymeleaf 技术,向新注册用户发送激活邮件(二)

本篇博客对应“2.2 开发注册功能”小结
对应视频:
开发注册功能
开发注册功能-续

注册功能是相对比较复制的功能,对于一个相对复杂的功能,可以把这个功能进行拆解。把这个功能的流程想清楚,就知道怎么拆解了:
在这里插入图片描述
也可以按照请求进行拆解,注册过程一共发生三次请求,对应服务器产生三次响应:
在这里插入图片描述

我们一次请求,一次请求的把它搞定,就可以开发出整个功能了。
每一次请求按照:数据访问层、业务层、视图层。三层架构进行实现。
当然,有一些功能可能只有其中的一层或两层,写代码的时候就知道了。

访问注册页面

点击顶部区域的链接,打开注册页面。

新建一个LoginController
在这里插入图片描述

创建处理获取注册页面,返回注册视图的方法

    @RequestMapping(path = "/register", method = RequestMethod.GET)
    public String getRegisterPage() {
        return "/site/register";
    }

需要对register模板进行修改:
在这里插入图片描述

提交注册数据

准备工作

导入工具包Commons Lang,该工具包可以帮助我们判断字符串、集合是否为空、数据是否符合规范等其他功能。
引入mavaen依赖

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>

引入jar包之后,还需要在配置文件中,把网站的域名配置号。

为什么要配置域名呢?

因为注册的过程中要发邮件,邮件里面得带上激活链接,这个激活链接得链接到我们的网站,而这个链接在开发、 测试、上线阶段是不同的,所以我们需要把他做成可配的。
application.properties文件中加入:

community:
    path:
        domain: http://localhost:8080

这三个都是自定义的,在程序里面用 @Value(“${属性名}”) 获取即可。

再写一个工具类,在工具类里面提供两个方法,注册的时候方便调用。
util包下新建一个工具类CommunityUtil
在这里插入图片描述

提供静态的方法:
生成随机字符串:生成激活码,每次是一个随机的字符串;给上传头像或上传文件的功能,每次上传时需要给图片或文件生成一个随机的名字,防止重复。使用java util包下的UUID类生成随机字符串,这是java自带的功能。
UUID生成的随机字符串会有横线,我们不需要

    // 生成随机字符串
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

除此之外,我们还要封装一个方法,叫做MD5加密,采用MD5算法对密码进行加密。

为什么要加密?

因为用户在注册的时候,提交的密码是明文形式的,我们在存储到数据库中,需要将密码加密,这样,即使数据库泄露,别人也不会知道用户的密码是什么。
MD5加密算法的特点:

  • 任意长度的信息,经处理后,输出为128位的信息
  • 不同的输入得到不同的结果,唯一性
  • 不可逆性,由于通过散列函数,hash算法,在计算过程中,原文的信息是部分丢失了的。
    MD5加密算法只能加密,不能解密,而且每次加密都是这个值。
    例如: hello --> dhasdhqofhasjfdhas
    每次hello对应的密文都是dhasdhqofhasjfdhas,而且不可以解密。
    如果用户密码设置的过于简单,比如就是hello。盗取密码的黑客也会知道dhasdhqofhasjfdhas,因为他有一个简单密码的库,hello啊生日啊都包含在内,他也会把这个明文加密成密文,所以进行破解。
    因此不管用户输入的是什么密码,都加上一个随机的字符串,例如hello + 3edr4, 那么加密之后的密文就是:dasdjoqiwhdoqwhfdsh(假设),由于3edr4是随机的,黑客密码库中没有对应的明文-密文记录,所以是很难进行破解的。提高了用户密码的安全性。

为什么要加盐?

加盐表示的含义是加噪声,因为人们在设置密码时,通常都是在某个长度之内,不会过于复杂,虽然MD5本时时不可逆的,无法通过密文知道原文是什么。但是攻击者可以构造一个对照表,将明文和密文全部列举出来存到一个对照表中,然后采用穷举法,一个一个比较密文是哪一个,如果有密文是相同的,就可以去对照表中查明文,由于前面说的MD5唯一性的特性,这个明文一定是用户输入的密码,这就进行了破解。
加盐操作可以预防这个攻击方式,通过加噪声数据,可以极大的增加密码的随机性和复杂性,如果要使用对照表穷举的方式,需要消耗大量的计算机资源,这在现实中是不可行的。

用spring自带的工具就可以实现MD5加密,org.springframework.util.DigestUtils。加密方法如下:

    // MD5加密
    // hello -> abc123def456
    // hello + 3e4a8 -> abc123def456abc
    public static String md5(String key) {
        /*
        * 判断key是否为空,空串、null、空格
        * */
        if (StringUtils.isBlank(key)) {
            return null;
        }
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }

有了这些准备之后,可以开发真正的注册业务了。

通过表单提交数据

在UserService中写逻辑

需要注入的对象:

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    @Value("${community.path.domain}")
    private String domain;

    @Value("${server.servlet.context-path}")
    private String contextPath;

创建register方法:

public Map<String, Object> register(User user) {}

空值处理

        // 空值处理
        if (user == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }
        if (StringUtils.isBlank(user.getUsername())) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(user.getPassword())) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }
        if (StringUtils.isBlank(user.getEmail())) {
            map.put("emailMsg", "邮箱不能为空!");
            return map;
        }

服务端验证账号是否已存在、邮箱是否已存在

        User u = userMapper.selectByName(user.getUsername());
        if (u != null) {
            map.put("usernameMsg", "该账号已存在!");
            return map;
        }

        // 验证邮箱
        u = userMapper.selectByEmail(user.getEmail());
        if (u != null) {
            map.put("emailMsg", "该邮箱已被注册!");
            return map;
        }

注册用户

        // 注册用户
        user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
        user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
        user.setType(0);
        user.setStatus(0);
        user.setActivationCode(CommunityUtil.generateUUID());
        user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
        user.setCreateTime(new Date());
        userMapper.insertUser(user);

知道了为什么要加盐之后,我们来看为什么需要 user.setSalt(),将这个salt值保存下来?

因为在用户成功注册,下一次登录的时候,用户需要再次输入账号以及明文的密码,但此时,数据库存储的是经MD5加密之后的密文,而且MD5是不可逆的,所以,需要将这个该用户的盐值取出来,然后和用户明文密码拼接,经过MD5算法再一次加密得到一个密文,将该密文与数据库中这个用户对应的密文进行字符串对比,如果相等,说明用户密码正确,予以登录,这是由于MD5加密算法的唯一性可以做出的判断。
总结:保存salt值是为了下一次登录密码比较使用。

为什么需要setActivationCode,设置激活码?

因为,服务

如果没有激活码的话,人为地构造一个请求也可以进行账号激活,就失去了通过邮件激活的意义,因为用户大可以用一个假邮箱注册,但是通过构造url进行注册。所以,只有激活码这种方式,激活码是一个随机字符串,用户不好认为构造,只能写一个真实的邮箱,然后点击邮件中的激活链接,邮件中的激活链接已经带上了用户id 和 激活码。所以直接点击,然后服务器会获取请求,从数据库对用的用户id取出激活码,判断这两个激活码是否一致。

服务端发送激活邮件

        // 激活邮件
        Context context = new Context();
        context.setVariable("email", user.getEmail());
        // http://localhost:8080/community/activation/101/code
        String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
        context.setVariable("url", url);
        String content = templateEngine.process("/mail/activation", context);
        mailClient.sendMail(user.getEmail(), "激活账号", content);

在这里插入图片描述
通过上下文对象、模板、模板引擎将动态数据渲染到html页面中,生成视图(html页面),就是字符串。
将字符串设置位邮件的content,然后发送给用户。

controller层 LoginController中添加register方法:

注册失败返回注册页面需要将错误信息现实在注册页面上。

激活注册账号

    public int activation(int userId, String code) {
        User user = userMapper.selectById(userId);
        if (user.getStatus() == 1) {
            return ACTIVATION_REPEAT;
        } else if (user.getActivationCode().equals(code)) {
            userMapper.updateStatus(userId, 1);
            return ACTIVATION_SUCCESS;
        } else {
            return ACTIVATION_FAILURE;
        }
    }

点击邮件中的链接,访问服务端的激活服务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值