javascript
Spring Boot 接入 GitHub 第三方登录,只要两行配置!
本文地址:https://www.zyc.red/Spring/Security/OAuth2/OAuth2-Client/
松哥在四月份出過一個 OAuth2 教程(公號后臺回復 OAuth2 可以獲取),里邊也和大家分享了 GitHub 第三方登錄,但是我用的是一個更加通用的方式,就是自己配置各種回調,自己去請求各種數據,效果雖然實現了,但是比較麻煩。最近松哥在網上看到一篇文章,里邊 Spring Security 自帶的 OAuth2 登錄功能,再結合 Spring Boot 的自動化配置,這種實現方式只需要在 application.properties 中配置一下 clientid 和 clientsecret 即可,今天就和大家分享一下這種方案,以下是正文(文章細節可能不是很詳細,但是思路沒問題)。
概述
OAuth(開放授權)是一個開放標準,允許用戶授權第三方網站訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方網站或分享他們數據的所有內容。網上有很多關于OAuth協議的講解,這里就不在詳細解釋OAuth相關的概念了,不了解的小伙伴可以在公號后臺回復 OAuth2 獲取教程鏈接。
Spring-Security 對 OAuth2.0的支持
截止到本文撰寫的日期為止,Spring已經提供了對OAuth提供的支持(spring-security-oauth),但是該工程已經被廢棄了,因為Spring-Security工程提供了最新的OAuth2.0支持。如果你的項目中使用了過期的Spring-Security-OAuth,請參考《OAuth 2.0遷移指南》,本文將對OAuth2.0中的客戶端模式進行原理分析,結合Spring官方指南中提供了一個簡單的基于spring-boot與oauth2.0集成第三方應用登錄的案例(spring-boot-oauth2),一步一步分析其內部實現的原理。
創建GitHub OAuth Apps
在Github OAuth Apps中創建一個新的應用
這個應用相當于我們自己的應用(客戶端),被注冊在Github(授權服務器)中了,如果我們應用中的用戶有github賬號的話,則可以基于oauth2來登錄我們的系統,替代原始的用戶名密碼方式。在官方指南的例子中,使用spring-security和oauth2進行社交登陸只需要在你的pom文件中加入以下幾個依賴即可
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency>然后在配置文件中填上剛剛注冊的應用的clientId和clientSecret
spring:security:oauth2:client:registration:github:clientId:?github-client-idclientSecret:?github-client-secret緊接著就像普通的spring-security應用一樣,繼承WebSecurityConfigurerAdapter,進行一些簡單的配置即可
@SpringBootApplication @RestController public?class?SocialApplication?extends?WebSecurityConfigurerAdapter?{//?...@Overrideprotected?void?configure(HttpSecurity?http)?throws?Exception?{//?@formatter:offhttp.authorizeRequests(a?->?a.antMatchers("/",?"/error",?"/webjars/**").permitAll().anyRequest().authenticated()).exceptionHandling(e?->?e.authenticationEntryPoint(new?HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))).oauth2Login();//?@formatter:on} }也就是說我們只需要添加maven依賴以及繼承WebSecurityConfigurerAdapter進行一些簡單的配置,一個oauth2客戶端應用就構建完成了。接下來按照指南上的步驟點擊頁面的github登錄鏈接我們的頁面就會跳轉到github授權登錄頁,等待用戶授權完成之后瀏覽器重定向到我們的callback URL最終請求user信息端點即可訪問到剛剛登入的github用戶信息,整個應用的構建是如此的簡單,背后的原理是什么呢?接下來我們開始分析。還是和以前一樣,我們在配置文件中將security的日志級別設置為debug
logging:level:org.springframework.security:?debug重新啟動應用之后,從控制臺輸出中我們可以看到與普通spring-security應用不同的地方在于整個過濾鏈多出了以下幾個過濾器
OAuth2AuthorizationRequestRedirectFilter OAuth2LoginAuthenticationFilter聯想oauth2的授權碼模式以及這兩個過濾器的名字,熟悉spring-security的同學心中肯定已經有了一點想法了。對沒錯,spring-security對客戶端模式的支持完全就是基于這兩個過濾器來實現的。現在我們來回想以下授權碼模式的執行流程
用戶在客戶端頁面點擊三方應用登錄按鈕(客戶端就是我們剛剛注冊的github應用)
頁面跳轉到三方應用注冊的授權方頁面(授權服務器即github)
用戶登入授權后,github調用我們應用的回調地址(我們剛剛注冊github應用時填寫的回調地址)
第三步的回調地址中github會將code參數放到url中,接下來我們的客戶端就會在內部拿這個code再次去調用github的access_token地址獲取令牌
上面就是標準的authorization_code授權模式,OAuth2AuthorizationRequestRedirectFilter的作用就是上面步驟中的1.2步的合體,當用戶點擊頁面的github授權url之后,OAuth2AuthorizationRequestRedirectFilter匹配這個請求,接著它會將我們配置文件中的clientId、scope以及構造一個state參數(防止csrf攻擊)拼接成一個url重定向到github的授權url,OAuth2LoginAuthenticationFilter的作用則是上面3.4步驟的合體,當用戶在github的授權頁面授權之后github調用回調地址,OAuth2LoginAuthenticationFilter匹配這個回調地址,解析回調地址后的code與state參數進行驗證之后內部拿著這個code遠程調用github的access_token地址,拿到access_token之后通過OAuth2UserService獲取相應的用戶信息(內部是拿access_token遠程調用github的用戶信息端點)最后將用戶信息構造成Authentication被SecurityContextPersistenceFilter過濾器保存到HttpSession中。下面我們就來看一下這兩個過濾器內部執行的原理
OAuth2AuthorizationRequestRedirectFilter
public?class?OAuth2AuthorizationRequestRedirectFilter?extends?OncePerRequestFilter?{......省略部分代碼@Overrideprotected?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解析器解析請求,解析器的默認實現是DefaultOAuth2AuthorizationRequestResolver,核心解析方法如下
//?第一步解析 @Override public?OAuth2AuthorizationRequest?resolve(HttpServletRequest?request)?{//?通過內部的authorizationRequestMatcher來解析當前請求中的registrationId//?也就是/oauth2/authorization/github中的githubString?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;}//?根據傳入的registrationId找到注冊的應用信息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;//?根據不同的AuthorizationGrantType構造不同的builderif?(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))?{//?Section?3.1.2.1?Authentication?Request?-?https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest//?scope//???REQUIRED.?OpenID?Connect?requests?MUST?contain?the?"openid"?scope?value.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值.state(this.stateGenerator.generateKey()).attributes(attributes).build();return?authorizationRequest; }DefaultOAuth2AuthorizationRequestResolver判斷請求是否是授權請求,最終返回一個OAuth2AuthorizationRequest對象給OAuth2AuthorizationRequestRedirectFilter,如果OAuth2AuthorizationRequest不為null的話,說明當前請求是一個授權請求,那么接下來就要拿著這個請求重定向到授權服務器的授權端點了,下面我們接著看OAuth2AuthorizationRequestRedirectFilter發送重定向的邏輯
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()); }如果當前是授權碼類型的授權請求那么就需要將這個請求信息保存下來,因為接下來授權服務器回調我們需要用到這個授權請求的參數進行校驗等操作(比對state),這里是通過authorizationRequestRepository保存授權請求的,默認的保存方式是通過HttpSessionOAuth2AuthorizationRequestRepository保存在httpsession中的,具體的保存邏輯很簡單,這里就不細說了。
保存完成之后就要開始重定向到授權服務端點了,這里默認的authorizationRedirectStrategy是DefaultRedirectStrategy,重定向的邏輯很簡單,通過response.sendRedirect方法使前端頁面重定向到指定的授權
OAuth2AuthorizationRequestRedirectFilter處理邏輯講完了,下面我們對它處理過程做一個總結
通過內部的OAuth2AuthorizationRequestResolver解析當前的請求,返回一個OAuth2AuthorizationRequest對象,如果當前請求是授權端點請求,那么就會返回一個構造好的對象,包含我們的client_id、state、redirect_uri參數,如果對象為null的話,那么就說明當前請求不是授權端點請求。注意如果OAuth2AuthorizationRequestResolver不為null的話,OAuth2AuthorizationRequestResolver內部會將其保存在httpsession中這樣授權服務器在調用我們的回調地址時我們就能從httpsession中取出請求將state進行對比以防csrf攻擊。
如果第一步返回的OAuth2AuthorizationRequest對象不為null的話,接下來就會通過response.sendRedirect的方法將OAuth2AuthorizationRequest中的授權端點請求發送到前端的響應頭中然后瀏覽器就會重定向到授權頁面,等待用戶授權。
OAuth2LoginAuthenticationFilter
public?class?OAuth2LoginAuthenticationFilter?extends?AbstractAuthenticationProcessingFilter?{@Overridepublic?Authentication?attemptAuthentication(HttpServletRequest?request,?HttpServletResponse?response)throws?AuthenticationException?{MultiValueMap<String,?String>?params?=?OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());//?如果請求參數中沒有state和code參數,說明當前請求是一個非法請求if?(!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params))?{OAuth2Error?oauth2Error?=?new?OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);throw?new?OAuth2AuthenticationException(oauth2Error,?oauth2Error.toString());}//?從httpsession中取出OAuth2AuthorizationRequestRedirectFilter中保存的授權請求,//?如果找不到的話說明當前請求是非法請求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委托給AuthenticationManager//?選擇合適的AuthenticationProvider來對其進行認證,這里的AuthenticationProvider是//?OAuth2LoginAuthenticationProviderOAuth2LoginAuthenticationToken?authenticationResult?=(OAuth2LoginAuthenticationToken)?this.getAuthenticationManager().authenticate(authenticationRequest);//?將最終的認證信息封裝成OAuth2AuthenticationTokenOAuth2AuthenticationToken?oauth2Authentication?=?new?OAuth2AuthenticationToken(authenticationResult.getPrincipal(),authenticationResult.getAuthorities(),authenticationResult.getClientRegistration().getRegistrationId());oauth2Authentication.setDetails(authenticationDetails);//?構造OAuth2AuthorizedClient,將所有經過授權的客戶端信息保存起來,默認是通過//?AuthenticatedPrincipalOAuth2AuthorizedClientRepository來保存的,//?然后就能通過其來獲取之前所有已授權的client?暫時不能確定其合適的用途OAuth2AuthorizedClient?authorizedClient?=?new?OAuth2AuthorizedClient(authenticationResult.getClientRegistration(),oauth2Authentication.getName(),authenticationResult.getAccessToken(),authenticationResult.getRefreshToken());this.authorizedClientRepository.saveAuthorizedClient(authorizedClient,?oauth2Authentication,?request,?response);return?oauth2Authentication;} }OAuth2LoginAuthenticationFilter的作用很簡單,就是響應授權服務器的回調地址,核心之處在于OAuth2LoginAuthenticationProvider對OAuth2LoginAuthenticationToken的認證。
OAuth2LoginAuthenticationProvider
public?class?OAuth2LoginAuthenticationProvider?implements?AuthenticationProvider?{...省略部分代碼@Overridepublic?Authentication?authenticate(Authentication?authentication)?throws?AuthenticationException?{OAuth2LoginAuthenticationToken?authorizationCodeAuthentication?=(OAuth2LoginAuthenticationToken)?authentication;//?Section?3.1.2.1?Authentication?Request?-?https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest//?scope//???REQUIRED.?OpenID?Connect?requests?MUST?contain?the?"openid"?scope?value.if?(authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest().getScopes().contains("openid"))?{//?This?is?an?OpenID?Connect?Authentication?Request?so?return?null//?and?let?OidcAuthorizationCodeAuthenticationProvider?handle?it?insteadreturn?null;}OAuth2AccessTokenResponse?accessTokenResponse;try?{OAuth2AuthorizationExchangeValidator.validate(authorizationCodeAuthentication.getAuthorizationExchange());//?遠程調用授權服務器的access_token端點獲取令牌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<String,?Object>?additionalParameters?=?accessTokenResponse.getAdditionalParameters();//?通過userService使用上一步拿到的accessToken遠程調用授權服務器的用戶信息OAuth2User?oauth2User?=?this.userService.loadUser(new?OAuth2UserRequest(authorizationCodeAuthentication.getClientRegistration(),?accessToken,?additionalParameters));Collection<??extends?GrantedAuthority>?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的執行邏輯很簡單,首先通過code獲取access_token,然后通過access_token獲取用戶信息,這和標準的oauth2授權碼模式一致。
自動配置
在spring指南的例子中,我們發現只是配置了一個簡單oauth2Login()方法,一個完整的oauth2授權流程就構建好了,其實這完全歸功于spring-boot的autoconfigure,我們找到spring-boot-autoconfigure.jar包中的security.oauth2.client.servlet包,可以發現spring-boot給我們提供了幾個自動配置類
OAuth2ClientAutoConfiguration OAuth2ClientRegistrationRepositoryConfiguration OAuth2WebSecurityConfiguration其中OAuth2ClientAutoConfiguration導入了OAuth2ClientRegistrationRepositoryConfiguration和OAuth2WebSecurityConfiguration的配置
OAuth2ClientRegistrationRepositoryConfiguration
@Configuration(proxyBeanMethods?=?false) @EnableConfigurationProperties(OAuth2ClientProperties.class) @Conditional(ClientsConfiguredCondition.class) class?OAuth2ClientRegistrationRepositoryConfiguration?{@Bean@ConditionalOnMissingBean(ClientRegistrationRepository.class)InMemoryClientRegistrationRepository?clientRegistrationRepository(OAuth2ClientProperties?properties)?{List<ClientRegistration>?registrations?=?new?ArrayList<>(OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());return?new?InMemoryClientRegistrationRepository(registrations);}}OAuth2ClientRegistrationRepositoryConfiguration將我們在配置文件中注冊的client構造成ClientRegistration然后保存到內存之中。這里有一個隱藏的CommonOAuth2Provider類,這是一個枚舉類,里面事先定義好了幾種常用的三方登錄授權服務器的各種參數例如GOOGLE、GITHUB、FACEBOO、OKTA
CommonOAuth2Provider
public?enum?CommonOAuth2Provider?{GOOGLE?{@Overridepublic?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?{@Overridepublic?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?{@Overridepublic?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?{@Overridepublic?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授權端點確能夠跳轉授權頁面的原因。
OAuth2WebSecurityConfiguration
OAuth2WebSecurityConfiguration配置一些web相關的類,像如何去保存和獲取已經授權過的客戶端,以及默認的oauth2客戶端相關的配置
@Configuration(proxyBeanMethods?=?false) @ConditionalOnBean(ClientRegistrationRepository.class) class?OAuth2WebSecurityConfiguration?{@Bean@ConditionalOnMissingBeanOAuth2AuthorizedClientService?authorizedClientService(ClientRegistrationRepository?clientRegistrationRepository)?{return?new?InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);}@Bean@ConditionalOnMissingBeanOAuth2AuthorizedClientRepository?authorizedClientRepository(OAuth2AuthorizedClientService?authorizedClientService)?{return?new?AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);}//?默認的oauth2客戶端相關的配置@Configuration(proxyBeanMethods?=?false)@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)static?class?OAuth2WebSecurityConfigurerAdapter?extends?WebSecurityConfigurerAdapter?{@Overrideprotected?void?configure(HttpSecurity?http)?throws?Exception?{http.authorizeRequests((requests)?->?requests.anyRequest().authenticated());http.oauth2Login(Customizer.withDefaults());http.oauth2Client();}}}推薦文章開發者工具 Top 100 名單
2020年國內互聯網公司的薪酬排名!
不要再封裝各種Util工具類了,這個神級框架值得擁有!
寫博客能月入10K?
一款基于 Spring Boot 的現代化社區(論壇/問答/社交網絡/博客)
這或許是最美的Vue+Element開源后臺管理UI
推薦一款高顏值的 Spring Boot 快速開發框架
一款基于 Spring Boot 的現代化社區(論壇/問答/社交網絡/博客)
13K點贊都基于 Vue+Spring 前后端分離管理系統ELAdmin,大愛
想接私活時薪再翻一倍,建議根據這幾個開源的SpringBoot項目
總結
以上是生活随笔為你收集整理的Spring Boot 接入 GitHub 第三方登录,只要两行配置!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL索引的分类、何时使用、何时不使
- 下一篇: 面试官:谈谈你对零拷贝的理解~