Skip to content

Commit

Permalink
feat: Member, Auth 추가 (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
sosow0212 authored Jan 21, 2024
1 parent 3894a4f commit 3df4a84
Show file tree
Hide file tree
Showing 56 changed files with 1,817 additions and 2 deletions.
54 changes: 54 additions & 0 deletions src/main/java/com/atwoz/member/application/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.atwoz.member.application.auth;

import com.atwoz.global.event.Events;
import com.atwoz.member.application.auth.dto.LoginRequest;
import com.atwoz.member.application.auth.dto.SignupRequest;
import com.atwoz.member.domain.auth.RegisteredEvent;
import com.atwoz.member.domain.auth.TokenProvider;
import com.atwoz.member.domain.member.Member;
import com.atwoz.member.domain.member.MemberRepository;
import com.atwoz.member.domain.member.NicknameGenerator;
import com.atwoz.member.exception.exceptions.member.MemberAlreadyExistedException;
import com.atwoz.member.exception.exceptions.member.MemberNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class AuthService {

private final MemberRepository memberRepository;
private final TokenProvider tokenProvider;
private final NicknameGenerator nicknameGenerator;

@Transactional
public String signup(final SignupRequest request) {
validateExistedMember(request.email());

Member member = Member.createDefaultRole(request.email(), request.password(), nicknameGenerator);
Member signupMember = memberRepository.save(member);
Events.raise(new RegisteredEvent(member.getId(), member.getEmail(), member.getNickname()));

return tokenProvider.create(signupMember.getId());
}

private void validateExistedMember(final String email) {
if (memberRepository.existsByEmail(email)) {
throw new MemberAlreadyExistedException();
}
}

@Transactional(readOnly = true)
public String login(final LoginRequest request) {
Member member = findMemberByEmail(request.email());
member.validatePassword(request.password());

return tokenProvider.create(member.getId());
}

private Member findMemberByEmail(final String email) {
return memberRepository.findByEmail(email)
.orElseThrow(MemberNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.atwoz.member.application.auth.dto;

import jakarta.validation.constraints.NotBlank;

public record LoginRequest(
@NotBlank(message = "이메일을 입력해주세요.")
String email,

@NotBlank(message = "패스워드를 입력해주세요.")
String password
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.atwoz.member.application.auth.dto;

import jakarta.validation.constraints.NotBlank;

public record SignupRequest(
@NotBlank(message = "이메일을 입력해주세요.")
String email,

@NotBlank(message = "패스워드를 입력해주세요.")
String password
) {
}
52 changes: 52 additions & 0 deletions src/main/java/com/atwoz/member/config/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.atwoz.member.config;

import com.atwoz.member.ui.auth.interceptor.LoginValidCheckerInterceptor;
import com.atwoz.member.ui.auth.interceptor.ParseMemberIdFromTokenInterceptor;
import com.atwoz.member.ui.auth.interceptor.PathMatcherInterceptor;
import com.atwoz.member.ui.auth.support.resolver.AuthArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

import static com.atwoz.member.ui.auth.interceptor.HttpMethod.ANY;
import static com.atwoz.member.ui.auth.interceptor.HttpMethod.OPTIONS;

@RequiredArgsConstructor
@Configuration
public class AuthConfig implements WebMvcConfigurer {

private final AuthArgumentResolver authArgumentResolver;
private final ParseMemberIdFromTokenInterceptor parseMemberIdFromTokenInterceptor;
private final LoginValidCheckerInterceptor loginValidCheckerInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(parseMemberIdFromTokenInterceptor());
registry.addInterceptor(loginValidCheckerInterceptor());
}

private HandlerInterceptor parseMemberIdFromTokenInterceptor() {
return new PathMatcherInterceptor(parseMemberIdFromTokenInterceptor)
.excludePathPattern("/**", OPTIONS)
.addPathPatterns("/admin/**", ANY);
}

/**
* @AuthMember를 통해서 인증이 필요한 경우에 해당 메서드에 URI를 추가해주면 된다.
* 추가를 해야지 인증,인가 가능
*/
private HandlerInterceptor loginValidCheckerInterceptor() {
return new PathMatcherInterceptor(loginValidCheckerInterceptor)
.excludePathPattern("/**", OPTIONS);
}

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authArgumentResolver);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/atwoz/member/domain/auth/RegisteredEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.atwoz.member.domain.auth;

import com.atwoz.global.event.Event;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class RegisteredEvent extends Event {

private final Long memberId;
private final String email;
private final String nickname;
}
8 changes: 8 additions & 0 deletions src/main/java/com/atwoz/member/domain/auth/TokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.atwoz.member.domain.auth;

public interface TokenProvider {

String create(final Long id);

Long extract(final String token);
}
64 changes: 64 additions & 0 deletions src/main/java/com/atwoz/member/domain/member/Member.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.atwoz.member.domain.member;

import com.atwoz.global.domain.BaseEntity;
import com.atwoz.member.exception.exceptions.member.PasswordNotMatchedException;
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.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@EqualsAndHashCode(of = "id", callSuper = false)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Member extends BaseEntity {

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

@Column(nullable = false, unique = true)
private String email;

@Column(nullable = false)
private String password;

@Column(nullable = false)
private String nickname;

@Enumerated(value = EnumType.STRING)
@Column(nullable = false)
private MemberRole memberRole;

public boolean isAdmin() {
return this.memberRole.isAdministrator();
}

public static Member createDefaultRole(final String email,
final String password,
final NicknameGenerator nicknameGenerator) {
return Member.builder()
.email(email)
.password(password)
.nickname(nicknameGenerator.createRandomNickname())
.memberRole(MemberRole.MEMBER)
.build();
}

public void validatePassword(final String password) {
if (!this.password.equals(password)) {
throw new PasswordNotMatchedException();
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/atwoz/member/domain/member/MemberRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.atwoz.member.domain.member;

import java.util.Optional;

public interface MemberRepository {

Optional<Member> findById(final Long id);

Optional<Member> findByNickname(final String nickname);

Optional<Member> findByEmail(final String email);

Member save(final Member member);

boolean existsByEmail(final String email);
}
30 changes: 30 additions & 0 deletions src/main/java/com/atwoz/member/domain/member/MemberRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.atwoz.member.domain.member;

import com.atwoz.member.exception.exceptions.member.RoleNotFoundException;
import lombok.Getter;

import java.util.Arrays;

@Getter
public enum MemberRole {

MEMBER("member"),
ADMIN("admin");

private final String role;

MemberRole(final String role) {
this.role = role;
}

public static MemberRole from(final String role) {
return Arrays.stream(values())
.filter(value -> value.role.equalsIgnoreCase(role))
.findFirst()
.orElseThrow(RoleNotFoundException::new);
}

public boolean isAdministrator() {
return this.equals(ADMIN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.atwoz.member.domain.member;

public interface NicknameGenerator {

String createRandomNickname();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.atwoz.member.exception;

import com.atwoz.member.exception.exceptions.auth.ExpiredTokenException;
import com.atwoz.member.exception.exceptions.auth.LoginInvalidException;
import com.atwoz.member.exception.exceptions.auth.SignatureInvalidException;
import com.atwoz.member.exception.exceptions.auth.TokenFormInvalidException;
import com.atwoz.member.exception.exceptions.auth.TokenInvalidException;
import com.atwoz.member.exception.exceptions.auth.UnsupportedTokenException;
import com.atwoz.member.exception.exceptions.member.MemberAlreadyExistedException;
import com.atwoz.member.exception.exceptions.member.MemberNotFoundException;
import com.atwoz.member.exception.exceptions.member.PasswordNotMatchedException;
import com.atwoz.member.exception.exceptions.member.RoleNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class MemberExceptionHandler {

// member
@ExceptionHandler(RoleNotFoundException.class)
public ResponseEntity<String> handleRoleNotFoundException(final RoleNotFoundException e) {
return getNotFoundResponse(e);
}

@ExceptionHandler(MemberAlreadyExistedException.class)
public ResponseEntity<String> handleMemberAlreadyExistedException(final MemberAlreadyExistedException e) {
return getConflicted(e);
}

@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<String> handleMemberNotFoundException(final MemberNotFoundException e) {
return getNotFoundResponse(e);
}

@ExceptionHandler(PasswordNotMatchedException.class)
public ResponseEntity<String> handlePasswordNotMatchedException(final PasswordNotMatchedException e) {
return getConflicted(e);
}

// auth
@ExceptionHandler(SignatureInvalidException.class)
public ResponseEntity<String> handleSignatureInvalidException(final SignatureInvalidException e) {
return getUnauthorized(e);
}

@ExceptionHandler(TokenFormInvalidException.class)
public ResponseEntity<String> handleTokenFormInvalidException(final TokenFormInvalidException e) {
return getUnauthorized(e);
}

@ExceptionHandler(ExpiredTokenException.class)
public ResponseEntity<String> handleExpiredTokenException(final ExpiredTokenException e) {
return getUnauthorized(e);
}

@ExceptionHandler(UnsupportedTokenException.class)
public ResponseEntity<String> handleUnsupportedTokenException(final UnsupportedTokenException e) {
return getUnauthorized(e);
}

@ExceptionHandler(TokenInvalidException.class)
public ResponseEntity<String> handleTokenInvalidException(final TokenInvalidException e) {
return getUnauthorized(e);
}

@ExceptionHandler(LoginInvalidException.class)
public ResponseEntity<String> handleLoginInvalidException(final LoginInvalidException e) {
return getUnauthorized(e);
}

private ResponseEntity<String> getNotFoundResponse(final Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(e.getMessage());
}

private ResponseEntity<String> getUnauthorized(final Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(e.getMessage());
}

private ResponseEntity<String> getConflicted(final Exception e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(e.getMessage());
}

private ResponseEntity<String> getBadRequest(final Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.atwoz.member.exception.exceptions.auth;

public class ExpiredTokenException extends RuntimeException {

public ExpiredTokenException() {
super("이미 만료된 토큰입니다");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.atwoz.member.exception.exceptions.auth;

public class LoginInvalidException extends RuntimeException {

public LoginInvalidException() {
super("로그인 정보를 찾을 수 없습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.atwoz.member.exception.exceptions.auth;

public class SignatureInvalidException extends RuntimeException {

public SignatureInvalidException() {
super("서명을 확인하지 못했습니다.");
}
}
Loading

0 comments on commit 3df4a84

Please sign in to comment.