后端登录校验

发布于:2025-02-10 ⋅ 阅读:(38) ⋅ 点赞:(0)

登录校验

在这里插入图片描述

登录标记

  • 用户登录成功之后,每一次请求中,都可以获取到该标记(会话技术)

统一拦截

  • 过滤器Filter
  • 拦截器Interceptor

一、会话技术

  • 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应
  • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

​ 浏览器于服务器之间遵循的是http协议,而http协议是无状态的,每次请求都是相互独立的,下一次请求并不会携带上一次请求中携带的数据,这样就保证了http协议的效率是比较高的,但是由于其无状态性,就导致在服务器端无法通过http协议来区分这两次协议是否来自于同一浏览器,是否来自于同一会话,所以就导致在同一个会话的多次请求之间没办法进行数据共享,因此就要用到会话跟踪技术

  • 会话跟踪方案
    • 客户端会话跟踪技术:Cookie
    • 服务端会话跟踪技术:Session
    • 令牌技术

1、Cookie

会话流程:
  1. 服务器会自动的将cookie响应给浏览器
  2. 浏览器接收到响应回来的数据,会自动的将cookie存储在本地
  3. 在后续的请求当中,浏览器会自动的将cookie携带到服务器端

在这里插入图片描述

优点:
  • HTTP协议中支持的技术
    • Cookie请求头:就是给服务器端传递cookie数据
    • Set-Cookie响应头:就是服务器端向浏览器端所发送的cookie数据
缺点:
  • 移动端APP无法使用Cookie
  • 不安全,用户可以自己禁用Cookie
  • Cookie不能跨域

跨域区分的三个维度:协议、IP/域名、端口

	// 设置cookie
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response) {
        response.addCookie(new Cookie("login_username", "lihua"));
        return Result.success();
    }

    // 获取cookie
    @GetMapping("/c2")
    public Result cookie2(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals("login_username")) {
                System.out.println("login_username:" + cookie.getValue());
            }
        }
        return Result.success();
    }

2、Session

  • Session的底层是cookie
会话流程:
  1. 浏览器在第一次请求服务器的时候就可以在服务器当中创建session,并获取其id
  2. 服务器在给浏览器响应数据的时候,会将这个session的id通过cookie响应给浏览器
  3. 浏览器在接收到这个响应数据时,会自动的将这个cookie存储在本地,并在后续的每一次请求当中,都会将cookie的数据获取出来,并且携带到服务端
  4. 服务器拿到cookie(也就是session的id)后,就会从众多的session中找到当前请求对应的会话对象session,就完成在同一次会话的多次请求之间共享数据

在这里插入图片描述

优点:
  • 存储在服务端,安全
缺点:
  • 服务器集群环境下无法直接使用Session
  • Cookie的缺点

3、令牌技术

会话流程:
  1. 在请求登录接口的时候,如果登录成功,就可以生成一个令牌(该用户的合法身份凭证)
  2. 在响应数据的时候,就可以直接将这个令牌响应给前端,前端接收到这个令牌之后就会将它存储起来(可以存储在cookie当中,也可以存储在其他的存储空间当中)
  3. 在后续的每一次请求当中,都需要将这个令牌携带到服务端,接下来就需要校验这个令牌的有效性(如果是有效的,说明用户已经执行了登录操作;如果是无效的,说明用户没有执行登录操作)
  4. 如果需要在同一次会话的多次请求之间共享数据,我们就可以将共享的数据存储在这个令牌当中即可

在这里插入图片描述

优点:
  • 支持PC端、移动端
  • 解决集群环境下的认证问题
  • 减轻服务器端存储压力
缺点:
  • 需要自己实现

二、JWT令牌

  • 全称:JSON Web Token

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

  • 组成

    • 第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JwT"}
    • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"Tom"}
    • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定密钥,通过指定签名算法计算而来

    前两部分都使用到了Base64编码:

    • 是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式

在这里插入图片描述

使用场景:登录认证

  1. 登录成功后,生成令牌
  2. 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理
