【重难点】【计算机网络 04】Token 认证、BIO、NIO、AIO分别是什么、IO多路复用、Tomcat、Netty 和 Tomcat 的区别及其特点
文章目录
一、Token 认证
1.Token 认证的大致流程
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会前发一个 Token 并保存(缓存或数据库),再把这个 Token 发送给 客户端
- 客户端收到 Token 后可以把它存储起来,比如放在 Cookie 或者 LocalStorage 里
- 客户端每次向服务端请求的时候需要携带服务端签发的 Token
- 服务端收到请求,首先会验证客户端请求里面携带的 Token,验证成功,才会处理客户端的请求
2.基于 JWT 的 Token 认证
实现 Token 认证的方案有很多,JWT(JSON Web Token)是最流行的实现方案。JWT 是一个非常轻巧的规范,JWT 规定 Token 要有以下三个部分:
- header(头部):包括 token 名称和字段 alg,alg 表明使用的加密算法(一般为 HS256,改加密算法不可逆),如果未加密,要设置为 none
- payload(载荷):里面是 Token 的具体内容,需要什么字段添加即可
- signature(签名):将头部和载荷里的内容用 Base64 编码后拼接起来,再加上保存在服务端的加密密码(一般为无意义的字符串,目的就是为了实现除了服务端任何人都无法伪造签名),最后使用头部里表明的加密算法进行加密
以上三部分中间用 “.” 隔开,并且都会使用 Base64 编码
3.代码实现
添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JWTUtil 工具类
package com.sisyphus.utils;
import com.sisyphus.exception.MyException;
import com.sisyphus.pojo.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;
public class JWTUtil {
public static String token = "token";
//加密密码
public static String jwt_secret = "313yphu3";
//有效期,时间单位为毫秒
public static long jwt_expr = 3600*24*1000;
//签发 token
public static String sign(User user){
//指定签名时使用的加密算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成签发时间
long nowMillis = System.currentTimeMillis();
Date date = new Date(nowMillis);
//创建 payload 的私有声明
HashMap<String,Object> claims = new HashMap<>();
claims.put("id", user.getId());
claims.put("userName", user.getName());
//生成签发人
String subject = user.getName();
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setId(UUID.randomUUID().toString())
.setIssuedAt(date)
.setSubject(subject)
.signWith(signatureAlgorithm,jwt_secret);
//设置过期时间
Date exprData = new Date(nowMillis + jwt_expr);
builder.setExpiration(exprData);
return builder.compact();
}
//验证 token
public static boolean verify(String token){
if (StringUtils.isEmpty(token)){
return false;
}
Jwts.parser().setSigningKey(jwt_secret).parseClaimsJws(token).getBody();
return true;
}
//获取用户信息
public static User getUser(String token){
if(StringUtils.isEmpty(token)){
throw new MyException("token 不能为空");
}
if(verify(token)){
Claims claims = Jwts.parser().setSigningKey(jwt_secret).parseClaimsJws(token).getBody();
User user = new User();
user.setId(Integer.parseInt(claims.get("id")+""));
user.setName(claims.get("userName")+"");
return user;
}else{
throw new MyException("超时或不合法的 token");
}
}
}
自定义异常类
package com.sisyphus.exception;
public class MyException extends RuntimeException{
public MyException(){}
public MyException(String message){super(message);}
}
Token 拦截器
package com.tedu.utils;
import com.tedu.exception.MyException;
import com.tedu.pojo.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader(JWTUtil.token);
User user = JWTUtil.getUser(token);
if (user == null){
throw new MyException("超时或不合法的 token");
}
String newToken = JWTUtil.sign(user);
response.setHeader(JWTUtil.token, newToken);
request.setAttribute("user", user);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
注册拦截器
package com.tedu.mvc;
import com.tedu.utils.TokenInterceptor;
import org.springframework.context.annotation.Bean;
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 MyWebMvcConfigurer implements WebMvcConfigurer {
@Bean
public TokenInterceptor tokenInterceptor(){
return new TokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor())
.addPathPatterns("/**") //Token 拦截器作用于所有请求
.excludePathPatterns("/login"); //登录时不验证 token,否则无法登录了
}
}
最后就是在用户登录的时候,为用户签发 token,随着响应信息一起返回给用户,然后在前端根据业务需要将 token 存入 Cookie 或者 LocalStorage
二、BIO、NIO、AIO分别是什么
- BIO:同步阻塞 IO,使用 BIO 读取数据时,线程会阻塞住,并且需要线程主动去查询是否有数据可读,并且需要处理完一个 Socket 之后才能处理下一个 Socket
- NIO:同步非阻塞 IO,使用 NIO 读取数据时,线程不会阻塞,但需要线程主动地去查询是否有 IO 事件
- AIO:也叫 NIO2.0,异步非阻塞 IO,使用 AIO 读取数据时,线程不会阻塞,并且当有数据可读时会通知给线程,不需要线程主动去查询
三、IO多路复用
举一个例子,模拟一个 TCP 服务器处理 30 个用户 Socket。假设你是一个老师,让 30 个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:
- 按顺序逐个检查:先检查 A,然后是 B,……,如果这中间有一个学生卡壳了,老师就只能等他整理思路,全班都会被耽误
这种模式就好比用循环挨个处理 Socket,不具备并发能力 - 创建 30 个分身:每个分身检查一个学生的答案是否正确
这种模式类似于为每个 Socket 创建一个进程或者线程处理连接 - 谁先完成谁举手:C、D 先举手,表示他们解答完毕,老师下去依次检查 C、D 的答案,然后继续回到讲台上等。然后 E、A 举手,老师再下去检查 E、A
这就是 IO 复用模型,Linux 下的 select、poll 和 epoll 就是干这个的
IO 多路复用是一种同步 IO 模型,实现一个线程可以监视多个文件句柄。一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作。没有文件句柄就绪时会阻塞应用程序,交出 CPU。多路是指网络连接,复用指的是同一个线程
四、Tomcat
1.含义
Tomcat 是一个轻量级应用服务器,是支持运行 Servlet/JSP 应用程序的容器,运行在 JVM 上,绑定 IP 地址并监听 TCP 端口
它是由 Apache 推出的一款免费开源的 Servlet 容器,可实现 JavaWeb 程序的装载,是配置 JSP(Java Server Page)和 Java 系统必备的一款环境。它也具有传统的 Web 服务器的功能:处理 HTML 页面
Tomcat 运行时占用的系统资源小,扩展性好,支持负载均衡与邮件服务等开发应用系统常用的功能,因而深受 Java 爱好者的喜爱,并得到了部分软件开发商的认可,和 Apache 一样,早已成为主流 Web 服务器的一种
2.作用
- 管理 Servlet 应用的生命周期
- 把客户端请求的 URL 映射到对应的 Servlet
- 与 Servlet 程序合作处理 HTTP
五、Netty 和 Tomcat 的区别及其特点
Netty 是一个基于 NIO 的异步网络通信框架,性能高,封装了原生 NIO 编码的复杂度,开发者可以直接使用 Netty 来开发高效率的各种网络服务器,并且编码简单
Tomcat 是一个 Web 服务器,是一个 Servlet 容器,基本上 Tomcat 内部只会运行 Servlet 程序,并处理 HTTP 请求,而 Netty 封装的是底层 IO 模型,关注的是网络数据的传输,而不关心具体的协议,可定制性更高
特点
- 异步、NIO 的网络通信框架
- 高性能
- 高扩展、高定制性
- 易用性