登陆认证学习笔记

会话技术

会话技术:在服务器端创建一个与客户端浏览器相关的数据,用来记录客户端浏览器的访问信息。

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

会话跟踪方案:

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

Cookie和Session都是传统的会话技术

优点:

  • HTTP协议中支持的技术
    缺点:
  • 存储空间有限,通常情况下不能存储二进制数据
  • 不能跨域访问
  • 不安全,用户可以自己禁用Cookie

Session

优点:

  • 部署在服务器,安全
    缺点;
  • 服务器集群环境下无法直接使用Session
  • Cookie的缺点

JWT令牌技术

JWT令牌技术:JSON Web Token,是一种用于在网络应用环境间传递声明的开放标准(RFC 7519)。

  • 全称称:JSON Web Token(https://jwt.io/)
  • 定义了一整简洁的、自包含的格式,用于在通信双法以JSON数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
  • 组成:
    • 第一部分:Header(头部),记录令牌类型、签名算法等。例如:{“alg”:”HS256”,”typ”:”JWT”}
    • 第二部分:Payload(有效负载),携带一些自定义信息】默认信息等。例如:{“id”:1,”name”:”zhangsan”}
    • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、.payload,并加入指定秘钥,通过指定签名算法计算而来。

第一和第二部分内容都是明文自定义的内容,第三部就像一把钥匙,使用Signature封装明文内容,解析也必须验证签名。

alt text

使用JWT技术实现登陆认证

使用的是0.9.1版本的JWT

引入依赖

1
2
3
4
5
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

JWT工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.nodaoli.utils;

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 = "nodaoli";
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;
}
}

过滤器Filter

概述

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

alt text

重写Filter接口的doFilter方法,进行放行操作,调用filterChain对象的doFilter()方法。
基本上就是用到了传入的三个参数。

Jakarta.servlet包下的

1
2
3
4
5
6
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截到了请求");
// 放行
filterChain.doFilter(servletRequest,servletResponse);
}

拦截路径

1
2
3
@WebFilter(urlPatterns = "/*",filterName = "myFilter")
public class MyFilter implements Filter {
}
拦截路径 urlPatterns值 含义
拦截具体路径 /login 只有访问/login路径时,才会拦截
目录拦截 /emps/* 访问/emps目录下的所有资源时,都会拦截
拦截所有 /* 访问所有资源时,都会拦截

过滤器链

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import com.alibaba.fastjson.JSONObject;
import com.nodaoli.pojo.Result;
import com.nodaoli.utils.JwtUtils;
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 java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/*") // 启用过滤器,并指定所有的请求都会经过过滤器
public class LoginCheckFilter implements Filter { // 过滤器必须实现Filter接口
@Override // 过滤器必须重写doFilter方法
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 强制转成HttpServlet类型,因为过滤器是ServletAPI的,所以需要强转才能使用
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;

//1.获取请求url.
String url = req.getRequestURL().toString();
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");
/**
* 手动转换
* 在Controller层,使用@ResponseBody注解,返回json数据,做到自动转换
* 这里使用的是fastjson,需要在POM中引入依赖
* */
String notLogin = JSONObject.toJSONString(error);
// 在响应处,使用getWriter()方法,将会获得一个PrintWriter对象,使用write()方法,将字符串写入响应中
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");
// 手动转换
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}

//6.放行。
log.info("令牌合法,放行");
filterChain.doFilter(servletRequest,servletResponse);
}
}

拦截器Interceptor

概述

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

创建

如果你需要自定义 Interceptor 的话必须实现 org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,并且需要重写下面下面 3 个方法:

  1. preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在请求处理之前被调用。该方法在 Interceptor 类中最先执行,用来进行一些前置初始化操作或是对当前请求做预处理,也可以进行一些判断来决定请求是否要继续进行下去。该方法的返回至是 Boolean 类型,当它返回 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当它返回为 true 时会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会调用当前请求的 Controller 方法。
  2. postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 方法在当前请求处理完成之后,也就是 Controller 方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
  3. afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法需要在当前对应的 Interceptor 类的 postHandler 方法返回值为 true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class LogInterceptor extends HandlerInterceptorAdapter {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
System.out.println("\n-------- LogInterception.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Start Time: " + System.currentTimeMillis());

request.setAttribute("startTime", startTime);

return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("\n-------- LogInterception.postHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("\n-------- LogInterception.afterCompletion --- ");

long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("End Time: " + endTime);

System.out.println("Time Taken: " + (endTime - startTime));
}
}

应用

创建一个配置类

1
2
3
4
5
6
7
8
9
@Configuration
public class WebConfig implements WebMvcConfigurer {

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

拦截器路径addPathPatterns("/**")直接加在addInterceptor()方法中即可。

使用excludePathPatterns()方法可以排除某些路径。

Interceptor路径.png