본문 바로가기
Spring/JPA + Security

[Security + JPA + OAuth2] - 페이스북 로그인 - 4

by pyogowoon 2023. 1. 17.
@RequiredArgsConstructor
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService {


    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        System.out.println("OAuth2 서비스 ");
        OAuth2User oauth2User = super.loadUser(userRequest);
        System.out.println(oauth2User.getAttributes());


        User user = User.builder()
                .username()
                .password()
                .email()
                .name()
                .build();
        User userEntity = userRepository.save();

        return null;
    }
}

 이전 시간에 이어서 이제 받아온 정보를 내 DB에 넣고 저장을 해야할텐데,

 본인의 경우 Model에 @Builder를 선언하였고 DTO 에서 빌드해서 가져가는 방식을 사용중이다.

 

 

Model 인 User

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {

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

    @Column(unique = true, length = 100)
    private String username;

    @Column(nullable = false)
    private String password;

    private String name;
    private String website;  //웹사이트
    private String bio; // 자기소개

    @Column(nullable = false)
    private String email;
    private String phone;
    private String gender;

    private String profileImageUrl;
    private String role;

    private LocalDateTime createDate;
    }

모델은 이렇게 생겼고 

 

 

DTO

@Data
public class SignupDto {

    @Size(min = 1 , max = 20)
    @NotBlank
    private String username;

    @NotBlank
    private String password;

    @NotBlank
    private String email;

    @NotBlank
    private String name;

    public User toEntity(){
        return User.builder()
                .username(username)
                .password(password)
                .email(email)
                .name(name)
                .build();
    }
}

 

DTO에 username , password , email , name 를 NotBlank 로 걸고 build 하도록 구현해놨기 때문에

DTO의 4가지 요소는 필수적으로 받아야 한다.

 

 

@RequiredArgsConstructor
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService {


    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        System.out.println("OAuth2 서비스 ");
        OAuth2User oauth2User = super.loadUser(userRequest);
        System.out.println(oauth2User.getAttributes());


        Map<String , Object> userInfo = oauth2User.getAttributes();
        String name =(String)userInfo.get("name");
        String email = (String)userInfo.get("email");
        String username = "facebook_"+(String)userInfo.get("id");
        String password = new BCryptPasswordEncoder().encode(UUID.randomUUID().toString());

        User user = User.builder()
                .username(username)
                .password(password)
                .email(email)
                .name(name)
                .build();
        User userEntity = userRepository.save(user);

        return null;
    }
}

 

그렇기 때문에

 

1. userRequest로 받아온 정보들을 getAttribute

 

2. userInfo 에 담는다, 이때 Map을 사용했는데 이유는  oauth2User.getAttributes()가 map값을 리턴하기때문

 

3. Map을 꺼내기위해 .get 메소드 사용하는데 이때 리턴이 Object 이므로 String으로 다운캐스팅 해서 담아줌

 

4. 빌더에 넣는다.

 

이때 username 에는 따로 "facebook_" 을 넣은건 식별하기 위해서 넣은것이고

password는 암호화 해서 랜덤값을 넣어줬다 (시큐리티는 암호화가 필수기 때문에.)

 

 password 를 저렇게 UUID 해서 Random 값으로 넣은 이유는

 

소셜로그인은 패스워드로 로그인할게 아니기 때문에 넣어줘도 된다.

 

로직은 얼추 완성되었지만 한가지 문제가 남았다

 매번 페이스북 로그인 할때마다 이 정보들을 insert 할 순 없다. 

즉 소셜 로그인을 한번이상 했다면 회원으로 남겨야 하기때문에,

 

@RequiredArgsConstructor
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService {


    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        OAuth2User oauth2User = super.loadUser(userRequest);

        Map<String, Object> userInfo = oauth2User.getAttributes();
        String name = (String) userInfo.get("name");
        String email = (String) userInfo.get("email");
        String username = "facebook_" + (String) userInfo.get("id");
        String password = new BCryptPasswordEncoder().encode(UUID.randomUUID().toString());


        User userEntity = userRepository.findByUsername(username);

        if (userEntity == null) { //최초 로그인

            User user = User.builder()
                    .username(username)
                    .password(password)
                    .email(email)
                    .name(name)
                    .build();

            return userRepository.save(user);

        } else {  // 이미 가입되어있음
            return userEntity;
        }


    }
}

 

그래서 if문으로 각각의 분기를 짜준다..

 여기서 return 되는 값은 자동으로 세션에 저장해줍니다. 

하지만 문제가 하나 있는데

 

 

타입 문제가 뜬다. 요구타입이 OAuth2User 타입인데 User타입을 넣었기 때문

이 경우를 해결하기 위해선 단순하게 User -> OAuth2User 타입으로 해주면 되지않느냐?

그러면 과정도 복잡해질뿐더러 추후에 매우 헷갈립니다.

 

 

 그렇기 때문에 세션값을 원래 로그인과 동일하게 해줘야하는데,

 

 

 

 

스프링 시큐리티의 userDetails 를 구현한 곳 (본인의 경우 PrincipalDetails , @AuthenticationPrincipal 로 사용하는 클래스) 에 가서 OAuth2User도 implements 해준다.

 

implements 했으니 당연히 override 해주고, 해준다면 2가지의 메소드를 구현해야 한다.

 

@Override
public String getName() {
    return null;
}
@Override
public Map<String, Object> getAttributes() {
    return null;
}

 

이를 구현하면

 

@Data
public class PrincipalDetails implements UserDetails , OAuth2User {


    private User user;
    
    // 새로 추가
    private Map<String, Object> attributes;

    public PrincipalDetails(User user){
        this.user = user;
    }
    
    											.
                                                .
                                                .
                                                .
                                                .
                                                .
    // 새로 추가                                           
    @Override
    public String getName() {
        return (String)attributes.get("name");
    }
    
     // 새로 추가 
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }


 	// 새로 추가 
    public PrincipalDetails(User user,Map<String, Object> attributes){
        this.user = user;
    }
}

 

 

Override에 의한 추가된 메소드를 구현하기위해 상단에 

private Map<String, Object> attriubutes; 를 선언하여 2가지 메소드를 구현해주고

 

 PrincipalDetails Overloading 해준다. attributes 매개변수를 추가해서

로그인 로직시 페이스북 로그인의 경우는 오버로딩 된 principalDetails 를 골라서 사용하면 된다..

 

 

그러면 이제

 

@RequiredArgsConstructor
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService {


    private final UserRepository userRepository;


    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        OAuth2User oauth2User = super.loadUser(userRequest);

        Map<String, Object> userInfo = oauth2User.getAttributes();
        String name = (String) userInfo.get("name");
        String email = (String) userInfo.get("email");
        String username = "facebook_" + (String) userInfo.get("id");
        String password = new BCryptPasswordEncoder().encode(UUID.randomUUID().toString());


        User userEntity = userRepository.findByUsername(username);

        if (userEntity == null) { //최초 로그인

            User user = User.builder()
                    .username(username)
                    .password(password)
                    .email(email)
                    .name(name)
                    .build();

            return new PrincipalDetails(userRepository.save(user), oauth2User.getAttributes());

        } else {  // 이미 가입되어있음
            return new PrincipalDetails(userEntity,oauth2User.getAttributes());
        }


    }
}

if문의 return 문 부분을 return new를 통해서 구현한 오버로딩된 메소드를 사용하면 된다.

 

그 후 실행해보면 로그인이 잘 된다.

 

로그인 후 DB에도 잘 들어온 모습.

댓글