2.7 登陆与登出

88 阅读1分钟

登陆页面

新建views/signin.ejs,比较简单,就是个包含了用户名密码的表单。

<%- include('header') %>

<div class="ui grid">
  <div class="four wide column"></div>
  <div class="eight wide column">
    <form class="ui form segment" method="post" enctype="multipart/form-data" action="/signin">
      <div class="field required">
        <label>用户名</label>
        <input placeholder="用户名" type="text" name="name" minlength="1" maxlength="10" required>
      </div>
      <div class="field required">
        <label>密码</label>
        <input placeholder="密码" type="password" name="password" minlength="6" maxlength="20" required>
      </div>
      <input type="submit" class="ui button fluid" value="登录">
    </form>
  </div>
</div>

<%- include('footer') %>

在src/user/user.controller.ts中增加signin的GET接口:

@Get("signin")
signinPage(@Render() render: Render) {
  return render("signin", {});
}

这时访问http://localhost:8000/signin,已经能看到页面了:

image.png

登陆接口

修改src/user/user.dto.ts,新增一个SigninDto:

export class SigninDto {
  @IsString()
  @MinLength(1)
  @MaxLength(10)
  name: string;

  @IsString()
  @MinLength(6)
  @MaxLength(20)
  password: string;
}

在src/user/user.service.ts新增一个方法:

 findByName(name: string) {
    return this.userModel.findOne({ name });
  }

在src/user/user.controller.ts中增加signin的POST接口:

 /** 登陆 */
  @Post("signin")
  async signin(
    @FormData() params: FormDataFormattedBody<SigninDto>,
    @Res() res: Response,
    @Flash() flash: Flash,
  ) {
    const fields = params.fields;
    try {
      await validateParams(SigninDto, fields);
    } catch (error) {
      flash("error", error.message);
      return res.redirect(REDIRECT_BACK);
    }
    const username = fields.name;
    const user = await this.userService.findByName(username);
    let error = "";
    if (!user) {
      this.logger.error(`用户名${username}不存在`);
      error = "用户名或密码错误";
    } else if (fields.password !== user.password) {
      this.logger.error(`密码${fields.password}不匹配`);
      error = "用户名或密码错误";
    }
    if (error) {
      flash("error", error);
      return res.redirect(REDIRECT_BACK);
    }

    assert(user);
    flash("userId", user.id!);
    flash("success", "登录成功");

    this.logger.info(`用户${username}登陆成功`);
    // 跳转到主页
    res.redirect("posts");
  }

FormData这个装饰器也引自oak_nest。

登出接口

前文说过,登出接口及删除操作不推荐使用GET,而应该用DELETE或者POST,为什么呢?因为GET请求更易于CSRF攻击,虽然后者也没法避免。

@Get("signout")
async signout(@Res() res: Response, @Flash() flash: Flash) {
  flash("userId", "");
  flash("success", "登出成功");
  res.redirect('posts');
}

验证

  1. 用正确的信息登陆

image.png

  1. 用错误的信息登陆:

image.png

  1. 在登陆成功后,退出登陆

image.png

  1. 反复切换登陆登出,查看登陆状态是否正常

作业

试想一下,假设用户已经登陆,但仍访问注册页面,是否不合适?

又或者用户没有登陆,但要访问必须登陆者才能访问的页面,怎么办?