个人技术总结——文件上传以及身份认证(JWT)

222200108朱元烨 2024-12-12 15:42:05

文件上传与下载技术以及身份认证(JWT)技术总结

目录

  • 文件上传与下载技术以及身份认证(JWT)技术总结
  • 技术概述
  • 文件上传与下载
  • 身份认证(JWT)
  • 技术详述
  • 1. 文件上传接口
  • 1.1用户更新头像
  • 1.2上传宠物头像
  • 1.3上传文章图片
  • 2.华为云OBS
  • 2.1 初始化客户端
  • 2.2 文件上传
  • 2.3 生成访问 URL
  • 3.身份认证(JWT)
  • 3.1什么是JWT?
  • 3.2JWT有什么用
  • 3.3JWT在我们项目中前后端的使用
  • 3.3.1生成Token,Token解析
  • 3.3.2认证与鉴权
  • 4. 总结
  • 5. 参考资料

技术概述

文件上传与下载

是许多移动端应用中的常见功能,比如图片上传、文件分享等。在我们的宠物APP中,用户可以上传宠物照片以及自己的头像,也可以自己更换app的背景。这项技术的难点在于网络请求的异步处理、传输数据的准确性和处理文件权限问题。文件的下载,其实有url就可以对我们的服务器中的图片或者视频资源进行展示了,所以url的生成和获取也是十分重要的

身份认证(JWT)

JWT(JSON Web Token)用于无状态的用户身份认证,在分布式系统或微服务架构中尤为重要。对于宠物APP的开发,我们从很多地方都需要进行身份认证,从用户登录的信息确认到获取到用户对应的信息和宠物信息,都是需要身份认证。它能有效避免传统会话认证的弊端。学习JWT是为了提高系统安全性,支持扩展。难点在于签名加密、Token过期策略和避免Replay攻击。


技术详述

1. 文件上传接口

1.1用户更新头像

这个接口的功能是允许用户上传一个头像文件,并将其保存到云存储中。下面是这个接口的详细分析:

请求部分:
使用了 @PostMapping 注解,这意味着该接口是一个处理 POST 请求的方法,路径是 /avatar/upload
接口需要传入Authorization头部,用于身份验证,同时传入一个文件 data,这是用户上传的头像。

//更新头像
    @PostMapping("/avatar/upload")
    public Result upload(@RequestHeader(name="Authorization") String token,@RequestParam("data")MultipartFile file) throws IOException {
      

文件验证:
首先检查上传文件的扩展名是否属于允许的图片类型,如 jpg、jpeg、png、gif 等。如果文件扩展名不符合要求,返回错误信息。
接着,通过 file.getContentType() 获取文件的 MIME 类型,确保文件是图片类型。如果不是图片类型,返回错误信息。


        String originalFilename=file.getOriginalFilename();
        // 判断文件是否为空
        if (originalFilename == null || originalFilename.isEmpty()) {
            return Result.error("上传文件为空");
        }
        // 获取文件扩展名
        String extension = FilenameUtils.getExtension(originalFilename).toLowerCase();

        // 定义允许的图片扩展名
        Set<String> allowedExtensions = Set.of("jpg", "jpeg", "png", "gif", "bmp", "webp");

        // 检查文件类型是否合法
        if (!allowedExtensions.contains(extension)) {
            return Result.error("只允许上传图片格式的文件(jpg, jpeg, png, gif, bmp, webp)");
        }


        String mimeType = file.getContentType();
        if (mimeType == null || !mimeType.startsWith("image")) {
            return Result.error("上传文件必须是图片");
        }

文件命名和上传:
通过 UUID.randomUUID().toString() 生成一个唯一的文件名,确保每次上传的文件名不重复。
调用 HuaweiObsUtil.uploadFile(filename, file.getInputStream()) 上传文件到华为云 OBS(对象存储服务),并获取上传后文件的 URL。


        //保证名字唯一
        String filename= UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));

        String url=HuaweiObsUtil.uploadFile(filename,file.getInputStream());

       

用户信息更新:
解析Authorization头部的 JWT token,获取当前登录用户的用户名。
根据用户名查询数据库,找到对应的用户并更新其头像 URL。
返回:

返回更新后的用户信息(包括新的头像 URL)给前端。


        Map<String,Object> map =JwtUtil.parseToken(token);
        String username=(String) map.get("username");
        User user=userService.findByUserName(username);

        user.setAvatar(url);
        userService.updateUserInfo(user);

        return Result.success(user);
    }

