[웹 개발 프로젝트] 3. 소셜 로그인 유저 정보와 DB 연결

adorableco·2024년 1월 7일
0

이번에는 구글 소셜 로그인으로부터 제공받은 이름, 이메일, 프로필 이미지를 데이터베이스에 저장할 수 있도록 구현하였다. 이때 회원가입을 하지 않은 구글 계정의 경우, 성별, 나이대, 성별/나이대 공개 여부 필드를 더 추가해야하므로 회원가입 여부를 확인해서 로직을 두개로 나누었다.


현재까지 구현한 도메인 계층구조는 다음과 같다. 이번에 중점적으로 볼 것은 LoginController.java, User.java, MemberStatusResponse, SaveUserRequest.java, UserService.java 이다.


LoginController.java 의 requestUserInfo 메서드

기존에 단순히 유저 정보를 로드했던 방식에서, 회원 여부를 확인하고 비회원일 경우 회원 가입 절차를 거칠 수 있도록 구글 이메일을 통해 여부를 파악하는 방식으로 변경하였다.

@GetMapping("/login/oauth2/code/google")
    public ResponseEntity<MemberStatusResponse> requestUserInfo(@RequestParam(name="code") String code) throws Exception{
        final String accessToken =  socialOAuth.requestAccessToken(code);
        String userInfo = socialOAuth.getUserInfo(accessToken);
        try{
            JSONObject jsonObject = new JSONObject(userInfo);
            Optional<User> user = userService.findByEmail(jsonObject.getString("email"));

            if (!user.isPresent()) {
                String name = jsonObject.getString("name");
                String email = jsonObject.getString("email");
                String picture = jsonObject.getString("picture");

                return ResponseEntity.ok()
                        .body(new MemberStatusResponse(Role.GUEST.name(), name, email,picture,accessToken));
            }else{
                return ResponseEntity.ok()
                        .body(new MemberStatusResponse(Role.USER.name(),accessToken));
            }

        }catch (Exception e){
            return ResponseEntity.internalServerError()
                    .body(new MemberStatusResponse(Role.GUEST.name(),"false","false","false","false"));
        }
    }
  • user 변수에 getUserInfo 메서드를 통해 얻은 유저 정보 중 이메일을 통해 찾은 User 객체를 담는다. 회원이라면 이메일을 통해 발견된 객체가 담길 것이고 비회원이라면 일치하는 정보가 없으므로 null 이 담길 것이다.
  • user 가 null 이라면 (if (!user.isPresent()))
    - 역할(GUEST), 이름, 이메일, 프로필 이미지, accessToken 을 response에 담아 보낸다. ➡️ 유저 정보는 회원 가입에 사용될 것이다.
  • null 이 아니라면
    - 역할(USER), accessToken 을 response에 담아 보낸다.
    ✅ 이때 accessToken이 Authorization 권한을 제공해주는 역할 (jwtToken 역할)을 한다고 오인하고 다 반환을 했는데 아무래도 빼는 것이 맞겠다.



  • 여기서 무조건 수정을 해야하는 부분 이 있는데 바로 catch 문이다.
    - Exception으로 인해 esponse에 internalServerError()와 함께 바디를 담아야할 때는 정해진 dto 형식을 계속 지켜야 하는건가? 다른 방법이 떠오르지 않아 일단 모든 필드를 "false" 로 메꿔 놓았는데 아무래도 더 좋은 방안이 있을 것 같다.

그럼 반환 dto 형식이 어떻게 되어있는지 확인해보자

MemberStatusResponse.java


@AllArgsConstructor
@RequiredArgsConstructor
@Getter
public class MemberStatusResponse {

    @NonNull
    private String role;
    private String name;
    private String email;
    private String picture;


    @NonNull
    private String accessToken;

    public User toEntity() {
        return User.builder()
                .role(Role.GUEST)
                .name(name)
                .email(email)
                .accessToken(accessToken)
                .build();
    }
}

@RequiredArgsConstructor 은 클래스 객체 생성 시 생성자에 @NonNull 어노테이션이 붙어있는 변수만 전달 인자로 담길 수 있도록 한다. 따라서 이미 회원인 계정인 경우에는 @NonNullroleaccessToken 만 response에 담을 수 있게 돼고 비회원인 경우에는 @AllArgsConstructor 로 모든 필드를 response에 담아 보내게 되는 것이다.



이제 비회원인 구글 계정이 이 서비스의 회원이 될 수 있도록 회원가입 기능을 추가한다.

LoginController.java 의 signUp 메서드


    @PostMapping("/api/signup")
    public ResponseEntity signUp(@RequestBody SaveUserRequest request) {
        User save = userService.save(request);
        try {
            return ResponseEntity.status(HttpStatus.CREATED)
                    .body(save);
        }catch (Exception e){
            return ResponseEntity.internalServerError()
                    .body(save);
        }
    }
  • 간단하다! SaveUserRequest dto 형식대로 작성된 json을 request로 받으면 이것을 save 해준다.
    - save 메서드를 가지고 있는 UserService 클래스는 JpaRepository<User,Long> 을 상속한다. 이렇게 코드가 간단해질 수가..

LoginController.javarequestUserInfo 메서드에서 회원 여부 확인 시에 사용된 findByEmail 도 jpaRepository의 기능을 이용한 것이다. findBy + 엔티티의 필드명 으로 메서드 이름을 짜면 해당 엔티티의 필드로 검색한 쿼리 결과를 반환한다. 좋다..
- 참고로countBy + 엔티티의 필드명 은 쿼리 결과의 레코드 수를 반환한다.

SaveUserRequest.java

회원 가입 시에 request body에 담아야하는 dto이다.

@Getter
public class SaveUserRequest {

    private String name;
    private String email;
    private String picture;

    private char gender;
    private String age;
    private boolean genderVisible;
    private boolean ageVisible;


    public User toEntity() {
        return User.builder()
                .name(name)
                .email(email)
                .picture(picture)
                .gender(gender)
                .age(age)
                .genderVisible(genderVisible)
                .ageVisible(ageVisible)
                .build();
    }
}
  • name, email, picture, gender, age, genderVisible, ageVisible 필드가 있다. 이때 name, email, picture 는 구글 로그인 시 반환 받는 정보이다. 나머지는 추가적으로 이 서비스를 이용하기 위해 필요한 필드들이므로 받아준다.

데이터베이스에 잘 저장되었음을 확인할 수 있다.

✅ 계속 403 forbidden 에러가 난다면?

SecurityConfig 파일을 확인해본다.
  • 나의 경우 SecurityConfig 에서 http.csrf().disable() 를 사용하지 않아서 오류가 발생하였다. csrf() 는 사용자 간 위조 요청을 방지하는 기능인데

다음 할 일

  • 이제 회원 정보도 가질 수 있으니 해당 유저가 등록하는 모집글과 참조관계가 잘 보존되도록 모집글 등록 api를 수정하자.
  • 모집글 등록시 tag 엔티티와의 참조관계도 잘 보존되도록 수정하자.
  • 매치 참가 신청 구현까지...?
profile
👩🏻‍💻

0개의 댓글