우주먼지

💡 회원 기능

 

요구사항

Open-Session-in-view 옵션: false (트랜잭션 내부의 프록시객체 초기화 필요)

@ManyToOne 단방향 매핑으로 진행
FetchType : Lazy

1. 회원 등록 (카카오 로그인 & 사이트 자체 회원가입)
2. 로그인 (사이트 자체 로그인)
3. 로그인한 회원 목록 조회
4. 로그인한 회원 Role 조회
5. 회원 삭제
6. 비밀번호 암호화
7. 비밀번호 복호화

💡 소스코드

 

Entity

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member extends BaseEntity {

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

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

    @Column(length = 200)
//    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@!%*#?&])[A-Za-z\\d@!%*#?&]{8,}$")
    private String password;
    // 소셜 로그인 인증 같은 경우 비밀번호를 직접 다루지 않기 때문에 nullable

    @Column(nullable = false, length = 20)
    private String memberName;
    //실명

    @Column(length = 10)
    private String nickName;
    //닉네임 추가

    @Column(length = 10)
    private String birth;
    // 생년월일 저장
    @Column(length = 200)
    private String profile;
    // 해당 회원의 프로필 사진 주소 저장
    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 10)
    private Role role;
    // 따로 권한테이블을 두어서 여러 역할을 가질 수 있도록 설정할 수도 있음
    // 현재는 한개만 갖도록 구성(일반 유저와 Admin 유저로 구분)

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 10)
    private MemberType memberType;
    // 카카오 회원인지, 네이버 회원인지 Enum으로 관리

    @Enumerated(EnumType.STRING)
    @Column
    private Status status;

    @Column(length = 250)
    private String refreshToken;
    // 리프레쉬 토큰
    private LocalDateTime tokenExpirationTime;
    // 토큰 만료 시간

    @Builder
    public Member(Long memberId, String email, String password, String memberName, String nickName, String birth, String profile, Role role, MemberType memberType, String refreshToken, LocalDateTime tokenExpirationTime) {
        this.memberId = memberId;
        this.email = email;
        this.password = password;
        this.memberName = memberName;
        this.nickName = nickName;
        this.birth = birth;
        this.profile = profile;
        this.role = role;
        this.memberType = memberType;
        this.refreshToken = refreshToken;
        this.tokenExpirationTime = tokenExpirationTime;
    }

    public void updateRefreshToken(JwtTokenDto jwtTokenDto) {
        this.refreshToken = jwtTokenDto.getRefreshToken();
        this.tokenExpirationTime = DateTimeUtils.convertToLocalDateTime(jwtTokenDto.getRefreshTokenExpireTime());
    }

    public void expireRefreshToken(LocalDateTime now) {
        this.tokenExpirationTime = now;
    }
}

 

DTO

public class MemberDto {

    @Getter
    public static class Post{

        @Email
        private String email;

        private String memberName;

//        @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@!%*#?&])[A-Za-z\\d@!%*#?&]{8,}$")
//        @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$")
        private String password;

        private String confirmPassword;

        private String nickName;

        private String birth;

    }

    @Getter
    public static class Login{
//        @Pattern(regexp = "\t^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$")
        private String email;
//        @Pattern(regexp = "\t^.*(?=^.{8,15}$)(?=.*\\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$")
        private String password;

        private String memberType;

    }
    @Getter
    @NoArgsConstructor
    public static class Patch{
        private Long memberId;

        private String nickName;

        private String password;

        private String newPassword;

        private String confirmNewPassword;

        private String birth;

        public void updateMemberId(Long memberId){
            this.memberId = memberId;
        }

    }

    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Getter
    @Setter
    public static class Response{
        private Long memberId;

        private String email;

        private String password;

        private String memberName;

        private String nickName;

        private String birth;

        private Role role;

        private MemberType memberType;

        private String refreshToken;

        private LocalDateTime tokenExpirationTime;

    }

    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Getter
    @Setter
    public static class MyPageResponse{
        private Long memberId;

        private Role role;

        private String email;

        private String memberName;

        private String nickName;

        private MemberType memberType;

        private String birth;

