我对 Swagger 自动生成的 Swagger 文档有疑问。问题是,尽管我已将项目设置为 UTF-8,但身份验证标头仍然以 ISO-8859-1 进行编码,这会导致密码中的特殊字符出现问题。我已经尝试了很多方法,但找不到任何有效的方法。
这是我的
application.yml
的相关部分:
server:
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
所讨论的端点如下所示:
@PostMapping(value = Endpoints.USER_CHECK,
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(
security = @SecurityRequirement(name = AuthGroups.BASIC_AUTH))
)
public UserCheckResponse postUserCheck(
@AuthenticationPrincipal UserDetails userDetails
) {
if (userDetails == null) {
throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED,"Login failed: Credentials not set");
}
return portalService.postUserCheck(userDetails.getUsername(), userDetails.getPassword());
}
...安全方案定义为:
@SecurityScheme(
name = AuthGroups.BASIC_AUTH,
type = SecuritySchemeType.HTTP,
scheme = "basic"
)
现在,我已经确认,如果我通过 Postman 调用端点,它工作得很好,但是当我使用自动生成的 Swagger 文档执行相同的请求时,授权标头会突然编码为 ISO-8859-1:
客户 | 输入验证 | 生成的身份验证标头 |
---|---|---|
邮差 | 测试:Müll | 授权:基本VGVzdDpNw7xsbA== |
Springdoc Swagger | 测试:Müll | 授权:基本VGVzdDpN/Gxs |
我需要做什么才能让 Springdoc Swagger 页面以 UTF-8 编码授权标头?
更新:
我还检查了生成的swagger页面,它的字符集设置为UTF-8,这让我更加困惑为什么标头采用ISO-8859-1编码:
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
好吧,虽然我没有找到在自动生成的 Swagger 文档页面中设置标头编码的方法,但我did通过创建能够解码 UTF-8 和 ISO-8859 的自定义过滤器找到了解决方法 - 1 标题正确。整个解决方法如下所示:
MyUserDetails
import java.util.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Getter;
@Getter
public class MyUserDetails implements UserDetails {
private final String username;
private final String password;
public MyUserDetails (String username, String password) {
this.username = username;
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
}
MyAuthenticationManager
import org.springframework.security.authentication.*;
import org.springframework.security.core.*;
import org.springframework.security.core.userdetails.UserDetails;
public class MyAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(
Authentication authentication
) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
UserDetails user = new MyUserDetails(username, password);
return UsernamePasswordAuthenticationToken.authenticated(
user,
password,
authentication.getAuthorities()
);
}
}
MyAuthenticationFilter
import java.nio.*;
import java.nio.charset.*;
import java.util.Base64;
import org.apache.commons.lang3.NotImplementedException;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import jakarta.servlet.http.HttpServletRequest;
public class MyAuthenticationFilter extends
AbstractPreAuthenticatedProcessingFilter {
MyAuthenticationFilter(){
setAuthenticationManager(new MyAuthenticationManager());
}
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Basic ")) {
String decodedHeader = decodeBasicAuthHeader(header);
String[] values = decodedHeader.split(":", 2);
return values[0];
}
return null;
}
@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Basic ")) {
String decodedHeader = decodeBasicAuthHeader(header);
String[] values = decodedHeader.split(":", 2);
return values[1];
}
return null;
}
private String decodeBasicAuthHeader(String header) {
String basicAuthHeader = header.substring("Basic".length()).trim();
byte[] credDecoded = Base64.getDecoder()
.decode(basicAuthHeader);
try {
return decode(credDecoded, StandardCharsets.UTF_8);
} catch (CharacterCodingException e) {
try {
return decode(credDecoded, StandardCharsets.ISO_8859_1);
} catch (CharacterCodingException ex) {
throw new NotImplementedException(
"Basic auth header is not encoded in a supported charset"
+ System.lineSeparator()
+ "Supported Charsets are: UTF-8, ISO_8859_1");
}
}
}
private static String decode(byte[] bytes, Charset charset)
throws CharacterCodingException {
CharsetDecoder decoder = charset.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);
decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
CharBuffer charBuffer = decoder.decode(byteBuffer);
return charBuffer.toString();
}
}
AuthConfiguration
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration
@EnableWebSecurity
@Order(1)
public class AuthConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(new MyAuthenticationFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(
authorizeRequests -> authorizeRequests
.anyRequest().permitAll()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
虽然这有效,但我觉得它过于复杂。如果有人知道更好的解决方案,请分享!