Spring Boot 接入 GitHub 第三方登錄
點(diǎn)擊上方藍(lán)字設(shè)為星標(biāo)![]()

前言
創(chuàng)建GitHub OAuth Apps

<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>spring:
??security:
????oauth2:
??????client:
????????registration:
??????????github:
????????????clientId: github-client-id
????????????clientSecret: github-client-secret@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();
????????
????}
}logging:
??level:
????org.springframework.security: debugOAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter用戶在客戶端頁面點(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地址獲取令牌
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;
????}
????????......省略部分代碼
}@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;
}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());
}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);
}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;
??}
}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 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;
??}
????...省略部分代碼
} OAuth2ClientAutoConfiguration
OAuth2ClientRegistrationRepositoryConfiguration
OAuth2WebSecurityConfigurationOAuth2ClientRegistrationRepositoryConfiguration:
@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);
????}
} 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);
}OAuth2WebSecurityConfiguration
@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();
????}
??}
}推薦閱讀
分享我常用的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ā)筆記 有驚喜哦
評論
圖片
表情

