菜鸟之路Day39一一登录

简介: 本文介绍了登录功能的实现及其相关技术细节,包括会话管理、令牌认证和异常处理等内容。作者通过 Java 实现了一个基于用户名和密码的登录接口,调用服务层和数据库层完成用户验证。同时,文章深入探讨了三种会话跟踪技术:Cookie、Session 和 JWT 令牌。在 JWT 部分,详细讲解了其生成与校验流程,实现了登录成功后返回 JWT 令牌的功能。此外,文章还介绍了过滤器(Filter)和拦截器(Interceptor)的概念及应用,演示了如何利用它们实现登录校验。最后,为解决前后端交互中异常响应不统一的问题,定义了一个全局异常处理器 将系统异常以统一的 JSON 格式返回给前端。

菜鸟之路Day39一一登录

作者:blue

时间:2025.6.4

0.概述

文章内容学习自黑马程序员BV1m84y1w7Tb

1.登录功能

登录功能可以抽象为以用户的username和password为查询条件,查询用户信息,看是否有匹配的用户,如果有,则登录成功,如果没有则登录失败

LoginController

@RestController
@Slf4j
public class LoginController {
   
    @Autowired
    EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
   
        log.info("用户登录{}",emp);
        Emp loginemp = empService.login(emp);
        if(loginemp != null){
   
            return Result.success();
        }
        else return Result.error("用户名或密码错误");
    }
}

EmpService

@Override
public Emp login(Emp emp) {
   
    return empMapper.login(emp);
}

Mapper

@Select("select * from emp where username = #{username} and password = #{password}")
Emp login(Emp emp);

2.登录校验

image-20250604211239722.png

2.1会话技术

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

会话跟踪方案:

客户端会话跟踪技术:Cookie

服务端会话跟踪技术:Session

令牌技术

image-20250604211849159.png

2.2cookie技术

Cookie 是由服务器生成、存储在客户端(浏览器)的小型文本文件,用于跟踪用户状态、存储用户信息(如登录凭证、偏好设置等)。它通过HTTP 请求和响应头在客户端与服务器之间传递,实现状态管理。

Cookie 的生命周期主要包括 创建、发送、存储、更新、删除 五个阶段

image-20250604222730968.png

1.服务器创建 Cookie 并发送给客户端

  • 触发场景:用户首次访问网站、登录 / 注册、提交表单等需要标识用户的操作。
  • 技术实现:
    • 服务器在响应中通过 Set-Cookie 响应头 向浏览器发送 Cookie 信息

2.客户端存储 Cookie

  • 浏览器接收到Set-Cookie响应后,会将 Cookie 存储在本地文件或内存中,具体规则如下:
    • 会话级 Cookie(非持久化):未设置 Expires/Max-Age,仅在浏览器会话期间有效,关闭浏览器后删除。
    • 持久化 Cookie:设置了过期时间,存储在本地硬盘,到期后自动删除

3.客户端在请求中携带 Cookie

  • 当浏览器再次访问同一域名下符合 Path 规则的资源时,会自动在 Cookie 请求头 中携带已存储的 Cookie 信息。

  • 作用:服务器通过解析 Cookie 识别用户身份,实现状态保持(如保持登录状态)、个性化设置(如语言偏好)等功能。

4.服务器更新或删除 Cookie

(1)更新 Cookie

  • 场景:用户信息变更(如修改密码、切换语言)。
  • 实现方式:
    • 服务器再次通过 Set-Cookie 响应头返回同名 Cookie,覆盖旧值(需确保 DomainPath 与旧 Cookie 一致)。

(2)删除 Cookie

  • 场景:用户注销、清除历史记录。
  • 实现方式:
    • 服务器返回同名 Cookie,设置 Expires 为过去的时间Max-Age=0,浏览器会立即删除该 Cookie。

5.Cookie 的过期与自动删除

  • 持久化 Cookie 到达 Expires 时间或 Max-Age 计时结束后,会被浏览器自动删除。
  • 会话级 Cookie 在浏览器关闭时自动清除。

Cookie的优缺点

优点:HTTP协议中支持的技术

缺点:

​ 移动端APP无法使用Cookie

​ 不安全,用户可以自己禁用Cookie

​ Cookie不能跨域

2.3Session技术

image-20250604222746722.png

Session(会话) 是服务器端用于跟踪用户状态的机制,通过存储用户数据(如登录信息、购物车内容等)实现跨请求的状态保持。与 Cookie 不同,Session 数据存储在服务器端,仅通过 会话 ID(Session ID) 与客户端通信,安全性更高。

