<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot 接入 GitHub 第三方登錄

          共 13717字,需瀏覽 28分鐘

           ·

          2020-11-11 21:32

          點(diǎn)擊上方藍(lán)字設(shè)為星標(biāo)



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

          前言

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

          創(chuàng)建GitHub OAuth Apps

          Github OAuth Apps中創(chuàng)建一個(gè)新的應(yīng)用
          這個(gè)應(yīng)用相當(dāng)于我們自己的應(yīng)用(客戶端),被注冊在Github(授權(quán)服務(wù)器)中了,如果我們應(yīng)用中的用戶有g(shù)ithub賬號的話,則可以基于oauth2來登錄我們的系統(tǒng),替代原始的用戶名密碼方式。在官方指南的例子中,使用spring-security和oauth2進(jìn)行社交登陸只需要在你的pom文件中加入以下幾個(gè)依賴即可:
          <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,進(jìn)行一些簡單的配置即可
          @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進(jìn)行一些簡單的配置,一個(gè)oauth2客戶端應(yīng)用就構(gòu)建完成了。接下來按照指南上的步驟點(diǎn)擊頁面的github登錄鏈接我們的頁面就會跳轉(zhuǎn)到github授權(quán)登錄頁,等待用戶授權(quán)完成之后瀏覽器重定向到我們的callback URL最終請求user信息端點(diǎn)即可訪問到剛剛登入的github用戶信息,整個(gè)應(yīng)用的構(gòu)建是如此的簡單,背后的原理是什么呢?接下來我們開始分析。
          還是和以前一樣,我們在配置文件中將security的日志級別設(shè)置為debug
          logging:
          ??level:
          ????org.springframework.security: debug
          重新啟動應(yīng)用之后,從控制臺輸出中我們可以看到與普通spring-security應(yīng)用不同的地方在于整個(gè)過濾鏈多出了以下幾個(gè)過濾器:
          OAuth2AuthorizationRequestRedirectFilter
          OAuth2LoginAuthenticationFilter
          聯(lián)想oauth2的授權(quán)碼模式以及這兩個(gè)過濾器的名字,熟悉spring-security的同學(xué)心中肯定已經(jīng)有了一點(diǎn)想法了。對沒錯(cuò),spring-security對客戶端模式的支持完全就是基于這兩個(gè)過濾器來實(shí)現(xiàn)的?,F(xiàn)在我們來回想以下授權(quán)碼模式的執(zhí)行流程
          1. 用戶在客戶端頁面點(diǎn)擊三方應(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)用時(shí)填寫的回調(diào)地址)
          4. 第三步的回調(diào)地址中g(shù)ithub會將code參數(shù)放到url中,接下來我們的客戶端就會在內(nèi)部拿這個(gè)code再次去調(diào)用github
            的access_token地址獲取令牌
          上面就是標(biāo)準(zhǔn)的authorization_code授權(quán)模式,OAuth2AuthorizationRequestRedirectFilter的作用就是上面步驟中的1.2步的合體,當(dāng)用戶點(diǎn)擊頁面的github授權(quán)url之后,OAuth2AuthorizationRequestRedirectFilter匹配這個(gè)請求,接著它會將我們配置文件中的clientId、scope以及構(gòu)造一個(gè)state參數(shù)(防止csrf攻擊)拼接成一個(gè)url重定向到github的授權(quán)url,OAuth2LoginAuthenticationFilter的作用則是上面3.4步驟的合體,當(dāng)用戶在github的授權(quán)頁面授權(quán)之后github調(diào)用回調(diào)地址,OAuth2LoginAuthenticationFilter匹配這個(gè)回調(diào)地址,解析回調(diào)地址后的code與state參數(shù)進(jìn)行驗(yàn)證之后內(nèi)部拿著這個(gè)code遠(yuǎn)程調(diào)用github的access_token地址,拿到access_token之后通過OAuth2UserService獲取相應(yīng)的用戶信息(內(nèi)部是拿access_token遠(yuǎn)程調(diào)用github的用戶信息端點(diǎn))最后將用戶信息構(gòu)造成Authentication被SecurityContextPersistenceFilter過濾器保存到HttpSession中。
          下面我們就來看一下這兩個(gè)過濾器內(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解析器解析請求,解析器的默認(rèn)實(shí)現(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)請求,最終返回一個(gè)OAuth2AuthorizationRequest對象給OAuth2AuthorizationRequestRedirectFilter,如果OAuth2AuthorizationRequest不為null的話,說明當(dāng)前請求是一個(gè)授權(quán)請求,那么接下來就要拿著這個(gè)請求重定向到授權(quán)服務(wù)器的授權(quán)端點(diǎ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. 如果當(dāng)前是授權(quán)碼類型的授權(quán)請求那么就需要將這個(gè)請求信息保存下來,因?yàn)榻酉聛硎跈?quán)服務(wù)器回調(diào)我們需要用到這個(gè)授權(quán)請求的參數(shù)進(jìn)行校驗(yàn)等操作(比對state),這里是通過authorizationRequestRepository保存授權(quán)請求的,默認(rèn)的保存方式是通過HttpSessionOAuth2AuthorizationRequestRepository保存在httpsession中的,具體的保存邏輯很簡單,這里就不細(xì)說了。
          2. 保存完成之后就要開始重定向到授權(quán)服務(wù)端點(diǎn)了,這里默認(rèn)的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處理邏輯講完了,下面我們對它處理過程做一個(gè)總結(jié)
          a. 通過內(nèi)部的OAuth2AuthorizationRequestResolver解析當(dāng)前的請求,返回一個(gè)OAuth2AuthorizationRequest對象,如果當(dāng)前請求是授權(quán)端點(diǎn)請求,那么就會返回一個(gè)構(gòu)造好的對象,包含我們的client_id、state、redirect_uri參數(shù),如果對象為null的話,那么就說明當(dāng)前請求不是授權(quán)端點(diǎn)請求。
          注意如果OAuth2AuthorizationRequestResolver不為null的話,OAuth2AuthorizationRequestResolver內(nèi)部會將其保存在httpsession中這樣授權(quán)服務(wù)器在調(diào)用我們的回調(diào)地址時(shí)我們就能從httpsession中取出請求將state進(jìn)行對比以防csrf攻擊。
          b. 如果第一步返回的OAuth2AuthorizationRequest對象不為null的話,接下來就會通過response.sendRedirect的方法將OAuth2AuthorizationRequest中的授權(quán)端點(diǎ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的認(rèn)證,

          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獲取用戶信息,這和標(biāo)準(zhǔn)的oauth2授權(quán)碼模式一致。
          自動配置
          在spring指南的例子中,我們發(fā)現(xiàn)只是配置了一個(gè)簡單oauth2Login()方法,一個(gè)完整的oauth2授權(quán)流程就構(gòu)建好了,其實(shí)這完全歸功于spring-boot的autoconfigure,我們找到spring-boot-autoconfigure.jar包中的security.oauth2.client.servlet包,可以發(fā)現(xiàn)spring-boot給我們提供了幾個(gè)自動配置類:
          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)存之中。這里有一個(gè)隱藏的CommonOAuth2Provider類,這是一個(gè)枚舉類,里面事先定義好了幾種常用的三方登錄授權(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)端點(diǎn)確能夠跳轉(zhuǎn)授權(quán)頁面的原因。

          OAuth2WebSecurityConfiguration

          OAuth2WebSecurityConfiguration配置一些web相關(guān)的類,像如何去保存和獲取已經(jīng)授權(quán)過的客戶端,以及默認(rè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個(gè)

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

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

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


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

          瀏覽 48
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人成在线视频 | 北条麻妃91在线播放 | 久久久精品免费视频 | 美女操逼视频网站 | 内射网站大全中文 |