        private String password;

    }
    @Getter @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class LoginResponse {

        private String grantType;

        private String accessToken;

        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
        private Date accessTokenExpireTime;

        private String refreshToken;

        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
        private Date refreshTokenExpireTime;

        private Role role;
        public static LoginResponse of(JwtTokenDto jwtTokenDto, Role role){
            return LoginResponse.builder()
                    .role(role)
                    .grantType(jwtTokenDto.getGrantType())
                    .accessToken(jwtTokenDto.getAccessToken())
                    .accessTokenExpireTime(jwtTokenDto.getAccessTokenExpireTime())
                    .refreshToken(jwtTokenDto.getRefreshToken())
                    .refreshTokenExpireTime(jwtTokenDto.getRefreshTokenExpireTime())
                    .build();
        }
    }
}

 

Mapper

@Mapper(componentModel = "spring")
public interface MemberMapper {


    MemberDto.MyPageResponse memberToMyPageResoponse(Member member);

    MemberDto.Response memberToMemberResponse(Member member);

    Member memberPostDtoToMember(MemberDto.Post memberPostDto);

    Member memberLoginDtoToMember(MemberDto.Login memberLoginDto);

    @Mapping(source = "newPassword", target = "password")
    Member memberPatchDtoToMember(MemberDto.Patch memberPatchDto);

    List<MemberDto.Response> membersToMemberMyPageResponses(List<Member> members);
}

 

Repository

public interface MemberRepository extends JpaRepository<Member, Long> {

    Optional<Member> findByEmail(String email);

    Optional<Member> findByRefreshToken(String refreshToken);

}

 

Service

