1. 프로젝트 개요

Oauth2가 어떻게 적용하는지만 확인을 위한 것이므로 간략하게 만들겠습니다. 밑에 조건만 만들겠습니다.

  • User가 로그인 전에 접속할 수 있는 URL
  • User가 로그인 후에만 접속할 수 있는 URL 
  • User가 로그인 했지만 ROLE= GUEST일 때는 접근 못하는 URL

 

1. User Entity

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long id;

    @Column(name = "user_email")
    private String email;

    @Column(name = "user_name")
    private String name;

    @Column(name = "user_picture_source")
    private String picture;

    @Enumerated(EnumType.STRING)
    @Column(name = "user_role")
    private Role role;

    @Builder
    public user(String email, String name, String picture, Role role){
        this.email=email;
        this.name=name;
        this.picture=picture;
        this.role=role;
    }

    public User Update(String name, String picture){
        this.name=name;
        this.picture=picture;
        return this;
    }

    public String getRoleKey(){
        return role.getKey();
    }
}

2. Role Enum

@Getter
@RequiredArgsConstructor
public enum Role {

    GUEST("ROLE_GUEST", "손님"),
    USER("ROLE_USER", "사용자");

    private final String key;
    private final String title;

}

3. UserRepository

public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByEmail(String email);
}

4. OauthAttributes 

@Getter
public class OauthAttributes {

    public Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;
    private String picture;

    @Builder
    public OauthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email
            ,String picture){
        this.attributes=attributes;
        this.nameAttributeKey=nameAttributeKey;
        this.name=name;
        this.email=email;
        this.picture=picture;
    }

    public static OauthAttributes of(String registrationId, String userNameAttributeName, Map<String,Object> attributes){
        return ofGoogle(userNameAttributeName, attributes);
    }
    
    private static OauthAttributes ofGoogle(String userNameAttributeName, Map<String,Object> attributes){
        
        return OauthAttributes.builder()
                .name((String) attributes.get("name"))
                .email((String)attributes.get("email"))
                .nameAttributeKey(userNameAttributeName)
                .attributes(attributes)
                .picture((String) attributes.get("picture"))
                .build();
    }
    
    public User toEntity(){
        return User.builder()
                .email(email)
                .name(name)
                .picture(picture)
                .role(Role.GUEST)
                .build();
    }


}
  • of에서는 GoogleOauth, Kakao, Facebook 등등 어떤 API Server를 써주는지 로직을 따로 두지만 저희는 Google만 쓰므로 바로 ofGoogle로 넘겨줍니다.
  • ofGoogle에서는 넘어온 attributes을 기반으로 OuathAttributes를 만들어줍니다.
  • toEntity는 OauthAttributes에 있는 값들로 User Entity를 생성합니다.

5. SessionUser 

@Getter
@NoArgsConstructor
public class SessionUser implements Serializable {

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

    public SessionUser(String name, String email, String picture) {
        this.name = name;
        this.email = email;
        this.picture = picture;
    }
}
  • User Entity로 Session을 넣으려 한다면 직렬화 오류가 뜹니다. 그렇다고 User를 직렬화 하는 것은 옳은 방법은 아닙니다. Entity는 DB에 저장되는 객체이며 여러 가지 관계가 포함되는데 이것이 성능 이슈로 이어질 수 있습니다. 그래서 SessionUser를 만들어서 활용합니다.

6. CustomOauth2UserService

@RequiredArgsConstructor
@Service
@Transactional
public class CustomOauth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
                .getUserNameAttributeName();

        OauthAttributes attributes = OauthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());

        User user = saveOrUpdate(attributes);

        httpSession.setAttribute("loginUser", new SessionUser(user.getName(),user.getEmail(),user.getPicture()));

        return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
                attributes.getAttributes(), attributes.getNameAttributeKey());
    }

    private User saveOrUpdate(OauthAttributes attributes){
        return userRepository.findByEmail(attributes.getEmail())
                .map(e -> e.update(e.getName(), e.getPicture()))
                .orElse(attributes.toEntity());
    }
}

 

  • registrationId: 로그인을 진행 중인 서비스를 구분하는 코드입니다. 
  • userNameAttributeName: Oauth2 로그인 진행 시 키가 되는 필드 값입니다. primary key라고 생각하면 됩니다. google의 기본 코드는 "sub"입니다.
  • HttpSession을 이용해서 Session을 만들어줍니다.

 

지금까지 Entity, Dto, Repository, Service를 만들었습니다. 다음은 이것들을 활용할 Configuration을 만들어보겠습니다. 감사합니다.

 

모든 코드는 아래 링크에서 확인 가능합니다.

https://github.com/rlaehdals/Oauth2Example

 

GitHub - rlaehdals/Oauth2Example

Contribute to rlaehdals/Oauth2Example development by creating an account on GitHub.

github.com

 

 

참고


http://www.yes24.com/Product/Goods/83849117

 

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - YES24

가장 빠르고 쉽게 웹 서비스의 모든 과정을 경험한다. 경험이 실력이 되는 순간!이 책은 제목 그대로 스프링 부트와 AWS로 웹 서비스를 구현한다. JPA와 JUnit 테스트, 그레이들, 머스테치, 스프링

www.yes24.com