什么是SSO
單點登錄系統(tǒng)主要解決統(tǒng)一認證授權(quán)的問題,在多個應用系統(tǒng)中,只需要登錄一次,就可以訪問其他相互信任的應用系統(tǒng)。
想要完成單點登錄的效果,必須先統(tǒng)一管理用戶信息,其他應用系統(tǒng)必須配合完成改造和對接。
什么場景下,需要用到SSO?
較大型的企業(yè),往往存在多套應用系統(tǒng)。各個應用系統(tǒng)都是在企業(yè)發(fā)展的某個階段,因業(yè)務發(fā)展的需求,開發(fā)研制而成。每套系統(tǒng)都會有一套自己的用戶體系,需要終端用戶注冊、登錄后才能使用。
隨著企業(yè)的發(fā)展,用到的系統(tǒng)隨之增多,用戶在操作不同的系統(tǒng)時,需要多次登錄,而且每個系統(tǒng)的賬號都不一樣,這對于用戶來說很不方便。
于是,設計一套統(tǒng)一的登錄認證系統(tǒng),避免不必要的反復登錄。減輕用戶操作負擔,提高效率,在企業(yè)的發(fā)展進程中,顯得越來越重要。
什么是OAuth 2.0
OAuth(開放授權(quán))是一個開放標準,允許用戶授權(quán)第三方移動應用訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方移動應用或分享他們數(shù)據(jù)的所有內(nèi)容。
OAuth 2.0是OAuth協(xié)議的下一版本,但不向后兼容OAuth 1.0。 OAuth 2.0關注客戶端開發(fā)者的簡易性,同時為Web應用,桌面應用和手機,和起居室設備提供專門的認證流程。
為什么需要OAuth 2.0?
上文提到,為了實現(xiàn)SSO,各個應用系統(tǒng)需要配合對接單點登錄系統(tǒng)。OAuth2.0提供了一套簡單,通用,可擴展的開放認證授權(quán)協(xié)議。既可以實現(xiàn)企業(yè)內(nèi)部系統(tǒng)的對接,也可以實現(xiàn)企業(yè)與第三方外部系統(tǒng)的認證互通。
鑒于OAuth2.0的開放特性,只要遵守OAuth2.0協(xié)議,可以大幅降低系統(tǒng)間對接開發(fā)的溝通調(diào)試成本。
什么是Spring Security OAuth
Spring Security對OAuth的實現(xiàn),借助SpringSecurity框架在認證授權(quán)方面既有的優(yōu)勢,可以讓開發(fā)者簡易地使用OAuth協(xié)議。

