diff --git a/eiam-authentication/eiam-authentication-all/pom.xml b/eiam-authentication/eiam-authentication-all/pom.xml index 315402fa..04b5c4af 100644 --- a/eiam-authentication/eiam-authentication-all/pom.xml +++ b/eiam-authentication/eiam-authentication-all/pom.xml @@ -57,6 +57,12 @@ eiam-authentication-wechat ${project.version} + + + cn.topiam + eiam-authentication-github + ${project.version} + cn.topiam diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderCategory.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderCategory.java index 94914308..84dbb045 100644 --- a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderCategory.java +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderCategory.java @@ -35,9 +35,11 @@ public enum IdentityProviderCategory implements BaseEnum { /** * 社交 */ - social("social", "社交", Lists.newArrayList( - IdentityProviderType.QQ, - IdentityProviderType.WECHAT_QR)), + social("social", "社交", + Lists.newArrayList( + IdentityProviderType.QQ, + IdentityProviderType.WECHAT_QR, + IdentityProviderType.GITHUB)), /** * 企业 */ diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderType.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderType.java index 239a196c..bef0fcd7 100644 --- a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderType.java +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderType.java @@ -93,6 +93,13 @@ public final class IdentityProviderType { */ public static final IdentityProviderType MAIL = new IdentityProviderType("mail", "邮件验证码认证", "通过邮件验证码进行身份认证"); + + /** + * GITHUB认证 + */ + public static final IdentityProviderType GITHUB = new IdentityProviderType( + "github_oauth", "GITHUB认证", "通过GITHUB进行身份认证"); + private final String value; private final String name; private final String desc; diff --git a/eiam-authentication/eiam-authentication-github/pom.xml b/eiam-authentication/eiam-authentication-github/pom.xml new file mode 100644 index 00000000..0cd5689b --- /dev/null +++ b/eiam-authentication/eiam-authentication-github/pom.xml @@ -0,0 +1,42 @@ + + + + + eiam-authentication + cn.topiam + 1.0.1-SNAPSHOT + ../pom.xml + + 4.0.0 + + eiam-authentication-github + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/GithubIdpOauthConfig.java b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/GithubIdpOauthConfig.java new file mode 100644 index 00000000..9ce82642 --- /dev/null +++ b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/GithubIdpOauthConfig.java @@ -0,0 +1,52 @@ +/* + * eiam-authentication-github - Employee Identity and Access Management + * Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.github; + +import java.io.Serial; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import jakarta.validation.constraints.NotBlank; + +/** + * GITHUB 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/4 23:58 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GithubIdpOauthConfig extends IdentityProviderConfig { + @Serial + private static final long serialVersionUID = -6850223527422243176L; + + /** + * Client ID + */ + @NotBlank(message = "Client ID 不能为空") + private String clientId; + + /** + * Client Secret + */ + @NotBlank(message = "Client Secret 不能为空") + private String clientSecret; +} diff --git a/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/configurer/GithubOauthAuthenticationConfigurer.java b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/configurer/GithubOauthAuthenticationConfigurer.java new file mode 100644 index 00000000..89c6ad06 --- /dev/null +++ b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/configurer/GithubOauthAuthenticationConfigurer.java @@ -0,0 +1,100 @@ +/* + * eiam-authentication-github - Employee Identity and Access Management + * Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.github.configurer; + +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.github.filter.GithubOAuth2AuthorizationRequestRedirectFilter; +import cn.topiam.employee.authentication.github.filter.GithubOAuth2LoginAuthenticationFilter; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 22:58 + */ +public final class GithubOauthAuthenticationConfigurer extends + AbstractAuthenticationFilterConfigurer { + + private final IdentityProviderRepository identityProviderRepository; + private final UserIdpService userIdpService; + + GithubOauthAuthenticationConfigurer(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + Assert.notNull(identityProviderRepository, "identityProviderRepository must not be null"); + Assert.notNull(userIdpService, "userIdpService must not be null"); + this.identityProviderRepository = identityProviderRepository; + this.userIdpService = userIdpService; + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl); + } + + @Override + public void init(HttpSecurity http) throws Exception { + //设置登录成功失败处理器 + //Github扫码登录认证 + GithubOAuth2LoginAuthenticationFilter loginAuthenticationFilter = new GithubOAuth2LoginAuthenticationFilter( + identityProviderRepository, userIdpService); + this.setAuthenticationFilter(loginAuthenticationFilter); + //处理URL + super.loginProcessingUrl( + GithubOAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + super.init(http); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + //GITHUB请求重定向 + GithubOAuth2AuthorizationRequestRedirectFilter requestRedirectFilter = new GithubOAuth2AuthorizationRequestRedirectFilter( + identityProviderRepository); + http.addFilterBefore(requestRedirectFilter, OAuth2AuthorizationRequestRedirectFilter.class); + http.addFilterBefore(this.getAuthenticationFilter(), OAuth2LoginAuthenticationFilter.class); + super.configure(http); + } + + public RequestMatcher getRequestMatcher() { + return new OrRequestMatcher( + GithubOAuth2AuthorizationRequestRedirectFilter.getRequestMatcher(), + GithubOAuth2LoginAuthenticationFilter.getRequestMatcher()); + } + + public static GithubOauthAuthenticationConfigurer github(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + return new GithubOauthAuthenticationConfigurer(identityProviderRepository, userIdpService); + } + +} diff --git a/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/constant/GithubAuthenticationConstants.java b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/constant/GithubAuthenticationConstants.java new file mode 100644 index 00000000..a33565c9 --- /dev/null +++ b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/constant/GithubAuthenticationConstants.java @@ -0,0 +1,40 @@ +/* + * eiam-authentication-github - Employee Identity and Access Management + * Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.github.constant; + +/** + * GITHUB + * + * @author TopIAM + * Created by support@topiam.cn on 2023/8/16 22:19 + */ +public final class GithubAuthenticationConstants { + /** + * 获取授权码地址 + */ + public static final String URL_AUTHORIZE = "https://github.com/login/oauth/authorize"; + /** + * 获取令牌地址 + */ + public static final String URL_GET_ACCESS_TOKEN = "https://github.com/login/oauth/access_token"; + /** + * 获取用户信息的地址 + */ + public static final String URL_GET_USER_INFO = "https://api.github.com/user"; + +} diff --git a/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/filter/GithubOAuth2AuthorizationRequestRedirectFilter.java b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/filter/GithubOAuth2AuthorizationRequestRedirectFilter.java new file mode 100644 index 00000000..e0d0b604 --- /dev/null +++ b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/filter/GithubOAuth2AuthorizationRequestRedirectFilter.java @@ -0,0 +1,138 @@ +/* + * eiam-authentication-github - Employee Identity and Access Management + * Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.github.filter; + +import java.io.IOException; +import java.util.Base64; +import java.util.Map; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.lang.NonNull; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.authentication.github.GithubIdpOauthConfig; +import cn.topiam.employee.common.entity.authn.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import static cn.topiam.employee.authentication.common.IdentityProviderType.GITHUB; +import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE; +import static cn.topiam.employee.authentication.github.constant.GithubAuthenticationConstants.URL_AUTHORIZE; +import static cn.topiam.employee.authentication.github.filter.GithubOAuth2LoginAuthenticationFilter.getLoginUrl; + +/** + * 微信扫码登录请求重定向过滤器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/20 21:22 + */ +@SuppressWarnings("ALL") +public class GithubOAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter { + + private final Logger logger = LoggerFactory + .getLogger(GithubOAuth2AuthorizationRequestRedirectFilter.class); + + /** + * AntPathRequestMatcher + */ + public static final AntPathRequestMatcher GITHUB_REQUEST_MATCHER = new AntPathRequestMatcher( + GITHUB.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_CODE + "}", + HttpMethod.GET.name()); + + /** + * 重定向策略 + */ + private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); + + /** + * 认证请求存储库 + */ + private final AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + + private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( + Base64.getUrlEncoder()); + private final IdentityProviderRepository identityProviderRepository; + + public GithubOAuth2AuthorizationRequestRedirectFilter(IdentityProviderRepository identityProviderRepository) { + this.identityProviderRepository = identityProviderRepository; + } + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws IOException, + ServletException { + RequestMatcher.MatchResult matcher = GITHUB_REQUEST_MATCHER.matcher(request); + if (!matcher.isMatch()) { + filterChain.doFilter(request, response); + return; + } + Map variables = matcher.getVariables(); + String providerCode = variables.get(PROVIDER_CODE); + Optional optional = identityProviderRepository + .findByCodeAndEnabledIsTrue(providerCode); + if (optional.isEmpty()) { + throw new NullPointerException("未查询到身份提供商信息"); + } + IdentityProviderEntity entity = optional.get(); + GithubIdpOauthConfig config = JSONObject.parseObject(entity.getConfig(), + GithubIdpOauthConfig.class); + Assert.notNull(config, "GITHUB登录配置不能为空"); + //构建授权请求 + OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode() + .clientId(config.getClientId()).authorizationUri(URL_AUTHORIZE) + .redirectUri(getLoginUrl(optional.get().getCode())) + .state(DEFAULT_STATE_GENERATOR.generateKey()); + builder.parameters(parameters -> { + parameters.put(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2ParameterNames.CODE); + }); + this.sendRedirectForAuthorization(request, response, builder.build()); + } + + private void sendRedirectForAuthorization(HttpServletRequest request, + HttpServletResponse response, + OAuth2AuthorizationRequest authorizationRequest) throws IOException { + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, + response); + this.authorizationRedirectStrategy.sendRedirect(request, response, + authorizationRequest.getAuthorizationRequestUri()); + } + + public static RequestMatcher getRequestMatcher() { + return GITHUB_REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/filter/GithubOAuth2LoginAuthenticationFilter.java b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/filter/GithubOAuth2LoginAuthenticationFilter.java new file mode 100644 index 00000000..0d60f27a --- /dev/null +++ b/eiam-authentication/eiam-authentication-github/src/main/java/cn/topiam/employee/authentication/github/filter/GithubOAuth2LoginAuthenticationFilter.java @@ -0,0 +1,184 @@ +/* + * eiam-authentication-github - Employee Identity and Access Management + * Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.github.filter; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; +import org.springframework.http.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.client.RestTemplate; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.google.common.collect.Lists; + +import cn.topiam.employee.authentication.common.authentication.IdpUserDetails; +import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthenticationProcessingFilter; +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.github.GithubIdpOauthConfig; +import cn.topiam.employee.common.entity.authn.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.core.help.ServerHelp; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.trace.TraceUtils; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import static cn.topiam.employee.authentication.common.IdentityProviderType.GITHUB; +import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.*; +import static cn.topiam.employee.authentication.github.constant.GithubAuthenticationConstants.*; + +/** + * GITHUB登录 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:11 + */ +@SuppressWarnings({ "AlibabaClassNamingShouldBeCamel", "DuplicatedCode" }) +public class GithubOAuth2LoginAuthenticationFilter extends + AbstractIdpAuthenticationProcessingFilter { + final String ERROR_CODE = "error"; + public final static String DEFAULT_FILTER_PROCESSES_URI = GITHUB + .getLoginPathPrefix() + "/" + "{" + PROVIDER_CODE + "}"; + public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher( + DEFAULT_FILTER_PROCESSES_URI, HttpMethod.GET.name()); + + /** + * Creates a new instance + * + * @param identityProviderRepository the {@link IdentityProviderRepository} + * @param userIdpService {@link UserIdpService} + */ + public GithubOAuth2LoginAuthenticationFilter(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + super(DEFAULT_FILTER_PROCESSES_URI, userIdpService, identityProviderRepository); + } + + /** + * GITHUB认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletRequest} + * @return {@link HttpServletRequest} + * @throws AuthenticationException {@link AuthenticationException} AuthenticationException + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, + IOException { + OAuth2AuthorizationRequest authorizationRequest = getOauth2AuthorizationRequest(request, + response); + TraceUtils.put(UUID.randomUUID().toString()); + RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request); + Map variables = matcher.getVariables(); + String providerCode = variables.get(PROVIDER_CODE); + String providerId = getIdentityProviderId(providerCode); + //code + String code = request.getParameter(OAuth2ParameterNames.CODE); + if (StringUtils.isEmpty(code)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_CODE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + // state + String state = request.getParameter(OAuth2ParameterNames.STATE); + if (StringUtils.isEmpty(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + if (!authorizationRequest.getState().equals(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取身份提供商 + IdentityProviderEntity provider = getIdentityProviderEntity(providerCode); + GithubIdpOauthConfig config = JSONObject.parseObject(provider.getConfig(), + GithubIdpOauthConfig.class); + if (Objects.isNull(config)) { + logger.error("未查询到GITHUB登录配置"); + //无效身份提供商 + OAuth2Error oauth2Error = new OAuth2Error(INVALID_IDP_CONFIG); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取access token + HashMap param = new HashMap<>(16); + param.put(OAuth2ParameterNames.CLIENT_ID, config.getClientId().trim()); + param.put(OAuth2ParameterNames.CLIENT_SECRET, config.getClientSecret().trim()); + param.put(OAuth2ParameterNames.CODE, code.trim()); + param.put(OAuth2ParameterNames.REDIRECT_URI, getLoginUrl(provider.getCode())); + JSONObject result = request(URL_GET_ACCESS_TOKEN, HttpMethod.POST, null, param); + if (Objects.nonNull(result.getString(ERROR_CODE))) { + logger.error("获取access_token发生错误: {}" + result.toJSONString()); + throw new TopIamException("获取access_token发生错误: " + result.toJSONString()); + } + // 获取id信息 + result = request(URL_GET_USER_INFO, HttpMethod.GET, + result.getString(OAuth2ParameterNames.TOKEN_TYPE) + " " + + result.getString( + OAuth2ParameterNames.ACCESS_TOKEN), + param); + if (!Objects.isNull(result.getString(ERROR_CODE))) { + logger.error("获取GITHUB用户OpenID发生错误: {}" + result.toJSONString()); + throw new TopIamException("获取GITHUB用户OpenID发生错误: " + result.toJSONString()); + } + // 返回 + String id = result.getString("id"); + IdpUserDetails idpUserDetails = IdpUserDetails.builder().openId(id).providerType(GITHUB) + .providerCode(providerCode).providerId(providerId).build(); + return attemptAuthentication(request, response, idpUserDetails); + + } + + @Nullable + private static JSONObject request(String url, HttpMethod method, String authorization, + HashMap param) { + RestTemplate client = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_JSON); + if (StringUtils.isNotBlank(authorization)) { + headers.set("Authorization", authorization); + } + HttpEntity requestEntity = new HttpEntity(param, headers); + ResponseEntity responseEntity = client.exchange(url, method, requestEntity, + String.class); + return JSON.parseObject(responseEntity.getBody()); + } + + public static String getLoginUrl(String providerId) { + String url = ServerHelp.getPortalPublicBaseUrl() + "/" + GITHUB.getLoginPathPrefix() + "/" + + providerId; + return url.replaceAll("(?. */ -package cn.topiam.employee.authentication.qq; \ No newline at end of file +package cn.topiam.employee.authentication.github; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java index bd85aaeb..ea6cf93b 100644 --- a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package cn.topiam.employee.portal.idp.qq.constant; +package cn.topiam.employee.authentication.qq.constant; /** * 企业微信 diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java index f0d317cd..5aab8905 100644 --- a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java @@ -51,8 +51,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import static cn.topiam.employee.authentication.common.IdentityProviderType.QQ; import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE; +import static cn.topiam.employee.authentication.qq.constant.QqAuthenticationConstants.URL_AUTHORIZE; import static cn.topiam.employee.authentication.qq.filter.QqOAuth2LoginAuthenticationFilter.getLoginUrl; -import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_AUTHORIZE; /** * 微信扫码登录请求重定向过滤器 diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java index 149483dd..e89d466c 100644 --- a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java @@ -55,8 +55,8 @@ import static com.nimbusds.oauth2.sdk.GrantType.AUTHORIZATION_CODE; import static cn.topiam.employee.authentication.common.IdentityProviderType.QQ; import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.*; -import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_GET_ACCESS_TOKEN; -import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_GET_OPEN_ID; +import static cn.topiam.employee.authentication.qq.constant.QqAuthenticationConstants.URL_GET_ACCESS_TOKEN; +import static cn.topiam.employee.authentication.qq.constant.QqAuthenticationConstants.URL_GET_OPEN_ID; /** * QQ登录 diff --git a/eiam-authentication/pom.xml b/eiam-authentication/pom.xml index 6c0feb75..4f08bcd8 100644 --- a/eiam-authentication/pom.xml +++ b/eiam-authentication/pom.xml @@ -42,6 +42,7 @@ eiam-authentication-all eiam-authentication-mail eiam-authentication-sms + eiam-authentication-github diff --git a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/Config/Config.tsx b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/Config/Config.tsx index 95e91284..c87bf057 100644 --- a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/Config/Config.tsx +++ b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/Config/Config.tsx @@ -23,6 +23,7 @@ import FeiShuScanCodeConfig from './FeiShuScanCodeConfig'; import QqOauthConfig from './QqOauthConfig'; import WeChatScanCode from './WeChatScanCodeConfig'; import WeWorkScanCode from './WeWorkScanCodeConfig'; +import GithubOauthConfig from './GithubOauthConfig'; import { useIntl } from '@umijs/max'; /** @@ -43,6 +44,7 @@ const Config = (props: { type: IdentityProviderType | string; isCreate?: boolean {type === IdentityProviderType.dingtalk_oauth && } {type === IdentityProviderType.qq && } {type === IdentityProviderType.feishu_oauth && } + {type === IdentityProviderType.github && } . + */ +import { ProFormText } from '@ant-design/pro-components'; +import CallbackUrl from './CallbackUrl'; +import { useIntl } from '@umijs/max'; + +/** + * Github Oauth 登录 + * + * @constructor + */ +const QqOauthConfig = (props: { isCreate: boolean }) => { + const { isCreate } = props; + const intl = useIntl(); + + return ( + <> + + + {!isCreate && } + + ); +}; +export default QqOauthConfig; diff --git a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/CreateModal/CreateModal.tsx b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/CreateModal/CreateModal.tsx index 20a4b466..536fac4f 100644 --- a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/CreateModal/CreateModal.tsx +++ b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/components/CreateModal/CreateModal.tsx @@ -121,6 +121,12 @@ export default (props: CreateDrawerProps) => { id: 'pages.authn.identity_provider.create_modal.form.type.qq', }), }, + { + value: IdentityProviderType.github, + label: intl.formatMessage({ + id: 'pages.authn.identity_provider.create_modal.form.type.github', + }), + }, ]} /> diff --git a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/constant.ts b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/constant.ts index f33b8111..6c00da07 100644 --- a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/constant.ts +++ b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/constant.ts @@ -36,6 +36,7 @@ export enum IdentityProviderType { //社交 qq = 'qq_oauth', wechat_qr = 'wechat_qr', + github = 'github_oauth', } /** diff --git a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/locales/zh-CN.ts b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/locales/zh-CN.ts index e8b3755a..252d21b2 100644 --- a/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/locales/zh-CN.ts +++ b/eiam-console/src/main/console-fe/src/pages/authn/IdentityProvider/locales/zh-CN.ts @@ -38,6 +38,7 @@ export default { 'pages.authn.identity_provider.create_modal.form.type.rule.0.message': '请选择认证提供商', 'pages.authn.identity_provider.create_modal.form.type.wechat_qr': '微信开放平台扫码认证', 'pages.authn.identity_provider.create_modal.form.type.qq': 'QQ认证', + 'pages.authn.identity_provider.create_modal.form.type.github': 'Github认证', 'pages.authn.identity_provider.create_modal.form.type.dingtalk_oauth': '钉钉认证', 'pages.authn.identity_provider.create_modal.form.type.dingtalk_qr': '钉钉扫码认证', 'pages.authn.identity_provider.create_modal.form.type.feishu_oauth': '飞书认证', @@ -75,4 +76,8 @@ export default { 'pages.authn.identity_provider.config.wework_scan_code.app_secret.placeholder': '请输入获取的Secret', 'pages.authn.identity_provider.add-success-content': '请复制以下链接访问门户端', + 'pages.authn.identity_provider.config.github_oauth.client_id.placeholder': '请填写Client ID', + 'pages.authn.identity_provider.config.github_oauth.client_secret.placeholder': '请填写Client Secret', + 'pages.authn.identity_provider.config.github_oauth.client_id.extra': 'GitHub应用的Client ID', + 'pages.authn.identity_provider.config.github_oauth.client_secret.extra': 'GitHub应用生成的Client secret', }; diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/authn/IdentityProviderConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/authn/IdentityProviderConverter.java index 4cf8553a..e7c55c4a 100644 --- a/eiam-console/src/main/java/cn/topiam/employee/console/converter/authn/IdentityProviderConverter.java +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/authn/IdentityProviderConverter.java @@ -39,6 +39,7 @@ import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; import cn.topiam.employee.authentication.dingtalk.DingTalkIdpOauthConfig; import cn.topiam.employee.authentication.dingtalk.DingTalkIdpScanCodeConfig; import cn.topiam.employee.authentication.feishu.FeiShuIdpScanCodeConfig; +import cn.topiam.employee.authentication.github.GithubIdpOauthConfig; import cn.topiam.employee.authentication.qq.QqIdpOauthConfig; import cn.topiam.employee.authentication.wechat.WeChatIdpScanCodeConfig; import cn.topiam.employee.authentication.wechatwork.WeChatWorkIdpScanCodeConfig; @@ -260,6 +261,9 @@ public interface IdentityProviderConverter { //飞书认证 } else if (type.equals(FEISHU_OAUTH.value())) { identityProviderConfig = config.to(FeiShuIdpScanCodeConfig.class); + //GITHUB认证 + } else if (type.equals(GITHUB.value())) { + identityProviderConfig = config.to(GithubIdpOauthConfig.class); } else { throw new TopIamException("不支持此身份提供商"); } @@ -311,6 +315,9 @@ public interface IdentityProviderConverter { if (WECHAT_WEB_PAGE.value().equals(type)) { return WECHAT_WEB_PAGE; } + if (GITHUB.value().equals(type)) { + return GITHUB; + } throw new IllegalArgumentException("未知身份提供商类型"); } } diff --git a/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/security/PortalSecurityConfiguration.java b/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/security/PortalSecurityConfiguration.java index 515b8d52..a57e7438 100644 --- a/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/security/PortalSecurityConfiguration.java +++ b/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/security/PortalSecurityConfiguration.java @@ -57,6 +57,7 @@ import cn.topiam.employee.authentication.common.service.UserIdpService; import cn.topiam.employee.authentication.dingtalk.configurer.DingtalkOAuth2AuthenticationConfigurer; import cn.topiam.employee.authentication.dingtalk.configurer.DingtalkScanCodeAuthenticationConfigurer; import cn.topiam.employee.authentication.feishu.configurer.FeiShuScanCodeAuthenticationConfigurer; +import cn.topiam.employee.authentication.github.configurer.GithubOauthAuthenticationConfigurer; import cn.topiam.employee.authentication.otp.mail.MailOtpAuthenticationConfigurer; import cn.topiam.employee.authentication.otp.sms.SmsOtpAuthenticationConfigurer; import cn.topiam.employee.authentication.qq.configurer.QqOauthAuthenticationConfigurer; @@ -92,6 +93,7 @@ import static cn.topiam.employee.authentication.common.configurer.IdpBindAuthent import static cn.topiam.employee.authentication.dingtalk.configurer.DingtalkOAuth2AuthenticationConfigurer.dingtalkOAuth2; import static cn.topiam.employee.authentication.dingtalk.configurer.DingtalkScanCodeAuthenticationConfigurer.dingtalkScanCode; import static cn.topiam.employee.authentication.feishu.configurer.FeiShuScanCodeAuthenticationConfigurer.feiShuScanCode; +import static cn.topiam.employee.authentication.github.configurer.GithubOauthAuthenticationConfigurer.github; import static cn.topiam.employee.authentication.otp.mail.MailOtpAuthenticationConfigurer.mailOtp; import static cn.topiam.employee.authentication.otp.sms.SmsOtpAuthenticationConfigurer.smsOtp; import static cn.topiam.employee.authentication.qq.configurer.QqOauthAuthenticationConfigurer.qq; @@ -164,6 +166,14 @@ public class PortalSecurityConfiguration extends AbstractSecurityConfiguration requestMatchers.add(chatScanCode.getRequestMatcher()); httpSecurity.apply(chatScanCode); + //GITHUB + GithubOauthAuthenticationConfigurer github = github(identityProviderRepository, userIdpService) + .successHandler(successHandler) + .failureHandler(failureHandler) + .authenticationDetailsSource(authenticationDetailsSource); + requestMatchers.add(github.getRequestMatcher()); + httpSecurity.apply(github); + //企业微信 WeChatWorkScanCodeAuthenticationConfigurer weChatWorkScanCode = weChatWorkScanCode(identityProviderRepository, userIdpService) .successHandler(successHandler) diff --git a/eiam-portal/src/main/portal-fe/src/pages/Login/Login.tsx b/eiam-portal/src/main/portal-fe/src/pages/Login/Login.tsx index 9a9b3c69..e53562c8 100644 --- a/eiam-portal/src/main/portal-fe/src/pages/Login/Login.tsx +++ b/eiam-portal/src/main/portal-fe/src/pages/Login/Login.tsx @@ -184,6 +184,21 @@ const Login = () => { window.open(path, '_self'); }; + /** + * GITHUB + * + * @param id + */ + const githubOauthOnClick = (id: string) => { + const query = queryString.parse(history.location.search); + const { redirect_uri } = query as { redirect_uri: string }; + let path = `/api/v1/authorization/github_oauth/${id}`; + if (redirect_uri) { + path = `${path}?redirect_uri=${redirect_uri}`; + } + window.open(path, '_self'); + }; + /** * 提交 * @@ -478,6 +493,11 @@ const Login = () => { weiBoOauthOnClick(value.code); return; } + //GITHUB,跳转页面 + if (value.type === IDP_TYPE.GITHUB_OAUTH) { + githubOauthOnClick(value.code); + return; + } //其他方式,跳转页面 else { setCurrentProvider({