应用
  • 要在项目中引入这个令牌就需要先引入依赖
<!--JWT令牌-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>

1、编写生成JWT令牌的测试代码

	@Test
    void testGenJWT() {
        Map<String, Object> claim = new HashMap<>();
        claim.put("id", 1);
        claim.put("name", "Tom");

        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "stu01")
                .setClaims(claim)
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置JWT的有效时间为1h
                .compact();

        System.out.println(jwt);
    }
  • 先设置数字签名的算法(使用HS256即可)和密钥(自定义密钥字符串)
  • 再设置自定义内容(载荷部分),可以直接使用HashMap进行传递
  • 再设置令牌的有效期(超出有效期,令牌就会失效)
  • 最后再调用一个方法.compact(),这样就可以获取一个字符串类型的返回值

我们可以将运行输出的结果,放到JWT官网/Base64解码工具,就可以解码出来(最后一部分是解码不出来的,因为是加了数字签名的结果)

在这里插入图片描述

在这里插入图片描述

2、编写在java代码中解析JWT令牌的测试代码

@Test
    void testParseJWT() {
        Claims body = Jwts.parser()
                .setSigningKey("stu01")
          				.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTczOTAwMDk3MH0.77zdsoBuGj_7VAD_zmKDVE9WCoyM9KMJ_zn5hJIhe6g")
                .getBody();

        System.out.println(body);
    }
  • 先输出自己设置的密钥
  • 再解析JWT令牌的自定义内容(注意这个方法传递的是Jws<Claims>这个参数的方法)
  • 最后调用.getBody()这个方法,获取HashMap对象

在这里插入图片描述

注意事项

  • JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的
  • 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法
登录生成令牌

步骤

  • 引入JWY令牌操作工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;

public class JwtUtils {

    private static String signKey = "sprstu01";
    private static Long expire = 43200000L;

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}
  • 登录完成后,调用工具类生成JWT令牌,并返回
@PostMapping("/login")
    public Result login(@RequestBody Emp emp) {
        Emp e = empService.login(emp);
        // 登录成功,生成令牌,下发令牌
        if (e != null) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", e.getId());
            claims.put("username", e.getUsername());
            claims.put("name", e.getName());
            String jwt = JwtUtils.generateJwt(claims);
            return Result.success(jwt);
        }else {
            return Result.error("用户名或密码不对");
        }

    }

在登录成功后的每一次请求都会在Token中携带的JWT令牌给服务端

在这里插入图片描述

三、过滤器Filter

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

  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等

1、Filter快速入门

  1. 定义Filter:定义一个类,实现Filter接口,并重写其所有方法
  2. 配置FilterFilter类上加@WebFilter注解,配置拦截资源的路径。引导类上加@ServletComponentScan开启Servlet组件支持
package com.springbootstu01.filter;

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

import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
    @Override   // 初始化方法,只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
//        Filter.super.init(filterConfig);
        System.out.println("init 初始化方法执行了");
    }

    @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();
        System.out.println("destroy 销毁方法执行了");
    }
}
@ServletComponentScan   // 开启了对servlet组件的支持
@SpringBootApplication
public class SpringbootStu01Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootStu01Application.class, args);
    }

}
@WebFilter(urlPatterns = "/*")表示拦截所有请求
filterChain.doFilter(servletRequest, servletResponse)放行拦截
@ServletComponentScan允许Springboot项目中使用servlet组件

2、详解

执行流程:(每一次发起请求)

  1. 首先先执行放行之前的逻辑,这部分逻辑执行完后,执行放行操作
  2. 放行操作会访问对应的web资源,访问完毕之后会回到Filter中,来执行放行之后的逻辑

拦截路径

  • Filter可以根据需求,配置不同的拦截资源路径:
