Skip to content

Commit

Permalink
feat: admin login (#216)
Browse files Browse the repository at this point in the history
* feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가

* test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가

* feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가

* feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러

* style: checkstyle 에러 fix

* refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo

* fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경

* refactor: 랭킹 응답 반환 객체 변수면 s 제거

Co-authored-by: Kim Heebin <[email protected]>

* refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경

* fix: ObjectMapper에러 수정

* fix: objectMapper 삭제 추가

* feat: 어드민 서비스 로그인 기능 추가

* refactor: 어드민 config 업데이트

* fix: test application.yml 수정

* test: stub에서의 타입 오류 해결

* style: 변수면 변경

---------

Co-authored-by: Kim Heebin <[email protected]>
  • Loading branch information
parksey and kmebin authored Dec 1, 2023
1 parent 6eec958 commit cc4056e
Show file tree
Hide file tree
Showing 19 changed files with 265 additions and 26 deletions.
12 changes: 12 additions & 0 deletions src/main/java/com/moabam/admin/application/admin/AdminMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.moabam.admin.application.admin;

import com.moabam.admin.domain.admin.Admin;

public class AdminMapper {

public static Admin toAdmin(Long socialId) {
return Admin.builder()
.socialId(String.valueOf(socialId))
.build();
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/moabam/admin/application/admin/AdminService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.moabam.admin.application.admin;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.moabam.admin.domain.admin.Admin;
import com.moabam.admin.domain.admin.AdminRepository;
import com.moabam.api.application.auth.AuthorizationService;
import com.moabam.api.application.auth.mapper.AuthMapper;
import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse;
import com.moabam.api.dto.auth.LoginResponse;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.model.ErrorMessage;

import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class AdminService {

@Value("${admin}")
private String adminLoginKey;

private final AuthorizationService authorizationService;
private final AdminRepository adminRepository;

public void validate(String state) {
if (!adminLoginKey.equals(state)) {
throw new BadRequestException(ErrorMessage.LOGIN_FAILED_ADMIN_KEY);
}
}

public LoginResponse signUpOrLogin(HttpServletResponse httpServletResponse,
AuthorizationTokenInfoResponse authorizationTokenInfoResponse) {
LoginResponse loginResponse = login(authorizationTokenInfoResponse);
authorizationService.issueServiceToken(httpServletResponse, loginResponse.publicClaim());

return loginResponse;
}

private LoginResponse login(AuthorizationTokenInfoResponse authorizationTokenInfoResponse) {
Optional<Admin> admin = adminRepository.findBySocialId(String.valueOf(authorizationTokenInfoResponse.id()));
Admin loginMember = admin.orElseGet(() -> signUp(authorizationTokenInfoResponse.id()));

return AuthMapper.toLoginResponse(loginMember, admin.isEmpty());
}

private Admin signUp(Long socialId) {
Admin admin = AdminMapper.toAdmin(socialId);

return adminRepository.save(admin);
}
}
53 changes: 53 additions & 0 deletions src/main/java/com/moabam/admin/domain/admin/Admin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.moabam.admin.domain.admin;

import static com.moabam.global.common.util.RandomUtils.*;
import static java.util.Objects.*;

import org.hibernate.annotations.ColumnDefault;

import com.moabam.api.domain.member.Role;
import com.moabam.global.common.entity.BaseTimeEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Admin extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "nickname", unique = true)
private String nickname;

@Column(name = "social_id", nullable = false, unique = true)
private String socialId;

@Enumerated(EnumType.STRING)
@Column(name = "role", nullable = false)
@ColumnDefault("'USER'")
private Role role;

@Builder
private Admin(String socialId) {
this.socialId = requireNonNull(socialId);
this.nickname = createNickName();
this.role = Role.ADMIN;
}

private String createNickName() {
return "오목눈이#" + randomStringValues();
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/moabam/admin/domain/admin/AdminRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.moabam.admin.domain.admin;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

public interface AdminRepository extends JpaRepository<Admin, Long> {

Optional<Admin> findBySocialId(String socialId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.moabam.admin.presentation.admin;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.moabam.admin.application.admin.AdminService;
import com.moabam.api.application.auth.AuthorizationService;
import com.moabam.api.dto.auth.AuthorizationCodeResponse;
import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse;
import com.moabam.api.dto.auth.AuthorizationTokenResponse;
import com.moabam.api.dto.auth.LoginResponse;

import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/admins")
@RequiredArgsConstructor
public class AdminController {

private final AuthorizationService authorizationService;
private final AdminService adminService;

@PostMapping("/login/kakao/oauth")
@ResponseStatus(HttpStatus.OK)
public LoginResponse authorizationTokenIssue(@RequestBody AuthorizationCodeResponse authorizationCodeResponse,
HttpServletResponse httpServletResponse) {
adminService.validate(authorizationCodeResponse.state());
AuthorizationTokenResponse tokenResponse = authorizationService.requestAdminToken(authorizationCodeResponse);
AuthorizationTokenInfoResponse authorizationTokenInfoResponse = authorizationService.requestTokenInfo(
tokenResponse);

return adminService.signUpOrLogin(httpServletResponse, authorizationTokenInfoResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,17 @@ public void redirectToLoginPage(HttpServletResponse httpServletResponse) {
oauth2AuthorizationServerRequestService.loginRequest(httpServletResponse, authorizationCodeUri);
}

public AuthorizationTokenResponse requestAdminToken(AuthorizationCodeResponse authorizationCodeResponse) {
validAuthorizationGrant(authorizationCodeResponse.code());

return issueTokenToAuthorizationServer(authorizationCodeResponse.code(),
oAuthConfig.provider().adminRedirectUri());
}

public AuthorizationTokenResponse requestToken(AuthorizationCodeResponse authorizationCodeResponse) {
validAuthorizationGrant(authorizationCodeResponse.code());

return issueTokenToAuthorizationServer(authorizationCodeResponse.code());
return issueTokenToAuthorizationServer(authorizationCodeResponse.code(), oAuthConfig.provider().redirectUri());
}

public AuthorizationTokenInfoResponse requestTokenInfo(AuthorizationTokenResponse authorizationTokenResponse) {
Expand Down Expand Up @@ -182,10 +189,9 @@ private void validAuthorizationGrant(String code) {
}
}

private AuthorizationTokenResponse issueTokenToAuthorizationServer(String code) {
private AuthorizationTokenResponse issueTokenToAuthorizationServer(String code, String redirectUri) {
AuthorizationTokenRequest authorizationTokenRequest = AuthorizationMapper.toAuthorizationTokenRequest(
oAuthConfig,
code);
oAuthConfig, code, redirectUri);
MultiValueMap<String, String> uriParams = generateTokenRequest(authorizationTokenRequest);
ResponseEntity<AuthorizationTokenResponse> authorizationTokenResponse =
oauth2AuthorizationServerRequestService.requestAuthorizationServer(oAuthConfig.provider().tokenUri(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.moabam.api.application.auth.mapper;

import com.moabam.admin.domain.admin.Admin;
import com.moabam.api.domain.member.Member;
import com.moabam.api.dto.auth.LoginResponse;
import com.moabam.api.dto.auth.TokenSaveValue;
Expand All @@ -22,6 +23,17 @@ public static LoginResponse toLoginResponse(Member member, boolean isSignUp) {
.build();
}

public static LoginResponse toLoginResponse(Admin admin, boolean isSignUp) {
return LoginResponse.builder()
.publicClaim(PublicClaim.builder()
.id(admin.getId())
.nickname(admin.getNickname())
.role(admin.getRole())
.build())
.isSignUp(isSignUp)
.build();
}

public static TokenSaveValue toTokenSaveValue(String refreshToken, String ip) {
return TokenSaveValue.builder()
.refreshToken(refreshToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ public static AuthorizationCodeRequest toAuthorizationCodeRequest(OAuthConfig oA
.build();
}

public static AuthorizationTokenRequest toAuthorizationTokenRequest(OAuthConfig oAuthConfig, String code) {
public static AuthorizationTokenRequest toAuthorizationTokenRequest(OAuthConfig oAuthConfig, String code,
String redirectUri) {
return AuthorizationTokenRequest.builder()
.grantType(oAuthConfig.client().authorizationGrantType())
.clientId(oAuthConfig.client().clientId())
.redirectUri(oAuthConfig.provider().redirectUri())
.redirectUri(redirectUri)
.code(code)
.clientSecret(oAuthConfig.client().clientSecret())
.build();
Expand Down
21 changes: 13 additions & 8 deletions src/main/java/com/moabam/global/auth/filter/CorsFilter.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.moabam.global.auth.filter;

import java.io.IOException;
import java.util.Objects;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;

import com.google.cloud.storage.HttpMethod;
import com.moabam.global.config.AllowOriginConfig;
import com.moabam.global.error.exception.UnauthorizedException;
import com.moabam.global.error.model.ErrorMessage;

Expand All @@ -31,26 +32,27 @@ public class CorsFilter extends OncePerRequestFilter {

private final HandlerExceptionResolver handlerExceptionResolver;

@Value("${allow}")
private String allowOrigin;
private final AllowOriginConfig allowOriginsConfig;

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
String refer = httpServletRequest.getHeader("referer");
String origin = secureMatch(refer);

try {
if (!secureMatch(httpServletRequest, allowOrigin)) {
if (Objects.isNull(origin)) {
throw new UnauthorizedException(ErrorMessage.INVALID_REQUEST_URL);
}
} catch (UnauthorizedException unauthorizedException) {
log.error("{}, {}", httpServletRequest.getHeader("referer"), allowOrigin);
log.error("{}, {}", httpServletRequest.getHeader("referer"), allowOriginsConfig.origin());
handlerExceptionResolver.resolveException(httpServletRequest, httpServletResponse, null,
unauthorizedException);

return;
}

httpServletResponse.setHeader("Access-Control-Allow-Origin", allowOrigin);
httpServletResponse.setHeader("Access-Control-Allow-Origin", origin);
httpServletResponse.setHeader("Access-Control-Allow-Methods", ALLOWED_METHOD_NAMES);
httpServletResponse.setHeader("Access-Control-Allow-Headers", ALLOWED_HEADERS);
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
Expand All @@ -63,8 +65,11 @@ protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServl
filterChain.doFilter(httpServletRequest, httpServletResponse);
}

public boolean secureMatch(HttpServletRequest request, String origin) {
return request.getHeader("referer").contains(origin);
public String secureMatch(String refer) {
return allowOriginsConfig.origin().stream()
.filter(refer::contains)
.findFirst()
.orElse(null);
}

public boolean isOption(String method) {
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/moabam/global/config/AllowOriginConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.moabam.global.config;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "allows")
public record AllowOriginConfig(
List<String> origin
) {

}
3 changes: 2 additions & 1 deletion src/main/java/com/moabam/global/config/OAuthConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public record Provider(
String redirectUri,
String tokenUri,
String tokenInfo,
String unlink
String unlink,
String adminRedirectUri
) {

}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/moabam/global/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public PathResolver pathResolver() {
PathMapper.pathWithMethod("/members", List.of(HttpMethod.POST)),
PathMapper.pathWithMethod("/members/login/oauth", List.of(HttpMethod.GET)),
PathMapper.parsePath("/members/login/*/oauth"),
PathMapper.parsePath("/admins/login/*/oauth"),
PathMapper.parsePath("/css/*"),
PathMapper.parsePath("/js/*"),
PathMapper.parsePath("/images/*"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.moabam.global.error.handler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
Expand All @@ -14,7 +16,7 @@
public class RestTemplateResponseHandler implements ResponseErrorHandler {

@Override
public boolean hasError(ClientHttpResponse response) {
public boolean hasError(ClientHttpResponse response) throws IOException {
try {
return response.getStatusCode().isError();
} catch (IOException ioException) {
Expand All @@ -25,13 +27,29 @@ public boolean hasError(ClientHttpResponse response) {
@Override
public void handleError(ClientHttpResponse response) {
try {
String errorMessage = parseErrorMessage(response);
HttpStatusCode statusCode = response.getStatusCode();

validResponse(statusCode);
} catch (IOException ioException) {
throw new BadRequestException(ErrorMessage.REQUEST_FAILED);
}
}

private String parseErrorMessage(ClientHttpResponse response) throws IOException {
BufferedReader errorMessage = new BufferedReader(new InputStreamReader(response.getBody()));

String line = errorMessage.readLine();
StringBuilder sb = new StringBuilder();

while (line != null) {
sb.append(line).append("\n");
line = errorMessage.readLine();
}

return sb.toString();
}

private void validResponse(HttpStatusCode statusCode) {
if (statusCode.is5xxServerError()) {
throw new BadRequestException(ErrorMessage.REQUEST_FAILED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public enum ErrorMessage {
IMAGE_CONVERT_FAIL("이미지 변환을 실패했습니다."),

LOGIN_FAILED("로그인에 실패했습니다."),
LOGIN_FAILED_ADMIN_KEY("어드민키가 달라요"),
REQUEST_FAILED("네트워크 접근 실패입니다."),
GRANT_FAILED("인가 코드 실패"),
AUTHENTICATE_FAIL("인증 실패"),
Expand Down
Loading

0 comments on commit cc4056e

Please sign in to comment.