본문 바로가기
카테고리 없음

Member 부분 구현해보자-3 (security,h2,mapstruct, verify,get,update Member)

by 티코딩 2024. 2. 27.

지난번 Spring Security 6.x 버전부터 기존에 사용하던 WebSecurityConfigurerAdapter 가 deprecated 되어 새로운방법을 물색해봤다.

그래서 Security Config에

@Bean
    public SecurityFilterChain filterChain(
            HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)  //csrf 비활성화
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))  //cors설정 적용
                .authorizeHttpRequests(authorize -> authorize   //HTTP 요청에 대한 인증 및 권한을 정의
                        .requestMatchers("/api/v1/members/**").permitAll()
                        .requestMatchers("/resources/**").permitAll()
                        .anyRequest().authenticated()
                )
                .build();
    }

이렇게 넣어줬다.

일단은 members/** 이렇게 했지만, 추후에 JWT 설정을 넣어주고 수정할거다.

이렇게 넣어주고, 다시 테스트를 해보면

우리가 설정했던 HTML파일에 randomKey가 담겨서 발송된다.

이제 Verify api를 구현해보자.

먼저 서비스 로직부터 구현해보자.

ㅇ service - verifyRandomKey

public void verifyRandomKey(String randomKey, String email){
        EmailConfirmRandomKey emailConfirmRandomKey = emailConfirmRandomKeyRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalStateException("유효하지 않은 이메일입니다."));
        String newRandomKey = emailConfirmRandomKey.getRandomKey();

        if(!randomKey.equals(newRandomKey)){
            throw new IllegalArgumentException("인증코드가 유효하지 않습니다.");
        }
        Optional<Member> existingMember = memberRepository.findByEmail(email);
        if(existingMember.isPresent()){
            Member member = existingMember.get();
            member.setEmailVerifiedYn(true);
            memberRepository.save(member);
        }
        emailConfirmRandomKeyRepository.deleteById(email);
    }

 

파라미터로 randomKey, email을 받아서 먼저 EmailConfirmRandomKey db에서 email로 randomkey를 찾아 넣어준다. email로 찾아지지 않는다면 email이 유효하지않은것이므로 예외처리해준다.

그다음, newRandomKey에다가 db에서 찾은 randomKey를 넣어주고, 파라미터로 받은 randomKey와 newRandomKey가 일치하는지를 판단한다. 다르다면 예외. 같다면 멤버의 EmailVerifiedYn을 true로 바꿔주고 저장해준다. 그리고 이제 필요없어진 emailConfirmRandomKey는 db에서 지워준다.

그리고 이제 controller메서드를 완성시켜준다.

ㅇ Controller - verify

@PostMapping("/verify")
    public ResponseEntity<?> verify(@RequestBody MemberDto.Verify requestBody) {
        try{
            memberService.verifyRandomKey(requestBody.getRandomKey(), requestBody.getEmail());
            return ResponseEntity.ok("이메일 인증이 완료되었습니다.");
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("인증 처리중 오류가 발생했습니다.");
        }
    }

 

이제 테스트를 진행해보면,

200 OK. 정말 잘 되었는지 한번 데이터베이스를 보자.

TRUE로 잘 바뀌어있는걸 볼 수 있다.

 

참고로 Spring Security설정이 바뀌어서 h2 콘솔에 들어가는게 굉장히 힘들었다.

securityConfig에서 설정을 추가해줬다.

ㅇ securityConfig - h2 enabled

@Bean
    public SecurityFilterChain filterChain(
            HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)//csrf 비활성화
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))  //cors설정 적용
                .authorizeHttpRequests(authorize -> authorize   //HTTP 요청에 대한 인증 및 권한을 정의
                        .requestMatchers("/h2-console/**").permitAll()
                        .requestMatchers("/api/v1/members/**").permitAll()
                        .requestMatchers("/resources/**").permitAll()
                        .anyRequest().authenticated()
                )
                .build();
    }
    
	@ConditionalOnProperty(name = "spring.h2.console.enabled",havingValue = "true")
    public WebSecurityCustomizer configureH2ConsoleEnable() {
        return web -> web.ignoring()
                .requestMatchers(PathRequest.toH2Console());
    }

requestMatchers("/h2-console/**").permitAll()

만 해주면 될줄알았는데 안됐다. 가장 편한방법으로 밑에 configureH2ConsoleEnable() 을 넣어줘서 해결했다.

 

이제 다음으로 회원정보를 불러오는 getMember를 구현해보자.

ㅇ Service - findMember

public Member findMember(Long memberId) {
        Optional<Member> member = memberRepository.findById(memberId);

        return member.orElseThrow(() -> new BusinessLogicException(ExceptionCode.USER_NOT_FOUND));
    }

ㅇ MemberMapper 에 아래 코드 추가

 MemberDto.MemberResponse memberToMemberDtoMemberResponse(Member member);

 

그러면 Mapstruct가 아래와같이 만들어줌.

@Override
    public MemberDto.MemberResponse memberToMemberDtoMemberResponse(Member member) {
        if ( member == null ) {
            return null;
        }

        MemberDto.MemberResponse.MemberResponseBuilder memberResponse = MemberDto.MemberResponse.builder();

        if ( member.getMemberId() != null ) {
            memberResponse.memberId( member.getMemberId().intValue() );
        }
        memberResponse.email( member.getEmail() );
        memberResponse.roleType( member.getRoleType() );
        memberResponse.address( member.getAddress() );
        memberResponse.modifiedAt( member.getModifiedAt() );
        memberResponse.createdAt( member.getCreatedAt() );

        return memberResponse.build();
    }

 

ㅇ Controller - getMember

@GetMapping("/{member-id}")
    public ResponseEntity<?> getMember(@PathVariable("member-id") Long memberId) {
        Member member = memberService.findMember(memberId);
        MemberDto.MemberResponse response = memberMapper.memberToMemberDtoMemberResponse(member);
        return ResponseEntity.ok().body(ApiResponse.ok("data",response));
    }

 

ㅇ 테스트

음 계획대로 잘 나온다.

 

다음으로는 회원정보 수정api를 해보겠다. 지금은 작은 미니 프로젝트이므로 수정할 필드가 address밖에 없지만, 일단 해보겠다.

mapper mapstruct를 사용하는데 내맘처럼 생성을 안해줘서, 일단은 미뤄두고 getMember과 updateMember메서드에서 mapper를 안쓰기로 했다.

그래서 수정한 코드를 보자.

ㅇ Controller - get, update

/**
     * 회원정보 불러오기
     * param = memberId
     * @return memberId, email, roleType, address, modifiedAt, createdAt
     *
     */
    @GetMapping("/{member-id}")
    public ResponseEntity<?> getMember(@PathVariable("member-id") Long memberId) {
        Member member = memberService.findMember(memberId);
        MemberDto.Response response = createMemberDtoResponse(member);
        //MemberDto.Response response = memberMapper.memberToMemberDtoResponse(member);
        return ResponseEntity.ok().body(ApiResponse.ok("data",response));
    }

    /**
     * 비밀번호,이메일 제외 회원정보 수정
     * param = address
     * @return = memberId, email, roleType, address, modifiedAt, createdAt
     */
    @PatchMapping("/{member-id}")
    public ResponseEntity<?> updateMember(@PathVariable("member-id") Long memberId, @RequestBody String address) {
        memberService.updateMember(memberId, address);
        Member member = memberService.findMember(memberId);
        //MemberDto.Response response = memberMapper.memberToMemberDtoResponse(member);
        MemberDto.Response response = createMemberDtoResponse(member);
        return ResponseEntity.ok().body(ApiResponse.ok("data",response));
    }

 

