登录认证,那什么是认证呢?
- 所谓认证指的就是根据用户名和密码校验用户身份的这个过程,认证成功之后,我们才可以访问系统当中的信息,否则就拒绝访问。
在前面的案例中,我们已经实现了部门管理、员工管理的基本功能,但是大家会发现,我们并没有登录,就直接访问到了Tlias智能学习辅助系统的后台。 这是不安全的,所以我们今天的主题就是登录认证。 最终我们要实现的效果就是用户必须登录之后,才可以访问后台系统中的功能。
- 在登录页面中,用户要输入用户名,输入密码,然后接下来点击登录,如果输入的用户名或者密码错误,此时就会停留在登录页面当中,并且提示出对应的错误信息;
- 如果用户名和密码都是正确的,我们点击登录按钮,此时才会进入到系统当中,进入到系统之后,我们就可以来操作系统当中的数据了。
要想实现用户登录的功能,我们需要两步操作来实现:
- 首先第一步,我们要先来完成最为基础的登录功能,这步操作就是来判断用户输入的用户名和密码是否正确;
- 第二步,我们要来完成登录校验操作:登录校验指的就是当我们浏览器发起一个请求之后,服务端需要判断这个用户是否登录了,如果登录了,则执行正常的业务操作;如果没有登录,就需要跳转到登录界面,让他完成登录之后再来访问这个系统。
注意:登录校验是整个登录功能的核心!
1. 登录功能
1.1 需求
在登录界面中,我们可以输入用户的用户名以及密码,然后点击 "登录" 按钮就要请求服务器,服务端判断用户输入的用户名或者密码是否正确。如果正确,则返回成功结果,前端跳转至系统首页面。
思考:在登录的时候,我们需要校验用户名和密码是否正确,这条SQL语句该怎么写?
回答:其实非常简单,逆向思考,就是根据用户名和密码来查询员工,如果根据用户名和密码,我查询到了员工,就说明用户名和密码是正确的;如果根据用户名和密码,我没有查询到员工,就说明用户名或密码错误。
SQL语句:
-- 登录时校验用户名和密码
select * from emp where username = '' and password = '';
思考:根据这条SQL语句查询出来的员工有没有可能是多个?
回答:不可能,因为之前我们创建emp员工表的时候,针对于username这个字段,我们添加的是unique唯一约束,所以username这个它是不可能重复的,因此最终我们查询出来的数据,最多只会有一条。
1.2 接口文档
- 我们参照接口文档来开发登录功能
基本信息
- 请求参数
参数格式:application/json
参数说明:
名称 | 类型 | 是否必须 | 备注 |
---|---|---|---|
username | string | 必须 | 用户名 |
password | string | 必须 | 密码 |
请求数据样例:
- 响应数据
参数格式:application/json
参数说明:
名称 | 类型 | 是否必须 | 默认值 | 备注 | 其他信息 |
---|---|---|---|---|---|
code | number | 必须 | 响应码, 1 成功 ; 0 失败 | ||
msg | string | 非必须 | 提示信息 | ||
data | string | 必须 | 返回的数据 , jwt令牌 |
响应数据样例:
1.3 登录 - 思路分析
说明:目前我们先不考虑返回JWT令牌,目前我们只是给前端响应成功还是失败。
首先第一件事,我们肯定需要在Controller当中定义一个方法来处理这个登录请求,此时需要思考登录这个请求方法我们应该定义哪一个Controller当中?
是DeptController,还是EmpController,还是UploadController,都不是,原因:DeptController的请求路径是/depts,EmpController的请求路径是/emps,UploadController的请求路径是/upload,并且UploadController是用来进行文件上传的。
因此,我们需要再定义一个Controller,专门用来处理登录请求,取名叫LoginController,然后我们在LoginController当中再来定义一个方法来处理登录的请求,由于登录的请求方式是一个POST请求,所以我们需要在该方法上面加上@PostMapping,而且请求格式的参数是一个JSON格式的请求参数,最终服务端要把JSON格式的参数封装到一个对象当中,所以我们要在方法的形参上加上@RequestBody注解来接收前端传递过来的JSON格式的数据并填充到实体类中,
登录服务端的核心逻辑就是:接收前端请求传递的用户名和密码 ,然后再根据用户名和密码查询用户信息,如果用户信息存在,则说明用户输入的用户名和密码正确。如果查询到的用户不存在,则说明用户输入的用户名和密码错误。
1.4 功能开发
LoginController
package com.gch.controller;
import com.gch.pojo.Emp;
import com.gch.pojo.Result;
import com.gch.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/login")
/**
登录功能控制器
*/
public class LoginController {
@Autowired
private EmpService empService;
/**
* 处理登录请求
* @param emp 员工对象
* @return 响应
*/
@PostMapping
public Result login(@RequestBody Emp emp) {
// 1.记录日志
log.info("处理该用户登录请求,username:{}, password:{}", emp.getUsername(), emp.getPassword());
// 2.调用service进行查询,查询/校验该用户信息是否存在
Emp e = empService.login(emp);
// 3.响应
return e != null ? Result.success(e) : Result.error("用户名或密码错误");
}
}
EmpService
package com.gch.service;
import com.gch.pojo.Emp;
import com.gch.pojo.PageBean;
import java.time.LocalDate;
import java.util.List;
/**
员工业务规则
*/
public interface EmpService {
/**
* 处理该用户的登录请求
* @param emp 员工对象
* @return 根据前端传递的用户信息返回查询到的员工对象
*/
Emp login(Emp emp);
}
EmpServiceImpl
package com.gch.service.impl;
import com.gch.mapper.EmpMapper;
import com.gch.pojo.Emp;
import com.gch.pojo.PageBean;
import com.gch.service.EmpService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
员工业务实现类
*/
@Slf4j
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
/**
* 处理该用户的登录请求
* @param emp 员工对象
* @return 根据前端传递的用户信息返回查询到的员工对象
*/
@Override
public Emp login(Emp emp) {
// 1.调用Mapper层查询该员工信息
Emp loginEmp = empMapper.getByUsernameAndPassword(emp);
// 2.返回查询结果给Controller
return loginEmp;
}
}
EmpMapper
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.time.LocalDate;
import java.util.List;
/**
员工管理
*/
@Mapper
public interface EmpMapper {
/**
* 处理该用户的登录请求
* 根据用户名和密码查询员工
* @param emp 员工对象
* @return 根据前端传递过来的请求参数中的用户信息查询员工是否存在
*/
@Select("select * from tlias.emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
}
1.5 测试
功能开发完毕后,我们就可以启动服务,打开Postman进行测试了。
发起POST请求,访问:http://localhost:8080/login
Postman测试通过了,那接下来,我们就可以结合着前端工程进行联调测试。
先退出系统,进入到登录页面:
在登录页面输入账户密码:
故意把密码输错,看登陆页面会不会提示错误!
注意:提示错误的信息,就是在Controller中响应给前端的信息!
登录成功之后进入到后台管理系统页面:
2. 登录校验
2.1 问题分析
我们已经完成了基础登录功能的开发与测试,并且完成了前后端联调,在我们登录成功后就可以进入到后台管理系统中进行数据的操作。
但是当我们在浏览器中新的页面上输入地址:http://localhost:9528/#/system/dept
,也就是复制了已经登录进入后台管理系统的页面地址,接着退出后台管理系统,然后关闭该页面,再次打开一个新的标签页,然后粘贴进入刚才已经登录进入后台管理系统的页面地址,发现没有登录仍然可以进入到后端管理系统页面。
而真正的登录功能应该是:登陆后才能访问后端系统页面,不登陆则跳转登陆页面进行登陆。
这是异常现象!
问题:在未登录情况下,我们也可以直接访问部门管理、员工管理等功能。
为什么会出现这个问题?
- 其实原因很简单,就是因为针对于我们当前所开发的部门管理、员工管理以及文件上传等相关接口来说,我们在服务器端并没有做任何的判断,没有去判断用户是否登录了。所以无论用户是否登录,都可以访问部门管理以及员工管理的相关数据。所以我们目前所开发的登录功能,它只是徒有其表。而我们要想解决这个问题,我们就需要完成一步非常重要的操作:登录校验。
什么是登录校验?
-
所谓登录校验,指的是我们在服务器端接收到浏览器发送过来的请求之后,首先我们要对请求进行校验。先要校验一下用户登录了没有,如果用户已经登录了,就直接执行对应的业务操作就可以了;如果用户没有登录,此时就不允许他执行相关的业务操作,直接给前端响应一个错误的结果,最终跳转到登录页面,要求他登录成功之后,再来访问对应的数据。
了解完什么是登录校验之后,接下来我们分析一下登录校验大概的实现思路。
首先我们在宏观上先有一个认知,然后再来逐个击破:
前面在讲解HTTP协议的时候,我们提到HTTP协议是无状态协议。什么又是无状态的协议?
- 所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互,是基于HTTP协议的,也就意味着现在我们通过浏览器来访问了登陆这个接口,实现了登陆的操作,接下来我们在执行其他业务操作时,服务器也并不知道这个员工到底登陆了没有。因为HTTP协议是无状态的,两次请求之间是独立的,所以是无法判断这个员工到底登陆了没有。
那应该怎么来实现登录校验的操作呢?具体的实现思路可以分为两部分:
- 在服务端要想判断这个员工是否已经登录,我们就需要在员工登录成功之后,要存储这么一个登录成功的标记,一旦员工登陆成功,那我们就存储登录成功的这样一个标记,记录用户已经登录成功的标记;
- 然后接下来我们在每一个接口方法执行之前,先来做一个条件判断,来判断一下这个员工到底登录了没有,如果这个员工已经登录了,那接下来,我们就执行正常的业务操作就可以了;如果这个员工没有登录,我们在这一块儿直接返回错误的信息,把这个错误的信息返回给前端,前端拿到这个错误的信息之后,它会自动的跳转到登陆页面。
我们程序中所开发的查询功能、删除功能、添加功能、修改功能,都需要使用以上套路进行登录校验。此时就会出现:相同代码逻辑,每个功能都需要编写,就会造成代码非常繁琐。
为了简化这块操作,我们可以使用一种技术:统一拦截技术。
- 通过统一拦截的技术,我们可以来拦截浏览器发送过来的所有的请求,拦截到这个请求之后,就可以通过请求来获取之前所存入的登录标记,在获取到登录标记,且标记为登录成功,就说明员工已经登录了。如果已经登录,我们就直接放行(意思就是可以访问正常的业务接口了)。
在员工登录成功后,需要将用户登录成功的信息存起来,记录用户已经登录成功的标记。
在浏览器发起请求时,需要在服务端进行统一拦截,拦截后进行登录校验。
我们要完成以上登录校验的操作,会涉及到Web开发中的两个技术:
-
会话技术
-
统一拦截技术
异常处理的方案!