@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    private final TokenManager tokenManager;

    private final JasyptConfig jasyptConfig; //암호화, 복호화를 위한 di

    /**
     * 회원가입(카카오 로그인 시 사용)
     */
    public Member registerMember(Member member) {
        validateDuplicateMember(member); //동일 이메일있는지 확인
        return memberRepository.save(member);
    }


    /**
     * 회원가입(사이트 자체 회원가입)
     */
    public Member createMember(Member member){
        validateDuplicateMember(member); //동일 이메일있는지 확인

        member.setMemberType(MemberType.DEFAULT); //자체 회원가입
        member.setRole(Role.USER);
        member.setPassword(encryptPassword(member.getPassword())); //비밀번호 암호화

        return memberRepository.save(member);
    }

    /**
     * 회원 로그인(사이트 자체 로그인)
     */
    public MemberDto.LoginResponse login(Member member) {
        JwtTokenDto jwtTokenDto;
        Optional<Member> optioanlMember = findMemberByEmail(member.getEmail()); //이메일로 회원 검색

        if(optioanlMember.isEmpty()) {
            throw new EntityNotFoundException(ErrorCode.MEMBER_NOT_EXISTS); //존재 안하면 예외 처리
        }

        Member findMember = optioanlMember.get();

        if(findMember.getStatus() == Status.DELETE){
            throw new EntityNotFoundException(ErrorCode.MEMBER_WITHDRAWN);
        }

        if(!decryptPassword(findMember.getPassword()).equals(member.getPassword())){
            throw new AuthenticationException(ErrorCode.WRONG_PASSWROD); //비밀번호 일치 하지 않으면 예외처리
        }

        jwtTokenDto = tokenManager.createJwtTokenDto(findMember.getMemberId(), findMember.getRole()); //토큰 생성

        findMember.updateRefreshToken(jwtTokenDto); //토큰값 설정
        memberRepository.save(findMember); //db에 리프레쉬 토큰 업데이트

        return MemberDto.LoginResponse.of(jwtTokenDto, findMember.getRole());
    }

    /**
     * 회원 목록 조회
     */
    @Transactional(readOnly = true)
    public Page<Member> findMembers(int page, int size) {
        Page<Member> findAllMember = memberRepository.findAll(
                PageRequest.of(page,size, Sort.by("memberId").descending())
        );
        return findAllMember;
    }
    /**
     * 회원 수정 (카카오 회원은 비밀번호 수정 불가능)
     */
    public Member updateMember(Member member) {

        Member preMember = findVerifiedMemberByMemberId(member.getMemberId()); //멤버 조회

        if(member.getPassword() == null){ //닉네임만 수정
            Optional.ofNullable(member.getNickName())
                    .ifPresent(nickName -> preMember.setNickName(nickName));
        }else{ //비밀번호도 같이 수정
            Optional.ofNullable(member.getNickName())
                    .ifPresent(nickName -> preMember.setNickName(nickName));

            String password = encryptPassword(member.getPassword()); //새 비밀번호 암호화
            preMember.setPassword(password); //암호화된 비밀번호 설정
        }

        Optional.ofNullable(member.getBirth())
                .ifPresent(birth -> preMember.setBirth(birth));

        return memberRepository.save(preMember);
    }

    /**
     * 회원 삭제 (마이페이지를 통해서 삭제하기 때문에 별도 인증 과정 X)
     */
    public Member deleteMember(Long memberId) {
        Member member = findVerifiedMemberByMemberId(memberId);
        member.setStatus(Status.DELETE);

        return memberRepository.save(member);
    }

    /**
     * 로그인 회원 정보 조회
     */
    @Transactional(readOnly = true)
    public Member getLoginMember(HttpServletRequest httpServletRequest){
        String authorizationHeader = httpServletRequest.getHeader("Authorization");
        String accessToken = authorizationHeader.split(" ")[1]; // Bearer askdhqwdkjwqbdkjwqbdkjqwbdkjwqb

        Claims tokenClaims = tokenManager.getTokenClaims(accessToken);
        Long memberId = Long.valueOf( (Integer) tokenClaims.get("memberId"));
        return findVerifiedMemberByMemberId(memberId);
    }

    /**
     * 로그인한 회원 Role 조회(USER OR counselor)
     * */
    @Transactional(readOnly = true)
        public Role getLoginRole(HttpServletRequest httpServletRequest) {
            String authorizationHeader = httpServletRequest.getHeader("Authorization");
            String accessToken = authorizationHeader.split(" ")[1];

            Claims tokenClaims = tokenManager.getTokenClaims(accessToken);
        String role = (String) tokenClaims.get("role");
        if(role.equals("USER")) {
            return (Role) Enum.valueOf(Role.class, role);
        }else if (role.equals("COUNSELOR")){
            return (Role) Enum.valueOf(Role.class, role);
        }else{
            return (Role) Enum.valueOf(Role.class, "ADMIN");
        }
    }


    /**
     * 회원 중복 확인(있으면 예외)
     */
    private void validateDuplicateMember(Member member) {
        Optional<Member> optionalMember = memberRepository.findByEmail(member.getEmail());
        if(optionalMember.isPresent()) {
            throw new BusinessException(ErrorCode.ALREADY_REGISTERED_MEMBER);
            //동일 이메일 있는 경우 에러 처리
        }
    }

    /**
     * 회원 존재 확인(없으면 예외)
     */
    @Transactional(readOnly = true)
    public Member findVerifiedMemberByMemberId(Long memberId) {
        return memberRepository.findById(memberId)
                .orElseThrow(() -> new EntityNotFoundException(ErrorCode.MEMBER_NOT_EXISTS));
    }

    @Transactional(readOnly = true)
    public Optional<Member> findMemberByEmail(String email) {
        return memberRepository.findByEmail(email);
    }

    @Transactional(readOnly = true)
    public Member findMemberByRefreshToken(String refreshToken) {
        Member member = memberRepository.findByRefreshToken(refreshToken)
                .orElseThrow(() -> new AuthenticationException(ErrorCode.REFRESH_TOKEN_NOT_FOUND));
        LocalDateTime tokenExpirationTime = member.getTokenExpirationTime();
        if(tokenExpirationTime.isBefore(LocalDateTime.now())) {
            throw new AuthenticationException(ErrorCode.REFRESH_TOKEN_EXPIRED); //refresh 토큰이 만료됐을 경우
        }
        return member;
    }

    /**
     * 비밀번호 암호화
     */
    public String encryptPassword(String password){
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        encryptor.setPoolSize(4);
        encryptor.setPassword(jasyptConfig.getPassword());
        encryptor.setAlgorithm("PBEWithMD5AndTripleDES");

        return encryptor.encrypt(password);
    }

    /**
     * 비밀번호 복호화
     */
    public String decryptPassword(String password){
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        encryptor.setPoolSize(4);
        encryptor.setPassword(jasyptConfig.getPassword());
        encryptor.setAlgorithm("PBEWithMD5AndTripleDES");

        return encryptor.decrypt(password);
    }
}

 

Controller

