Spring Boot 接入 GitHub 第三方登錄,只要兩行配置!
點(diǎn)擊上方 java項(xiàng)目開發(fā) ,選擇 星標(biāo) 公眾號
重磅資訊,干貨,第一時(shí)間送達(dá)
本文地址:https://www.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)的概念了,不了解的小伙伴可以在公號后臺回復(fù) OAuth2 獲取教程鏈接。
Spring-Security 對 OAuth2.0的支持
截止到本文撰寫的日期為止,Spring已經(jīng)提供了對OAuth提供的支持(spring-security-oauth),但是該工程已經(jīng)被廢棄了,因?yàn)镾pring-Security工程提供了最新的OAuth2.0支持。如果你的項(xiàng)目中使用了過期的Spring-Security-OAuth,請參考《OAuth 2.0遷移指南》,本文將對OAuth2.0中的客戶端模式進(jìn)行原理分析,結(jié)合Spring官方指南中提供了一個(gè)簡單的基于spring-boot與oauth2.0集成第三方應(yīng)用登錄的案例(spring-boot-oauth2),一步一步分析其內(nèi)部實(shí)現(xiàn)的原理。
創(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.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>
然后在配置文件中填上剛剛注冊的應(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 {
// @formatter:off
http
.authorizeRequests(a -> a
.antMatchers("/", "/error", "/webjars/**").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(e -> e
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
)
.oauth2Login();
// @formatter:on
}
}
也就是說我們只需要添加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
重新啟動(dòng)應(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)的。現(xiàn)在我們來回想以下授權(quán)碼模式的執(zhí)行流程
用戶在客戶端頁面點(diǎn)擊三方應(yīng)用登錄按鈕(客戶端就是我們剛剛注冊的github應(yīng)用) 頁面跳轉(zhuǎn)到三方應(yīng)用注冊的授權(quán)方頁面(授權(quán)服務(wù)器即github) 用戶登入授權(quán)后,github調(diào)用我們應(yīng)用的回調(diào)地址(我們剛剛注冊github應(yīng)用時(shí)填寫的回調(diào)地址) 第三步的回調(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í)行的原理
OAuth2AuthorizationRequestRedirectFilter
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) {
// 通過內(nèi)部的authorizationRequestMatcher來解析當(dāng)前請求中的registrationId
// 也就是/oauth2/authorization/github中的github
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;
}
// 根據(jù)傳入的registrationId找到注冊的應(yīng)用信息
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;
// 根據(jù)不同的AuthorizationGrantType構(gòu)造不同的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)) {
// 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())
// 生成隨機(jī)state值
.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());
}
如果當(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ì)說了。 保存完成之后就要開始重定向到授權(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é)
通過內(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攻擊。 如果第一步返回的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());
// 如果請求參數(shù)中沒有state和code參數(shù),說明當(dāng)前請求是一個(gè)非法請求
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// 從httpsession中取出OAuth2AuthorizationRequestRedirectFilter中保存的授權(quán)請求,
// 如果找不到的話說明當(dāng)前請求是非法請求
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());
}
// 如果當(dāng)前注冊的應(yīng)用中找不到授權(quán)請求時(shí)的應(yīng)用了,那么也是一個(gè)不正確的請求
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);
// 將未認(rèn)證的OAuth2LoginAuthenticationToken委托給AuthenticationManager
// 選擇合適的AuthenticationProvider來對其進(jìn)行認(rèn)證,這里的AuthenticationProvider是
// OAuth2LoginAuthenticationProvider
OAuth2LoginAuthenticationToken authenticationResult =
(OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);
// 將最終的認(rèn)證信息封裝成OAuth2AuthenticationToken
OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken(
authenticationResult.getPrincipal(),
authenticationResult.getAuthorities(),
authenticationResult.getClientRegistration().getRegistrationId());
oauth2Authentication.setDetails(authenticationDetails);
// 構(gòu)造OAuth2AuthorizedClient,將所有經(jīng)過授權(quán)的客戶端信息保存起來,默認(rèn)是通過
// AuthenticatedPrincipalOAuth2AuthorizedClientRepository來保存的,
// 然后就能通過其來獲取之前所有已授權(quán)的client?暫時(shí)不能確定其合適的用途
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)證。
OAuth2LoginAuthenticationProvider
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
...省略部分代碼
@Override
public 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 instead
return null;
}
OAuth2AccessTokenResponse accessTokenResponse;
try {
OAuth2AuthorizationExchangeValidator.validate(
authorizationCodeAuthentication.getAuthorizationExchange());
// 遠(yuǎn)程調(diào)用授權(quán)服務(wù)器的access_token端點(diǎn)獲取令牌
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遠(yuǎn)程調(diào)用授權(quán)服務(wù)器的用戶信息
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
authorizationCodeAuthentication.getClientRegistration(), accessToken, additionalParameters));
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
// 構(gòu)造認(rèn)證成功之后的認(rèn)證信息
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)碼模式一致。
自動(dòng)配置
在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è)自動(dòng)配置類
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構(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);
}
// 默認(rèn)的oauth2客戶端相關(guān)的配置
@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();
}
}
} --完--推薦閱讀:
怎么接私貨?這個(gè)渠道你100%有用!請收藏!喜歡文章,點(diǎn)個(gè)在看