整体代码:

//更新头像
    @PostMapping("/avatar/upload")
    public Result upload(@RequestHeader(name="Authorization") String token,@RequestParam("data")MultipartFile file) throws IOException {
        log.info("用户更新头像");

        String originalFilename=file.getOriginalFilename();
        // 判断文件是否为空
        if (originalFilename == null || originalFilename.isEmpty()) {
            return Result.error("上传文件为空");
        }
        // 获取文件扩展名
        String extension = FilenameUtils.getExtension(originalFilename).toLowerCase();

        // 定义允许的图片扩展名
        Set<String> allowedExtensions = Set.of("jpg", "jpeg", "png", "gif", "bmp", "webp");

        // 检查文件类型是否合法
        if (!allowedExtensions.contains(extension)) {
            return Result.error("只允许上传图片格式的文件(jpg, jpeg, png, gif, bmp, webp)");
        }


        String mimeType = file.getContentType();
        if (mimeType == null || !mimeType.startsWith("image")) {
            return Result.error("上传文件必须是图片");
        }

        //保证名字唯一
        String filename= UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));

        String url=HuaweiObsUtil.uploadFile(filename,file.getInputStream());

        //////至此用户图片已经检验上传

        Map<String,Object> map =JwtUtil.parseToken(token);
        String username=(String) map.get("username");
        User user=userService.findByUserName(username);

        user.setAvatar(url);
        userService.updateUserInfo(user);

        return Result.success(user);
    }

1.2上传宠物头像

 //更新宠物头像
    @PostMapping("/{id}/avatar/upload")
    public Result upload(@RequestHeader(name="Authorization") String token,@RequestParam("data") MultipartFile file,@PathVariable int id) throws IOException {

        log.info("宠物更新头像");

        String originalFilename=file.getOriginalFilename();
        // 判断文件是否为空
        if (originalFilename == null || originalFilename.isEmpty()) {
            return Result.error("上传文件为空");
        }
        // 获取文件扩展名
        String extension = FilenameUtils.getExtension(originalFilename).toLowerCase();

        // 定义允许的图片扩展名
        Set<String> allowedExtensions = Set.of("jpg", "jpeg", "png", "gif", "bmp", "webp");

        // 检查文件类型是否合法
        if (!allowedExtensions.contains(extension)) {
            return Result.error("只允许上传图片格式的文件(jpg, jpeg, png, gif, bmp, webp)");
        }


        String mimeType = file.getContentType();
        if (mimeType == null || !mimeType.startsWith("image")) {
            return Result.error("上传文件必须是图片");
        }

        //保证名字唯一
        String filename= UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));

        String url= HuaweiObsUtil.uploadFile(filename,file.getInputStream());

        //////至此用户图片已经检验上传
        Map<String,Object> map =JwtUtil.parseToken(token);
        int userId=(int) map.get("id");


        Pet pet =petService.getPetById(id);

        pet.setAvatar(url);
        petService.updatePet(userId,id,pet);

        return Result.success(pet);
    }

此接口功能与用户更新头像接口类似,唯一的不同是它针对的是宠物头像。详细流程如下:

请求部分:
使用 @PostMapping("/{id}/avatar/upload"),id 为宠物的 ID,表示上传特定宠物的头像。
需要传入 Authorization 头部用于验证身份,并上传文件 data。

文件验证与上传:
与用户头像上传接口相同,进行文件类型和 MIME 类型的验证。
为了确保文件名唯一,使用 UUID 来生成文件名,并将文件上传到华为云 OBS。

宠物信息更新:
解析 Authorization token,获取当前用户的 ID。
根据传入的宠物 ID 查找宠物对象,并更新其头像 URL。
返回:

1.3上传文章图片