Session 的生命周期包括 创建、标识、数据存储、更新、销毁 五个阶段

  1. 服务器创建 Session 并生成会话 ID
  • 触发场景:用户首次访问需要会话管理的页面(如登录页、购物车页)。
  • 技术实现:
    • 服务器通过编程语言(如 Java Servlet、Python Flask、Node.js Express 等)的会话管理模块创建 Session 对象。
    • 生成唯一的 会话 ID(Session ID)(通常为随机字符串,如 sess_abc123),作为客户端与服务器会话的关联标识。
  1. 服务器将会话 ID 传递给客户端
  • 会话 ID 需要通过 Cookie 或 URL 重写 等方式传递给浏览器,以便后续请求识别用户会话。
  1. 客户端在请求中携带会话 ID
  • 浏览器收到会话 ID 后,会在后续同域名请求中自动通过 Cookie 请求头URL 参数 携带会话 ID。

  • 服务器解析逻辑:

    • 从请求中提取会话 ID,查找对应的 Session 对象(通常存储在内存、数据库或缓存中)。
    • 若找到有效 Session,则恢复用户状态;若未找到或 Session 过期,则创建新 Session。
  1. 服务器操作 Session 数据
  • 服务器通过会话 ID 找到对应的 Session 对象后,可对其进行 读写、更新或删除 操作。
  1. Session 的过期与销毁
  • 过期机制:
    • 闲置超时:若 Session 在指定时间(如 30 分钟)内未被访问,自动过期(由服务器配置,如 Tomcat 默认 30 分钟)。
    • 绝对超时:设置 Session 的绝对过期时间(较少使用)。
  • 主动销毁:
    • 用户注销时,服务器调用 session.invalidate() 方法立即销毁 Session。
    • 服务器重启或定期清理过期 Session(释放内存 / 存储空间)。
  • 客户端影响:
    • Session 过期或销毁后,客户端携带的会话 ID 变为无效,下次请求时服务器将创建新 Session。

2.4JWT令牌

JWT全称:JSON Web Token

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

组成:
◆第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}

◆第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"Tom")

◆第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

应用场景:登录认证

①登录成功后,生成令牌

②后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理

2.4.1JWT令牌生成

导入相关依赖

<!--JWT令牌-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成JWT令牌

/*
* 生成JWT
* */
@Test
public void testGenJwt(){
   
    Map<String,Object> claims = new HashMap<>();
    claims.put("id",1);
    claims.put("name","test");

    String jwt = Jwts.builder()
            .signWith(SignatureAlgorithm.HS256,"bluening")//设置签名算法和密钥
            .setClaims(claims)//自定义内容
            .setExpiration(new Date(System.currentTimeMillis()+3600*1000))//设置有效期为一个小时
            .compact();//生成JWT
    System.out.println(jwt);
}
eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidGVzdCIsImlkIjoxLCJleHAiOjE3NDkxMTE4OTR9.sWaLc8jLMlM_H-FBGHPpqAsH0tt3UAqLC4NkhKNPa7Q

2.4.2JWT令牌校验

/*
* JWT校验
* */
@Test
public void praseJwt(){
   
    Claims claims = Jwts.parser()
            .setSigningKey("bluening")//指定签名密钥
            .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidGVzdCIsImlkIjoxLCJleHAiOjE3NDkxMTE4OTR9.sWaLc8jLMlM_H-FBGHPpqAsH0tt3UAqLC4NkhKNPa7Q")//解析令牌
            .getBody();
    System.out.println(claims);
}

解析结果:

{
   name=test, id=1, exp=1749111894}

注意事项:

JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。

如果JWT令牌解析校验时报错,则说明JWT令牌被篡改 或 失效了,令牌非法

2.5改造LoginController

我们需要在用户第一次登录成功后为用户发放jwt令牌。

将原本的LoginController改造为以下形式,在这里我们使用jwt工具类中的静态方法来生成jwt令牌,该方法生成令牌的方式,与以上介绍的方式相同

@PostMapping("/login")
public Result login(@RequestBody Emp emp){
   
    log.info("用户登录{}",emp);
    Emp loginemp = empService.login(emp);
    if(loginemp != null){
   
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", loginemp.getId());
        claims.put("name", loginemp.getName());
        claims.put("username", loginemp.getUsername());
        String jwt = JwtUtils.generateJwt(claims);

        return Result.success(jwt);
    }

    return Result.error("用户名或密码错误");
}

image-20250605160056492.png

2.6Filter过滤器

概念:Filter 过滤器,是JavaWeb 三大组件(Servlet、Filter、Listener)之一

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

image-20250605163531120.png

Filter快速入门

定义Filter:定义一个类,实现 Filter接口,并重写其所有方法。

配置Filter:Filter类上加 @Webfilter 注解,配置拦截资源的路径。引导类上加 @servletComponentscan 开启Servlet组件支持。

package com.bluening.talis_web_demo.Filter;


