如何在分布式環(huán)境中搭建單點登錄系統(tǒng)| 第二篇:基于Oauth2.0開發(fā)SSO核心代碼

什么是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é)議。

image.png

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));
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容