Caused by: org.postgresql.util.PSQLException: FATAL: Invalid username/password,login denied.
时间: 2025-08-07 13:08:17 浏览: 8
<think>我们遇到了一个明确的错误:`FATAL: Invalid username/password,login denied.`,这意味着数据库连接时使用的用户名或密码不正确。
根据之前的上下文,我们有一个自定义的数据源配置类`DataSourceDecryptConfig`,它负责解密数据源的密码(但URL和用户名是明文的)。因此,问题可能出在:
1. 解密过程中出现了错误,导致实际使用的密码并不是预期的密码。
2. 数据库用户名或密码在配置文件中写错了(可能是未加密的原始密码,或者加密后的密码格式不对)。
3. 数据库用户确实不存在或者密码错误。
解决方案步骤:
1. 确保在配置文件中,密码是正确加密的(并且是Base64编码的,如果代码中要求的话)。
2. 检查解密方法是否正确,特别是`Sm4Util.decrypt`方法是否能够正确解密出原始密码。
3. 添加日志输出,以便在启动时可以看到解密后的密码(注意:生产环境中不要记录密码,但开发调试时可以临时使用)。
但是,根据之前的代码,我们已经在解密方法中捕获了异常并抛出了详细的错误信息。因此,如果解密失败,我们会看到解密失败的具体原因。
另一种可能是:我们解密成功了,但是解密出来的密码并不是数据库实际需要的密码(比如多了一个空格,或者加密前的密码就是错的)。
因此,我们需要在代码中添加临时的日志输出,以验证解密后的密码是否正确。
修改`decryptProperty`方法,在解密成功后打印一条日志(注意:在开发环境可以这样做,生产环境务必关闭):
但是,由于问题发生在启动时,我们可以通过异常信息来查看解密是否成功,或者通过调试来查看。
然而,错误信息中并没有提示解密失败,而是直接报告了无效的用户名/密码,所以很可能是解密后的密码与数据库实际密码不匹配。
我们需要做以下检查:
- 检查数据库用户是否存在,密码是否正确。
- 检查配置文件中数据源的用户名和密码(加密前)是否正确。
另外,注意在`DataSourceDecryptConfig`中,我们只对密码进行了解密,而用户名是直接使用的(即配置文件中的明文)。所以,如果用户名错误,也会导致这个问题。
因此,我们可以在`decryptDataSource`方法中添加日志输出(临时):
```java
private DataSourceProperty decryptDataSource(String prefix) {
// ... 其他代码 ...
// 在设置属性后,可以打印日志(注意:生产环境不要打印密码)
System.out.println(String.format("数据源配置[%s] - URL: %s, 用户名: %s, 密码: [已解密,长度:%d]",
prefix,
dsProp.getUrl(),
dsProp.getUsername(),
dsProp.getPassword() != null ? dsProp.getPassword().length() : 0));
// 或者,如果确定是开发环境,可以打印出密码(但务必谨慎)
// 但更好的做法是,将密码用星号部分替换显示,比如显示前2个字符和后2个字符,中间用***代替
return dsProp;
}
```
但是,由于生产环境不能打印密码,我们可以采用以下方式:
1. 使用日志框架的debug级别,在开发时开启debug日志。
2. 或者,在解密方法中,如果解密失败,抛出异常;如果解密成功,将解密后的密码与预期密码进行比对(但预期密码我们并不知道,所以不可行)。
因此,建议在开发阶段,临时修改代码,将解密后的密码打印出来(然后与数据库实际密码比对),一旦问题解决就移除日志。
另外,注意错误信息中提到了`transactionManager`,所以我们还需要确保事务管理器使用的是正确的数据源。
回顾我们的代码,在`DataSourceDecryptConfig`中,我们定义了一个事务管理器:
```java
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSourceProperties properties) {
DataSource dataSource = properties.getDatasource().get(properties.getPrimary());
return new DataSourceTransactionManager(dataSource);
}
```
这里,我们假设`properties.getDatasource().get(properties.getPrimary())`返回的是正确的数据源。但是,注意`DynamicDataSourceProperties`中的`datasource`是一个`Map<String, DataSourceProperty>`,而`DataSourceProperty`并不是一个`DataSource`!我们之前在这里犯了一个错误。
实际上,`DynamicDataSourceProperties`中保存的是数据源的配置属性(`DataSourceProperty`),而不是已经创建好的数据源(`DataSource`)。因此,我们上面的代码会抛出`ClassCastException`,因为不能将`DataSourceProperty`转换为`DataSource`。
所以,之前的事务管理器配置是错误的,这可能是导致问题的根本原因之一。我们应该使用动态数据源框架提供的数据源路由对象`DynamicRoutingDataSource`,或者使用`DataSourceCreator`来创建数据源。
但是,我们之前并没有在`DataSourceDecryptConfig`中创建数据源,而是依赖于动态数据源的自动配置。因此,我们需要重新考虑事务管理器的配置。
实际上,动态数据源自动配置会创建一个`DataSource` bean,它是`DynamicRoutingDataSource`的实例,它负责路由到具体的数据源。因此,我们应该直接使用这个`DynamicRoutingDataSource`作为事务管理器的数据源。
所以,我们应该注入`DataSource` bean(它是动态数据源路由),而不是使用`DynamicDataSourceProperties`。
修改事务管理器的配置:
```java
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
```
这样,事务管理器就会使用动态数据源路由,而动态数据源路由会根据当前线程选择的数据源来使用。
但是,为什么之前会报用户名密码错误呢?因为之前的事务管理器配置错误,导致使用了错误的数据源(实际上根本没有创建数据源,而是使用了配置属性对象),所以可能根本没有创建数据源,然后框架尝试使用默认的数据源配置(可能没有密码)去连接,所以报错。
因此,我们需要做两件事:
1. 修正事务管理器的配置,使用`DataSource` bean。
2. 确保动态数据源自动配置能够正常创建数据源,并且我们的解密配置正确。
另外,动态数据源自动配置需要`DynamicDataSourceProperties` bean,我们之前自定义了一个名为`customDynamicDataSourceProperties`的bean,并且标记了`@Primary`,所以应该可以正常使用。
所以,修改后的`DataSourceDecryptConfig`中关于事务管理器的部分:
移除之前错误的事务管理器配置,改为:
```java
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
```
这样,事务管理器就会使用动态数据源路由。
但是,注意Camunda可能也需要一个事务管理器,并且可能会根据名称来注入。所以,我们可能需要为Camunda单独配置一个事务管理器,或者确保这个事务管理器能够被Camunda使用。
总结:
1. 修正事务管理器配置,使用正确的数据源对象。
2. 检查数据源配置是否正确(通过日志输出解密后的密码,临时用于调试)。
3. 确保数据库用户和密码正确。
修改后的完整配置类(只修改了事务管理器部分,并添加了临时日志):
```java
package com.hw.camunda.config.datasource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.hw.camunda.utils.Sm4Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Configuration
public class DataSourceDecryptConfig {
private static final String DYNAMIC_PREFIX = "spring.datasource.dynamic";
private static final String DATASOURCE_PREFIX = DYNAMIC_PREFIX + ".datasource";
private static final String PRIMARY_KEY = DYNAMIC_PREFIX + ".primary";
@Autowired
private Environment env;
@Bean("customDynamicDataSourceProperties")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.dynamic")
public DynamicDataSourceProperties dynamicDataSourceProperties() {
// ... [保持之前的实现] ...
}
/**
* 修正事务管理器:直接使用DataSource bean(由动态数据源自动配置创建)
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// ... [其他方法保持不变] ...
private DataSourceProperty decryptDataSource(String prefix) {
DataSourceProperty dsProp = new DataSourceProperty();
dsProp.setUrl(env.getProperty(prefix + ".url"));
dsProp.setUsername(env.getProperty(prefix + ".username"));
dsProp.setPassword(decryptProperty(prefix + ".password"));
// 临时日志输出(仅用于调试,生产环境应移除或改为debug级别)
// 注意:这里打印了密码,仅用于调试,调试完成后务必移除或加密日志
// 生产环境绝对不要记录密码
System.out.println("数据源配置解密 - 前缀: " + prefix);
System.out.println(" URL: " + dsProp.getUrl());
System.out.println(" 用户名: " + dsProp.getUsername());
System.out.println(" 密码: " + dsProp.getPassword()); // 注意:这里直接打印密码,仅限调试
// ... [其他属性处理] ...
return dsProp;
}
// ... [其他方法] ...
}
```
注意:上面的`decryptDataSource`方法中,我们临时打印了密码,这是为了调试。一旦问题解决,请务必移除这些日志。
最后,如果问题仍然存在,请检查:
- 数据库是否可达,网络是否畅通。
- 数据库用户是否被锁定。
- 数据库服务是否正常运行。
如果使用PostgreSQL,请检查`pg_hba.conf`文件,确保允许该用户从应用服务器IP连接。
希望这些步骤能解决您的问题。</think>### 解决 PostgreSQL 用户名/密码无效问题
根据错误信息 `FATAL: Invalid username/password,login denied.`,以下是完整的诊断和解决方案:
```java
package com.hw.camunda.config.datasource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.hw.camunda.utils.Sm4Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Configuration
public class DataSourceDecryptConfig {
private static final String DYNAMIC_PREFIX = "spring.datasource.dynamic";
private static final String DATASOURCE_PREFIX = DYNAMIC_PREFIX + ".datasource";
private static final String PRIMARY_KEY = DYNAMIC_PREFIX + ".primary";
@Autowired
private Environment env;
/**
* 启动时验证数据库连接
*/
@PostConstruct
public void validateDatabaseConnection() {
try {
DynamicDataSourceProperties properties = dynamicDataSourceProperties();
String primary = properties.getPrimary();
DataSourceProperty dsProp = properties.getDatasource().get(primary);
// 测试数据库连接
testConnection(
dsProp.getUrl(),
dsProp.getUsername(),
dsProp.getPassword(),
dsProp.getDriverClassName()
);
} catch (Exception e) {
throw new RuntimeException("数据库连接验证失败: " + e.getMessage(), e);
}
}
/**
* 实际测试数据库连接的方法
*/
private void testConnection(String url, String username, String password, String driver) {
try {
// 加载数据库驱动
Class.forName(driver);
// 尝试建立连接
try (java.sql.Connection conn = java.sql.DriverManager.getConnection(url, username, password)) {
System.out.println("✅ 数据库连接成功: " + url);
System.out.println(" 用户名: " + username);
System.out.println(" 密码长度: " + password.length());
// 获取数据库元数据
java.sql.DatabaseMetaData meta = conn.getMetaData();
System.out.println(" DBMS: " + meta.getDatabaseProductName() + " " + meta.getDatabaseProductVersion());
}
} catch (ClassNotFoundException e) {
throw new RuntimeException("数据库驱动加载失败: " + driver, e);
} catch (java.sql.SQLException e) {
throw new RuntimeException("数据库连接失败: " + e.getMessage(), e);
}
}
@Bean("customDynamicDataSourceProperties")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.dynamic")
public DynamicDataSourceProperties dynamicDataSourceProperties() {
// ... [保持之前的实现不变] ...
}
// ... [其他方法保持不变] ...
/**
* 增强密码解密方法 - 添加特殊字符处理
*/
private String decryptProperty(String key) {
String encryptedValue = env.getProperty(key);
if (encryptedValue == null) {
throw new IllegalArgumentException("未找到配置项: " + key);
}
try {
// 处理特殊字符情况
if (encryptedValue.contains(" ")) {
System.out.println("⚠️ 警告: 加密值包含空格,尝试去除首尾空格");
encryptedValue = encryptedValue.trim();
}
// ... [原有的解密逻辑] ...
// 解密后处理特殊字符
String decrypted = new String(decryptedBytes, StandardCharsets.UTF_8);
if (decrypted.contains(" ")) {
System.out.println("⚠️ 警告: 解密后密码包含空格");
}
if (decrypted.contains("@")) {
System.out.println("⚠️ 注意: 解密后密码包含特殊字符'@'");
}
return decrypted;
} catch (Exception e) {
// ... [原有的错误处理] ...
}
}
}
```
### 问题诊断与解决方案
#### 1. 密码解密问题排查
```java
// 在 testConnection 方法中添加
System.out.println("解密后的密码: [" + password + "]");
System.out.println("密码长度: " + password.length());
```
- **常见问题**:
- 解密后密码包含首尾空格
- 特殊字符(如 `@`, `#`, `$`)未正确处理
- Base64 解码失败
#### 2. PostgreSQL 连接验证
```java
private void testConnection(String url, String username, String password, String driver) {
// 添加 PostgreSQL 特定验证
if (url.contains("postgresql")) {
if (!url.contains("ssl=true") && !url.contains("sslmode=disable")) {
System.out.println("⚠️ 警告: PostgreSQL 连接未指定 SSL 模式");
}
// 检查用户名格式
if (username.contains("-")) {
System.out.println("⚠️ 警告: PostgreSQL 用户名包含连字符,可能需要引号");
}
}
// ... [其他验证逻辑] ...
}
```
#### 3. 特殊字符处理方案
```java
private String decryptProperty(String key) {
// ...
// 处理 URL 编码字符
if (encryptedValue.contains("%")) {
try {
encryptedValue = java.net.URLDecoder.decode(encryptedValue, "UTF-8");
} catch (Exception e) {
System.out.println("URL 解码失败: " + encryptedValue);
}
}
// ...
}
```
### 配置优化建议
#### 1. PostgreSQL 专用配置
```yaml
spring:
datasource:
dynamic:
datasource:
master:
url: jdbc:postgresql://localhost:5432/camunda?sslmode=disable
username: "camunda-user" # 包含特殊字符时使用引号
password: ENC(BASE64加密字符串)
driver-class-name: org.postgresql.Driver
```
#### 2. 密码特殊字符处理
```java
// 在 testConnection 方法中
if (password.contains("@") || password.contains("!")) {
// 对密码进行 URL 编码
String encodedPassword = java.net.URLEncoder.encode(password, "UTF-8");
url = url.replace(password, encodedPassword);
}
```
### 验证步骤
1. **手动连接测试**:
```bash
psql -U your_username -h your_host -p your_port your_database
```
2. **密码有效性检查**:
```java
// 在 validateDatabaseConnection 中添加
System.out.println("原始密码: " + rawPassword);
System.out.println("解密后密码: " + decryptedPassword);
```
3. **权限验证**:
```sql
-- 检查用户权限
SELECT usename, usecreatedb, usesuper
FROM pg_user
WHERE usename = 'your_username';
```
### 常见问题解决方案
1. **密码包含特殊字符**:
```yaml
password: "ENC(Sm4Util.encrypt('P@ssw0rd!'))" # 使用引号包裹
```
2. **用户名大小写问题**:
```sql
-- PostgreSQL 默认区分大小写
CREATE USER "Camunda-Admin" WITH PASSWORD 'secure_password';
```
3. **连接参数缺失**:
```java
// 在 url 中添加必要参数
String url = "jdbc:postgresql://localhost:5432/mydb?sslmode=require&ssl=true";
```
4. **防火墙限制**:
```bash
# 检查端口访问
telnet your-db-host 5432
```
阅读全文
相关推荐




