@RestController
@RequestMapping("/api/members")
@Validated
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    private final MemberMapper memberMapper;

    private final OauthValidator oauthValidator;

    //회원가입
    @PostMapping("/new")
    public ResponseEntity postMember(@Valid @RequestBody MemberDto.Post memberPostDto){
        if(!memberPostDto.getPassword().equals(memberPostDto.getConfirmPassword())){ //비밀번호와 비밀번호 확인이 같지 않으면
            throw new AuthenticationException(ErrorCode.PASSWORD_MISMATCH); //에러 발생
        }
        Member member = memberService.createMember(memberMapper.memberPostDtoToMember(memberPostDto));
        MemberDto.Response response = memberMapper.memberToMemberResponse(member);

        return new ResponseEntity<>(
                new SingleResponseDto<>(response), HttpStatus.OK
        );
    }

    //로그인
    @PostMapping("/login")
    public ResponseEntity loginMember(@Valid @RequestBody MemberDto.Login memberLoginDto){
        oauthValidator.validateMemberType(memberLoginDto.getMemberType());
        MemberDto.LoginResponse jwtTokenResponseDto = memberService.login(memberMapper.memberLoginDtoToMember(memberLoginDto));

        return new ResponseEntity<>(
                new SingleResponseDto<>(jwtTokenResponseDto), HttpStatus.OK
        );
    }

    //회원 조회(회원정보 수정 페이지)
//    @GetMapping("/look-up/{memberId}")
//    public ResponseEntity getMember(@PathVariable("memberId") @Positive Long memberId){
//        Member member = memberService.findVerifiedMemberByMemberId(memberId);
//        MemberDto.MyPageResponse response = memberMapper.memberToMyPageResoponse(member);
//
//        return new ResponseEntity<>(
//                new SingleResponseDto<>(response), HttpStatus.OK
//        );
//    }

    //회원 조회(회원정보 수정 페이지) 수정본
    @GetMapping("/look-up")
    public ResponseEntity getMember(HttpServletRequest httpServletRequest){
        Member member = memberService.getLoginMember(httpServletRequest);
        MemberDto.MyPageResponse response = memberMapper.memberToMyPageResoponse(member);

        return new ResponseEntity<>(
                new SingleResponseDto<>(response), HttpStatus.OK
        );
    }

////    getLoginMemberId 정상 확인 테스트
//    @GetMapping("/info")
//    public void getMember(HttpServletRequest httpServletRequest){
////        Long Id = memberService.getLoginMemberId(httpServletRequest);
//        System.out.printf("======================================"+ Id + "===========================================");
//    }


    /**
     * 회원 전체 조회
     */
    @GetMapping("/total-look-up")
    public ResponseEntity getMembers(@Positive @RequestParam("page") int page,
                                     @Positive @RequestParam("size") int size){
        Page<Member> pageMembers = memberService.findMembers(page-1, size);
        List<Member> members = pageMembers.getContent();

        return new ResponseEntity<>(new MultiResponseDto<>(
                memberMapper.membersToMemberMyPageResponses(members), pageMembers)
        ,HttpStatus.OK);
    }

    //회원 수정
    @PatchMapping("/edit/{memberId}")
    public ResponseEntity updateMember(@PathVariable("memberId") @Positive Long memberId,
                                       @Valid @RequestBody MemberDto.Patch memberPatchDto){
        memberPatchDto.updateMemberId(memberId);

        Member preMember = memberService.findVerifiedMemberByMemberId(memberId); //멤버 조회

        if(memberPatchDto.getPassword() != null) {
            if (!memberPatchDto.getNewPassword().equals(memberPatchDto.getConfirmNewPassword())) {
                throw new EntityNotFoundException(ErrorCode.PASSWORD_MISMATCH); //새 비밀번호와 비밀번호 확인이 같지 않을 경우 예외 처리
            }

            String password = memberService.decryptPassword(preMember.getPassword()); //기존 비밀번호 복호화

            if (!password.equals(memberPatchDto.getPassword())) {
                    throw new AuthenticationException(ErrorCode.WRONG_PASSWROD); //기존 비밀번호와 현재 비밀번호가 일치 하지 않으면 예외처리
            }
        }


        Member member = memberService.updateMember(memberMapper.memberPatchDtoToMember(memberPatchDto));
        MemberDto.MyPageResponse response = memberMapper.memberToMyPageResoponse(member);

        return new ResponseEntity<>(
                new SingleResponseDto<>(response), HttpStatus.OK
        );
    }

    //회원 삭제
    @PatchMapping("/delete/{memberId}")
    public ResponseEntity deleteMember(@PathVariable("memberId") @Positive Long memberId){
        memberService.deleteMember(memberId);

        return new ResponseEntity<>(HttpStatus.OK);
    }
}
profile

우주먼지

@o귤o

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그