You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

认证API:如何在客户端使用动态变更的Salt?

动态Salt下的客户端-服务端认证方案

核心思路

  • 为每个用户分配唯一的随机Salt,由服务端在用户注册时生成,与哈希后的密码一同存储在数据库中
  • 登录流程拆分为两步:客户端先请求目标用户的专属Salt,再用该Salt哈希密码后提交认证请求
  • 全程无需硬编码固定Salt或密钥,所有动态生成的Salt由服务端统一管理并同步给客户端

数据库表结构调整

首先需要修改用户表,新增Salt字段存储每个用户的专属Salt:

ALTER TABLE customers.Unfallmeldung ADD COLUMN Salt VARCHAR(255) NOT NULL;

服务端代码修改(Node.js/Express)

新增获取用户Salt的接口,同时调整登录接口的验证逻辑:

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const mysql = require('mysql');

// 创建数据库连接池
const pool = mysql.createPool({
  host: 'xx.xx.xx.xx', user: 'xxxx', password: 'XXXXXXXXX', database: 'customers'
});

app.use(bodyParser.json());

// 获取用户专属Salt的接口
app.post('/get-salt', (req, res) => {
  const username = req.body.username;
  if (!username) {
    return res.status(400).json({message: 'Username is required'});
  }

  pool.query('SELECT Salt FROM customers.Unfallmeldung WHERE Username = ?', [username], (error, results) => {
    if (error) {
      return res.status(500).json({error});
    }
    if (results.length === 0) {
      return res.status(404).json({message: 'User not found'});
    }
    res.status(200).json({salt: results[0].Salt});
  });
});

// 修改后的登录验证接口
app.post('/login', (req, res) => {
  const username = req.body.username;
  const hashedPassword = req.body.password;

  if (!username || !hashedPassword) {
    return res.status(400).json({message: 'Username and password are required'});
  }

  pool.query('SELECT * FROM customers.Unfallmeldung WHERE Username = ?', [username], (error, results) => {
    if (error) {
      return res.status(500).json({error});
    }
    if (results.length === 0) {
      return res.status(401).json({message: 'Invalid credentials'});
    }

    const storedHashedPassword = results[0].Password;
    // 直接对比客户端传来的哈希值与数据库存储的哈希值
    if (hashedPassword === storedHashedPassword) {
      const response = [results[0].Company, results[0].Access, results[0].Databases];
      return res.status(200).json({message: response});
    } else {
      return res.status(401).json({message: 'Invalid credentials'});
    }
  });
});

app.listen(3000, () => console.log('Server started on port 3000'));

客户端代码修改(Java)

调整登录逻辑,先获取用户Salt再哈希密码:

package com.pichlmayr.unfallmeldung;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class APIClient {
    private static final String API_URL_LOGIN = "https://xx.xx/login";
    private static final String API_URL_GET_SALT = "https://xx.xx/get-salt";

    public static String[][] authenticate(String username, String password) {
        try {
            // 第一步:获取用户专属Salt
            String salt = getSalt(username);
            if (salt == null) {
                System.out.println("Error: Failed to get salt for user");
                return null;
            }

            // 第二步:用获取到的Salt哈希密码
            String hashedPassword = HashPassword.encrypt(password, salt);

            // 第三步:提交登录请求
            URL url = new URL(API_URL_LOGIN);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setDoOutput(true);

            String requestBody = "{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}";
            OutputStream os = connection.getOutputStream();
            os.write(requestBody.getBytes());
            os.flush();
            os.close();

            // 处理响应
            BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String response = br.readLine();
            br.close();

            if (response.contains("Error: ") || response.contains("Invalid credentials")) {
                System.out.println("Error: " + response);
                return null;
            }

            // 解析响应(保留原逻辑)
            response = response.replace("\"", "")
                               .replace("[", "")
                               .replace("]", "")
                               .replace("{", "")
                               .replace("}", "");
            response = response.substring(8);
            String[] responseArray = response.split(",");
            
            String[] company = new String[]{responseArray[0]};
            String[] access = new String[]{responseArray[1]};
            String[] databases = new String[responseArray.length - 2];
            System.arraycopy(responseArray, 2, databases, 0, responseArray.length - 2);

            return new String[][]{company, access, databases};
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // 获取用户Salt的工具方法
    private static String getSalt(String username) throws Exception {
        URL url = new URL(API_URL_GET_SALT);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setDoOutput(true);

        String requestBody = "{\"username\":\"" + username + "\"}";
        OutputStream os = connection.getOutputStream();
        os.write(requestBody.getBytes());
        os.flush();
        os.close();

        BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String response = br.readLine();
        br.close();

        if (response.contains("User not found") || response.contains("Error")) {
            return null;
        }

        // 解析返回的Salt值
        return response.split("\"salt\":\"")[1].split("\"}")[0];
    }
}

哈希方法修改(Java)

调整为支持传入动态Salt,同时添加生成随机Salt的方法(用于用户注册):

package com.pichlmayr.unfallmeldung;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class HashPassword {
    // 接收外部传入的Salt进行哈希
    public static String encrypt(String passwordToHash, String salt) throws NoSuchAlgorithmException {
        return get_SHA_512_SecurePassword(passwordToHash, salt);
    }

    private static String get_SHA_512_SecurePassword(String passwordToHash, String salt) {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            md.update(salt.getBytes());
            byte[] bytes = md.digest(passwordToHash.getBytes());
            StringBuilder sb = new StringBuilder();
            for (byte aByte : bytes) {
                sb.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
            }
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    // 生成随机Salt(用户注册时在服务端调用)
    public static String generateRandomSalt() throws NoSuchAlgorithmException {
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] salt = new byte[16];
        sr.nextBytes(salt);
        return Base64.getEncoder().encodeToString(salt);
    }
}

额外说明

  • 用户注册时,服务端需调用generateRandomSalt()生成随机Salt,用该Salt哈希用户密码后,将用户名、Salt、哈希后的密码存入数据库
  • 必须使用HTTPS传输所有请求,避免Salt和哈希值在网络中被窃听
  • 若需进一步提升安全性,可采用挑战-响应模式:服务端生成随机挑战值,客户端用密码哈希挑战值后返回,服务器验证结果,防止重放攻击

内容的提问来源于stack exchange,提问作者Matze._

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 03:55:05