mirror of https://gitee.com/topiam/eiam
✨ 集成github登录
parent
a69dc3a12b
commit
2980babe1b
|
@ -57,6 +57,12 @@
|
|||
<artifactId>eiam-authentication-wechat</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- github -->
|
||||
<dependency>
|
||||
<groupId>cn.topiam</groupId>
|
||||
<artifactId>eiam-authentication-github</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- wechatwork-->
|
||||
<dependency>
|
||||
<groupId>cn.topiam</groupId>
|
||||
|
|
|
@ -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)),
|
||||
/**
|
||||
* 企业
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>eiam-authentication</artifactId>
|
||||
<groupId>cn.topiam</groupId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>eiam-authentication-github</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<!-- common -->
|
||||
<dependency>
|
||||
<groupId>cn.topiam</groupId>
|
||||
<artifactId>eiam-authentication-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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;
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<HttpSecurity, GithubOauthAuthenticationConfigurer, GithubOAuth2LoginAuthenticationFilter> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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";
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<OAuth2AuthorizationRequest> 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<String, String> variables = matcher.getVariables();
|
||||
String providerCode = variables.get(PROVIDER_CODE);
|
||||
Optional<IdentityProviderEntity> 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;
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<String, String> 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<String, String> 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<String, String> 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<String> 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("(?<!(http:|https:))/+", "/");
|
||||
}
|
||||
|
||||
public static RequestMatcher getRequestMatcher() {
|
||||
return REQUEST_MATCHER;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* eiam-authentication-qq - Employee Identity and Access Management
|
||||
* 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
|
||||
|
@ -15,4 +15,4 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.authentication.qq;
|
||||
package cn.topiam.employee.authentication.github;
|
|
@ -15,7 +15,7 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.portal.idp.qq.constant;
|
||||
package cn.topiam.employee.authentication.qq.constant;
|
||||
|
||||
/**
|
||||
* 企业微信
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 微信扫码登录请求重定向过滤器
|
||||
|
|
|
@ -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登录
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<module>eiam-authentication-all</module>
|
||||
<module>eiam-authentication-mail</module>
|
||||
<module>eiam-authentication-sms</module>
|
||||
<module>eiam-authentication-github</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
@ -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 && <DingTalkOauthConfig isCreate={isCreate} />}
|
||||
{type === IdentityProviderType.qq && <QqOauthConfig isCreate={isCreate} />}
|
||||
{type === IdentityProviderType.feishu_oauth && <FeiShuScanCodeConfig isCreate={isCreate} />}
|
||||
{type === IdentityProviderType.github && <GithubOauthConfig isCreate={isCreate} />}
|
||||
<ProFormSwitch
|
||||
name={['displayed']}
|
||||
extra={intl.formatMessage({
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* eiam-console - 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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 (
|
||||
<>
|
||||
<ProFormText
|
||||
name={['config', 'clientId']}
|
||||
label="Cilent ID"
|
||||
rules={[{ required: true }]}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.authn.identity_provider.config.github_oauth.client_id.placeholder',
|
||||
})}
|
||||
extra={intl.formatMessage({
|
||||
id: 'pages.authn.identity_provider.config.github_oauth.client_id.extra',
|
||||
})}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
rules={[{ required: true }]}
|
||||
name={['config', 'clientSecret']}
|
||||
label="Client Secret"
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.authn.identity_provider.config.github_oauth.client_secret.placeholder',
|
||||
})}
|
||||
extra={intl.formatMessage({
|
||||
id: 'pages.authn.identity_provider.config.github_oauth.client_secret.extra',
|
||||
})}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
/>
|
||||
{!isCreate && <CallbackUrl />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default QqOauthConfig;
|
|
@ -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',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -36,6 +36,7 @@ export enum IdentityProviderType {
|
|||
//社交
|
||||
qq = 'qq_oauth',
|
||||
wechat_qr = 'wechat_qr',
|
||||
github = 'github_oauth',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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("未知身份提供商类型");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Reference in New Issue