Java阶段四04

news/2025/1/11 20:21:21 标签: javascript, 开发语言, 学习, java

第4章-第4节

一、知识点

CSRF、token、JWT

二、目标

  • 理解什么是CSRF攻击以及如何防范

  • 理解什么是token

  • 理解什么是JWT

  • 理解session验证和JWT验证的区别

  • 学会使用JWT

三、内容分析

  • 重点

    • 理解什么是CSRF攻击以及如何防范

    • 理解什么是token

    • 理解什么是JWT

    • 理解session验证和JWT验证的区别

    • 学会使用JWT

  • 难点

    • 理解session验证和JWT验证的区别

    • 学会使用JWT

四、内容

1、CSRF

1.1 概述

CSRF全称为跨站请求伪造(Cross-site request forgery),是一种网络攻击方式,也被称为 one-click attack 或者 session riding。

1.2 原理

CSRF攻击利用网站对于用户网页浏览器的信任,挟持用户当前已登陆的Web应用程序,去执行并非用户本意的操作。网站是通过cookie来实现登录功能的,而cookie只要存在浏览器中,那么浏览器在访问这个cookie的服务器的时候,就会自动的携带cookie信息到服务器上去。那么这时候就存在一个漏洞了,如果你访问了一个别有用心或病毒网站,这个网站可以在网页源代码中插入js代码,使用js代码给其他服务器发送请求(比如ICBC的转账请求)。那么因为在发送请求的时候,浏览器会自动的把cookie发送给对应的服务器,这时候相应的服务器(比如ICBC网站),就不知道这个请求是伪造的,就被欺骗过去了。从而达到在用户不知情的情况下,给某个服务器发送了一个请求(比如转账)。

 

1.3 解决方案

CSRF攻击的要点就是在向服务器发送请求的时候,相应的cookie会自动的发送给对应的服务器。造成服务器不知道这个请求是用户发起的还是伪造的。这时候,我们可以在用户每次访问有表单的页面的时候,在网页源代码中加一个随机的字符串叫做csrf_token,在cookie中也加入一个相同值的csrf_token字符串。以后给服务器发送请求的时候,必须在body中以及cookie中都携带csrf_token,服务器只有检测到cookie中的csrf_tokenbody中的csrf_token都相同,才认为这个请求是正常的,否则就是伪造的。那么黑客就没办法伪造请求了。

2、JWT

2.1 什么是token

Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。

2.2 什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

2.3 JWT的请求流程

 

  • 用户使用账号和面发出登录请求

  • 服务器验证并创建一个jwt

  • 服务器返回这个jwt给浏览器

  • 浏览器将该jwt串在请求头中向服务器发送请求

  • 服务器验证该jwt

  • 返回响应的资源给浏览器

2.4 为什么使用JWT
2.4.1 传统Session认证的弊端

在用户首次登录成功后,在服务器存储一份用户登录的信息(session),这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这是传统的基于session认证的过程。

然而,传统的session认证有如下的问题:

  • 每个用户的登录信息都会保存到服务器的session中,随着用户的增多,服务器开销会明显增大

  • 对于非浏览器的客户端、手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie

  • 因为session认证本质基于cookie,所以如果cookie被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie,这种方式也会失效

  • 由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用

2.4.2 JWT认证的优势
  • JWT Token数据量小,传输速度也很快

  • 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持

  • 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端

  • 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题

  • 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端

身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递。 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。

2.4 JWT结构

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串

 

2.5.1 Header

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256)

2.5.2 Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。

默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

2.5.3 Signature

签名部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密钥仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名

3、SpringBoot使用JWT

3.1 添加jwt依赖
java"><dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
3.2 封装工具类

封装返回值的类

java">@Data
@AllArgsConstructor
@NoArgsConstructor
public class AjaxResult<T> {
    private Integer code;
    private String msg;
    private T data;
​
    /**
     * 成功的返回结果
     *
     * @param msg  成功信息
     * @param data 返回的值
     * @param <T>  返回值的泛型
     * @return 返回AjaxResult对象
     */
    public static <T> AjaxResult<T> success(String msg, T data) {
        return new AjaxResult<T>(200, msg, data);
    }
​
    /**
     * 成功的返回结果
     *
     * @param data 返回的值
     * @param <T>  返回值的泛型
     * @return 返回AjaxResult对象
     */
    public static <T> AjaxResult<T> success(T data) {
        return new AjaxResult<T>(200, "操作成功", data);
    }
​
    /**
     * 失败的返回结果
     *
     * @param msg  成功信息
     * @param data 返回的值
     * @param <T>  返回值的泛型
     * @return 返回AjaxResult对象
     */
    public static <T> AjaxResult<T> error(String msg, T data) {
        return new AjaxResult<T>(500, msg, data);
    }
​
    /**
     * 失败的返回结果
     *
     * @param data 返回的值
     * @param <T>  返回值的泛型
     * @return 返回AjaxResult对象
     */
    public static <T> AjaxResult<T> error(T data) {
        return new AjaxResult<T>(500, "操作失败", data);
    }
​
    /**
     * 认证失败的返回结果
     *
     * @param msg  成功信息
     * @param data 返回的值
     * @param <T>  返回值的泛型
     * @return 返回AjaxResult对象
     */
    public static <T> AjaxResult<T> unAuth(String msg, T data) {
        return new AjaxResult<T>(401, msg, data);
    }
}

