登录认证(1):登录的基本逻辑及实现思路

52 阅读5分钟

登录

在当今的大部分网站、应用、游戏中,为了确保资源的安全性和隐私保护,通常需要用户先进行身份验证(即登录),然后才能使用特定的功能和服务。这样的做法不仅增加了系统安全性,还能够根据用户的偏好提供个性化体验

登录基本逻辑

而登录的基本逻辑很简单:用户输入用户名密码,并发起登录请求,服务端接收到登录请求后,从数据库中查询对应的用户信息,如果能根据用户输入的用户名和密码查询到对应的用户信息,则登录成功,反之登录失败。

简单登录实例

下面是一个简单的员工登录的后端代码示例:

LoginInfo登录结果类

/**
 * 登录成功结果封装类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginInfo {
    // 登录员工的id
    private Integer id;
    private String username;
    private String name;
    // 返回的JWT令牌
    private String token;
}

在登录认证功能中,服务端并不需要响应整个Emp对象,只需要给客户端响应:1.登录员工的唯一标识符id、2.登录员工的用户名、3.登录员工的姓名、4.登录员工的token(用于认证,后面再讲),对此建议根据登录需要的数据封装一个专门用于返回登录结果的LoginInfo类。

Controller

/**
 * 登录控制器
 */
@RestController
@RequestMapping("/login")
@Slf4j
public class LoginController {

    private final EmpService empService;

    @Autowired
    public LoginController(EmpService empService) {
        this.empService = empService;
    }
    
    @PostMapping
    public Result<LoginInfo> login(@RequestBody Emp emp) {
        log.info("员工{}正在登录", emp.getUsername());
        LoginInfo loginInfo = empService.login(emp);
        if (loginInfo != null) {
            log.info("员工{}登录成功", emp.getUsername());
            return Result.success(loginInfo);
        }
        log.info("员工{}登录失败", emp.getUsername());
        return Result.error("用户名或密码错误");
    }
}

Service

/**
 * 员工登录
 * @param emp 登录请求数据封装的Emp实体对象
 * @return LoginInfo员工登录信息
 */
@Override
public LoginInfo login(Emp emp) {
    Emp empLogin = empMapper.getUserByUsernameAndPassword(emp);
    if (empLogin != null) {
        return new LoginInfo(empLogin.getId(), empLogin.getUsername(), empLogin.getName(), "");
    }
    return null;
}

Mapper

/**
 * 员工登录-根据用户名和密码匹配
 * @param emp 登录请求数据封装的Emp实体对象
 * @return 根据username和password查询的Emp对象
 */
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getUserByUsernameAndPassword(Emp emp);

用户登录时先输入用户名和密码,然后客户端向服务端发起登录请求Controller接收请求,将请求数据封装到Emp对象中;然后调用Service中的login方法,login方法将调用mapper中的getUserByUsernameAndPassword方法,将根据请求数据中的用户名和密码从数据库中查询对应的员工,如果能够查询到,就将其数据封装为Emp对象返回给Service,Service将其封装为LoginInfo对象再返回给controller,最后将LoginInfo对象封装为统一响应结果Result响应给客户端。

登录校验

上述代码就是一个简单的登录逻辑,但是在这个逻辑中有一个bug:这个登录功能只实现了登录的功能,但是并没有对登录进行验证,此时用户仍然可以不登录就可以使用特定功能。换句话说,这个登录功能只是一个摆设。所以为了真正实现安全完善的登录功能,我们还要对用户的登录进行校验

登录校验是指:服务端在接收到客户端发起的请求之后,先要对请求进行校验校验发起这个请求的用户是否登录

统一拦截

如果用户已经登录,那么服务端就正常的接收请求,执行业务操作即可;假如用户并未登录,此时就不能允许其执行相关业务操作,并给客户端响应一个错误结果,并跳转至登录界面让用户进行登录。

但是这个校验并不能写在Controller方法中,因为一个程序少说也得有几百个Controller方法处理客户端请求,如果将校验登录逻辑写在Controller方法中,就算逻辑是一样的,也会造成代码的大量重复、臃肿

所以说我们需要在请求到达服务端之前,对请求进行统一拦截,在统一拦截中对登录进行校验。

登录标记

我们向服务端发起请求是基于HTTP协议id,但是我们知道,HTTP协议是一个无状态的协议(详情可以查看这篇文章:Http请求响应——请求,每一次HTTP请求都是独立的,下一次的请求并不会携带上一次请求的数据。换句话说:当我们发起登录请求成功登录之后,再次发起其他业务功能的请求时,服务端是不知道用户是否登录了的。这是因为其他请求并没有携带登录成功之后服务端响应的数据。为了判断用户是否登录,我们就需要在用户登录成功之后,将其登录信息存储起来,标记该用户已经成功登录;然后使用会话技术在登录成功的后续每一次请求中都获取该标记。

如上文所讲解,登录的基本逻辑就应该如图所示:

image.png

但是如何使用会话技术实现多次请求中的通信,从而使得登录请求之后的后续请求都获得登录成功的标记,从而成功请求呢?且听下回分解。