oauth2客户端,授权服务器依赖版本升级 spring-boot:2.5.5升级到2.6.8 spring-cloud:2020.0.4升级到2021.0.3 授权服务器使用spring-cloud-starter-oauth2:2.2.5搭建 客户端申请访问令牌失败,授权服务器产生客户端证书错误异常事件
spring-boot:2.5.5 对应spring-security:5.5.2 spring-boot 2.6.8 对应spring-scurity:5.6.5 客户端申请访问令牌时,使用Basic Authentication方式认证,将客户端证书信息通过Authorization请求头部传递给授权服务器。spring-scurity-oauth2-client:5.6.5中客户端证书信息编码格式发生了变化,而授权服务器spring-scurity:5.6.5没有对BasicAuthentication认证信息进行正确解码,导致授权服务校验客户端失败
spring-security-oauth2-client:5.6.5
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken)this.authorizationCodeAuthenticationProvider.authenticate(new OAuth2AuthorizationCodeAuthenticationToken(loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange()));
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken)authentication;
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationResponse();
if (authorizationResponse.statusError()) {
throw new OAuth2AuthorizationException(authorizationResponse.getError());
} else {
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest();
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error("invalid_state_parameter");
throw new OAuth2AuthorizationException(oauth2Error);
} else {
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange()));
OAuth2AuthorizationCodeAuthenticationToken authenticationResult = new OAuth2AuthorizationCodeAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange(), accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken(), accessTokenResponse.getAdditionalParameters());
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
}
}
}
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null");
RequestEntity<?> request = (RequestEntity)this.requestEntityConverter.convert(authorizationCodeGrantRequest);
ResponseEntity<OAuth2AccessTokenResponse> response = this.getResponse(request);
OAuth2AccessTokenResponse tokenResponse = (OAuth2AccessTokenResponse)response.getBody();
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse).scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes()).build();
}
return tokenResponse;
}
private Converter<T, HttpHeaders> headersConverter = (authorizationGrantRequest) -> {
return OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(authorizationGrantRequest.getClientRegistration());
};
public RequestEntity<?> convert(T authorizationGrantRequest) {
HttpHeaders headers = (HttpHeaders)this.getHeadersConverter().convert(authorizationGrantRequest);
MultiValueMap<String, String> parameters = (MultiValueMap)this.getParametersConverter().convert(authorizationGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(authorizationGrantRequest.getClientRegistration().getProviderDetails().getTokenUri()).build().toUri();
return new RequestEntity(parameters, headers, HttpMethod.POST, uri);
}
static HttpHeaders getTokenRequestHeaders(ClientRegistration clientRegistration) {
HttpHeaders headers = new HttpHeaders();
headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS);
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod()) || ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
String clientId = encodeClientCredential(clientRegistration.getClientId());
String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
headers.setBasicAuth(clientId, clientSecret);
}
return headers;
}
private static String encodeClientCredential(String clientCredential) {
try {
return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException var2) {
throw new IllegalArgumentException(var2);
}
}
static HttpHeaders getTokenRequestHeaders(ClientRegistration clientRegistration) {
HttpHeaders headers = new HttpHeaders();
headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS);
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod())
|| ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
}
return headers;
}
private BasicAuthenticationConverter authenticationConverter = new BasicAuthenticationConverter();
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
UsernamePasswordAuthenticationToken authRequest = this.authenticationConverter.convert(request);
...
}
public UsernamePasswordAuthenticationToken convert(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header == null) {
return null;
} else {
header = header.trim();
if (!StringUtils.startsWithIgnoreCase(header, "Basic")) {
return null;
} else if (header.equalsIgnoreCase("Basic")) {
throw new BadCredentialsException("Empty basic authentication token");
} else {
byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
byte[] decoded = this.decode(base64Token);
String token = new String(decoded, this.getCredentialsCharset(request));
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
} else {
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(token.substring(0, delim), token.substring(delim + 1));
result.setDetails(this.authenticationDetailsSource.buildDetails(request));
return result;
}
}
}
}
通过请求头传递BasicAuthentication认证信息时会进行base64编码,因此无需进行urlencode,自定义HeadersConverter替换默认实现 oauth2sso客户端安全配置修改如下
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义converter
OAuth2AuthorizationCodeGrantRequestEntityConverter converter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
converter.setHeadersConverter((authorizationGrantRequest) -> {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
MediaType contentType = MediaType.valueOf("application/x-www-form-urlencoded;charset=UTF-8");
headers.setContentType(contentType);
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod()) || ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
}
return headers;
});
DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
client.setRequestEntityConverter(converter);
http.oauth2Client().authorizationCodeGrant().accessTokenResponseClient(client);
http.oauth2Login().tokenEndpoint().accessTokenResponseClient(client);
}
}