将jwt的验证等功能封装一下方便后续调用

java">public class JWTUtil {
    // 加密的秘钥封装一下
    private static final String SECRET = "secret";
    // id字段
    private static final String ID_FIELD = "userID";
    // token的有效时间 30 天
    private static final Integer TIME_OUT_DAY = 30;
​
    /**
     * 创建token
     *
     * @param user 登陆的用户
     * @return 返回Token字符串
     */
    public static String createToken(SysUser user) {
        // 获取日历对象实例
        Calendar calendar = Calendar.getInstance();
        // 在当前日期加上 TIME_OUT_DAY 的时间,用于设置过期时间
        calendar.add(Calendar.DATE, TIME_OUT_DAY);
        System.out.println(user.getId());
        // 创建jwt
        return JWT.create()
            // 可以在token中设置数据,设置一个userId为用户的id
            // 后续可以直接在token中获取id
            .withClaim(ID_FIELD, user.getId())
            // 设置token过期时间
            .withExpiresAt(calendar.getTime())
            // Algorithm.HMAC256(SECRET) 使用HMAC256的加密方式
            // secret 指的是秘钥,在这个秘钥的基础上,进行加密,加大破解的难度这个秘钥爱写什么写什么
            .sign(Algorithm.HMAC256(SECRET));
    }
​
    /**
     * 验证JWT,返回为false的时候表示验证失败
     *
     * @param token token字符串
     * @return 返回boolean 表示是否登录成功
     */
    public static boolean verifyToken(String token) {
        try {
            // 验证JWT,验证不通过会报错
            JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
​
    /**
     * 获取用户id,返回值是0表示没有找到id
     *
     * @param token token 字符串
     * @return 返回对应的用户id,如果为0则表示没有用户
     */
    public static Long getUserId(String token) {
        try {
            // 获取id,没有id则会报错
            return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getClaim(ID_FIELD).asLong();
        } catch (Exception e) {
            // 如果报错就返回null表示没有找到对应的用户
            return 0L;
        }
    }
}
​
3.3 token创建测试

写一个登录接口进行测试,控制层、持久层等代码不展示

java">@PostMapping("/login")
public AjaxResult<String> login(User user) {
    // 添加用户的帐号密码的条件
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getUsername, user.getUsername());
    wrapper.eq(User::getPassword, user.getPassword());
    // 使用Mybatis-Plus查询用户
    User loginUser = service.getOne(wrapper);
    // 只有登录成功才需要jwt
    if (loginUser == null) {
        return AjaxResult.unAuth("登录失败", null);
    }
    String token = JWTUtil.createToken(loginUser);
    // 登录成功后把token返回给前端
    return AjaxResult.success(token);
}

发现这个登录接口可以获得到生成的token,说明我们的token的创建是没有问题,那么验证呢?

要验证接口首先要先把请求拦截下来,所以需要添加拦截器

3.4 添加拦截器
3.4.1 拦截器

拦截请求,验证请求头是否携带了token

java">/**
 * 授权拦截器,用来验证用户是否有权利访问接口
 */
public class AuthInterceptor implements HandlerInterceptor {
    /**
     * 要验证用户是否有权限,那么就要验证token
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从头部获取我们的token
        String token = request.getHeader("token");
        // 设置返回值类型
        response.setContentType("text/plain;charset=utf-8");
        // 如果token是空的就说明没有token,没有权限
        if (token == null) {
            response.getWriter().write("没有token");
            return false;
        }
        boolean b = JWTUtil.verifyToken(token);
        if (!b) {
            response.getWriter().write("用户认证失败");
            return false;
        }
        // 统一处理用户id不存在的情况
        Long userId = JWTUtil.getUserId(token);
        if (userId == null) {
            response.getWriter().write("用户不存在或者过期");
            return false;
        }
        return b;
    }
}
​
3.4.2 拦截器配置
java">@Configuration
public class AuthInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/user/login");// 登录接口是不要验证token的
    }
}
3.5 token验证测试
java">@GetMapping
public AjaxResult<User> getUserInfo(HttpServletRequest req) {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    // 获取Token
    String token = req.getHeader("token");
    // 获取用户Id
    Long id = JWTUtil.getUserId(token);
    // 根据Id查询
    User loginUser = service.getById(id);
    if (loginUser == null) {
        return AjaxResult.error("用户不存在", null);
    }
    return AjaxResult.success(loginUser);
}
3.6 优化返回值

优化返回值,改成json格式的

3.6.1 封装响应工具类ResponseUtil
java">public class ResponseUtil {
    /**
     *
     * @param response 响应对象
     * @param data 要返回的对象
     * @throws IOException
     */
    public static void responseToWeb(HttpServletResponse response, Object data) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        // 使用jackson的对象转JSON字符串
        String resultStr = mapper.writeValueAsString(data);
        // 设置返回的数据类型和编码格式
        response.setContentType("text/plain;charset=utf-8");
        // 通过写出流发送给调用者
        response.getWriter().write(resultStr);
    }
}
3.6.2 直接调用修改即可
java">if (token == null) {
    ResponseUtil.responseToWeb(response, AjaxResult.unAuth("没有Token", null));
    return false;
}

