1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring Boot 接入 GitHub 第三方登錄

        共 13717字,需瀏覽 28分鐘

         ·

        2020-11-11 21:32

        點擊上方藍字設(shè)為星標



        鏈接:zyc.red/Spring/Security/OAuth2/OAuth2-Client/

        前言

        OAuth(開放授權(quán))是一個開放標準,允許用戶授權(quán)第三方網(wǎng)站訪問他們存儲在另外的服務(wù)提供者上的信息,而不需要將用戶名和密碼提供給第三方網(wǎng)站或分享他們數(shù)據(jù)的所有內(nèi)容。網(wǎng)上有很多關(guān)于OAuth協(xié)議的講解,這里就不在詳細解釋OAuth相關(guān)的概念了,請讀者自行查閱相關(guān)資料,否則本文接下來的內(nèi)容可能會很難理解。
        Spring-Security對OAuth2.0的支持
        截止到本文撰寫的日期為止,Spring已經(jīng)提供了對OAuth提供的支持(spring-security-oauth:https://github.com/spring-projects/spring-security-oauth),但是該工程已經(jīng)被廢棄了,因為Spring-Security工程提供了最新的OAuth2.0支持。如果你的項目中使用了過期的Spring-Security-OAuth,請參考《OAuth 2.0遷移指南:https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide》
        本文將對OAuth2.0中的客戶端模式進行原理分析,結(jié)合Spring官方指南中提供了一個簡單的基于spring-boot與oauth2.0集成第三方應(yīng)用登錄的案例(spring-boot-oauth2:https://spring.io/guides/tutorials/spring-boot-oauth2/),一步一步分析其內(nèi)部實現(xiàn)的原理。
        公眾號同樣發(fā)布過近百篇 Spring Boot 相關(guān)的實戰(zhàn)文章,關(guān)注微信公眾號 Java后端,回復(fù) 666 下載這本技術(shù)棧手冊。

        創(chuàng)建GitHub OAuth Apps

        Github OAuth Apps中創(chuàng)建一個新的應(yīng)用
        這個應(yīng)用相當于我們自己的應(yīng)用(客戶端),被注冊在Github(授權(quán)服務(wù)器)中了,如果我們應(yīng)用中的用戶有g(shù)ithub賬號的話,則可以基于oauth2來登錄我們的系統(tǒng),替代原始的用戶名密碼方式。在官方指南的例子中,使用spring-security和oauth2進行社交登陸只需要在你的pom文件中加入以下幾個依賴即可:
        <dependency>
        ????<groupId>org.springframework.bootgroupId>

        ????<artifactId>spring-boot-starter-oauth2-clientartifactId>
        dependency>
        <dependency>
        ????<groupId>org.springframework.bootgroupId>
        ????<artifactId>spring-boot-starter-securityartifactId>
        dependency>
        <dependency>
        ????<groupId>org.springframework.bootgroupId>
        ????<artifactId>spring-boot-starter-webartifactId>
        dependency>
        然后在配置文件中填上剛剛注冊的應(yīng)用的clientId和clientSecret:
        spring:
        ??security:
        ????oauth2:
        ??????client:
        ????????registration:
        ??????????github:
        ????????????clientId: github-client-id
        ????????????clientSecret: github-client-secret
        緊接著就像普通的spring-security應(yīng)用一樣,繼承WebSecurityConfigurerAdapter,進行一些簡單的配置即可
        @SpringBootApplication
        @RestController
        public class SocialApplication extends WebSecurityConfigurerAdapter {

        ????@Override
        ????protected void configure(HttpSecurity http) throws Exception {
        ??????
        ????????http
        ????????????.authorizeRequests(a -> a
        ????????????????.antMatchers("/", "/error", "/webjars/**").permitAll()
        ????????????????.anyRequest().authenticated()
        ????????????)
        ????????????.exceptionHandling(e -> e
        ????????????????.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
        ????????????)
        ????????????.oauth2Login();
        ????????
        ????}
        }
        也就是說我們只需要添加maven依賴以及繼承WebSecurityConfigurerAdapter進行一些簡單的配置,一個oauth2客戶端應(yīng)用就構(gòu)建完成了。接下來按照指南上的步驟點擊頁面的github登錄鏈接我們的頁面就會跳轉(zhuǎn)到github授權(quán)登錄頁,等待用戶授權(quán)完成之后瀏覽器重定向到我們的callback URL最終請求user信息端點即可訪問到剛剛登入的github用戶信息,整個應(yīng)用的構(gòu)建是如此的簡單,背后的原理是什么呢?接下來我們開始分析。
        還是和以前一樣,我們在配置文件中將security的日志級別設(shè)置為debug
        logging:
        ??level:
        ????org.springframework.security: debug
        重新啟動應(yīng)用之后,從控制臺輸出中我們可以看到與普通spring-security應(yīng)用不同的地方在于整個過濾鏈多出了以下幾個過濾器:
        OAuth2AuthorizationRequestRedirectFilter
        OAuth2LoginAuthenticationFilter
        聯(lián)想oauth2的授權(quán)碼模式以及這兩個過濾器的名字,熟悉spring-security的同學(xué)心中肯定已經(jīng)有了一點想法了。對沒錯,spring-security對客戶端模式的支持完全就是基于這兩個過濾器來實現(xiàn)的。現(xiàn)在我們來回想以下授權(quán)碼模式的執(zhí)行流程
        1. 用戶在客戶端頁面點擊三方應(yīng)用登錄按鈕(客戶端就是我們剛剛注冊的github應(yīng)用)
        2. 頁面跳轉(zhuǎn)到三方應(yīng)用注冊的授權(quán)方頁面(授權(quán)服務(wù)器即github)
        3. 用戶登入授權(quán)后,github調(diào)用我們應(yīng)用的回調(diào)地址(我們剛剛注冊github應(yīng)用時填寫的回調(diào)地址)
        4. 第三步的回調(diào)地址中g(shù)ithub會將code參數(shù)放到url中,接下來我們的客戶端就會在內(nèi)部拿這個code再次去調(diào)用github
          的access_token地址獲取令牌
        上面就是標準的authorization_code授權(quán)模式,OAuth2AuthorizationRequestRedirectFilter的作用就是上面步驟中的1.2步的合體,當用戶點擊頁面的github授權(quán)url之后,OAuth2AuthorizationRequestRedirectFilter匹配這個請求,接著它會將我們配置文件中的clientId、scope以及構(gòu)造一個state參數(shù)(防止csrf攻擊)拼接成一個url重定向到github的授權(quán)url,OAuth2LoginAuthenticationFilter的作用則是上面3.4步驟的合體,當用戶在github的授權(quán)頁面授權(quán)之后github調(diào)用回調(diào)地址,OAuth2LoginAuthenticationFilter匹配這個回調(diào)地址,解析回調(diào)地址后的code與state參數(shù)進行驗證之后內(nèi)部拿著這個code遠程調(diào)用github的access_token地址,拿到access_token之后通過OAuth2UserService獲取相應(yīng)的用戶信息(內(nèi)部是拿access_token遠程調(diào)用github的用戶信息端點)最后將用戶信息構(gòu)造成Authentication被SecurityContextPersistenceFilter過濾器保存到HttpSession中。
        下面我們就來看一下這兩個過濾器內(nèi)部執(zhí)行的原理:
        public?class?OAuth2AuthorizationRequestRedirectFilter?extends?OncePerRequestFilter?{
        ????
        ????......省略部分代碼

        ??@Override
        ??protected?void?doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        ??????throws?ServletException, IOException
        {

        ????try?{
        ??????OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
        ??????if?(authorizationRequest != null) {
        ????????this.sendRedirectForAuthorization(request, response, authorizationRequest);
        ????????return;
        ??????}
        ????} catch?(Exception failed) {
        ??????this.unsuccessfulRedirectForAuthorization(request, response, failed);
        ??????return;
        ????}
        ????????......省略部分代碼
        }
        通過authorizationRequestResolver解析器解析請求,解析器的默認實現(xiàn)是DefaultOAuth2AuthorizationRequestResolver,核心解析方法如下:
        @Override
        public?OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
        ????
        ????String?registrationId = this.resolveRegistrationId(request);
        ????String?redirectUriAction = getAction(request, "login");
        ????return?resolve(request, registrationId, redirectUriAction);
        }


        private?OAuth2AuthorizationRequest resolve(HttpServletRequest request, String?registrationId, String?redirectUriAction) {
        ????if?(registrationId == null) {
        ????????return?null;
        ????}
        ??
        ????ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
        ????if?(clientRegistration == null) {
        ????????throw?new?IllegalArgumentException("Invalid Client Registration with Id: "?+ registrationId);
        ????}

        ????Map<String, Object> attributes = new?HashMap<>();
        ????attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());

        ????OAuth2AuthorizationRequest.Builder builder;
        ????
        ????if?(AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
        ????????builder = OAuth2AuthorizationRequest.authorizationCode();
        ????????Map<String, Object> additionalParameters = new?HashMap<>();
        ????????if?(!CollectionUtils.isEmpty(clientRegistration.getScopes()) &&
        ????????????clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
        ????????????
        ????????????
        ????????????
        ????????????addNonceParameters(attributes, additionalParameters);
        ????????}
        ????????if?(ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
        ????????????addPkceParameters(attributes, additionalParameters);
        ????????}
        ????????builder.additionalParameters(additionalParameters);
        ????} else?if?(AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
        ????????builder = OAuth2AuthorizationRequest.implicit();
        ????} else?{
        ????????throw?new?IllegalArgumentException("Invalid Authorization Grant Type ("??+
        ???????????????????????????????????????????clientRegistration.getAuthorizationGrantType().getValue() +
        ???????????????????????????????????????????") for Client Registration with Id: "?+ clientRegistration.getRegistrationId());
        ????}

        ????String?redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);

        ????OAuth2AuthorizationRequest authorizationRequest = builder
        ????????.clientId(clientRegistration.getClientId())
        ????????.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
        ????????.redirectUri(redirectUriStr)
        ????????.scopes(clientRegistration.getScopes())
        ????????
        ????????.state(this.stateGenerator.generateKey())
        ????????.attributes(attributes)
        ????????.build();

        ????return?authorizationRequest;
        }
        DefaultOAuth2AuthorizationRequestResolver判斷請求是否是授權(quán)請求,最終返回一個OAuth2AuthorizationRequest對象給OAuth2AuthorizationRequestRedirectFilter,如果OAuth2AuthorizationRequest不為null的話,說明當前請求是一個授權(quán)請求,那么接下來就要拿著這個請求重定向到授權(quán)服務(wù)器的授權(quán)端點了,下面我們接著看OAuth2AuthorizationRequestRedirectFilter發(fā)送重定向的邏輯:
        private?void?sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
        ??????????????????????????????????????????OAuth2AuthorizationRequest authorizationRequest
        ) throws IOException
        {

        ????if?(AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) {
        ????????this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
        ????}
        ????this.authorizationRedirectStrategy.sendRedirect(request, response, authorizationRequest.getAuthorizationRequestUri());
        }
        1. 如果當前是授權(quán)碼類型的授權(quán)請求那么就需要將這個請求信息保存下來,因為接下來授權(quán)服務(wù)器回調(diào)我們需要用到這個授權(quán)請求的參數(shù)進行校驗等操作(比對state),這里是通過authorizationRequestRepository保存授權(quán)請求的,默認的保存方式是通過HttpSessionOAuth2AuthorizationRequestRepository保存在httpsession中的,具體的保存邏輯很簡單,這里就不細說了。
        2. 保存完成之后就要開始重定向到授權(quán)服務(wù)端點了,這里默認的authorizationRedirectStrategy是DefaultRedirectStrategy,重定向的邏輯很簡單,通過response.sendRedirect方法使前端頁面重定向到指定的授權(quán)
        public?void?sendRedirect(HttpServletRequest request, HttpServletResponse response,
        ?????????????????????????String?url) throws IOException {
        ????String?redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
        ????redirectUrl = response.encodeRedirectURL(redirectUrl);

        ????if?(logger.isDebugEnabled()) {
        ????????logger.debug("Redirecting to '"?+ redirectUrl + "'");
        ????}

        ????response.sendRedirect(redirectUrl);
        }
        OAuth2AuthorizationRequestRedirectFilter處理邏輯講完了,下面我們對它處理過程做一個總結(jié)
        a. 通過內(nèi)部的OAuth2AuthorizationRequestResolver解析當前的請求,返回一個OAuth2AuthorizationRequest對象,如果當前請求是授權(quán)端點請求,那么就會返回一個構(gòu)造好的對象,包含我們的client_id、state、redirect_uri參數(shù),如果對象為null的話,那么就說明當前請求不是授權(quán)端點請求。
        注意如果OAuth2AuthorizationRequestResolver不為null的話,OAuth2AuthorizationRequestResolver內(nèi)部會將其保存在httpsession中這樣授權(quán)服務(wù)器在調(diào)用我們的回調(diào)地址時我們就能從httpsession中取出請求將state進行對比以防csrf攻擊。
        b. 如果第一步返回的OAuth2AuthorizationRequest對象不為null的話,接下來就會通過response.sendRedirect的方法將OAuth2AuthorizationRequest中的授權(quán)端點請求發(fā)送到前端的響應(yīng)頭中然后瀏覽器就會重定向到授權(quán)頁面,等待用戶授權(quán)。

        OAuth2LoginAuthenticationFilter


        public?class?OAuth2LoginAuthenticationFilter extends?AbstractAuthenticationProcessingFilter {
        ????@Override
        ??public?Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        ??????throws AuthenticationException {

        ????MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
        ????????
        ????if?(!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
        ??????OAuth2Error oauth2Error = new?OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
        ??????throw?new?OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        ????}
        ????
        ????????
        ????OAuth2AuthorizationRequest authorizationRequest =
        ????????this.authorizationRequestRepository.removeAuthorizationRequest(request, response);
        ????if?(authorizationRequest == null) {
        ??????OAuth2Error oauth2Error = new?OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
        ??????throw?new?OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        ????}
        ????
        ????????
        ????String?registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
        ????ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
        ????if?(clientRegistration == null) {
        ??????OAuth2Error oauth2Error = new?OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
        ??????????"Client Registration not found with Id: "?+ registrationId, null);
        ??????throw?new?OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        ????}
        ????String?redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
        ????????.replaceQuery(null)
        ????????.build()
        ????????.toUriString();
        ????OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);

        ????Object?authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
        ????OAuth2LoginAuthenticationToken authenticationRequest = new?OAuth2LoginAuthenticationToken(
        ????????clientRegistration, new?OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
        ????authenticationRequest.setDetails(authenticationDetails);
        ????
        ????OAuth2LoginAuthenticationToken authenticationResult =
        ??????(OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);
        ????
        ????OAuth2AuthenticationToken oauth2Authentication = new?OAuth2AuthenticationToken(
        ??????authenticationResult.getPrincipal(),
        ??????authenticationResult.getAuthorities(),
        ??????authenticationResult.getClientRegistration().getRegistrationId());
        ????oauth2Authentication.setDetails(authenticationDetails);??
        ????????
        ????OAuth2AuthorizedClient authorizedClient = new?OAuth2AuthorizedClient(
        ??????authenticationResult.getClientRegistration(),
        ??????oauth2Authentication.getName(),
        ??????authenticationResult.getAccessToken(),
        ??????authenticationResult.getRefreshToken());

        ????this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);

        ????return?oauth2Authentication;
        ??}
        }
        OAuth2LoginAuthenticationFilter的作用很簡單,就是響應(yīng)授權(quán)服務(wù)器的回調(diào)地址,核心之處在于OAuth2LoginAuthenticationProvider對OAuth2LoginAuthenticationToken的認證,

        OAuth2LoginAuthenticationToken

        OAuth2LoginAuthenticationProvider


        public?class?OAuth2LoginAuthenticationProvider?implements?AuthenticationProvider?{
        ????
        ?????...省略部分代碼
        ????
        ????@Override
        ??public?Authentication authenticate(Authentication authentication)?throws?AuthenticationException {
        ????OAuth2LoginAuthenticationToken authorizationCodeAuthentication =
        ??????(OAuth2LoginAuthenticationToken) authentication;

        ????
        ????
        ????
        ????if?(authorizationCodeAuthentication.getAuthorizationExchange()
        ??????.getAuthorizationRequest().getScopes().contains("openid")) {
        ??????
        ??????
        ??????return?null;
        ????}

        ????OAuth2AccessTokenResponse accessTokenResponse;
        ????try?{
        ??????OAuth2AuthorizationExchangeValidator.validate(
        ??????????authorizationCodeAuthentication.getAuthorizationExchange());
        ??????
        ??????accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
        ??????????new?OAuth2AuthorizationCodeGrantRequest(
        ??????????????authorizationCodeAuthentication.getClientRegistration(),
        ??????????????authorizationCodeAuthentication.getAuthorizationExchange()));

        ????} catch?(OAuth2AuthorizationException ex) {
        ??????OAuth2Error oauth2Error = ex.getError();
        ??????throw?new?OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        ????}
        ????
        ?????????
        ????OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
        ????Map additionalParameters = accessTokenResponse.getAdditionalParameters();
        ????
        ?????????
        ????OAuth2User oauth2User = this.userService.loadUser(new?OAuth2UserRequest(
        ????????authorizationCodeAuthentication.getClientRegistration(), accessToken, additionalParameters));

        ????Collection mappedAuthorities =
        ??????this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
        ????
        ?????????
        ????OAuth2LoginAuthenticationToken authenticationResult = new?OAuth2LoginAuthenticationToken(
        ??????authorizationCodeAuthentication.getClientRegistration(),
        ??????authorizationCodeAuthentication.getAuthorizationExchange(),
        ??????oauth2User,
        ??????mappedAuthorities,
        ??????accessToken,
        ??????accessTokenResponse.getRefreshToken());
        ????authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());

        ????return?authenticationResult;
        ??}
        ????...省略部分代碼
        }
        OAuth2LoginAuthenticationProvider的執(zhí)行邏輯很簡單,首先通過code獲取access_token,然后通過access_token獲取用戶信息,這和標準的oauth2授權(quán)碼模式一致。
        自動配置
        在spring指南的例子中,我們發(fā)現(xiàn)只是配置了一個簡單oauth2Login()方法,一個完整的oauth2授權(quán)流程就構(gòu)建好了,其實這完全歸功于spring-boot的autoconfigure,我們找到spring-boot-autoconfigure.jar包中的security.oauth2.client.servlet包,可以發(fā)現(xiàn)spring-boot給我們提供了幾個自動配置類:
        OAuth2ClientAutoConfiguration
        OAuth2ClientRegistrationRepositoryConfiguration
        OAuth2WebSecurityConfiguration
        其中OAuth2ClientAutoConfiguration導(dǎo)入了OAuth2ClientRegistrationRepositoryConfiguration和OAuth2WebSecurityConfiguration的配置

        OAuth2ClientRegistrationRepositoryConfiguration:


        @Configuration(proxyBeanMethods = false)
        @EnableConfigurationProperties(OAuth2ClientProperties.class)
        @Conditional(ClientsConfiguredCondition.class)
        class?OAuth2ClientRegistrationRepositoryConfiguration?{

        ????@Bean
        ????@ConditionalOnMissingBean(ClientRegistrationRepository.class)
        ????InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
        ????????List registrations = new ArrayList<>(
        ????????????OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
        ????????return?new InMemoryClientRegistrationRepository(registrations);
        ????}

        }
        OAuth2ClientRegistrationRepositoryConfiguration將我們在配置文件中注冊的client構(gòu)造成ClientRegistration然后保存到內(nèi)存之中。這里有一個隱藏的CommonOAuth2Provider類,這是一個枚舉類,里面事先定義好了幾種常用的三方登錄授權(quán)服務(wù)器的各種參數(shù)例如GOOGLE、GITHUB、FACEBOO、OKTA

        CommonOAuth2Provider


        public?enum?CommonOAuth2Provider {

        ??GOOGLE {

        ????@Override
        ????public?Builder getBuilder(String?registrationId) {
        ??????ClientRegistration.Builder builder = getBuilder(registrationId,
        ??????????ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
        ??????builder.scope("openid", "profile", "email");
        ??????builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
        ??????builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
        ??????builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");
        ??????builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");
        ??????builder.userNameAttributeName(IdTokenClaimNames.SUB);
        ??????builder.clientName("Google");
        ??????return?builder;
        ????}
        ??},

        ??GITHUB {

        ????@Override
        ????public?Builder getBuilder(String?registrationId) {
        ??????ClientRegistration.Builder builder = getBuilder(registrationId,
        ??????????ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
        ??????builder.scope("read:user");
        ??????builder.authorizationUri("https://github.com/login/oauth/authorize");
        ??????builder.tokenUri("https://github.com/login/oauth/access_token");
        ??????builder.userInfoUri("https://api.github.com/user");
        ??????builder.userNameAttributeName("id");
        ??????builder.clientName("GitHub");
        ??????return?builder;
        ????}
        ??},

        ??FACEBOOK {

        ????@Override
        ????public?Builder getBuilder(String?registrationId) {
        ??????ClientRegistration.Builder builder = getBuilder(registrationId,
        ??????????ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);
        ??????builder.scope("public_profile", "email");
        ??????builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth");
        ??????builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token");
        ??????builder.userInfoUri("https://graph.facebook.com/me?fields=id,name,email");
        ??????builder.userNameAttributeName("id");
        ??????builder.clientName("Facebook");
        ??????return?builder;
        ????}
        ??},

        ??OKTA {

        ????@Override
        ????public?Builder getBuilder(String?registrationId) {
        ??????ClientRegistration.Builder builder = getBuilder(registrationId,
        ??????????ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
        ??????builder.scope("openid", "profile", "email");
        ??????builder.userNameAttributeName(IdTokenClaimNames.SUB);
        ??????builder.clientName("Okta");
        ??????return?builder;
        ????}
        ??};

        ??private?static?final String?DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}";

        ??protected?final ClientRegistration.Builder getBuilder(String?registrationId,
        ??????????????????????????????ClientAuthenticationMethod method, String?redirectUri) {
        ????ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId);
        ????builder.clientAuthenticationMethod(method);
        ????builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
        ????builder.redirectUriTemplate(redirectUri);
        ????return?builder;
        ??}
        ????
        ??public?abstract?ClientRegistration.Builder getBuilder(String?registrationId);

        }
        這就是為什么我們沒有配置github授權(quán)端點確能夠跳轉(zhuǎn)授權(quán)頁面的原因。

        OAuth2WebSecurityConfiguration

        OAuth2WebSecurityConfiguration配置一些web相關(guān)的類,像如何去保存和獲取已經(jīng)授權(quán)過的客戶端,以及默認的oauth2客戶端相關(guān)的配置
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnBean(ClientRegistrationRepository.class)
        class OAuth2WebSecurityConfiguration {

        ??@Bean
        ??@ConditionalOnMissingBean
        ??OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
        ????return?new?InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
        ??}

        ??@Bean
        ??@ConditionalOnMissingBean
        ??OAuth2AuthorizedClientRepository?authorizedClientRepository(OAuth2AuthorizedClientService authorizedClientService) {
        ????return?new?AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
        ??}
        ??
        ????
        ??@Configuration(proxyBeanMethods = false)
        ??@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
        ??static?class?OAuth2WebSecurityConfigurerAdapter?extends?WebSecurityConfigurerAdapter?{

        ????@Override
        ????protected void configure(HttpSecurity http) throws Exception {
        ??????http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
        ??????http.oauth2Login(Customizer.withDefaults());
        ??????http.oauth2Client();
        ????}

        ??}

        }
        參考:
        集成GitHub和QQ社交登錄
        https://github.com/Allurx/spring-security-oauth2-demo/tree/master/spring-security-oauth2-client
        spring-security-oauth更新路線
        https://spring.io/blog/2019/11/14/spring-security-oauth-2-0-roadmap-update
        spring-security對oauth2.0授權(quán)服務(wù)器的支持
        https://github.com/spring-projects/spring-security/issues/6320
        使用spring-boot和oauth2.0構(gòu)建社交登陸
        https://spring.io/guides/tutorials/spring-boot-oauth2/

        推薦閱讀


        代碼對比工具,我就用這6個

        分享我常用的5個免費的在線 SQL 數(shù)據(jù)庫環(huán)境,簡直太方便了!

        Spring Boot 三招組合拳,手把手教你打出優(yōu)雅的后端接口

        MySQL 5.7 vs 8.0,你選那個?網(wǎng)友:我繼續(xù)原地踏步~


        最后,推薦給大家一個有趣有料的公眾號:寫代碼的渣渣鵬,7年老程序員教你寫bug,回復(fù) 面試|資源 送一你整套開發(fā)筆記 有驚喜哦

        瀏覽 48
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久久噜噜噜久久熟女av | 久久日精品 | 午夜男女羞羞影院 | www.狠狠色婷婷综合蜜桃 | 免费看啪啪啪网站 | 国产天堂一区二区三区 | 激情A片免费毛片无码免费 | 狠狠V欧美v日韩V亚洲v | 欧美男男激情 | 中文不卡视频 |