import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
   
    @Override
    //初始化方法,Web服务器启动,创建Filter时调用,只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
   
        Filter.super.init(filterConfig);
    }

    @Override
    //拦截到请求时,调用该方法,可调用多次
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
   
        System.out.println("拦截方法执行,拦截到了请求...");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    //销毁方法,服务器关闭时调用,只调用一次
    public void destroy() {
   
        Filter.super.destroy();
    }
}

2.6.1Filter执行流程

image-20250605165559496.png

放行后访问对应资源,资源访问完成后,还会回到Filter中吗?

如果回到Filter中,是重新执行还是执行放行后的逻辑呢? 执行放行后逻辑

2.6.2Filter拦截路径

image-20250605165733888.png

2.6.4过滤器链

介绍: 一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。

顺序: 注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。

image-20250605172301105.png

2.7利用Filter实现登录校验

对所有请求进行拦截,拦截到后,所有请求都需要校验其令牌吗?

答:不是的,有一个请求例外,登录请求,因为登录时是没有发放令牌的

拦截到请求后,什么情况下才可以放行,执行业务操作?

答:有令牌,且令牌校验通过(合法);否则都返回未登录错误结果

登录校验Filter-流程

①获取请求url

②判断请求url中是否包含login,如果包含,说明是登录操作,放行

③获取请求头中的令牌(token)

④判断令牌是否存在,如果不存在,返回错误结果(未登录)

⑤解析token,如果解析失败,返回错误结果(未登录)

⑥放行

image-20250605184658953.png

相关依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

具体实现

@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
   
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
   
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;

        //1.获取请求url
        String url = req.getRequestURI();
        log.info("请求的url:{}",url);

        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if(url.contains("login")){
   
            log.info("登录操作,放行");
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        //3.获取请求头中的令牌(token)
        String jwt = req.getHeader("token");

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(jwt)){
   
            log.info("请求头token为空,返回未登录信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换,对象--json ------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)
        try {
   
            JwtUtils.parseJWT(jwt);
        }
        catch (Exception e){
   
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }
        //6.放行
        log.info("令牌合法,放行");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

2.8拦截器(Interceptor)

概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。

作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码

image-20250605194531421.png

Interceptor快速入门

1.定义拦截器,实现HandlerInterceptor接口,并重写其中所有方法

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
   
    @Override//目标资源方法执行前执行,返回true;放行;返回false:不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        System.out.println("preHandle");
        return true;
    }

    @Override//目标资源方法执行后执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
   
        System.out.println("postHandle");
    }

    @Override//视图渲染完毕后执行,最后执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
   
        System.out.println("afterCompletion");
    }
}

2.注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
   
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry){
   
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

2.8.1拦截器拦截路径

拦截器可以根据需求,配置不同的拦截路径

image-20250605202452065.png

image-20250605202431374.png

拦截器执行流程
image-20250605202719796.png

接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。

拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

2.8.2利用拦截器实现登录校验

@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
   
    @Override//目标资源方法执行前执行,返回true;放行;返回false:不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
   
        //1.获取请求url
        String url = req.getRequestURI();
        log.info("请求的url:{}",url);

        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if(url.contains("login")){
   
            log.info("登录操作,放行");
            return true;
        }

        //3.获取请求头中的令牌(token)
        String jwt = req.getHeader("token");

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(jwt)){
   
            log.info("请求头token为空,返回未登录信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换,对象--json ------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)
        try {
   
            JwtUtils.parseJWT(jwt);
        }
        catch (Exception e){
   
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }
        //6.放行
        log.info("令牌合法,放行");
        return true;
    }

    @Override//目标资源方法执行后执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
   
        System.out.println("postHandle");
    }

    @Override//视图渲染完毕后执行,最后执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
   
        System.out.println("afterCompletion");
    }
}

3.异常处理

平时当系统与服务器交互时出现的异常,会被抛到Controller层,然后再由Controller层抛到框架中,由框架返回一个错误响应

类似于这样:

{
   
    "timestamp": "2025-06-05T12:52:44.062+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/depts"
}

但是这种错误响应是不符合我们和前端约定好的前后端交互统一响应结果Result,所以前端无法识别这个错误响应,无法给出用户对应的提示,所以我们要捕获这些异常的信息,然后将这些异常信息,以统一的Result形式返回给前端。

这时我们就可以定义一个全局异常处理器,作为处理异常的方案。

image-20250605204834492.png

/*
* 全局异常处理器
* */
@RestControllerAdvice
public class GlobalExceptionHandler {
   
    @ExceptionHandler(Exception.class)//捕获所有异常
    public Result exception(Exception e) {
   
        e.printStackTrace();
        return Result.error("对不起操作失败,请联系管理员");
    }
}

出现错误后后端返回给前端的数据

{
   "code":0,"msg":"对不起操作失败,请联系管理员","data":null}

image-20250605210013131.png