拦截路径 urlPatterns值 含义
拦截具体路径 /login 只有访问/login路径时,才会被拦截
目录拦截 /emps/* 访问/emps下的所有资源,都会被拦截
拦截所有 /* 访问所有资源,都会被拦截

过滤器链

  • 介绍:一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
  • 顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序

在这里插入图片描述

核心就是:FilterChain中的doFilter(request,response)方法,该方法时它代表的是会放行到下一个过滤器,如果当前是最后一个过滤器时,就会放行到web资源当中

在这里插入图片描述

3、融合到项目中

@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 需要获取请求的url 所以需要强转为子类(子类中有获取url的方法)
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 1.获取请求的url
        String url = request.getRequestURI();
        System.out.println("请求的url:" + url);

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

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

        // 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(jwt)) {  // 使用的是springboot框架自带的util
            System.out.println("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            // 此时不在Controller层 无@RestController注解,需要手动转化为json格式 对象-->json  使用阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            // 发送响应数据
            response.getWriter().write(notLogin);
            return;
        }

        // 5.解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {// jwt解析失败
//            throw new RuntimeException(e);
            e.printStackTrace();
            System.out.println("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            // 此时不在Controller层 无@RestController注解,需要手动转化为json格式 对象-->json  使用阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            // 发送响应数据
            response.getWriter().write(notLogin);
            return;
        }

        // 6.放行
        System.out.println("令牌合法,放行");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

上面用到的依赖:

<!--fastJSON-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

使用前面页面测试时,记得先删除本地存的的JWT令牌

四、拦截器Interceptor

  • 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行
  • 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码

在这里插入图片描述

1、Interceptor快速入门

  1. 定义拦截器,实现HandlerInterceptor接口,并重写其所有方法
  2. 注册拦截器
package com.springbootstu01.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

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

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

配置类中注册拦截器

package com.springbootstu01.config;

import com.springbootstu01.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration // 配置类
public class WebConfig implements WebMvcConfigurer {

    // 注入创建的拦截器
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;


    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

2、详解

拦截路径

registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");

.addPathPatterns()-----需要拦截哪些资源

.excludePathPatterns()-----不需要拦截哪些资源

  • 拦截器可以根据需求,配置不同的拦截路径:
拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配/depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2、/depts
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1

执行流程:(如果过滤器和拦截器同时存在)

在这里插入图片描述

当我们打开浏览器访问部署在web服务器的web应用时,此时我们定义的过滤器会拦截 ,由于项目时基于springboot开发的所以需要进入spring的web环境中,但由于Tomcat服务器是不识别编写的Controller程序,因为它是一个Servlet容器,所以在spring的web环境中提供了一个非常核心的Servlet—前端控制器DispatcherServlet,然后在给Controller传递的过程中会被拦截器拦截

3、FilterInterceptor

  • 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现Handlerlnterceptor接口
  • 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源

4、融合到项目中

package com.springbootstu01.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.springbootstu01.pojo.Result;
import com.springbootstu01.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override   // 目标资源方法执行前执行 返回true--放行; 返回false--不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求的url
        String url = request.getRequestURI();
        System.out.println("请求的url:" + url);

        // 2.判断url中是否包含login,如果包含则说明是登录操作,放行
        if (url.contains("login")) {
            System.out.println("登录操作,放行……");
            return true;
        }

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

        // 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(jwt)) {  // 使用的是springboot框架自带的util
            System.out.println("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            // 此时不在Controller层 无@RestController注解,需要手动转化为json格式 对象-->json  使用阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            // 发送响应数据
            response.getWriter().write(notLogin);
            return false;
        }

        // 5.解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {// jwt解析失败
//            throw new RuntimeException(e);
            e.printStackTrace();
            System.out.println("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            // 此时不在Controller层 无@RestController注解,需要手动转化为json格式 对象-->json  使用阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            // 发送响应数据
            response.getWriter().write(notLogin);
            return false;
        }

        // 6.放行
        System.out.println("令牌合法,放行");
        return true;
    }

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

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

上面需要引入的依赖和过滤器一样,都是阿里巴巴fastJSON



网站公告

今日签到

点亮在社区的每一天
去签到