> 文章列表 > 【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结

【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结

【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结

之前搞 oauth 登录一直没有搞好,客户端、授权服务端、资源端一起搞对于我刚接触的小菜鸡来说,难度有点大。
然后就先搞了个 Client 端对接 Github 登录。 网上关于 Github 登录的资料有很多,而且框架对 Github 集成的也很好,配置起来并不麻烦。

对接Github登录

首先在 Github 上申请注册一个 oauth application,填写 callback url得到 client ID和 secret
默认的callback url 格式:{baseUrl}/login/oauth2/code/{registrationId}

在这里对接 GIthub 的默认重定向的地址就是http://xxxx/api/login/oauth2/code/github
如果对接其他的第三方就将 registrationId 改成 和我们配置文件中的一致即可

1、作为 Client 端只需要引入依赖

   implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

2、然后在applicatiom.yml中进行相关配置

spring:security:oauth2:client:registration:github:clientId: 56ca77ae71xxxxxxxclientSecret: e1aa08298c5d0f5f9c35414355666b81xxxxxxxscope:- user:email- read:user

3、Create OAuth2User and OAuth2UserService Classes

主要用来定义 授权用户 的数据结构 和 加载 授权用户的相关数据

3.1、创建一个实体类去实现 OAuth2User 重写里面的方法

例如:

import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;public class GitHubCustomOauth2User implements OAuth2User {private OAuth2User user;public GitHubCustomOauth2User(OAuth2User user) {this.user = user;}@Overridepublic Map<String, Object> getAttributes() {return user.getAttributes();}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return user.getAuthorities();}@Overridepublic String getName() {return user.getAttribute("name");}public String getLogin() {return user.getAttribute("login");}public String getEmail() {return user.getAttribute("email");}
}

⚠️注意:对于对接不同的授权端是getName() 方法返回的值的名称发生变化

3.2 创建一个service 去继承 DefaultOAuth2UserService

里面实现加载授权用户数据的功能,根据不同授权端转变为不同的 OAuth2User
例如:

package com.openbayes.application.oauth;import com.openbayes.domain.oauth.GitHubCustomOauth2User;
import com.openbayes.domain.oauth.SiomCustomOauth2User;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class CustomOauth2UserService extends DefaultOAuth2UserService {@Overridepublic OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {OAuth2AccessToken accessToken = userRequest.getAccessToken();String tokenValue = accessToken.getTokenValue();log.info("accessToken value: {}", tokenValue);ClientRegistration clientRegistration = userRequest.getClientRegistration();ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails();log.info("clientRegistration 为:{}", clientRegistration);log.info("clientRegistration providerDetails 为:{}", providerDetails);String attributeName =userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();log.info("attributeName 为:{}", attributeName);Map<String, Object> additionalParameters = userRequest.getAdditionalParameters();log.info("additionalParameters 为:{}", additionalParameters);OAuth2User oAuth2User = super.loadUser(userRequest);log.info("User information response: {}", oAuth2User.getAttributes());if (clientRegistration.getRegistrationId().equals("github")) {GitHubCustomOauth2User gitHubCustomOauth2User = new GitHubCustomOauth2User(oAuth2User);log.info("gitHubCustomOauth2User 的 Attributes 为:{}", gitHubCustomOauth2User.getAttributes());return gitHubCustomOauth2User;} else {xxxCustomOauth2User xxCustomOauth2User = new xxCustomOauth2User(oAuth2User);log.info("xxxCustomOauth2User 的 Attributes 为:{}", xxxxCustomOauth2User.getAttributes());return siomCustomOauth2User;}}
}

4、Configure Spring Security for OAuth2 Login

配置 OAuth Login 的配置
例如

   @Autowired private OAuth2AuthorizedClientService authorizedClientService;@Autowired private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository;@Autowired private CustomOauth2UserService customOauth2UserService;@Autowired private Oauth2LoginSuccessHandler oauth2LoginSuccessHandler;@Autowired private Oauth2LoginFailedHandler oauth2LoginFailedHandler;@Autowired private Oauth2LoginOutSuccessHandler oauth2LoginOutSuccessHandler;http.oauth2Login().loginPage("/login").authorizedClientService(authorizedClientService).authorizedClientRepository(oAuth2AuthorizedClientRepository).userInfoEndpoint().userService(customOauth2UserService).and().successHandler(oauth2LoginSuccessHandler).failureHandler(oauth2LoginFailedHandler);

这里面的配置包含了 OAuth 集群下的一些配置(下面讲到),加载用户信息的服务为刚才我们定义的 Service ,以及用户登录成功 和登录失败的 handler

5、extends SimpleUrlAuthenticationSuccessHandler

这个里面主要用来处理授权成功之后的逻辑,是注册用户还是直接登录等业务逻辑

一张图来解释

在这里插入图片描述
例子