目录
相关文章
|
7月前
|
SQL 监控 Java
菜鸟之路Day40一一事物管理&AOP
本文主要介绍了事物管理和AOP(面向切面编程)的相关知识。在事物管理部分,详细讲解了SQL中的事物控制语句以及Spring框架中的事物注解@Transactional的使用方法和属性配置,结合删除部门及员工的实际案例说明了事物传播行为的应用场景。AOP部分首先概述了其概念和优势,如代码复用与关注点分离,并通过计算service层方法运行时间的实例展示了AOP的实现方式。接着深入探讨了AOP的核心概念,包括切面、连接点、切入点和通知类型,最后通过一个综合案例——操作日志记录到数据库中,演示了如何利用自定义注解和AOP技术完成统一功能的添加。
214 40
|
8月前
|
SQL XML Java
菜鸟之路Day35一一Mybatis之XML映射与动态SQL
本文介绍了MyBatis框架中XML映射与动态SQL的使用方法,作者通过实例详细解析了XML映射文件的配置规范,包括namespace、id和resultType的设置。文章还对比了注解与XML映射的优缺点,强调复杂SQL更适合XML方式。在动态SQL部分,重点讲解了`&lt;if&gt;`、`&lt;where&gt;`、`&lt;set&gt;`、`&lt;foreach&gt;`等标签的应用场景,如条件查询、动态更新和批量删除,并通过代码示例展示了其灵活性与实用性。最后,通过`&lt;sql&gt;`和`&lt;include&gt;`实现代码复用,优化维护效率。
837 5
|
4月前
|
机器学习/深度学习 人工智能 搜索推荐
从零构建短视频推荐系统:双塔算法架构解析与代码实现
短视频推荐看似“读心”,实则依赖双塔推荐系统:用户塔与物品塔分别将行为与内容编码为向量,通过相似度匹配实现精准推送。本文解析其架构原理、技术实现与工程挑战,揭秘抖音等平台如何用AI抓住你的注意力。
1224 7
从零构建短视频推荐系统:双塔算法架构解析与代码实现
|
5月前
|
数据采集 JSON 监控
Python高效工作必备:20个实用脚本推荐!
Python是提升效率的终极自动化利器!本文精选20个实用脚本,覆盖文件批量处理、数据清洗转换、网络爬取、邮件通知、系统监控等高频场景,每项均附完整代码,可直接复制使用。无需深厚编程基础,用几行代码就能节省数小时手动操作,让你的工作流全面自动化,轻松成为高效能人士!
|
5月前
|
人工智能 运维 监控
IT运维数字化转型:不是换工具,而是换思路
IT运维数字化转型:不是换工具,而是换思路
172 9
|
5月前
|
存储 数据可视化
单细胞分析: Scanpy 核心绘图 (3)
单细胞分析: Scanpy 核心绘图 (3)
单细胞分析: Scanpy 核心绘图 (3)
|
7月前
|
存储 前端开发 Java
菜鸟之路Day38一一Web开发综合案例(三)
本文介绍了Web开发中的文件上传与员工信息修改的综合案例,涵盖前端到后端的完整流程。重点讲解了阿里云OSS的集成,包括Bucket创建、密钥获取及SDK使用,并通过Spring Boot实现文件上传功能。同时,详细描述了员工信息查询与修改的操作逻辑,涉及Controller、Service和Mapper层代码实现。最后探讨了配置文件的优化,对比@Value与@ConfigurationProperties注解,展示了如何通过实体类批量注入配置参数,提升代码可维护性与灵活性。
220 1
|
11月前
|
人工智能 SpringCloudAlibaba 自然语言处理
SpringCloud Alibaba AI整合DeepSeek落地AI项目实战
在现代软件开发领域,微服务架构因其灵活性、可扩展性和模块化特性而受到广泛欢迎。微服务架构通过将大型应用程序拆分为多个小型、独立的服务,每个服务运行在其独立的进程中,服务与服务间通过轻量级通信机制(通常是HTTP API)进行通信。这种架构模式有助于提升系统的可维护性、可扩展性和开发效率。
4062 2
|
安全 Java 数据库
SpringSecurity实现多种登录方式,如邮件验证码、电话号码登录
SpringSecurity实现多种登录方式,如邮件验证码、电话号码登录
3071 2
|
11月前
|
存储 Java 程序员
菜鸟之路Day15一一IO流(一)
- **初识IO流**:解释了什么是IO流及其作用,按方向和文件类型分类。 - **字节输出流**:详细讲解了`FileOutputStream`的用法,包括创建对象、写数据和释放资源。 - **字节输入流**:介绍了`FileInputStream`的基本操作,如读取数据和文件拷贝。 - **异常处理**:简要说明了如何处理IO异常。 - **字符集与乱码问题**:解释了字符集的概念及常见乱码原因。 - **字符输入/输出流**:介绍了`FileReader`和`FileWriter`的使用场景和步骤。
224 8