updateMember에서 원래 Dto를 사용할려했는데 address밖에 바꿀필드가 없으니 파라미터로 그냥 바로 address를 넣어줬다.

service로직을 보자.

ㅇ Service - updateMember

public void updateMember(Long memberId, String newAddress) {
        Member existMember = memberRepository.findById(memberId)
                .orElseThrow(()-> new EntityNotFoundException("사용차를 찾을 수 없습니다."));
        existMember.setAddress(newAddress);
        memberRepository.save(existMember);
    }

 

이제 테스트해보자.

 

아... address를 문자열로 그대로 받았기 때문에 저렇게 나온다.

객체로 받아야 한다.

그래서 다시 mapstruct를 사용하기로 하고, 원래대로 되돌렸다.

/**
     * 회원정보 불러오기
     * param = memberId
     * @return memberId, email, roleType, address, modifiedAt, createdAt
     *
     */
    @GetMapping("/{member-id}")
    public ResponseEntity<?> getMember(@PathVariable("member-id") Long memberId) {
        Member member = memberService.findMember(memberId);
        MemberDto.Response response = memberMapper.memberToMemberDtoResponse(member);
        return ResponseEntity.ok().body(ApiResponse.ok("data",response));
    }

    /**
     * 비밀번호,이메일 제외 회원정보 수정
     * param = address
     * @return = memberId, email, roleType, address, modifiedAt, createdAt
     */
    @PatchMapping("/{member-id}")
    public ResponseEntity<?> updateMember(@PathVariable("member-id") Long memberId, @RequestBody MemberDto.Update requestBody) {
        memberService.updateMember(memberId, requestBody.getAddress());
        Member member = memberService.findMember(memberId);
        MemberDto.Response response = memberMapper.memberToMemberDtoResponse(member);
        return ResponseEntity.ok().body(ApiResponse.ok("data",response));
    }

 

