JWT、过滤器、拦截器详解

667 阅读8分钟

一、JWT技术 ★★★★★

JWT介绍

JWT:JSON Web Token,是一种用户身份识别的令牌技术。用于解决web开发中身份识别问题。

JWT结构

由三部分组成:

Header.Body.signature

Header 头:里面存放的是令牌的类型、签名算法等等元数据信息

Body 体/载荷:里面存放的是用户的身份信息

Signature 签名:用于进行防篡改的校验的。

image-20230824112850426.png

特性:

  1. 可以防篡改:因为令牌里含有签名,通过校验签名可以发现令牌是否被篡改
  2. 不能防泄漏:因为令牌里的数据使用的是Base64编码得到的,可以在进行解码还原

JWT的使用

1.添加依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

2.JWT的生成与解析

生成令牌:Jwts.builder().addClaim(用户身份信息map).setExpirtion(过期时间).signWith(签名算法,密钥).compact();

解析令牌:Jwts.parse().setSigningKey(密钥).parseClaimJws(令牌).getBody() 如果令牌过期或被篡改,会抛异常

public class DemoJwt {
    public static void main(String[] args) throws InterruptedException {
        //1. 生成JWT令牌
        //1.1 准备载荷信息,即用户的身份信息
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", 1);
        claims.put("username", "tom");
        //1.2 生成JWT令牌
        String token = Jwts.builder()
                //把载荷内容设置到JWT令牌里
                .setClaims(claims)
                //设置令牌的过期时间。这里设置有效期为:截止到当前时间+1000毫秒,即有效期只有1秒(可以根据实际情况自定义有效期)
                .setExpiration(new Date(System.currentTimeMillis() + 1000))
                //设置令牌的密钥。在将来解析令牌时,根据密钥校验令牌的签名,防止令牌被篡改
                .signWith(SignatureAlgorithm.HS256, "itheima")
                //生成令牌
                .compact();
        System.out.println("token = " + token);


        //2. 校验与解析令牌:如果令牌过期,会抛出异常;如果令牌被篡改,会抛出异常;如果一切正常,会得到令牌里的载荷内容
        Claims claimsRes = Jwts.parser()
                //设置密钥。用于稍后的令牌签名校验,判断令牌是否被篡改了。如果令牌被篡改,会抛出异常
                .setSigningKey("itheima")
                //校验并解析令牌。如果令牌被篡改或已过期,会抛出异常
                .parseClaimsJws(token)
                //获取令牌载荷内容
                .getBody();
        System.out.println(claimsRes);
    }
}

登录功能生成JWT令牌

分析JWT的使用过程

image-20240320104757145.png

代码

@RestController
public class LoginController {
    @Autowired
    private EmpService empService;
    @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());
            String token = JwtUtils.generateJwt(claims);
            return Result.success(token);
        }
        return Result.error("帐号或密码错误");
    }
}

二、过滤器Filter

JavaEE原始技术,如果开发时没有使用框架,就要使用过滤器进行拦截过滤。

