Spring Boot 接入 GitHub 第三方登录,只要两行配置!

Map<String, Object> attributes = new HashMap<>();

attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());

OAuth2AuthorizationRequest.Builder builder;

// 根据不同的AuthorizationGrantType构造不同的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())

// 生成随机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());

}

  1. 如果当前是授权码类型的授权请求那么就需要将这个请求信息保存下来,因为接下来授权服务器回调我们需要用到这个授权请求的参数进行校验等操作(比对state),这里是通过authorizationRequestRepository保存授权请求的,默认的保存方式是通过HttpSessionOAuth2AuthorizationRequestRepository保存在httpsession中的,具体的保存逻辑很简单,这里就不细说了。

  2. 保存完成之后就要开始重定向到授权服务端点了,这里默认的authorizationRedirectStrategy是DefaultRedirectStrategy,重定向的逻辑很简单,通过response.sendRedirect方法使前端页面重定向到指定的授权

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处理逻辑讲完了,下面我们对它处理过程做一个总结

  1. 通过内部的OAuth2AuthorizationRequestResolver解析当前的请求,返回一个OAuth2AuthorizationRequest对象,如果当前请求是授权端点请求,那么就会返回一个构造好的对象,包含我们的client_id、state、redirect_uri参数,如果对象为null的话,那么就说明当前请求不是授权端点请求。注意如果OAuth2AuthorizationRequestResolver不为null的话,OAuth2AuthorizationRequestResolver内部会将其保存在httpsession中这样授权服务器在调用我们的回调地址时我们就能从httpsession中取出请求将state进行对比以防csrf攻击。

  2. 如果第一步返回的OAuth2AuthorizationRequest对象不为null的话,接下来就会通过response.sendRedirect的方法将OAuth2AuthorizationRequest中的授权端点请求发送到前端的响应头中然后浏览器就会重定向到授权页面,等待用户授权。

OAuth2LoginAuthenticationFilter


public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

@Override

public 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是

// OAuth2LoginAuthenticationProvider

OAuth2LoginAuthenticationToken authenticationResult =

(OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);

// 将最终的认证信息封装成OAuth2AuthenticationToken

OAuth2AuthenticationToken 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 {

…省略部分代码

@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());

// 远程调用授权服务器的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 registrations = new ArrayList<>(

OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());

return new InMemoryClientRegistrationRepository(registrations);

}

}

OAuth2ClientRegistrationRepositoryConfiguration将我们在配置文件中注册的client构造成ClientRegistration然后保存到内存之中。这里有一个隐藏的CommonOAuth2Provider类,这是一个枚举类,里面事先定义好了几种常用的三方登录授权服务器的各种参数例如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);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

谈到面试,其实说白了就是刷题刷题刷题,天天作死的刷。。。。。

为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的

并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

66个Java面试知识点

架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

算法刷题(PDF)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
img-community.csdnimg.cn/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

总结

谈到面试,其实说白了就是刷题刷题刷题,天天作死的刷。。。。。

为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的

并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)

[外链图片转存中…(img-banw5xfq-1712070089319)]

66个Java面试知识点

架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)

[外链图片转存中…(img-b2V18OPS-1712070089320)]

算法刷题(PDF)

[外链图片转存中…(img-AIWWlZLy-1712070089320)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 27
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要将 GitHub 第三方登录接入到你的应用程序中,你可以按照以下步骤进行操作: 1. 创建一个 GitHub 开发者帐户:在 GitHub 上创建一个帐户(如果你还没有)。然后登录到你的帐户并导航到开发者设置页面。 2. 创建一个新的 OAuth 应用程序:在开发者设置页面中,点击 "New OAuth App" 按钮创建一个新的应用程序。填写应用程序的名称、主页 URL(可以是你的应用程序网站)、回调 URL(GitHub 会将用户重定向到此 URL),并选择适当的权限范围。 3. 获取应用程序的客户端 ID 和客户端密钥:创建应用程序后,你将获得一个客户端 ID 和一个客户端密钥。这些信息将在后续步骤中使用。 4. 在你的应用程序中实现登录功能:根据你所使用的编程语言和框架,找到适当的库或插件来帮助你实现 GitHub 第三方登录功能。不同语言和框架可能有不同的实现方式,你可以参考 GitHub 的官方文档或搜索相关示例代码。 5. 配置登录回调处理:当用户使用 GitHub 登录成功后,GitHub 会将用户重定向到你在步骤 2 中设置的回调 URL。在你的应用程序中,需要处理这个回调请求,并从中提取授权码或访问令牌。使用这些令牌,你可以与 GitHub API 进行交互,获取用户的信息或执行其他操作。 6. 使用 GitHub API:一旦用户成功登录并授权你的应用程序,你可以使用 GitHub API 访问用户的存储库、个人信息等。根据你的需求,调用适当的 API 端点来获取所需的数据。 这些步骤涵盖了一个基本的 GitHub 第三方登录接入流程。具体实现方式可能因你所使用的编程语言和框架而有所不同,但上述步骤应该能够帮助你开始进行接入。记得在实际开发过程中查阅相关文档和示例代码,以确保正确地实现登录功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值