// 上传文章图片
    @PostMapping("/{id}/addPicture")
    public Result addPicture(@RequestHeader(name="Authorization") String token, @PathVariable int id,
                             @RequestParam("data") MultipartFile file) throws IOException {
        log.info("上传文章图片: {}", id);

        String originalFilename=file.getOriginalFilename();
        // 判断文件是否为空
        if (originalFilename == null || originalFilename.isEmpty()) {
            return Result.error("上传文件为空");
        }
        // 获取文件扩展名
        String extension = FilenameUtils.getExtension(originalFilename).toLowerCase();

        // 定义允许的图片扩展名
        Set<String> allowedExtensions = Set.of("jpg", "jpeg", "png", "gif", "bmp", "webp");

        // 检查文件类型是否合法
        if (!allowedExtensions.contains(extension)) {
            return Result.error("只允许上传图片格式的文件(jpg, jpeg, png, gif, bmp, webp)");
        }


        String mimeType = file.getContentType();
        if (mimeType == null || !mimeType.startsWith("image")) {
            return Result.error("上传文件必须是图片");
        }

        //保证名字唯一
        String filename= UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));

        String url=HuaweiObsUtil.uploadFile(filename,file.getInputStream());

        //////至此图片已经检验上传

        articleService.uploadPicture(id,url);

        return Result.success();
    }

该接口允许用户上传与文章相关的图片。

请求部分:

使用 @PostMapping("/{id}/addPicture"),id 为文章的 ID,表示上传与特定文章相关的图片。
请求需要传入 Authorization 头部和图片文件。
文件验证与上传:

与前两个接口类似,验证文件类型和 MIME 类型,并生成唯一的文件名。
上传文件到华为云 OBS 并获取文件 URL。
文章更新:

调用 articleService.uploadPicture(id, url),将图片 URL 保存到对应文章的记录中。
返回:

返回成功响应。

这三个接口的设计逻辑大体是相同的

2.华为云OBS

有关华为云obs的官方文档

代码部分

package com.petgeeks.pethub_backend.utils;

import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.PutObjectRequest;

import java.io.File;
import java.io.InputStream;

public class HuaweiObsUtil {
    public static String uploadFile(String filename, InputStream in) {
        String url="";
        // 您可以通过环境变量获取访问密钥AK/SK,也可以使用其他外部引入方式传入。如果使用硬编码可能会存在泄露风险。
        // 您可以登录访问管理控制台获取访问密钥AK/SK
        String ak ="5M7M6JWJXMI1SC4ZQ3Q7";
        String sk ="S0QUy4HJb1Nlu5Zf40oL64ELf891tBMEJx4LpOqD";
        // 【可选】如果使用临时AK/SK和SecurityToken访问OBS,同样建议您尽量避免使用硬编码,以降低信息泄露风险。
        // 您可以通过环境变量获取访问密钥AK/SK/SecurityToken,也可以使用其他外部引入方式传入。
        // String securityToken = System.getenv("SECURITY_TOKEN");
        // endpoint填写桶所在的endpoint, 此处以中国-香港为例,其他地区请按实际情况填写。
        String endPoint = "https://obs.cn-south-1.myhuaweicloud.com";
        // 您可以通过环境变量获取endPoint,也可以使用其他外部引入方式传入。
        //String endPoint = System.getenv("ENDPOINT");

        // 创建ObsClient实例
        // 使用永久AK/SK初始化客户端
        ObsClient obsClient = new ObsClient(ak,sk,endPoint);
        // 使用临时AK/SK和SecurityToken初始化客户端
        // ObsClient obsClient = new ObsClient(ak, sk, securityToken, endPoint);

        try {
            // 文件上传
            // localfile 为待上传的本地文件路径,需要指定到具体的文件名
            PutObjectRequest request = new PutObjectRequest();

            request.setBucketName("pethub");
            request.setObjectKey(filename);
            url="https://"+request.getBucketName()+"."+endPoint.substring(endPoint.lastIndexOf("/")+1)+"/"+filename;
            request.setInput(in);
            obsClient.putObject(request);
            System.out.println("putObject successfully");
        } catch (ObsException e) {
            System.out.println("putObject failed");
            // 请求失败,打印http状态码
            System.out.println("HTTP Code:" + e.getResponseCode());
            // 请求失败,打印服务端错误码
            System.out.println("Error Code:" + e.getErrorCode());
            // 请求失败,打印详细错误信息
            System.out.println("Error Message:" + e.getErrorMessage());
            // 请求失败,打印请求id
            System.out.println("Request ID:" + e.getErrorRequestId());
            System.out.println("Host ID:" + e.getErrorHostId());
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("putObject failed");
            // 其他异常信息打印
            e.printStackTrace();
        }
        return url;
    }

}

2.1 初始化客户端

