feat: 소셜로그인 및 유저 등급
This commit is contained in:
@@ -57,7 +57,7 @@ public class TokenProvider {
|
||||
/** 서명/만료/issuer 검증 */
|
||||
public boolean validate(String token) {
|
||||
try {
|
||||
parser.parseClaimsJws(stripBearer(token));
|
||||
parser.parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
return false;
|
||||
@@ -94,7 +94,7 @@ public class TokenProvider {
|
||||
|
||||
/** Claims 추출(검증 포함) */
|
||||
public Claims getClaims(String token) {
|
||||
return parser.parseClaimsJws(stripBearer(token)).getBody();
|
||||
return parser.parseClaimsJws(token).getBody();
|
||||
}
|
||||
|
||||
public String getSubject(String token) {
|
||||
@@ -108,9 +108,4 @@ public class TokenProvider {
|
||||
public List<String> getRoles(String token) {
|
||||
return getClaims(token).get("roles", List.class);
|
||||
}
|
||||
|
||||
private String stripBearer(String token) {
|
||||
if (token == null || !token.startsWith("Bearer ")) return null;
|
||||
return token.substring(7);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@@ -34,7 +39,8 @@ public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtFilter) throws Exception {
|
||||
http.csrf(AbstractHttpConfigurer::disable)
|
||||
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(allAuthorizedUrls).permitAll()
|
||||
@@ -58,6 +64,21 @@ public class SecurityConfig {
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
|
||||
configuration.setAllowedOrigins(List.of("http://localhost:3000"));
|
||||
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setExposedHeaders(List.of("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
|
||||
@@ -2,15 +2,18 @@ package com.boilerplate.core.auth.api.controller;
|
||||
|
||||
import com.boilerplate.core.auth.api.dto.req.LoginRequest;
|
||||
import com.boilerplate.core.auth.api.dto.req.SignUpRequest;
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialLoginRequest;
|
||||
import com.boilerplate.core.auth.api.dto.res.LoginResponse;
|
||||
import com.boilerplate.core.auth.api.dto.res.SignUpResponse;
|
||||
import com.boilerplate.core.auth.application.LoginUseCase;
|
||||
import com.boilerplate.core.auth.application.RegisterUseCase;
|
||||
import com.boilerplate.core.auth.application.SocialLoginUseCase;
|
||||
import com.boilerplate.core.auth.application.dto.command.RegisterUseCaseCommand;
|
||||
import com.boilerplate.core.auth.application.dto.result.LoginResult;
|
||||
import com.boilerplate.core.auth.application.dto.result.RegisterUseCaseResult;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -23,13 +26,16 @@ public class AuthController {
|
||||
|
||||
private final RegisterUseCase registerUseCase;
|
||||
private final LoginUseCase loginUseCase;
|
||||
private final SocialLoginUseCase socialLoginUseCase;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<SignUpResponse> register(
|
||||
@RequestBody SignUpRequest request
|
||||
) {
|
||||
RegisterUseCaseResult result = registerUseCase.execute(
|
||||
RegisterUseCaseCommand.builder().loginId(request.getLoginId()).password(request.getPassword()).build());
|
||||
RegisterUseCaseResult result = registerUseCase.execute(RegisterUseCaseCommand.builder()
|
||||
.email(request.getEmail())
|
||||
.password(request.getPassword())
|
||||
.build());
|
||||
|
||||
SignUpResponse response = SignUpResponse.builder()
|
||||
.accessToken(result.getAccessToken())
|
||||
@@ -43,7 +49,7 @@ public class AuthController {
|
||||
public ResponseEntity<LoginResponse> login(
|
||||
@RequestBody LoginRequest request
|
||||
) {
|
||||
LoginResult result = loginUseCase.execute(request.getLoginId(), request.getPassword());
|
||||
LoginResult result = loginUseCase.execute(request.getEmail(), request.getPassword());
|
||||
LoginResponse response = LoginResponse.builder()
|
||||
.accessToken(result.getAccessToken())
|
||||
.refreshToken(result.getRefreshToken())
|
||||
@@ -51,4 +57,15 @@ public class AuthController {
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/social")
|
||||
public ResponseEntity<LoginResponse> socialLogin(@RequestBody SocialLoginRequest request) {
|
||||
LoginResult result = socialLoginUseCase.execute(request.getProvider(), request.getToken());
|
||||
|
||||
LoginResponse response = LoginResponse.builder()
|
||||
.accessToken(result.getAccessToken())
|
||||
.refreshToken(result.getRefreshToken())
|
||||
.build();
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class LoginRequest {
|
||||
|
||||
private String loginId;
|
||||
private String email;
|
||||
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ import lombok.NoArgsConstructor;
|
||||
@Getter
|
||||
public class SignUpRequest {
|
||||
|
||||
private String loginId;
|
||||
private String email;
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.boilerplate.core.auth.api.dto.req;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class SocialLoginRequest {
|
||||
private SocialProvider provider;
|
||||
private String token;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.boilerplate.core.auth.api.dto.req;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public enum SocialProvider {
|
||||
LOCAL, GOOGLE, FACEBOOK;
|
||||
|
||||
@JsonCreator
|
||||
public static SocialProvider from(String value) {
|
||||
return Stream.of(SocialProvider.values())
|
||||
.filter(p -> p.name().equalsIgnoreCase(value))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ public class LoginUseCase {
|
||||
private final AccountService accountService;
|
||||
private final TokenProvider tokenProvider;
|
||||
|
||||
public LoginResult execute(String loginId, String password) {
|
||||
long accountId = accountService.login(loginId, password);
|
||||
public LoginResult execute(String email, String password) {
|
||||
long accountId = accountService.login(email, password);
|
||||
|
||||
String accessToken = tokenProvider.createUserAccessToken(accountId);
|
||||
String refreshToken = tokenProvider.createUserRefreshToken(accountId);
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.boilerplate.common.TokenProvider;
|
||||
import com.boilerplate.core.auth.application.dto.command.RegisterUseCaseCommand;
|
||||
import com.boilerplate.core.auth.application.dto.result.RegisterUseCaseResult;
|
||||
import com.boilerplate.core.auth.domain.service.AccountService;
|
||||
import com.boilerplate.core.exception.auth.ExistLoginIdException;
|
||||
import com.boilerplate.core.exception.auth.ExistEmailException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -18,15 +18,15 @@ public final class RegisterUseCase {
|
||||
private final TokenProvider tokenProvider;
|
||||
|
||||
public RegisterUseCaseResult execute(RegisterUseCaseCommand command) {
|
||||
String loginId = command.getLoginId();
|
||||
String email = command.getEmail();
|
||||
|
||||
boolean isExistLoginId = accountService.isExistLoginId(loginId);
|
||||
boolean isExistEmail = accountService.isExistEmail(email);
|
||||
|
||||
if (isExistLoginId) {
|
||||
throw new ExistLoginIdException(loginId);
|
||||
if (isExistEmail) {
|
||||
throw new ExistEmailException(email);
|
||||
}
|
||||
|
||||
long accountId = accountService.register(loginId, command.getPassword());
|
||||
long accountId = accountService.register(email, command.getPassword());
|
||||
String accessToken = tokenProvider.createUserAccessToken(accountId);
|
||||
String refreshToken = tokenProvider.createUserRefreshToken(accountId);
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.boilerplate.core.auth.application;
|
||||
|
||||
import com.boilerplate.common.TokenProvider;
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialProvider;
|
||||
import com.boilerplate.core.auth.application.dto.result.LoginResult;
|
||||
import com.boilerplate.core.auth.domain.SocialProviderClient;
|
||||
import com.boilerplate.core.auth.domain.SocialUserInfo;
|
||||
import com.boilerplate.core.auth.domain.service.AccountService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SocialLoginUseCase {
|
||||
|
||||
private final List<SocialProviderClient> socialProviderClients;
|
||||
private final AccountService accountService;
|
||||
private final TokenProvider tokenProvider;
|
||||
|
||||
@Transactional
|
||||
public LoginResult execute(SocialProvider provider, String token) {
|
||||
SocialProviderClient client = socialProviderClients.stream()
|
||||
.filter(c -> c.getProvider() == provider)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unsupported provider: " + provider));
|
||||
|
||||
SocialUserInfo userInfo = client.getUserInfo(token);
|
||||
|
||||
long accountId = accountService.getOrCreateSocialAccount(userInfo);
|
||||
|
||||
String accessToken = tokenProvider.createUserAccessToken(accountId);
|
||||
String refreshToken = tokenProvider.createUserRefreshToken(accountId);
|
||||
|
||||
return LoginResult.builder()
|
||||
.accessToken(accessToken)
|
||||
.refreshToken(refreshToken)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
|
||||
@Getter
|
||||
public class RegisterUseCaseCommand {
|
||||
|
||||
private String loginId;
|
||||
private String email;
|
||||
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ public final class User implements UserDetails {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.boilerplate.core.auth.domain;
|
||||
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialProvider;
|
||||
|
||||
public interface SocialProviderClient {
|
||||
SocialUserInfo getUserInfo(String token);
|
||||
SocialProvider getProvider();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.boilerplate.core.auth.domain;
|
||||
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialProvider;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public class SocialUserInfo {
|
||||
private final String providerId;
|
||||
private final String email;
|
||||
private final String name;
|
||||
private final SocialProvider provider;
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.boilerplate.core.auth.domain.entity;
|
||||
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialProvider;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
@@ -24,10 +27,17 @@ public class Account {
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String loginId;
|
||||
private String email;
|
||||
private String passwordHash;
|
||||
private String name;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@jakarta.persistence.Column(name = "auth_provider")
|
||||
private SocialProvider provider;
|
||||
|
||||
@jakarta.persistence.Column(name = "oauth_id")
|
||||
private String providerId;
|
||||
|
||||
@Builder.Default
|
||||
private LocalDateTime createdAt = LocalDateTime.now();
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.boilerplate.core.auth.domain.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Entity
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@ToString
|
||||
public class Grade {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.boilerplate.core.auth.domain.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@ToString
|
||||
public class GradeHistory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private Long accountId;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "grade_id")
|
||||
private Grade grade;
|
||||
|
||||
private String status; // ACTIVE, EXPIRED, CANCELED
|
||||
|
||||
@Builder.Default
|
||||
private LocalDateTime purchasedAt = LocalDateTime.now();
|
||||
|
||||
private LocalDateTime expiresAt;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.boilerplate.core.auth.domain.repository;
|
||||
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialProvider;
|
||||
import com.boilerplate.core.auth.domain.entity.Account;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
@@ -7,5 +8,6 @@ import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface AccountRepository extends JpaRepository<Account, Long> {
|
||||
Optional<Account> findByLoginId(String loginId);
|
||||
Optional<Account> findByEmail(String email);
|
||||
Optional<Account> findByProviderAndProviderId(SocialProvider provider, String providerId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.boilerplate.core.auth.domain.repository;
|
||||
|
||||
import com.boilerplate.core.auth.domain.entity.GradeHistory;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface GradeHistoryRepository extends JpaRepository<GradeHistory, Long> {
|
||||
|
||||
@Query("SELECT gh FROM GradeHistory gh " +
|
||||
"JOIN FETCH gh.grade " +
|
||||
"WHERE gh.accountId = :accountId AND gh.status = 'ACTIVE' " +
|
||||
"ORDER BY gh.purchasedAt DESC LIMIT 1")
|
||||
Optional<GradeHistory> findCurrentActiveGrade(@Param("accountId") Long accountId);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.boilerplate.core.auth.domain.repository;
|
||||
|
||||
import com.boilerplate.core.auth.domain.entity.Grade;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface GradeRepository extends JpaRepository<Grade, Long> {
|
||||
Optional<Grade> findByName(String name);
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.boilerplate.core.auth.domain.service;
|
||||
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialProvider;
|
||||
import com.boilerplate.core.auth.domain.SocialUserInfo;
|
||||
import com.boilerplate.core.auth.domain.entity.Account;
|
||||
import com.boilerplate.core.auth.domain.entity.Grade;
|
||||
import com.boilerplate.core.auth.domain.entity.GradeHistory;
|
||||
import com.boilerplate.core.auth.domain.repository.AccountRepository;
|
||||
import com.boilerplate.core.auth.domain.repository.GradeHistoryRepository;
|
||||
import com.boilerplate.core.auth.domain.repository.GradeRepository;
|
||||
import com.boilerplate.core.exception.auth.InvalidCredentialsException;
|
||||
import com.boilerplate.core.exception.common.NotExistEntityException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -16,37 +22,78 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class AccountService {
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
private final GradeRepository gradeRepository;
|
||||
private final GradeHistoryRepository gradeHistoryRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Transactional
|
||||
public long register(String loginId, String password) {
|
||||
log.info("회원 등록: {}", loginId);
|
||||
public long register(String email, String password) {
|
||||
log.info("회원 등록: {}", email);
|
||||
|
||||
String hashedPassword = passwordEncoder.encode(password);
|
||||
Grade normalGrade = gradeRepository.findByName("NORMAL")
|
||||
.orElseThrow(() -> new NotExistEntityException(Grade.class.getName()));
|
||||
|
||||
Account account = Account.builder()
|
||||
.loginId(loginId)
|
||||
.email(email)
|
||||
.name("임시 이름")
|
||||
.passwordHash(hashedPassword)
|
||||
.provider(SocialProvider.LOCAL)
|
||||
.build();
|
||||
|
||||
log.info("account: {}", account);
|
||||
|
||||
accountRepository.save(account);
|
||||
|
||||
// 등급 이력 생성
|
||||
GradeHistory gradeHistory = GradeHistory.builder()
|
||||
.accountId(account.getId())
|
||||
.grade(normalGrade)
|
||||
.status("ACTIVE")
|
||||
.build();
|
||||
gradeHistoryRepository.save(gradeHistory);
|
||||
|
||||
return account.getId();
|
||||
}
|
||||
|
||||
public boolean isExistLoginId(String loginId) {
|
||||
log.info("로그인 아이디 존재 여부 조회");
|
||||
@Transactional
|
||||
public long getOrCreateSocialAccount(SocialUserInfo userInfo) {
|
||||
return accountRepository.findByProviderAndProviderId(userInfo.getProvider(), userInfo.getProviderId())
|
||||
.map(Account::getId)
|
||||
.orElseGet(() -> {
|
||||
Grade normalGrade = gradeRepository.findByName("NORMAL")
|
||||
.orElseThrow(() -> new NotExistEntityException(Grade.class.getName()));
|
||||
|
||||
return accountRepository.findByLoginId(loginId).isPresent();
|
||||
Account newAccount = Account.builder()
|
||||
.email(userInfo.getEmail())
|
||||
.name(userInfo.getName())
|
||||
.provider(userInfo.getProvider())
|
||||
.providerId(userInfo.getProviderId())
|
||||
.build();
|
||||
accountRepository.save(newAccount);
|
||||
|
||||
// 등급 이력 생성
|
||||
GradeHistory gradeHistory = GradeHistory.builder()
|
||||
.accountId(newAccount.getId())
|
||||
.grade(normalGrade)
|
||||
.status("ACTIVE")
|
||||
.build();
|
||||
gradeHistoryRepository.save(gradeHistory);
|
||||
|
||||
return newAccount.getId();
|
||||
});
|
||||
}
|
||||
|
||||
public long login(String loginId, String password) {
|
||||
public boolean isExistEmail(String email) {
|
||||
log.info("로그인 아이디 존재 여부 조회");
|
||||
|
||||
return accountRepository.findByEmail(email).isPresent();
|
||||
}
|
||||
|
||||
public long login(String email, String password) {
|
||||
log.info("로그인");
|
||||
|
||||
Account account = accountRepository.findByLoginId(loginId)
|
||||
Account account = accountRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new NotExistEntityException(Account.class.getName()));
|
||||
|
||||
if (!passwordEncoder.matches(password, account.getPasswordHash())) {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.boilerplate.core.auth.infra.social;
|
||||
|
||||
import com.boilerplate.core.auth.api.dto.req.SocialProvider;
|
||||
import com.boilerplate.core.auth.domain.SocialProviderClient;
|
||||
import com.boilerplate.core.auth.domain.SocialUserInfo;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class GoogleSocialProviderClient implements SocialProviderClient {
|
||||
|
||||
private static final String GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v3/userinfo";
|
||||
|
||||
@Override
|
||||
public SocialUserInfo getUserInfo(String accessToken) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(accessToken);
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
try {
|
||||
ResponseEntity<Map> response = restTemplate.exchange(
|
||||
GOOGLE_USERINFO_URL,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
Map.class
|
||||
);
|
||||
|
||||
Map<String, Object> body = response.getBody();
|
||||
|
||||
if (body == null) {
|
||||
throw new RuntimeException("Failed to get response from Google");
|
||||
}
|
||||
|
||||
String userId = (String) body.get("sub");
|
||||
String email = (String) body.get("email");
|
||||
String name = (String) body.get("name");
|
||||
|
||||
log.info("google user name: {}", userId);
|
||||
log.info("email: {}", email);
|
||||
log.info("name: {}", name);
|
||||
|
||||
return SocialUserInfo.builder()
|
||||
.providerId(userId)
|
||||
.email(email)
|
||||
.name(name)
|
||||
.provider(SocialProvider.GOOGLE)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error fetching Google user info with access token", e);
|
||||
throw new RuntimeException("Failed to fetch Google user info", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocialProvider getProvider() {
|
||||
return SocialProvider.GOOGLE;
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,14 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
|
||||
public class ExistLoginIdException extends ErrorResponseException {
|
||||
public class ExistEmailException extends ErrorResponseException {
|
||||
|
||||
public ExistLoginIdException(String loginId) {
|
||||
public ExistEmailException(String email) {
|
||||
super(
|
||||
HttpStatus.CONFLICT,
|
||||
ProblemDetail.forStatusAndDetail(
|
||||
HttpStatus.CONFLICT,
|
||||
"이미 존재하는 loginId 입니다: " + loginId
|
||||
"이미 존재하는 email 입니다: " + email
|
||||
),
|
||||
null
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.boilerplate.core.user.api.controller;
|
||||
|
||||
import com.boilerplate.core.auth.application.userDetail.User;
|
||||
import com.boilerplate.core.user.api.dto.res.UserMeResponse;
|
||||
import com.boilerplate.core.user.application.GetUserMeUseCase;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/user")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
private final GetUserMeUseCase getUserMeUseCase;
|
||||
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<UserMeResponse> getMe(@AuthenticationPrincipal User user) {
|
||||
UserMeResponse response = getUserMeUseCase.execute(user.getId());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.boilerplate.core.user.api.dto.res;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public class UserMeResponse {
|
||||
private final Long id;
|
||||
private final String name;
|
||||
private final String email;
|
||||
private final String grade;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.boilerplate.core.user.application;
|
||||
|
||||
import com.boilerplate.core.auth.domain.entity.Account;
|
||||
import com.boilerplate.core.auth.domain.entity.GradeHistory;
|
||||
import com.boilerplate.core.auth.domain.repository.AccountRepository;
|
||||
import com.boilerplate.core.auth.domain.repository.GradeHistoryRepository;
|
||||
import com.boilerplate.core.exception.common.NotExistEntityException;
|
||||
import com.boilerplate.core.user.api.dto.res.UserMeResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class GetUserMeUseCase {
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
private final GradeHistoryRepository gradeHistoryRepository;
|
||||
|
||||
public UserMeResponse execute(Long accountId) {
|
||||
Account account = accountRepository.findById(accountId)
|
||||
.orElseThrow(() -> new NotExistEntityException(Account.class.getName()));
|
||||
|
||||
String gradeName = gradeHistoryRepository.findCurrentActiveGrade(accountId)
|
||||
.map(gh -> gh.getGrade().getName())
|
||||
.orElse("NORMAL"); // 기본값
|
||||
|
||||
return UserMeResponse.builder()
|
||||
.id(account.getId())
|
||||
.name(account.getName())
|
||||
.email(account.getEmail())
|
||||
.grade(gradeName)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user