그리고 lombok-mapstruct-binding 의존성을 추가했다.

annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'

이걸 추가해주고나니, mapstruct가 정신을 차렸다.

추가안했을땐, mapperImpl이 개판이었다.

ㅇ MemberMapperImpl

@Component
public class MemberMapperImpl implements MemberMapper {

    @Override
    public Member memberDtoSignupToMember(MemberDto.Signup requestBody) {
        if ( requestBody == null ) {
            return null;
        }

        Member.MemberBuilder member = Member.builder();

        return member.build();
    }

    @Override
    public MemberDto.Response memberToMemberDtoResponse(Member member) {
        if ( member == null ) {
            return null;
        }

        MemberDto.Response response = new MemberDto.Response();

        return response;
    }
}

이렇게 생성했는데, 이제는

@Component
public class MemberMapperImpl implements MemberMapper {

    @Override
    public Member memberDtoSignupToMember(MemberDto.Signup requestBody) {
        if ( requestBody == null ) {
            return null;
        }

        Member.MemberBuilder member = Member.builder();

        member.password( requestBody.getPassword() );
        member.email( requestBody.getEmail() );
        member.address( requestBody.getAddress() );

        return member.build();
    }

    @Override
    public MemberDto.Response memberToMemberDtoResponse(Member member) {
        if ( member == null ) {
            return null;
        }

        MemberDto.Response.ResponseBuilder response = MemberDto.Response.builder();

        response.memberId( member.getMemberId() );
        response.email( member.getEmail() );
        response.roleType( member.getRoleType() );
        response.address( member.getAddress() );
        response.modifiedAt( member.getModifiedAt() );
        response.createdAt( member.getCreatedAt() );

        return response.build();
    }
}

이렇게 예쁘게 잘 만들어준다. 진짜 이 한줄때문에 거의 1.5일은 열받아있었다.

**lombok과 mapstruct를 함께 사용할시에 꼭 추가해줘야 한다고 한다.**

그렇게 다시 테스트해본결과 잘 나오는걸 확인 할 수 있다.

휴~

 

그다음으로 회원삭제 로직 deleteMember를 구현해보자.