初始化客户端:利用华为云提供的 ObsClient 初始化与 OBS 的连接。

  String url="";
        // 您可以通过环境变量获取访问密钥AK/SK,也可以使用其他外部引入方式传入。如果使用硬编码可能会存在泄露风险。
        // 您可以登录访问管理控制台获取访问密钥AK/SK
        String ak ="5M7M6JWJXMI1SC4ZQ3Q7";
        String sk ="S0QUy4HJb1Nlu5Zf40oL64ELf891tBMEJx4LpOqD";
        // 【可选】如果使用临时AK/SK和SecurityToken访问OBS,同样建议您尽量避免使用硬编码,以降低信息泄露风险。
        // 您可以通过环境变量获取访问密钥AK/SK/SecurityToken,也可以使用其他外部引入方式传入。
        // String securityToken = System.getenv("SECURITY_TOKEN");
        // endpoint填写桶所在的endpoint, 此处以中国-香港为例,其他地区请按实际情况填写。
        String endPoint = "https://obs.cn-south-1.myhuaweicloud.com";
        // 您可以通过环境变量获取endPoint,也可以使用其他外部引入方式传入。
        //String endPoint = System.getenv("ENDPOINT");

        // 创建ObsClient实例
        // 使用永久AK/SK初始化客户端
        ObsClient obsClient = new ObsClient(ak,sk,endPoint);
        // 使用临时AK/SK和SecurityToken初始化客户端
        // ObsClient obsClient = new ObsClient(ak, sk, securityToken, endPoint);

2.2 文件上传

文件上传:将文件流上传到指定的 OBS 存储桶中。

  try {
            // 文件上传
            // localfile 为待上传的本地文件路径,需要指定到具体的文件名
            PutObjectRequest request = new PutObjectRequest();

            request.setBucketName("pethub");
            request.setObjectKey(filename);
            url="https://"+request.getBucketName()+"."+endPoint.substring(endPoint.lastIndexOf("/")+1)+"/"+filename;
            request.setInput(in);
            obsClient.putObject(request);
            System.out.println("putObject successfully");
        }

在这里我们是定义了一个HuaweiObsUtil的工具类,通过它我们可以使用华为云的OBS对象存储功能,利用桶对象,把用户上传的照片以及文章照片等信息收集起来。

在这里插入图片描述

2.3 生成访问 URL

生成访问 URL:返回文件上传后的外部访问链接。

 url="https://"+request.getBucketName()+"."+endPoint.substring(endPoint.lastIndexOf("/")+1)+"/"+filename;
            request.setInput(in);
            obsClient.putObject(request);
            System.out.println("putObject successfully"); 

这样我们可以获得上传文件后的url来确定资源,并且可以将文件对应的url存储到数据库中以便后续用户对它的访问

3.身份认证(JWT)

关于这个知识我这里介绍个b站视频方便大家进行学习参考
身份认证(JWT)

3.1什么是JWT?

在这里插入图片描述


这里用视频的图片感觉就可以简单易懂地解释JWT的构成了

3.2JWT有什么用

JWT的主要用途:

身份认证:在用户登录后生成Token,Token携带用户信息用于后续认证。
信息传输:前后端在无需保存状态的情况下安全地传递数据。
权限管理:根据Token中角色信息(如admin、user)控制资源访问权限。
跨服务认证:适合微服务架构,减少因分布式环境而带来的状态同步问题。
优点:
无状态,支持分布式系统扩展。
数据自包含,减少数据库查询压力。
安全性强(签名验证防篡改)。
缺点:
数据体积较大,不适合存储敏感数据。
Token一旦泄露可能造成滥用。

3.3JWT在我们项目中前后端的使用

核心步骤:
生成Token:用户登录时生成Token,并返回给前端;
Token解析:请求携带Token时,通过中间件解析获取用户信息;
认证与鉴权:根据解析结果进行权限验证。

3.3.1生成Token,Token解析

因为在我们的宠物APP中,我的前后端都有参与开发,我就从我开发的模块对这些部分进行讲解

这是我们写的JwtUtil

public class JwtUtil {
    private static final String KEY = "petgeeks";

    //接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7))// token有效时间为7天
                .sign(Algorithm.HMAC256(KEY));
    }

    //接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

在这里我们设置了token的有效期限

 public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7))// token有效时间为7天
                .sign(Algorithm.HMAC256(KEY));
    }