Spring Security OAuth存在的問題
- 鑒于OAuth協(xié)議本身就有較多的概念(4種角色,4種授權(quán)模式),使用Spring Security OAuth就需要定義和管理OAuth相關的Bean。
- 另一個比較大的問題,在于Spring Security OAuth默認基于Session實現(xiàn),這在微服務場景下并不適用。這也是本系列文章要解決的核心問題。
代碼實現(xiàn)
Springsecurity基礎配置
- 聲明認證管理器AuthenticationManager,認證階段的用戶身份鑒別使用自定義的UserDetailsService
- 采用BCryptPasswordEncoder對登錄密碼進行加密及編碼,BCryptPasswordEncoder基于隨機鹽+密鑰對密碼進行加密,并通過SHA-256算法進行編碼
- 自定義認證邏輯過濾器,滿足登錄請求參數(shù)個性化,多樣化需求
- 自定義token解析過濾器,解決微服務無狀態(tài)場景下,Spring Security OAuth無法在Session中獲取用戶認證信息的問題
package com.codeiy.auth.oauth.config;
import com.codeiy.auth.oauth.filter.AuthenticationProcessingFilter;
import com.codeiy.auth.oauth.filter.TokenAuthenticationFilter;
import com.codeiy.auth.oauth.handler.OAuthFailureHandler;
import com.codeiy.auth.oauth.handler.OAuthSuccessHandler;
import com.codeiy.auth.oauth.handler.SsoLogoutSuccessHandler;
import com.codeiy.auth.oauth.service.UserDetailsServiceImpl;
import com.codeiy.core.constant.AuthConstants;
import com.codeiy.user.client.UserClient;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.Filter;
/**
* SpringSecurity基礎配置
* 聲明認證管理器{@link AuthenticationManager},認證階段的用戶身份鑒別使用自定義的{@link UserDetailsService}
* 采用{@link BCryptPasswordEncoder}對登錄密碼進行加密及編碼,{@link BCryptPasswordEncoder}基于隨機鹽+密鑰對密碼進行加密,并通過SHA-256算法進行編碼
*
* @author free@codeiy.com
*/
@Primary
@Order(90)
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
private final UserClient userClient;
/**
* 基于隨機鹽+密鑰對密碼進行加密,并通過SHA-256算法進行編碼
*/
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
/**
* 自定義身份認證服務
*/
@Override
@Bean
public UserDetailsService userDetailsService() {
UserDetailsServiceImpl userDetailsService = new UserDetailsServiceImpl();
userDetailsService.setUserClient(userClient);
userDetailsService.setPasswordEncoder(passwordEncoder);
return userDetailsService;
}
/**
* 1. 禁用csrf
* 2. 請求會話采用無狀態(tài)方式
* 3. 自定義登錄登出路徑
* 4. 自定義登錄請求參數(shù){@link UsernamePasswordAuthenticationFilter}
*
* @param http http請求安全相關配置
*/
@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
Filter loginFilter = loginFilter();
Filter tokenAuthenticationFilter = tokenAuthenticationFilter();
http.formLogin()
.loginProcessingUrl(AuthConstants.LOGIN_PROCESSING_URL).permitAll()
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().logout().logoutSuccessHandler(new SsoLogoutSuccessHandler())
.and().authorizeRequests()
.antMatchers(AuthConstants.LOGOUT_URL).permitAll()
.anyRequest().authenticated()
// 登錄請求參數(shù)除了用戶名,密碼之外,還有驗證碼等其他參數(shù),通過過濾器自定義認證邏輯
.and().addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class)
// 解決微服務架構(gòu)無狀態(tài)請求場景下,如何識別當前請求所屬用戶,并綁定到SpringSecurity上下文
.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
/**
* 認證管理器
*/
@Bean
@Override
@SneakyThrows
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}
/**
* 登錄密碼編碼工具
*/
@Bean
public PasswordEncoder passwordEncoder() {
return passwordEncoder;
}
/**
* 自定義登錄認證過濾器,滿足登錄請求參數(shù)個性化,多樣化需求
*/
private Filter loginFilter() throws Exception {
AuthenticationProcessingFilter loginFilter = new AuthenticationProcessingFilter();
loginFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(AuthConstants.LOGIN_PROCESSING_URL, AuthConstants.METHOD_POST));
loginFilter.setAuthenticationSuccessHandler(new OAuthSuccessHandler());
loginFilter.setAuthenticationFailureHandler(new OAuthFailureHandler());
loginFilter.setAuthenticationManager(authenticationManager());
return loginFilter;
}
/**
* token解析過濾器,解決微服務無狀態(tài)場景下,Spring Security OAuth無法在Session中獲取用戶認證信息的問題
*/
private Filter tokenAuthenticationFilter() {
TokenAuthenticationFilter tokenAuthenticationFilter = new TokenAuthenticationFilter();
return tokenAuthenticationFilter;
}
}
OAuth認證服務器配置
- 通過注解@EnableAuthorizationServer聲明認證服務器
- 基于ClientDetailsServiceImpl自定義應用系統(tǒng)信息管理
- 基于Redis存儲OAuth協(xié)議的AccessToken
- 聲明OAuth2.0簡化模式AccessToken生成規(guī)則
- 自定義Auth2.0協(xié)議確認授權(quán)以及認證請求鏈接
package com.codeiy.auth.oauth.config;
import com.codeiy.auth.oauth.service.AuthenticationCodeServiceImpl;
import com.codeiy.auth.oauth.service.ClientDetailsServiceImpl;
import com.codeiy.core.constant.AuthConstants;
import com.codeiy.user.client.AppClient;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
/**
* 通過注解{@code @EnableAuthorizationServer}聲明認證服務器
* 基于{@link ClientDetailsServiceImpl}自定義應用系統(tǒng)信息管理
* 基于Redis存儲OAuth協(xié)議的AccessToken
* 聲明OAuth2.0簡化模式AccessToken生成規(guī)則
* 自定義Auth2.0協(xié)議確認授權(quán)以及認證請求鏈接
*/
@RequiredArgsConstructor
@EnableAuthorizationServer
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final AppClient appClient;
private final AuthenticationManager authenticationManager;
private final RedisConnectionFactory redisConnectionFactory;
private final PasswordEncoder passwordEncoder;
private ClientDetailsServiceImpl clientDetailsService;
private RedisTokenStore redisTokenStore;
private AuthorizationServerTokenServices tokenServices;
private OAuth2RequestFactory oAuth2RequestFactory;
private ImplicitTokenGranter implicitTokenGranter;
private AuthorizationCodeServices authorizationCodeServices;
/**
* 基于Redis存儲OAuth協(xié)議的AccessToken
*/
@Bean
public TokenStore tokenStore() {
if (redisTokenStore == null) {
redisTokenStore = new RedisTokenStore(redisConnectionFactory);
redisTokenStore.setPrefix(AuthConstants.OAUTH_PREFIX);
}
return redisTokenStore;
}
/**
* 令牌權(quán)限范圍配置,使用默認的{@link DefaultOAuth2RequestFactory},可匹配到用戶的角色。
*/
@Bean
public OAuth2RequestFactory oAuth2RequestFactory() {
if (oAuth2RequestFactory == null) {
oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService());
}
return oAuth2RequestFactory;
}
/**
* OAuth2.0簡化模式AccessToken生成規(guī)則
*/
@Bean
public ImplicitTokenGranter implicitTokenGranter() {
if (implicitTokenGranter == null) {
implicitTokenGranter = new ImplicitTokenGranter(tokenServices(), clientDetailsService(), oAuth2RequestFactory());
}
return implicitTokenGranter;
}
/**
* OAuth2.0授權(quán)碼模式,自定義授權(quán)碼的存儲方式
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
if (authorizationCodeServices == null) {
authorizationCodeServices = new AuthenticationCodeServiceImpl(redisConnectionFactory);
}
return authorizationCodeServices;
}
/**
* 自定義應用系統(tǒng)信息管理
*/
private ClientDetailsService clientDetailsService() {
if (clientDetailsService == null) {
clientDetailsService = new ClientDetailsServiceImpl();
clientDetailsService.setPasswordEncoder(passwordEncoder);
clientDetailsService.setAppClient(appClient);
}
return clientDetailsService;
}
/**
* 聲明AccessToken管理服務
*/
private AuthorizationServerTokenServices tokenServices() {
if (tokenServices == null) {
tokenServices = new DefaultTokenServices();
}
return tokenServices;
}
/**
* 配置應用系統(tǒng)管理,基于{@link ClientDetailsServiceImpl}自定義應用系統(tǒng)信息管理
*/
@Override
@SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {
clients.withClientDetails(clientDetailsService());
}
/**
* 配置獲取AccessToken請求的訪問權(quán)限
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.tokenKeyAccess("permitAll()").allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
}
/**
* 自定義Auth2.0協(xié)議確認授權(quán)以及認證請求鏈接
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenServices(tokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.POST)
.pathMapping("/oauth/confirm_access", "/oauth/confirmAccess")
.pathMapping("/oauth/authorize", "/oauth/customAuthorize")
;
}
}
確認授權(quán)核心代碼
package com.codeiy.auth.oauth.endpoint;
import cn.hutool.core.util.StrUtil;
import com.codeiy.core.util.R;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.*;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver;
import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.*;
/**
* OAuth授權(quán)自定義端點
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/oauth")
public class AuthEndpoint {
private final ClientDetailsService clientDetailsService;
private final ImplicitTokenGranter implicitTokenGranter;
private final AuthorizationCodeServices authorizationCodeServices;
private final OAuth2RequestFactory oAuth2RequestFactory;
private RedirectResolver redirectResolver = new DefaultRedirectResolver();
private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();
/**
* 授權(quán)確認頁面
*
* @return
*/
@GetMapping("/confirmAccess")
public R confirmAccess(HttpServletRequest request) {
String clientId = request.getParameter("clientId");
if (StrUtil.isBlank(clientId)) {
return R.failed("clientId不能為空");
}
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
return R.failed("根據(jù)clientId查無數(shù)據(jù)");
}
return R.ok(clientDetails);
}
/**
* 自定義確認授權(quán)
*
* @return
*/
@RequestMapping("/customAuthorize")
public R customAuthorize(HttpServletRequest request, @RequestParam Map<String, String> parameters) {
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(parameters);
Set<String> responseTypes = authorizationRequest.getResponseTypes();
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
}
if (authorizationRequest.getClientId() == null) {
throw new InvalidClientException("A client id must be provided");
}
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
if (principal == null || !principal.isAuthenticated()) {
throw new InsufficientAuthenticationException(
"User must be authenticated with Spring Security before authorization can be completed.");
}
ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
// The resolved redirect URI is either the redirect_uri from the parameters or the one from
// clientDetails. Either way we need to store it on the AuthorizationRequest.
String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
if (!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException(
"A redirectUri must be either supplied or preconfigured in the ClientDetails");
}
authorizationRequest.setRedirectUri(resolvedRedirect);
// We intentionally only validate the parameters requested by the client (ignoring any data that may have
// been added to the request by the manager).
oauth2RequestValidator.validateScope(authorizationRequest, client);
authorizationRequest.setApproved("true".equals(parameters.get("user_oauth_approval")));
// Validation is all done, so we can check for auto approval...
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
ModelAndView mv = getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
String url = getSuccessfulRedirect(authorizationRequest, generateCode(authorizationRequest, principal));
log.info(url);
}
}
String clientId = request.getParameter("clientId");
if (StrUtil.isBlank(clientId)) {
return R.failed("clientId不能為空");
}
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
return R.failed("根據(jù)clientId查無數(shù)據(jù)");
}
return R.ok(clientDetails);
}
private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
if (authorizationCode == null) {
throw new IllegalStateException("No authorization code found in the current request scope.");
}
Map<String, String> query = new LinkedHashMap<String, String>();
query.put("code", authorizationCode);
String state = authorizationRequest.getState();
if (state != null) {
query.put("state", state);
}
return append(authorizationRequest.getRedirectUri(), query, null, false);
}
private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication)
throws AuthenticationException {
try {
OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);
return code;
} catch (OAuth2Exception e) {
if (authorizationRequest.getState() != null) {
e.addAdditionalInformation("state", authorizationRequest.getState());
}
throw e;
}
}
private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorizationRequest) {
try {
TokenRequest tokenRequest = oAuth2RequestFactory.createTokenRequest(authorizationRequest, "implicit");
OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
if (accessToken == null) {
throw new UnsupportedResponseTypeException("Unsupported response type: token");
}
return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken), false, true,
false));
} catch (OAuth2Exception e) {
return new ModelAndView(new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false,
true, false));
}
}
private String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken) {
Map<String, Object> vars = new LinkedHashMap<String, Object>();
Map<String, String> keys = new HashMap<String, String>();
if (accessToken == null) {
throw new InvalidRequestException("An implicit grant could not be made");
}
vars.put("access_token", accessToken.getValue());
vars.put("token_type", accessToken.getTokenType());
String state = authorizationRequest.getState();
if (state != null) {
vars.put("state", state);
}
Date expiration = accessToken.getExpiration();
if (expiration != null) {
long expires_in = (expiration.getTime() - System.currentTimeMillis()) / 1000;
vars.put("expires_in", expires_in);
}
String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE);
if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) {
vars.put("scope", OAuth2Utils.formatParameterList(accessToken.getScope()));
}
Map<String, Object> additionalInformation = accessToken.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
Object value = additionalInformation.get(key);
if (value != null) {
keys.put("extra_" + key, key);
vars.put("extra_" + key, value);
}
}
// Do not include the refresh token (even if there is one)
return append(authorizationRequest.getRedirectUri(), vars, keys, true);
}
private String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest, OAuth2Exception failure,
boolean fragment) {
if (authorizationRequest == null || authorizationRequest.getRedirectUri() == null) {
// we have no redirect for the user. very sad.
throw new UnapprovedClientAuthenticationException("Authorization failure, and no redirect URI.", failure);
}
Map<String, String> query = new LinkedHashMap<String, String>();
query.put("error", failure.getOAuth2ErrorCode());
query.put("error_description", failure.getMessage());
if (authorizationRequest.getState() != null) {
query.put("state", authorizationRequest.getState());
}
if (failure.getAdditionalInformation() != null) {
for (Map.Entry<String, String> additionalInfo : failure.getAdditionalInformation().entrySet()) {
query.put(additionalInfo.getKey(), additionalInfo.getValue());
}
}
return append(authorizationRequest.getRedirectUri(), null, query, fragment);
}
private String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
UriComponentsBuilder template = UriComponentsBuilder.newInstance();
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
URI redirectUri;
try {
// assume it's encoded to start with (if it came in over the wire)
redirectUri = builder.build(true).toUri();
} catch (Exception e) {
// ... but allow client registrations to contain hard-coded non-encoded values
redirectUri = builder.build().toUri();
builder = UriComponentsBuilder.fromUri(redirectUri);
}
template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
if (fragment) {
StringBuilder values = new StringBuilder();
if (redirectUri.getFragment() != null) {
String append = redirectUri.getFragment();
values.append(append);
}
for (String key : query.keySet()) {
if (values.length() > 0) {
values.append("&");
}
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
values.append(name + "={" + key + "}");
}
if (values.length() > 0) {
template.fragment(values.toString());
}
UriComponents encoded = template.build().expand(query).encode();
builder.fragment(encoded.getFragment());
} else {
for (String key : query.keySet()) {
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
template.queryParam(name, "{" + key + "}");
}
template.fragment(redirectUri.getFragment());
UriComponents encoded = template.build().expand(query).encode();
builder.query(encoded.getQuery());
}
return builder.build().toUriString();
}
private synchronized OAuth2AccessToken getAccessTokenForImplicitGrant(TokenRequest tokenRequest,
OAuth2Request storedOAuth2Request) {
return implicitTokenGranter.grant("implicit", new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));
}
}