feat: support gitlab oauth login

This commit is contained in:
vran 2022-03-02 22:35:46 +08:00
parent cd20dfd7cf
commit a432ade3ec
11 changed files with 231 additions and 38 deletions

View File

@ -3,7 +3,7 @@ package com.databasir.api;
import com.databasir.common.JsonData;
import com.databasir.core.domain.app.OpenAuthAppService;
import com.databasir.core.domain.app.data.*;
import com.databasir.core.domain.app.handler.OpenAuthHandler;
import com.databasir.core.domain.app.handler.OpenAuthHandlers;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@ -12,8 +12,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static org.springframework.data.domain.Sort.Direction.DESC;
@ -21,17 +23,19 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
@RequiredArgsConstructor
public class OpenAuth2AppController {
private final OpenAuthHandler openAuthHandler;
private final OpenAuthAppService openAuthAppService;
private final OpenAuthHandlers openAuthHandlers;
/**
* 无需授权
*/
@GetMapping("/oauth2/authorization/{registrationId}")
@ResponseBody
public JsonData<String> authorization(@PathVariable String registrationId) {
String authorization = openAuthHandler.authorization(registrationId);
public JsonData<String> authorization(@PathVariable String registrationId,
HttpServletRequest request) {
Map<String, String[]> parameters = request.getParameterMap();
String authorization = openAuthHandlers.authorizeUrl(registrationId, parameters);
return JsonData.ok(authorization);
}

View File

@ -25,7 +25,8 @@ public enum DomainErrors implements DatabasirErrors {
UPDATE_PASSWORD_CONFIRM_FAILED("A_10010", "两次密码输入不一致"),
ORIGIN_PASSWORD_NOT_CORRECT("A_10011", "原密码不正确"),
INVALID_CRON_EXPRESSION("A_10012", "不合法的 cron 表达式"),
REGISTRATION_ID_DUPLICATE("A_10013", "应用注册 ID 不能重复");
REGISTRATION_ID_DUPLICATE("A_10013", "应用注册 ID 不能重复"),
MISS_REQUIRED_PARAMETERS("A_10014", "缺少必填参数");
private final String errCode;

View File

@ -4,9 +4,8 @@ import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.app.converter.OAuthAppPojoConverter;
import com.databasir.core.domain.app.converter.OAuthAppResponseConverter;
import com.databasir.core.domain.app.data.*;
import com.databasir.core.domain.app.handler.OpenAuthHandler;
import com.databasir.core.domain.app.handler.OAuthProcessContext;
import com.databasir.core.domain.app.handler.OAuthProcessResult;
import com.databasir.core.domain.app.handler.OpenAuthHandlers;
import com.databasir.core.domain.user.data.UserCreateRequest;
import com.databasir.core.domain.user.data.UserDetailResponse;
import com.databasir.core.domain.user.service.UserService;
@ -16,7 +15,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
@ -29,7 +27,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class OpenAuthAppService {
private final List<OpenAuthHandler> openAuthHandlers;
private final OpenAuthHandlers openAuthHandlers;
private final OauthAppDao oauthAppDao;
@ -40,20 +38,8 @@ public class OpenAuthAppService {
private final OAuthAppPojoConverter oauthAppPojoConverter;
public UserDetailResponse oauthCallback(String registrationId, Map<String, String[]> params) {
// match handler
OauthAppPojo app = oauthAppDao.selectByRegistrationId(registrationId);
OpenAuthHandler openAuthHandler = openAuthHandlers.stream()
.filter(handler -> handler.support(app.getAppType()))
.findFirst()
.orElseThrow(() -> new UsernameNotFoundException("暂不支持该类型登陆"));
// process by handler
OAuthProcessContext context = OAuthProcessContext.builder()
.callbackParameters(params)
.registrationId(registrationId)
.build();
OAuthProcessResult result = openAuthHandler.process(context);
OAuthProcessResult result = openAuthHandlers.process(registrationId, params);
// get or create new user
return userService.get(result.getEmail())

View File

@ -4,7 +4,6 @@ import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.app.exception.DatabasirAuthenticationException;
import com.databasir.core.infrastructure.remote.github.GithubRemoteService;
import com.databasir.dao.enums.OAuthAppType;
import com.databasir.dao.impl.OauthAppDao;
import com.databasir.dao.tables.pojos.OauthAppPojo;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
@ -21,16 +20,13 @@ public class GithubOpenAuthHandler implements OpenAuthHandler {
private final GithubRemoteService githubRemoteService;
private final OauthAppDao oauthAppDao;
@Override
public boolean support(String oauthAppType) {
return OAuthAppType.GITHUB.isSame(oauthAppType);
}
@Override
public String authorization(String registrationId) {
OauthAppPojo app = oauthAppDao.selectByRegistrationId(registrationId);
public String authorizationUrl(OauthAppPojo app, Map<String, String[]> requestParams) {
String authUrl = app.getAuthUrl();
String clientId = app.getClientId();
String authorizeUrl = authUrl + "/login/oauth/authorize";
@ -44,14 +40,12 @@ public class GithubOpenAuthHandler implements OpenAuthHandler {
}
@Override
public OAuthProcessResult process(OAuthProcessContext context) {
OauthAppPojo authApp = oauthAppDao.selectByRegistrationId(context.getRegistrationId());
String clientId = authApp.getClientId();
String clientSecret = authApp.getClientSecret();
String baseUrl = authApp.getResourceUrl();
public OAuthProcessResult process(OauthAppPojo app, Map<String, String[]> requestParams) {
String clientId = app.getClientId();
String clientSecret = app.getClientSecret();
String baseUrl = app.getResourceUrl();
Map<String, String[]> params = context.getCallbackParameters();
String code = params.get("code")[0];
String code = requestParams.get("code")[0];
JsonNode tokenNode = githubRemoteService.getToken(baseUrl, clientId, clientSecret, code)
.get("access_token");
if (tokenNode == null) {

View File

@ -0,0 +1,76 @@
package com.databasir.core.domain.app.handler;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.app.exception.DatabasirAuthenticationException;
import com.databasir.core.infrastructure.remote.gitlab.GitlabRemoteService;
import com.databasir.dao.enums.OAuthAppType;
import com.databasir.dao.tables.pojos.OauthAppPojo;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class GitlabOpenAuthHandler implements OpenAuthHandler {
private final GitlabRemoteService gitlabRemoteService;
@Override
public boolean support(String oauthAppType) {
return OAuthAppType.GITLAB.isSame(oauthAppType);
}
@Override
public String authorizationUrl(OauthAppPojo app, Map<String, String[]> params) {
if (!params.containsKey("redirect_uri")) {
throw DomainErrors.MISS_REQUIRED_PARAMETERS.exception("缺少参数 redirect_uri", null);
}
String authUrl = app.getAuthUrl();
String clientId = app.getClientId();
String authorizeUrl = authUrl + "/oauth/authorize";
String redirectUri = params.get("redirect_uri")[0];
String url = UriComponentsBuilder.fromUriString(authorizeUrl)
.queryParam("client_id", clientId)
.queryParam("redirect_uri", redirectUri)
.queryParam("response_type", "code")
.queryParam("state", redirectUri)
.encode()
.build()
.toUriString();
return url;
}
@Override
public OAuthProcessResult process(OauthAppPojo app, Map<String, String[]> requestParams) {
if (!requestParams.containsKey("redirect_uri")) {
throw DomainErrors.MISS_REQUIRED_PARAMETERS.exception("缺少参数 redirect_uri", null);
}
String url = app.getAuthUrl();
String code = requestParams.get("code")[0];
String state = requestParams.get("state")[0];
String redirectUri = requestParams.get("redirect_uri")[0];
JsonNode accessTokenData =
gitlabRemoteService.getAccessToken(url, code, app.getClientId(), app.getClientSecret(), redirectUri);
if (accessTokenData == null) {
throw new DatabasirAuthenticationException(DomainErrors.NETWORK_ERROR.exception());
}
String accessToken = accessTokenData.get("access_token").asText();
JsonNode userData = gitlabRemoteService.getUser(app.getResourceUrl(), accessToken);
if (userData == null) {
throw new DatabasirAuthenticationException(DomainErrors.NETWORK_ERROR.exception());
}
String email = userData.get("email").asText();
String avatar = userData.get("avatar_url").asText();
String name = userData.get("name").asText();
OAuthProcessResult result = new OAuthProcessResult();
result.setAvatar(avatar);
result.setEmail(email);
result.setNickname(name);
result.setUsername(email);
return result;
}
}

View File

@ -1,5 +1,6 @@
package com.databasir.core.domain.app.handler;
import com.databasir.dao.tables.pojos.OauthAppPojo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -16,7 +17,9 @@ public class OAuthProcessContext {
private String registrationId;
private OauthAppPojo app;
@Builder.Default
private Map<String, String[]> callbackParameters = new HashMap<>();
private Map<String, String[]> parameters = new HashMap<>();
}

View File

@ -1,10 +1,14 @@
package com.databasir.core.domain.app.handler;
import com.databasir.dao.tables.pojos.OauthAppPojo;
import java.util.Map;
public interface OpenAuthHandler {
boolean support(String oauthAppType);
String authorization(String registrationId);
String authorizationUrl(OauthAppPojo app, Map<String, String[]> requestParams);
OAuthProcessResult process(OAuthProcessContext context);
OAuthProcessResult process(OauthAppPojo app, Map<String, String[]> requestParams);
}

View File

@ -0,0 +1,36 @@
package com.databasir.core.domain.app.handler;
import com.databasir.dao.impl.OauthAppDao;
import com.databasir.dao.tables.pojos.OauthAppPojo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class OpenAuthHandlers {
private final List<OpenAuthHandler> handlers;
private final OauthAppDao oauthAppDao;
public String authorizeUrl(String registrationId, Map<String, String[]> parameters) {
OauthAppPojo app = oauthAppDao.selectByRegistrationId(registrationId);
return handlers.stream()
.filter(handler -> handler.support(app.getAppType()))
.findFirst()
.map(handler -> handler.authorizationUrl(app, parameters))
.orElseThrow();
}
public OAuthProcessResult process(String registrationId, Map<String, String[]> parameters) {
OauthAppPojo app = oauthAppDao.selectByRegistrationId(registrationId);
return handlers.stream()
.filter(handler -> handler.support(app.getAppType()))
.findFirst()
.map(handler -> handler.process(app, parameters))
.orElseThrow();
}
}

View File

@ -2,6 +2,7 @@ package com.databasir.core.infrastructure.remote;
import com.databasir.core.infrastructure.remote.github.GithubApiClient;
import com.databasir.core.infrastructure.remote.github.GithubOauthClient;
import com.databasir.core.infrastructure.remote.gitlab.GitlabApiClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
@ -31,4 +32,13 @@ public class ClientConfig {
.build();
return retrofit.create(GithubOauthClient.class);
}
@Bean
public GitlabApiClient gitlabApiClient() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://gitlab.com")
.addConverterFactory(JacksonConverterFactory.create())
.build();
return retrofit.create(GitlabApiClient.class);
}
}

View File

@ -0,0 +1,19 @@
package com.databasir.core.infrastructure.remote.gitlab;
import com.fasterxml.jackson.databind.JsonNode;
import retrofit2.Call;
import retrofit2.http.*;
public interface GitlabApiClient {
@GET
@Headers(value = {
"Accept: application/json"
})
Call<JsonNode> getUser(@Url String url, @Header("Authorization") String token);
@POST
@Headers(value = {
"Accept: application/json"
})
Call<JsonNode> getAccessToken(@Url String url);
}

View File

@ -0,0 +1,60 @@
package com.databasir.core.infrastructure.remote.gitlab;
import com.databasir.common.SystemException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import retrofit2.Call;
import retrofit2.Response;
import java.io.IOException;
@Service
@RequiredArgsConstructor
@Slf4j
public class GitlabRemoteService {
private final GitlabApiClient gitlabApiClient;
public JsonNode getAccessToken(String baseUrl,
String code,
String clientId,
String clientSecret,
String redirectUri) {
String path = "/oauth/token";
String uri = baseUrl + path;
String uriStr = UriComponentsBuilder.fromUriString(uri)
.queryParam("client_id", clientId)
.queryParam("client_secret", clientSecret)
.queryParam("code", code)
.queryParam("grant_type", "authorization_code")
.queryParam("redirect_uri", redirectUri)
.encode()
.toUriString();
return execute(gitlabApiClient.getAccessToken(uriStr));
}
public JsonNode getUser(String baseUrl, String accessToken) {
String tokenHeaderValue = "Bearer " + accessToken;
String uri = baseUrl + "/api/v4/user";
return execute(gitlabApiClient.getUser(uri, tokenHeaderValue));
}
private <T> T execute(Call<T> call) {
try {
Response<T> response = call.execute();
if (!response.isSuccessful()) {
log.error("request error: " + call.request() + ", response = " + response);
throw new SystemException("Call Remote Error");
} else {
log.info("response " + response);
T body = response.body();
return body;
}
} catch (IOException e) {
throw new SystemException("System Error", e);
}
}
}