JS
[Next JS] Commerce Project #6
상도동 카르마
2023. 4. 15. 20:05
What to do?
Google OAuth
회원가입 기능(Sign Up) 만들기
데모
- 로그인 버튼 클릭
- 구글 로그인 팝업창이 뜨면, 로그인
- 로그인 성공
- 새로고침하면 로그인된걸 볼 수 있음
- 데이터베이스 실제 정보가 들어간걸 확인 할 수 있음
Flow
- Google Login
- Google OAuth를 사용해 credential을 가져옴
- View에서 Next JS API 요청
- End Point : /api/auth/google-sign-in
- method : GET
- parameter : credential
- Nest JS에서 Spring 서버로 POST request
- End Point : localhost:8080/api/user/signIn
- payload : 회원정보 (username/email/imgUrl)
- Spring 서버에서 받은 회원정보로 SignIn 처리
- 이미 존재하는 이메일 - 이메일로 회원정보 조회 후, 회원정보 return
- 존재하지 않는 이메일 - DB에 회원정보 저장(회원가입 처리) 후, 회원정보 return
- Spring 서버에서 Next JS로 회원정보 return
Back End
- 유저 권한
- 향후 유저 권한을 정의할 수 있도록 만든 필드
@Getter
@AllArgsConstructor
public enum UserRole {
USER("ROLE_USER"),
MANAGER("ROLE_MANGER"),
ADMIN("ROLE_ADMIN");
private final String name;
}
- 유저 상태
@Getter
@AllArgsConstructor
public enum UserStatus {
ACTIVE("활동중인 유저"),
BLOCKED("차단된 유저"),
DEACTIVATED("비활성화된 유저"),
REMOVED("회원탈퇴한 유저");
private final String description;
}
- 유저 Entity
- id
- username
- imgUrl : 프로필 사진
- password
- user role
- user status
@Entity
@Getter
@Table(name = "USER_ACCOUNT")
@SQLDelete(sql = "UPDATE USER_ACCOUNT SET removed_at = NOW() WHERE id=?")
@Where(clause = "removed_at is NULL")
@EntityListeners(AuditingEntityListener.class)
public class UserAccountEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true) @Setter
private String username;
@Column(unique = true) @Setter
private String email;
@Column(name = "img_url") @Setter
private String imgUrl;
@Column @Setter
private String password;
@Enumerated(EnumType.STRING) @Setter
private UserRole userRole = UserRole.USER;
@Enumerated(EnumType.STRING) @Setter
private UserStatus userStatus = UserStatus.ACTIVE;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @CreatedDate
@Column(updatable = false, name = "created_at")
private LocalDateTime createdAt;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @LastModifiedDate
@Column(name = "modified_at")
private LocalDateTime modifiedAt;
@Column(name = "removed_at") @Setter
private LocalDateTime removedAt;
private UserAccountEntity(String username, String email, String imgUrl, String password, UserRole userRole, UserStatus userStatus) {
this.username = username;
this.email = email;
this.imgUrl = imgUrl;
this.userRole = userRole;
this.userStatus = userStatus;
}
protected UserAccountEntity(){}
public static UserAccountEntity of(String username, String email, String imgUrl, String password, UserRole userRole, UserStatus userStatus){
return new UserAccountEntity(username, email, imgUrl, password, userRole, userStatus);
}
}
- User Dto
public record UserAccountDto(
Long id,
String username,
String email,
String imgUrl,
String password,
UserRole userRole,
UserStatus userStatus,
LocalDateTime createdAt,
LocalDateTime modifiedAt,
LocalDateTime removedAt
) {
public static UserAccountDto of(
String username,
String email,
String imgUrl,
String password,
UserRole userRole,
UserStatus userStatus
) {
return new UserAccountDto(
null,
username,
email,
imgUrl,
password,
userRole,
userStatus,
null,
null,
null
);
}
public static UserAccountDto from(UserAccountEntity entity) {
return new UserAccountDto(
entity.getId(),
entity.getUsername(),
entity.getEmail(),
entity.getImgUrl(),
entity.getPassword(),
entity.getUserRole(),
entity.getUserStatus(),
entity.getCreatedAt(),
entity.getModifiedAt(),
entity.getRemovedAt()
);
}
}
- Repository
- findByEmail : Email로 해당 유저가 있는지 조회
@Repository
public interface UserAccountRepository extends JpaRepository<UserAccountEntity, Long> {
Optional<UserAccountEntity> findByEmail(String email);
}
- Service
- Email로 이미 존재하는 회원인지 확인
- 이미 존재하는 이메일 → DB에서 해당 회원 정보 return
- 존재하지 않는 이메일 → DB에 회원정보 저장
- 아직 비밀번호로 해당 유저가 맞는지 판단하는 기능은 구현하지 않음
- Email로 이미 존재하는 회원인지 확인
@Service
@RequiredArgsConstructor
public class UserAccountService {
private final UserAccountRepository userAccountRepository;
public UserAccountDto signUp(String username, String email, String imgUrl) {
return UserAccountDto.from(userAccountRepository.findByEmail(email)
.orElseGet(() -> userAccountRepository.save(
UserAccountEntity.of(username, email, imgUrl, null, UserRole.USER, UserStatus.ACTIVE)
)));
}
}
- Request
@Data
public class SignUpRequest {
private String username;
private String email;
private String imgUrl;
private String password;
}
- Controller
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserAccountController {
private final UserAccountService userAccountService;
@PostMapping("/signUp")
public UserAccountDto signUp(@RequestBody SignUpRequest req){
return userAccountService.signUp(req.getUsername(), req.getEmail(), req.getImgUrl());
}
}
Front End
- MyGoogleSignUp.ts
- Sign Up 버튼
export default function MyGoogleSignUp() {
const handleSuccess = (credentialResponse: CredentialResponse): void => {
const endPoint = `/api/auth/google-sign-up?credential=${credentialResponse.credential}`
fetch(endPoint)
.then(res => res.json())
.then((data) => {
console.log(data)
})
}
const handleError = () => {
console.error('error')
}
return (
<GoogleLogin onSuccess={handleSuccess} onError={handleError}></GoogleLogin>
)
}
- api/auth/sign-in.ts
- Request
- End point : localhost:8080/api/user/signIn(Spring 서버)
- mehtod : POST
- payload : username, email, imgUrl
- Response : Spring 서버로부터 받은 회원정보
- Request
import type { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios'
import jwtDecode from 'jwt-decode'
type Data = {
message : String,
}
type DecodedData = {
aud:String,
azp:String,
email:String,
email_verified:Boolean,
exp: Number,
family_name:String,
given_name:String,
iat:Number,
iss:String,
jti:String,
name:String,
nbf:Number,
picture:String,
sub:String
}
async function signUp(credential:string) {
const decoded : DecodedData = jwtDecode(credential)
const data = {
username : `GOOGLE_${decoded.name}`,
email :decoded.email,
imgUrl : decoded.picture,
password : null
}
try {
await axios.post("http://localhost:8080/api/user/signUp", data)
.then(console.log)
} catch (err) {
console.log(err);
}
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const {credential} = req.query
try {
await signUp(String(credential))
res.status(200).json({ message : 'Sign-in success'})
} catch (e) {
console.error(e)
return res.status(400).json({ message: 'Fail to login'})
}
}