@Component
@Slf4j
public class Oauth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {@Autowired private CustomOauth2UserService customOauth2UserService;@Autowired private UserRepository userRepository;@Autowired private UserApplicationService userApplicationService;@Autowired private TokenService tokenService;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException {if (authentication instanceof OAuth2AuthenticationToken) {OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;String authorizedProvider = oauth2Token.getAuthorizedClientRegistrationId();log.info("authorizedProvider为{}", authorizedProvider);String redirectUrl = null;if (authorizedProvider.equals("github")) {GitHubCustomOauth2User oauth2User = (GitHubCustomOauth2User) authentication.getPrincipal();是注册呢?还是直接返回 token呢? 根据相关业务逻辑来判断} else if (authorizedProvider.equals("siom")) {SiomCustomOauth2User siomCustomOauth2User =(SiomCustomOauth2User) authentication.getPrincipal();和上面同理}}
}

6、extends SimpleUrlAuthenticationFailureHandler

这里主要为了登录失败的情况,主要做一些打印日志,以及给前端返回错误信息。这样前端可以根据返回的结果判断授权登录是成功还是失败,做不一样的操作以及跳转到不同的页面

到这里, 在单节点上使用 Github 登录就已经结束了

OAuth Logout

其实还可以引入登出的逻辑,登出的 handler 里面就是清理 session 的工作

  • 首先要进行 logout 的配置
    例如
 http.logout().logoutUrl("/auth/logout").logoutSuccessHandler(oauth2LoginOutSuccessHandler).invalidateHttpSession(true).clearAuthentication(true).deleteCookies("JSESSIONID");

上面的配置就是,等你调用接口 /auth/logout 登出的时候,会对seeion 以及 cookie进行处理,oauth2LoginOutSuccessHandler 也会做相应的操作(打印日志,返回信息给前端)
例如

@Component
@Slf4j
public class Oauth2LoginOutSuccessHandler extends SimpleUrlLogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException {HttpSession session = request.getSession(false);if (session != null) {session.invalidate();}log.info("User logged out at {}", new Date());response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.getWriter().write("{\\"message\\": \\"Logout successful\\"}");response.getWriter().flush();}
}

对接其他作为Client登录

如果我们还想引入其他第三方的登录,和上面的流程一下的。 我们这次增加一个和框架没有集成的第三方(像github和谷歌等因为集成的很好,我们的配置就会很好,流程也会比较顺利)

1、首先就是client 的配置

security:oauth2:client:registration:github:clientId: 56ca77ae71cxxxxxxclientSecret: e1aa08298c5d0f5f9c35414355666b8xxxxxxxxxscope:- user:email- read:useraaaa:client-id: 6b91bc5axxxxxxclient-secret: 97eabe644084f6442a58bxxxxxxauthorization-grant-type: authorization_coderedirect-uri: "http://xxxxxxx/api/login/oauth2/code/aaaa"client-name: SIOMprovider:aaaa:authorization-uri: xxxxxxxtoken-uri: xxxxxxuser-info-uri: xxxxxxxuser-name-attribute: id

这里的配置就是看官方文档进行配置。学习网址:OAuth2

2、创建新的实体类去实现 OAuth2User

例如

import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;public class xxxxCustomOauth2User implements OAuth2User {private OAuth2User user;public xxxxCustomOauth2User(OAuth2User user) {this.user = user;}@Overridepublic Map<String, Object> getAttributes() {return user.getAttributes();}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return user.getAuthorities();}@Overridepublic String getName() {return user.getAttribute("id");}public String getAccountNo() {return (String)((Map<String, Object>) user.getAttributes().get("attributes")).get("account_no");}public String getUserId() {return (String) ((Map<String, Object>) user.getAttributes().get("attributes")).get("user_uid");}public String getEmail() {return (String) ((Map<String, Object>) user.getAttributes().get("attributes")).get("email");}public String getMobilePhone() {return (String) ((Map<String, Object>) user.getAttributes().get("attributes")).get("mobile");}
}

⚠️注意:这里重写的 getName() 就是根据返回的授权用户的 id

3、在 extends DefaultOAuth2UserService 的Service 中增加判断,授权用户最终转变为那个授权实体类进行返回

上面的例子上已经有所展示

4、在 loginSuccessHandler 里面增加判断是那个 AuthorizedClientRegistrationId ,进行不同的登录或者创建用户操作

在这里单节点下两个不同的第三方的 OAuth Client 的整体流程就已经结束了,但是在集群环境下就会出现新的问题

1、 OAuth Client 集群环境下的会话丢失问题

解决方式

关于 JDBC Session的配置

1、引入 spring-session-jdbc 进行会话存储
application.yml

 spring:session:store-type: jdbcjdbc:initialize-schema: always

关于 OAuth Client 的配置

@Configuration
public class OAuth2AuthorizedClientConfig {@Beanpublic OAuth2AuthorizedClientRepository authorizedClientRepository() {return new HttpSessionOAuth2AuthorizedClientRepository();}@Beanpublic OAuth2AuthorizedClientService authorizedClientService(JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository) {return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);}
}

WebSecurity

   http.oauth2Login().loginPage("/login").authorizedClientService(authorizedClientService).authorizedClientRepository(oAuth2AuthorizedClientRepository).userInfoEndpoint().userService(customOauth2UserService).and().successHandler(oauth2LoginSuccessHandler).failureHandler(oauth2LoginFailedHandler);

OAuth 2.0 Client supports application clustering

上面链接的摘录
【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结
【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结

2、authorization_request_not_fund

情况:在第一次成功登录后,客户端和授权端都登出,客户端再发起第三方登录的请求会出现这个问题。(不一定,需要看授权端的登出操作是否彻底请求用户的session及cookie)

无关集群环境,只是纯属工作中的坑。

解决:授权服务器在登出的时候要彻底清除用户的seesion 和 cookie信息

到此这个需求终于结束🎉🎉,踩的坑不少