diff --git a/api/src/main/java/com/databasir/api/OpenAuth2AppController.java b/api/src/main/java/com/databasir/api/OpenAuth2AppController.java index d3bbf2c..8d3748b 100644 --- a/api/src/main/java/com/databasir/api/OpenAuth2AppController.java +++ b/api/src/main/java/com/databasir/api/OpenAuth2AppController.java @@ -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 authorization(@PathVariable String registrationId) { - String authorization = openAuthHandler.authorization(registrationId); + public JsonData authorization(@PathVariable String registrationId, + HttpServletRequest request) { + Map parameters = request.getParameterMap(); + String authorization = openAuthHandlers.authorizeUrl(registrationId, parameters); return JsonData.ok(authorization); } diff --git a/core/src/main/java/com/databasir/core/domain/DomainErrors.java b/core/src/main/java/com/databasir/core/domain/DomainErrors.java index 606361f..8be823e 100644 --- a/core/src/main/java/com/databasir/core/domain/DomainErrors.java +++ b/core/src/main/java/com/databasir/core/domain/DomainErrors.java @@ -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; diff --git a/core/src/main/java/com/databasir/core/domain/app/OpenAuthAppService.java b/core/src/main/java/com/databasir/core/domain/app/OpenAuthAppService.java index d25ffcb..c2b4c17 100644 --- a/core/src/main/java/com/databasir/core/domain/app/OpenAuthAppService.java +++ b/core/src/main/java/com/databasir/core/domain/app/OpenAuthAppService.java @@ -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 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 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()) diff --git a/core/src/main/java/com/databasir/core/domain/app/handler/GithubOpenAuthHandler.java b/core/src/main/java/com/databasir/core/domain/app/handler/GithubOpenAuthHandler.java index 79d58ec..24ac022 100644 --- a/core/src/main/java/com/databasir/core/domain/app/handler/GithubOpenAuthHandler.java +++ b/core/src/main/java/com/databasir/core/domain/app/handler/GithubOpenAuthHandler.java @@ -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 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 requestParams) { + String clientId = app.getClientId(); + String clientSecret = app.getClientSecret(); + String baseUrl = app.getResourceUrl(); - Map 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) { diff --git a/core/src/main/java/com/databasir/core/domain/app/handler/GitlabOpenAuthHandler.java b/core/src/main/java/com/databasir/core/domain/app/handler/GitlabOpenAuthHandler.java new file mode 100644 index 0000000..fb8327e --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/app/handler/GitlabOpenAuthHandler.java @@ -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 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 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; + } + +} diff --git a/core/src/main/java/com/databasir/core/domain/app/handler/OAuthProcessContext.java b/core/src/main/java/com/databasir/core/domain/app/handler/OAuthProcessContext.java index 9f57768..205f674 100644 --- a/core/src/main/java/com/databasir/core/domain/app/handler/OAuthProcessContext.java +++ b/core/src/main/java/com/databasir/core/domain/app/handler/OAuthProcessContext.java @@ -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 callbackParameters = new HashMap<>(); + private Map parameters = new HashMap<>(); } diff --git a/core/src/main/java/com/databasir/core/domain/app/handler/OpenAuthHandler.java b/core/src/main/java/com/databasir/core/domain/app/handler/OpenAuthHandler.java index a99f401..96f8a62 100644 --- a/core/src/main/java/com/databasir/core/domain/app/handler/OpenAuthHandler.java +++ b/core/src/main/java/com/databasir/core/domain/app/handler/OpenAuthHandler.java @@ -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 requestParams); - OAuthProcessResult process(OAuthProcessContext context); + OAuthProcessResult process(OauthAppPojo app, Map requestParams); } diff --git a/core/src/main/java/com/databasir/core/domain/app/handler/OpenAuthHandlers.java b/core/src/main/java/com/databasir/core/domain/app/handler/OpenAuthHandlers.java new file mode 100644 index 0000000..d9df275 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/app/handler/OpenAuthHandlers.java @@ -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 handlers; + + private final OauthAppDao oauthAppDao; + + public String authorizeUrl(String registrationId, Map 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 parameters) { + OauthAppPojo app = oauthAppDao.selectByRegistrationId(registrationId); + return handlers.stream() + .filter(handler -> handler.support(app.getAppType())) + .findFirst() + .map(handler -> handler.process(app, parameters)) + .orElseThrow(); + } +} diff --git a/core/src/main/java/com/databasir/core/infrastructure/remote/ClientConfig.java b/core/src/main/java/com/databasir/core/infrastructure/remote/ClientConfig.java index 356cec3..7faa9ef 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/remote/ClientConfig.java +++ b/core/src/main/java/com/databasir/core/infrastructure/remote/ClientConfig.java @@ -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); + } } diff --git a/core/src/main/java/com/databasir/core/infrastructure/remote/gitlab/GitlabApiClient.java b/core/src/main/java/com/databasir/core/infrastructure/remote/gitlab/GitlabApiClient.java new file mode 100644 index 0000000..e13279c --- /dev/null +++ b/core/src/main/java/com/databasir/core/infrastructure/remote/gitlab/GitlabApiClient.java @@ -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 getUser(@Url String url, @Header("Authorization") String token); + + @POST + @Headers(value = { + "Accept: application/json" + }) + Call getAccessToken(@Url String url); +} diff --git a/core/src/main/java/com/databasir/core/infrastructure/remote/gitlab/GitlabRemoteService.java b/core/src/main/java/com/databasir/core/infrastructure/remote/gitlab/GitlabRemoteService.java new file mode 100644 index 0000000..b333ee1 --- /dev/null +++ b/core/src/main/java/com/databasir/core/infrastructure/remote/gitlab/GitlabRemoteService.java @@ -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 execute(Call call) { + try { + Response 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); + } + } +}