登录功能
思路分析
登录服务端的核心逻辑就是:接收前端请求传递的用户名和密码 ,然后再根据用户名和密码查询用户信息,如果用户信息存在,则说明用户输入的用户名和密码正确。如果查询到的用户不存在,则说明用户输入的用户名和密码错误。
代码实现
LoginController
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp){
Emp e = empService.login(emp);
return e != null ? Result.success():Result.error("用户名或密码错误");
}
}
EmpService
public interface EmpService {
/**
* 用户登录
* @param emp
* @return
*/
public Emp login(Emp emp);
//省略其他代码...
}
EmpServiceImpl
@Slf4j
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public Emp login(Emp emp) {
//调用dao层功能:登录
Emp loginEmp = empMapper.getByUsernameAndPassword(emp);
//返回查询结果给Controller
return loginEmp;
}
//省略其他代码...
}
EmpMapper
@Mapper
public interface EmpMapper {
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time " +
"from emp " +
"where username=#{username} and password =#{password}")
public Emp getByUsernameAndPassword(Emp emp);
//省略其他代码...
}
登录校验
为什么需要登录校验
我们已经完成了基础登录功能的开发与测试 但是我们发现没有登录仍然可以进入到其他页面。
而真正的登录功能应该是:登陆后才能访问其他页面,没登陆则跳转登陆页面进行登陆
思路分析
那么我们怎么实现呢?
- 可以使用会话技术+拦截器来实现这个功能
- 会话技术将用户登录信息保存方便后续判断用户是否登录
- 而拦截器对所有除了登录页面之外的页面进行拦截,然后通过你使用的会话技术来判断用户是否登录,实现了登陆后才能访问其他页面(放行),没登陆则跳转登陆页面进行登陆(拦截)
三种会话技术
cookie(不常用)
设置的cookie,通过响应头Set-Cookie响应给浏览器,并且浏览器会将Cookie,存储在浏览器端。
-
缺点:
-
移动端APP(Android、IOS)中无法使用Cookie
-
不安全,用户可以自己禁用Cookie
-
Cookie不能跨域
-
现在的项目,大部分都是前后端分离的,前后端最终也会分开部署,前端部署在服务器 192.168.150.200 上,端口 80,后端部署在 192.168.150.100上,端口 8080,这个时候这个Cookie是不能使用的,因为Cookie无法跨域
-
-
session(传统方案)
介绍
Session,它是服务器端会话跟踪技术,所以它是存储在服务器端的。而 Session 的底层其实就是基于我们刚才所介绍的 Cookie 来实现的。
session的存储
服务器端在给浏览器响应数据的时候,它会将 Session 的 ID 通过 Cookie 响应给浏览器。其实在响应头当中增加了一个 Set-Cookie 响应头。这个 Set-Cookie的名字是固定的 JSESSIONID 代表的服务器端会话对象 Session 的 ID。浏览器会自动识别这个响应头,然后自动将Cookie存储在浏览器本地。
代码实现
<!--hutool-用来将对象转json--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.17</version> </dependency>
WebConfig:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加loginCheckInterceptor拦截器,并且指定拦截所有路径除了login请求
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
LoginCheckInterceptor:
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
// 使用session会话技术
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Emp emp = (Emp)request.getAttribute("emp");//获取session中的用户数据
//判断session中用户是否存在
if(emp == null){//如果没登陆就进行拦截
//向前端响应错误信息
//设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
response.setContentType("application/json;charset=utf-8");
//封装Result返回给前端,统一错误信息
Result result = Result.error("NOT_LOGIN");
String json = JSONUtil.toJsonStr(result);
response.getWriter().write(json);
return false;
}
return true;
}
}
LoginController:
@Slf4j
@RestController
public class LoginController {
@Autowired
private EmpService empService;
//session会话技术
@PostMapping("/login")
public Result login(@RequestBody Emp emp, HttpSession session){
Emp e = empService.login(emp);
//用户登录校验
if(e == null){//账号密码错误,原因:数据库查不到数据返回null
return Result.error("用户名或密码错误");
}
//账号密码都正确
//保存session方便后续别的业务校验用户是否登录过
session.setAttribute("emp",e);
return Result.success();
}
}
优缺点
-
优点:Session是存储在服务端的,安全
-
缺点:
-
服务器集群环境下无法直接使用Session
-
比如你有三个集群1,2,3,前端发请求进行论巡,先集群1然后保存用户信息,然后再发请求集群2,这个时候因为cookie和session不能在集群下使用做不到俩个集群直接的cookie和session共享
-
-
移动端APP(Android、IOS)中无法使用Cookie
-
用户可以自己禁用Cookie
-
Cookie不能跨域
-
解决这些问题可以使用jwt令牌
jwt(推荐)
这里的数据存储是存储在浏览器的localStorage中
-
优点:
-
支持PC端、移动端
-
解决集群环境下的认证问题
-
减轻服务器的存储压力(无需在服务器端存储)
-
介绍
JWT的组成: (JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)
-
第一部分:Header(头), 记录令牌类型、签名算法等。
-
例如:{"alg":"HS256","type":"JWT"}
-
-
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。
-
例如:{"id":"1","username":"Tom"}
-
-
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。
思路分析
-
在浏览器发起请求来执行登录操作,此时会访问登录的接口,如果登录成功之后,我们需要生成一个jwt令牌,将生成的 jwt令牌返回给前端。
-
前端拿到jwt令牌之后,会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端。
-
服务端统一拦截请求之后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接拒绝访问,如果带过来了,还要校验一下令牌是否是有效。如果有效,就直接放行进行请求的处理。
具体实现
<!-- JWT依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!--hutool-用来将对象和json之间进行转换--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.17</version> </dependency>
WebConfig:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加loginCheckInterceptor拦截器,并且指定拦截所有路径除了login请求
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
LoginCheckInterceptor:
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
//使用jwt令牌技术
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求头中的令牌(token)
String token = request.getHeader("token");
//判断token是否存在
if(!StringUtils.hasLength(token)){//token不存在
log.info("token不存在");
//向前端响应错误信息
//设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
response.setContentType("application/json;charset=utf-8");
//封装Result返回给前端,统一错误信息
Result result = Result.error("NOT_LOGIN");
String json = JSONUtil.toJsonStr(result);
response.getWriter().write(json);
return false;
}
try {
//解析jwt是会失败的,失败汇报出异常,这里进行处理
JwtUtils.parseJWT(token);
} catch (Exception e) {
log.info("令牌解析失败!");
//向前端响应错误信息
//设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
response.setContentType("application/json;charset=utf-8");
//封装Result返回给前端,统一错误信息
Result result = Result.error("NOT_LOGIN");
String json = JSONUtil.toJsonStr(result);
response.getWriter().write(json);
return false;
}
return true;
}
}
LoginController:
@Slf4j
@RestController
public class LoginController {
@Autowired
private EmpService empService;
//jwt令牌实现
@PostMapping("/login")
public Result login(@RequestBody Emp emp){
Emp e = empService.login(emp);
//用户登录校验
if(e == null){//账号密码错误,原因:数据库查不到数据返回null
return Result.error("用户名或密码错误");
}
//账号密码都正确
//构建jwt令牌
Map<String, Object> claims = BeanUtil.beanToMap(e);
String jwt = JwtUtils.generateJwt(claims);
log.info("登录生成的token令牌为:"+jwt);
return Result.success(jwt);
}
}