使用步骤

  1. 创建Java类:

    • 实现javax.servlet.Filter接口,重写接口的doFilter方法

      • doFilter方法参数 doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        • ServletRequest:代表请求的request对象。如果要接收请求里的数据,就使用request对象。使用时强转成HttpServletRequest
          • getMethod():获取请求方式
          • getRequestURI():获取请求路径
          • getHeader(String name):获取请求头的值
        • ServletResponse:代表响应的response对象。如果要给客户端返回数据,就使用response对象。要强转成HttpServletResponse
          • setStatus(int code):设置响应状态码
          • setContentType("application/json;charset=utf-8"):告诉客户端本次响应的内容是json格式,并且是utf-8
          • getWriter().print(String str):给客户端响应的内容
        • FilterChain :过滤器链对象。
          • doFilter(request, response):放行到下一个过滤器。如果后边没有过滤器了,就放行到目标资源
    • 类上加注解@WebFilter

      拦截范围

      • @WebFilter(urlPatterns = "拦截范围")
      • @WebFilter(value = "拦截范围")
      • @WebFilter("拦截范围")

      拦截范围的写法,常用的有:

      拦截范围语法示例作用
      精确拦截/emps只拦截客户端对/emps的请求
      范围拦截/emps/*(必须以/开头以*结尾)拦截客户端对/emps下的资源请求
      比如/emps,/emps/1, /emps/xx/yy等等
  2. 在引导类上加@ServletComponentScan

执行过程

03.过滤器Filter的执行原理.png

使用Filter校验登录状态

分析:

  1. 获取本次请求的uri路径
  2. 如果是登录(/login):直接放行
  3. 判断是否携带token,如果没有携带不放行
  4. 解析校验token是否正确,不正确不放行
  5. token检查一切正常,放行

代码示例:

@WebFilter("/*")
public class LonginFilterTest1 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        //1.获取本次请求的URI路径
        String uri = httpServletRequest.getRequestURI();
        //2.判断是不是登录
        if("/login".equals(uri)){
            //如果是登录,直接放行
            chain.doFilter(request, response);
            return;
        }
        //3.判断是否携带token,如果没有携带不放行
        String token = httpServletRequest.getHeader("token");
        if(token == null || "".equals(token)){
            //setStatus():设置响应状态码
            httpServletResponse.setStatus(401);
            //getWriter().println()给客户端响应的内容
            httpServletResponse.getWriter().println(JSON.toJSONString("未登录"));
            return;
        }
        //解析校验token是否正确,如果不正确不放行
        try {
            JwtUtils.parseJWT(token);
        } catch (Exception e) {
            httpServletResponse.setStatus(401);
            httpServletResponse.getWriter().println(JSON.toJSONString("token解析异常"));
            return;
        }
        //token一切正常,直接放行
        chain.doFilter(request, response);
    }
}

三、拦截器interceptor ★★★★★

由SpringMVC框架(SpringBootWeb起步依赖)提供的技术

使用步骤

  1. 创建拦截器类:创建Java类,实现HandlerInterceptor接口,重写接口里的方法(需要哪个,就重写哪个)

    • preHandle方法:在目标方法执行前的预处理方法
    • postHandle方法:在目标方法执行成功后的后处理方法
    • afterCompletion方法:在服务端给客户端返回响应之前执行的最终方法
  2. 配置拦截器

    1. 创建Java类,作为Spring的配置类

    2. 添加注解@Configuration,把类标记为Spring的配置类

    3. 实现接口WebMvcConfigurer,作为Web层框架SpringMVC的配置类,必须实现此接口

    4. 重写接口的addInterceptor(IntegerceptorRegistry registry)方法

      在方法里配置拦截器

      registry.addInterceptor(拦截器对象)
          .addPathPatterns("拦截范围")
          .excludePathPatterns("排除不拦截的范围");
      

拦截范围

写法示例作用
拦截一级匹配路径/emp/*只拦截一级目录
拦截 /emp/1
不拦截/emp/xx/yy
拦截任意级匹配路径/emp/**拦截任意级目录
拦截/emp,/emp/1,emp/xx/yy...

使用Interceptor校验登录状态

分析:

  1. 创建拦截器LoginInterceptor

    1. 如果本次请求是登录,就直接放行
    2. 获取本次请求的token
    3. 如果没有token:直接返回响应结果
    4. 如果token过期或非法:直接返回响应结果
    5. 一切正常放行
  2. 配置拦截器

    配置拦截范围

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1. 如果本次请求是登录,就直接放行
        String uri = request.getRequestURI();
        if ("/login".equals(uri)) {
            log.info("本次请求是登录,直接放行");
            return true;
        }
        //2. 获取本次请求携带的token
        String token = request.getHeader("token");
        //   如果没有token:直接返回响应结果
        if (!StringUtils.hasText(token)) {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(401);
            response.getWriter().println(JSON.toJSONString(Result.error("未登录")));
            return false;
        }
        //   如果token过期或非法:直接返回响应结果
        try {
            JwtUtils.parseJWT(token);
        } catch (Exception e) {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(401);
            response.getWriter().println(JSON.toJSONString(Result.error("请重新登录")));
            return false;
        }
        //3. 放行
        return true;
    }
}

WebConfig里配置拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
    }
}

四、过滤器和拦截器的区别

过滤器拦截器
技术来源Java EE本身提供的技术规范SpringMVC框架(Spring Boot web)提供
适用范围任意JavaWeb都可以使用只有项目引用了SpringMVC矿建,才可以使用
接口规范需要实现javax.servlet.Filter接口需要实现HandlerInterceptor接口
拦截范围可以拦截客户端的所有请求只能拦截SpringMVC管理的资源

五、用到的API

request对象

我们通常说“request”对象,指的是 Tomcat帮我们把HTTP请求的信息封装成的对象。Tomcat提供了两个类:

  • ServletRequest接口:封装请求信息的基础对象接口
  • HttpServletRequest接口:继承了 ServletRequest,提供了更多操作HTTP请求的方式

我们通过request对象,可以HTTP请求的一切信息

方法说明
getMethod()获取请求方式
getRequestURI()获取本次请求的资源路径
getHeader(String name)根据请求头的名称,获取请求头的值

response对象

我们通常说的“response”对象,指的是Tomcat帮我们提供的一个响应对象,可以看成一个容器

我们得到这个对象以后,向这个对象里设置的数据,最终会被Tomcat转换成HTTP响应,返回给客户端。

  • ServletResponse接口:用于封装响应信息的对象接口
  • HttpServletResponse接口:继承了 ServletResponse,提供了更多操作HTTP响应的方法
方法说明
setStatus(int code)设置响应状态码。401未认证(未登录)
setHeader(String name, String value)设置响应头
setContentType(String contentType)设置“Content-Type”响应头的值,通常用于防止响应内容乱码
getWriter().print(String str)使用字符输出流,设置响应体内容。通常用于响应文本内容
getOutputStream()使用字节输出流,设置响应体内容。通常用于响应二进制内容

JSON类

服务端通常需要 json格式字符串 和 javaBean对象之间进行转换。可以使用一些小工具:

  • Jackson:SpringMVC框架内置的工具包。

    ObjectMapper om = new ObjectMapper();

    Java对象转json字符串:String jsonStr = om.writeValueAsString(Java对象)

    json字符串转Java对象:Xxx xxx = om.readValue(json字符串, Xxx.class)

  • FastJson:Alibaba提供的工具包。需要额外添加依赖坐标

    Java对象转json字符串: String jsonStr = JSON.toJSONString(Java对象)

    json字符串转Java对象:Xxx xxx = JSON.parseObject(json字符串, Xxx.class)

  • Gson:Google提供的工具包。国外使用比较多,也需要额外添加依赖坐标

    Gson gson = new Gson();

    通过gson的方法,实现Java对象和json字符串的互相转换