4、小结

本章节中,我们学习了CSRF攻击以及如何防范、理解了什么是token、什么是JWT、学些了session验证和JWT验证的区别、同时在SpringBoot中对JWT进行了封装和使用,对开发中的登录流程有了一个更加清晰的认知。

下一节中,我们将会学习Redis技术,了解什么是NoSql、为什么要使用NoSql、什么是Redis、Redis的优势是什么、如何使用Redis。


http://www.niftyadmin.cn/n/5820126.html

相关文章

Java - Http 通讯

Java - Http 通讯 PS&#xff1a; 1. Http 协议 POST | GET 请求&#xff1b; 2. 支持 报头、报文、参数 自定义配置&#xff1b; 3. GET 返回支持 String | Stream; 4. 相关依赖&#xff1a; <dependency><groupId>org.apache.httpcomponents</groupId><…

OpenBSD之安装指南

安装介质下载 OpenBSD的官网下载地址&#xff1a;https://www.openbsd.org/faq/faq4.html#Download&#xff0c;同时也是《OpenBSD FAQ - Installation Guide》。长篇大论了很多&#xff0c;每一个章节都能看懂是干嘛的&#xff0c;连起来就容易晕。并且是英文的&#xff0c;要…

Linux_进程间通信_共享内存

什么是共享内存&#xff1f; 对于两个进程&#xff0c;通过在内存开辟一块空间&#xff08;操作系统开辟的&#xff09;&#xff0c;进程的虚拟地址通过页表映射到对应的共享内存空间中&#xff0c;进而实现通信&#xff1b;物理内存中的这块空间&#xff0c;就叫做共享内存。…

C/C++ 数据结构与算法【排序】 常见7大排序详细解析【日常学习,考研必备】带图+详细代码

常见7种排序算法 冒泡排序&#xff08;Bubble Sort&#xff09;选择排序&#xff08;Selection Sort&#xff09;插入排序&#xff08;Insertion Sort&#xff09;希尔排序&#xff08;Shell Sort&#xff09;归并排序&#xff08;Merge Sort&#xff09;快速排序&#xff08;…

使用Buildroot开始嵌入式Linux系统之旅-1

文章目录 安装所需要的软件下载buildroot和buildroot-external软件包下载buildroot-mchp下载buildroot-external-microchip 针对目标设备配置buildroot代码buildroot下查看配置执行buildroot编译 buildroot目录下文件夹说明视频教程 Microchip官方Buildroot教程 Microchip官方B…

计算机网络之---静态路由与动态路由

静态路由 静态路由是由网络管理员手动配置并固定的路由方式。路由器通过静态配置的路由条目来转发数据包&#xff0c;而不会自动调整。它不依赖于任何路由协议。 特点&#xff1a; 手动配置&#xff1a;网络管理员需要手动在路由器中配置每条静态路由。不自动更新&#xff1a;…

2025-1-10-sklearn学习(36、37) 数据集转换-无监督降维+随机投影 沙上并禽池上暝。云破月来花弄影。

文章目录 sklearn学习(36、37) 数据集转换-无监督降维随机投影sklearn学习(36) 数据集转换-无监督降维36.1 PCA: 主成份分析36.2 随机投影36.3 特征聚集 sklearn学习(37) 数据集转换-随机投影37.1 Johnson-Lindenstrauss 辅助定理37.2 高斯随机投影37.3 稀疏随机矩阵 sklearn学…

使用 IntelliJ IDEA 创建简单的 Java Web 项目

以下是使用 IntelliJ IDEA 创建几个简单的 Java Web 项目的步骤&#xff0c;每个项目实现基本的登录、注册和查看列表功能&#xff0c;依赖 Servlet/JSP 和基本的 Java Web 开发。 前置准备 确保安装了 IntelliJ IDEA Ultimate&#xff08;社区版不支持 Web 应用&#xff09;。…