Spring boot关于登录功能实现(过滤器、拦截器)笔记

登录校验

会话技术

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

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

会话跟踪方案:

  • 客户端会话跟踪技术:Cookie

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

    缺点:

    • 移动端APP无法使用
    • 不安全,用户可以自己禁用Cookie
    • Cookie不能跨域
  • 服务端会话跟踪技术:Session

    优点:存储在服务端,安全

    缺点:

    • 服务器集群环境下无法直接使用Session
    • Cookie的缺点
  • 令牌技术

    优点:

    • 支持PC、移动端
    • 解决集群环境下的认证问题
    • 减轻服务端储存压力

    缺点:需要自己实现

JWT令牌

全称:Json Web Token

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

组成:

  • 第一部分:Header(头),记录令牌类型,签名算法等。例如{“alg”:“HS256”}

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

  • Payload(有效载荷),携带一些自定义信息、默认信息等。如:{“id”:“1”}

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

eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwNjUyNjI0MX0.8dsYUdjer2uOkB6gyEobGZBtOvWH1xjP0WHVyPwbWrE

场景:登录认证

  • 登录成功后,生成令牌
  • 后续每个请求,都要携带]WT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理

JWT-生成

依赖

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

生成JWT

/**
* 测试JWT令牌
*/
@Test
void testJWT() {
    Map<String,Object> claims = new HashMap<>();
    claims.put("id",1);
    claims.put("name","Tom");
    String jwt = Jwts.builder()
    .signWith(SignatureAlgorithm.HS256,"whopxx") // 签名算法
    .setClaims(claims) // 自定义内容(载荷)
    .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置有效期为1h
    .compact();
    System.out.println(jwt);
}

校验JWT

/**
* JWT解析
*/
@Test
public void testParseJwt(){
    Claims claims = Jwts.parser()
    .setSigningKey("whopxx") // 指定签名秘钥
	.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwNjUyNjI0MX0.8dsYUdjer2uOkB6gyEobGZBtOvWH1xjP0WHVyPwbWrE") // 解析令牌
    .getBody();
    System.out.println(claims);
}
  • JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
  • 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改 或 失效了,令牌非法。
public class JwtUtils {

    private static String signKey = "whopxx";
    private static Long expire = 43200000L; // 12h

    /**
     * 生成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;
    }
}
过滤器Filter

概念: Filter 过滤器,是JavaWeb 三大组件(Servlet、Filter、Listener)之一
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

执行流程:请求 --> 放行前逻辑 --> 放行 --> 资源 --> 放行后逻辑

package whopxx.start.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 {
        System.out.println("init 初始化方法执行");
        Filter.super.init(filterConfig);
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("拦截到了请求");
        // 放行
        chain.doFilter(request,response); 

    }
    @Override // 销毁方法,只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行");
        Filter.super.destroy();
    }
}

主要是实现doFilter方法实现放行操作

chain.doFilter(request,response); --> 放行

拦截路径

Filter可以根据需求,配置不同的拦截资源路径:

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
  • 拦截具体路径 /login
  • 目录拦截 /emps/*
  • 拦截所有 /*

过滤器链

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

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

登录校验Filter-流程

  1. 获取请求url。
  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
  3. 获取请求头中的令牌 (token)
  4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)。
  5. 解析token,如果解析失败,返回错误结果(未登录)
  6. 放行。

LoginCgeckFilter.java

package whopxx.start.filter;


import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import whopxx.start.pojo.Result;
import whopxx.start.utils.JwtUtils;

import java.io.IOException;

@Slf4j
@WebFilter("/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // request -> 获取请求参数
        // response -> 获取相应结果
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        // 强转

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

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

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

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

        // 6. 放行
        log.info("令牌合法,放行");
        chain.doFilter(request,response);
    }
}
拦截器Interceptor

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

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

拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。Spring MVC中,所有的拦截

器都需要实现HandlerInterceptor接口,该接口包含如下三个方法:preHandle()、postHandle()、

afterCompletion()。

执行流程:

  • 执行preHandle方法,它会返回一个布尔值。如果为false,则结束所有流程,如果为true,则执行下一步。
  • 执行处理器逻辑,它包含控制器的功能。
  • 执行postHandle方法。
  • 执行视图解析和视图渲染。
  • 执行afterCompletion方法。

开发步骤:

  1. 开发拦截器:

实现handlerInterceptor接口,从三个方法中选择合适的方法,实现拦截时要执行的具体业务逻

辑。

  1. 注册拦截器:

定义配置类,并让它实现WebMvcConfigurer接口,在接口的addInterceptors方法中,注册拦截

器,并定义该拦截器匹配哪些请求路径。

开发拦截器

定义拦截器,实现Handlerlnterceptor接口,并重写其所有方法

@Component // 交给spring IOC容器管理
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, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

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

注册拦截器

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

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

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

  • /* -> 一级路径
  • /** -> 任意级路径
  • /depts/* -> /depts下的一级路径
  • /depts/** -> /depts下的任意级路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
   registry.addInterceptor(loginCheckInterceptor).addPathPatterns(需要拦截的资源).excludePathPatterns(不需要拦截的资源);
}

校验流程

LoginCheckInterceptor.java

package whopxx.start.interceptor;

import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import whopxx.start.pojo.Result;
import whopxx.start.utils.JwtUtils;

@Slf4j
@Component // 交给spring IOC容器管理
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override //目标方法运行前运行,返回true:放行,返回false:不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求url
        String url = request.getRequestURL().toString();
        log.info("请求的url:{}",url);

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

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

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

        // 5. 解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) { // jwt解析失败
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            // 手动转换 对象->json -->阿里巴巴fastJson
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }

        // 6. 放行
        log.info("令牌合法,放行");
        return true;
    }

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

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

怎么去做请求拦截?

如果是对Controller记性拦截,则可以使用Spring MVC的拦截器。

如果是对所有的请求(如访问静态资源的请求)进行拦截,则可以使用Filter。

如果是对除了Controller之外的其他Bean的请求进行拦截,则可以使用Spring AOP。

Filter和Interceptor的区别

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值