并且不仅我们可以通过业务数据从而生成token,也可以在收到token时对其进行解析

3.3.2认证与鉴权

后端springboot的登录接口


@PostMapping("/login")
    public Result<String> login(@RequestBody User user) {
        log.info("用户登录:"+user.toString());
        // 根据用户名查询用户
        User loginUser = userService.findByUserName(user.getUsername());
        // 判断用户名是否存在
        // 用户名不存在
        if (loginUser == null) {
            return Result.error("用户名错误");
        }
        // 用户名存在

        // 密码正确
        if (Md5Util.getMD5String(user.getPassword()).equals(loginUser.getPassword())) {
            // 登陆成功
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", loginUser.getId());
            claims.put("username", loginUser.getUsername());
            String token = JwtUtil.genToken(claims);
            return Result.success(token);
        }

        // 密码错误
        return Result.error("密码错误");

    }

较为关键的代码就是


        // 密码正确
        if (Md5Util.getMD5String(user.getPassword()).equals(loginUser.getPassword())) {
            // 登陆成功
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", loginUser.getId());
            claims.put("username", loginUser.getUsername());
            String token = JwtUtil.genToken(claims);
            return Result.success(token);
        }

        // 密码错误
        return Result.error("密码错误");

    }

claims内容设置JWT中载荷所带的id和username的数据
String token = JwtUtil.genToken(claims);通过上面提到的JwtUtil类,在用户登录验证通过后,我们就相当于用自己设置的载荷(id,username)和我们JwtUtil中生成JWT的方法,为每个用户生成了不同的JWT令牌。这样一来,每个用户的权限,所访问到的内容,都会基于他们登录所生成的令牌

举个例子

       // 获取用户信息
    @GetMapping("/userInfo")
    public Result<User> userInfo(@RequestHeader(name="Authorization") String token) {

        Map<String,Object> map =JwtUtil.parseToken(token);
        int id= (int)map.get("id");
        User user=userService.findByUserId(id);

        log.info("获取用户信息:"+user.toString());

        return Result.success(user);

    }

    }

RequestHeader(name="Authorization") String token,获得token并解析出对应用户的登录名和id,这样以来,我们可以根据这两个重要信息去数据库搜索到对应的用户信息

  private var selectedDate: String = ""  // 默认值为空字符串
    private lateinit var binding: ActivityWriteLogBinding
    private lateinit var logAdapter: LogAdapter
    private lateinit var etDate: EditText
    private var allLogEntries = mutableListOf<LogEntry>()  // 存储所有日志条目
    val token = TokenManager.getToken(App.INSTANCE) ?: ""
    // 创建 Retrofit 实例
    val retrofit = Retrofit.Builder()
        .baseUrl("http://113.44.68.6:8080/")
        .addConverterFactory(GsonConverterFactory.create())
        .client(
            OkHttpClient.Builder()
                .addInterceptor { chain ->
                    val request = chain.request().newBuilder()
                        .addHeader("Authorization", token)
                        .build()
                    chain.proceed(request)
                }
                .build())
        .build()

获取Token:从 TokenManager 获取当前存储的 JWT,用于后续请求。
自动附加Token:通过 OkHttp 的拦截器为每个网络请求的请求头添加 Authorization 字段,携带 JWT。
身份验证:服务器通过解析 JWT 验证请求来源及权限。
前端的话则是在创建 Retrofit 实例会用到addHeader("Authorization", token)进行身份认证和获取对应信息

4. 总结

文件上传与下载: 实现了用户头像、宠物头像和文章图片的上传功能。通过文件验证确保上传的安全性和准确性,采用UUID生成唯一文件名,并使用华为云OBS存储文件,同时返回存储URL供前端展示。

华为云OBS集成: 借助华为云OBS SDK实现文件的高效上传和管理,确保文件传输的稳定性与存储的可靠性,为文件存储提供云端支持。

身份认证(JWT): 使用JWT进行用户身份验证,确保请求合法性,并通过解析Token关联用户或宠物的相关数据,提升系统的安全性和扩展性。

这些技术相辅相成,为宠物APP提供了便捷、安全的操作体验和高效的云服务支持。

5. 参考资料

身份认证(JWT)

...全文
232 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

111

社区成员

发帖
与我相关
我的任务
社区描述
202401_CS_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_TeacherL
  • 林日臻
  • 防震水泥
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