異常現象
有時候使用OAuth2做SSO時,客戶端訪問統一認證中心,經過驗證通過,重定向返回後可能會遇到下列問題
OAuth Error
error=“invalid_grant”, error_description=“Invalid redirect: http://localhost:8082/login does not match one of the registered values.”
解決方案
這個時候你需要檢查你重定向地址的協議,域名,端口是否與在認證服務器註冊的重定向地址保持一致。如果不一致就需要將多個域名配置到認證服務器。
例如我的認證服務器配置
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client1")
.secret(passwordEncoder.encode("client1"))
.autoApprove(true)
.redirectUris("http://127.0.0.1:8082/index")
.scopes("admin")
.accessTokenValiditySeconds(7200)
.authorizedGrantTypes("authorization_code");
}
這個時候如果我客戶端訪問下面兩個地址都是可以重定向到統一認證中心的
- http://127.0.0.1:8082/index
- http://localhost:8082/index
但是使用第二種就會出現下面的界面
原因就是我的host和配置的不一致,我的host配置的是127.0.0.1。
原因
我們從代碼入手來解析一下爲何會出現這種情況。具體邏輯在DefaultRedirectResolver.java中
/**
* Attempt to match one of the registered URIs to the that of the requested one.
*
* @param redirectUris the set of the registered URIs to try and find a match. This cannot be null or empty.
* @param requestedRedirect the URI used as part of the request
* @return redirect uri
* @throws RedirectMismatchException if no match was found
*/
private String obtainMatchingRedirect(Set<String> redirectUris, String requestedRedirect) {
Assert.notEmpty(redirectUris, "Redirect URIs cannot be empty");
// 重定向爲空的話直接調用默認配置的重定向地址
if (redirectUris.size() == 1 && requestedRedirect == null) {
return redirectUris.iterator().next();
}
// 不爲空的話,也就是我們出現異常的這種情況
for (String redirectUri : redirectUris) {
// 判斷重定向地址和配置的列表中的是否一致,都不一致的話就會拋出does not match one of the registered values這個異常
if (requestedRedirect != null && redirectMatches(requestedRedirect, redirectUri)) {
// Initialize with the registered redirect-uri
UriComponentsBuilder redirectUriBuilder = UriComponentsBuilder.fromUriString(redirectUri);
UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
if (this.matchSubdomains) {
redirectUriBuilder.host(requestedRedirectUri.getHost());
}
if (!this.matchPorts) {
redirectUriBuilder.port(requestedRedirectUri.getPort());
}
redirectUriBuilder.replaceQuery(requestedRedirectUri.getQuery()); // retain additional params (if any)
redirectUriBuilder.fragment(null);
return redirectUriBuilder.build().toUriString();
}
}
throw new RedirectMismatchException("Invalid redirect: " + requestedRedirect
+ " does not match one of the registered values.");
}
檢測是否匹配的邏輯
/**
* Whether the requested redirect URI "matches" the specified redirect URI. For a URL, this implementation tests if
* the user requested redirect starts with the registered redirect, so it would have the same host and root path if
* it is an HTTP URL. The port, userinfo, query params also matched. Request redirect uri path can include
* additional parameters which are ignored for the match
* <p>
* For other (non-URL) cases, such as for some implicit clients, the redirect_uri must be an exact match.
*
* @param requestedRedirect The requested redirect URI.
* @param redirectUri The registered redirect URI.
* @return Whether the requested redirect URI "matches" the specified redirect URI.
*/
protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();
// scheme也就是協議是否一值
boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme());
// 攜帶的用戶信息是否一值
boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
// host也就是域名是否一致
boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
// 端口是否一致
boolean portMatch = matchPorts ? registeredRedirectUri.getPort() == requestedRedirectUri.getPort() : true;
// path是否一致
boolean pathMatch = isEqual(registeredRedirectUri.getPath(),
StringUtils.cleanPath(requestedRedirectUri.getPath()));
// 攜帶參數是否一致
boolean queryParamMatch = matchQueryParams(registeredRedirectUri.getQueryParams(),
requestedRedirectUri.getQueryParams());
// 全部一致纔算通過
return schemeMatch && userInfoMatch && hostMatch && portMatch && pathMatch && queryParamMatch;
}