[Spring Boot] Spring Security - OAuth2 (Google login)

2023. 7. 20. 00:53·개발/Spring Boot
728x90
반응형

Spring Security - Oauth 2.0

모든 소스는 깃허브에 있습니다.

https://github.com/TeTedo/blog-code/tree/main/spring-security-oauth

Google login

1. 구글 클라우드 플랫폼 주소으로 이동해서 프로젝트 만들기

google cloud

2. 완성된 프로젝트 생성 후 API 및 서비스 클릭

oauth-practice

3. OAuth 클라이언트 ID 만들기

oauth

4. OAuth는 리다이렉션 URI를 설정하여 로그인 성공시 보여줄 화면을 지정할 수 있다.

oauth-redirect

5. application-oatuh.yml 파일 생성

spring:
    security:
        oauth2:
            client:
                registration:
                    google:
                        client-id: ${GOOGLE_CLIENT_ID}
                        client-secret: ${GOOGLE_CLIENT_SECRET}
                        scope: profile, email

scope를 설정하지 않으면 기본으로 openid, profile, email이 등록된다.

이렇게 되면 openid Provider로 인식하여 추후 네이버, 카카오 로그인시 나눠서 Service를 만들어야 한다.

하나의 Service로 구현하기 위해 openid를 빼고 등록했다.

client-id와 client-scret은 env파일로 숨겨서 보관하자.

6. User Entity 만들기

// domain.user.model.vo.Role
@RequiredArgsConstructor
@Getter
public enum Role {
    GUEST("ROLE_GUEST", "게스트"),
    USER("ROLE_USER", "유저");

    private final String type;
    private final String role;
}

// domain.user.model.User
@Entity
@NoArgsConstructor
@Getter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role;

    @Builder
    public User(Long id, String name, String email, Role role) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.role = role;
    }

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

유저 엔티티를 만들고 Role이라는 enum을 생성했다.

7. UserRepository 생성

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

이메일을 통해 가입이 되어있는지 안되어있는지 확인하기 위해 findByEmail을 미리 만들어 준다.

8. OAuthAttributes Class 생성

@Getter
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;

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

    public static OAuthAttributes of(String registrationId, String userNameAttributeName,
                                     Map<String, Object> attributes) {
        // 어떤 플랫폼에서 로그인하는지 체크한다.
        if("naver".equals(registrationId)) {
            return ofNaver("id", 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"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    private static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String,Object> response = (Map<String, Object>) attributes.get("response");
        return OAuthAttributes.builder()
                .name((String) response.get("name"))
                .email((String) response.get("email"))
                .attributes(response)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

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

}

이 클래스는 유저가 로그인 시도를 할때 어떤 플랫폼에서 로그인 시도를 하는지 체크하고 기존 유저아이디의 정보를 받아오는 클래스이다.

9. SessionUser Class 생성

@Getter
public class SessionUser implements Serializable {
    private String name;
    private String email;

    public SessionUser(User user) {
        name = user.getName();
        email = user.getEmail();
    }
}

유저의 정보를 세션에 담기 위해 따로 클래스를 생성한다.

User Entity를 세션에 담을 수도 있지만 나중에 자식 엔티티를 갖게 될경우 너무 많은 정보를 담게 된다면 성능에 문제가 생길 수 도 있다.

필요한 정보만 세션에 담기 위해 클래스를 만들어 준다.

10. CustomOAuth2UserService

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();

        OAuth2User oAuth2User = delegate.loadUser(userRequest);
        String registrationId = userRequest.getClientRegistration()
                .getRegistrationId();
        String userNameAttributeName = userRequest.getClientRegistration()
                .getProviderDetails()
                .getUserInfoEndpoint()
                .getUserNameAttributeName();

        OAuthAttributes oAuthAttributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());

        User user = saveOrUpdate(oAuthAttributes);

        httpSession.setAttribute("user", new SessionUser(user));

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

    private User saveOrUpdate(OAuthAttributes oAuthAttributes) {
        // 중복 체크
        User user = userRepository.findByEmail(oAuthAttributes.getEmail())
                .map(entity -> entity.update(oAuthAttributes.getName()))
                .orElse(oAuthAttributes.toEntity());

        return userRepository.save(user);
    }
}

유저가 로그인 요청시 위에서 만들었던 OAuthAttributes 클래스를 이용하여 OAuth2User 를 만든다.

이때 User Entity에서 설정했던 Role을 부여하여 가지고 있는다.

11. SecurityConfig 생성

@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
                .and().authorizeHttpRequests()
                .requestMatchers("/","/css/**","/images/**","/js/**","/h2-console/**").permitAll()
                .requestMatchers("/api/v1/**").hasRole(Role.USER.name())
                .anyRequest().authenticated()
                .and().logout().logoutSuccessUrl("/")
                .and().oauth2Login()
                .userInfoEndpoint()
                .userService(customOAuth2UserService);

        return http.build();

    }
}

지금 spring security가 7.0을 준비한다고 속성들이 deprecated 되었다고 하는데 무시하고 실행했더니 잘된다.

spring security 공식문서 참고

참고

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - 이동욱 지음 p163~190 책에 더 자세한 내용이 나와있습니다.

728x90
반응형

'개발 > Spring Boot' 카테고리의 다른 글

[Spring Boot] JsonDeserialize 활용해서 요청 Body 커스텀하기  (0) 2024.09.03
[Spring Boot] Spring Boot WebClient  (0) 2023.08.04
[Spring Boot] Spring-REST-Docs로 자동으로 API 문서화  (0) 2023.07.17
[Spring Boot] 오버로딩 언제 할 수 있을까  (0) 2023.06.11
[Spring Boot] Mockito란?  (1) 2023.05.31
'개발/Spring Boot' 카테고리의 다른 글
  • [Spring Boot] JsonDeserialize 활용해서 요청 Body 커스텀하기
  • [Spring Boot] Spring Boot WebClient
  • [Spring Boot] Spring-REST-Docs로 자동으로 API 문서화
  • [Spring Boot] 오버로딩 언제 할 수 있을까
TeTedo.
TeTedo.
  • TeTedo.
    TeTedo 개발 일기
    TeTedo.
  • 전체
    오늘
    어제
    • 분류 전체보기 (319)
      • 개발 (274)
        • Article (4)
        • 정리 (21)
        • Spring Boot (17)
        • JPA (2)
        • JAVA (6)
        • Database (4)
        • 자료구조 (11)
        • 알고리즘 (32)
        • React (20)
        • Docker (10)
        • node.js (18)
        • Devops (11)
        • Linux (4)
        • TypeScript (3)
        • Go (10)
        • HyperLedger (4)
        • BlockChain (43)
        • html, css, js (48)
        • CS (3)
        • AWS (3)
      • 모아두고 나중에 쓰기 (3)
      • 팀프로젝트 (18)
        • SNS(키보드워리어) (9)
        • close_sea (9)
      • 개인프로젝트 (1)
        • Around Flavor (1)
        • CHAM (13)
        • ethFruitShop (5)
      • 독서 (0)
        • 스프링부트와 AWS로 혼자 구현하는 웹 서비스 (0)
  • 블로그 메뉴

    • 홈
    • 개발일기
    • CS
    • 실습
    • 코딩테스트
    • 웹
    • Go
    • node.js
    • 팀플
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    도커
    go
    node
    ERC721
    node.js
    프로그래머스
    nodejs
    CSS
    30일 챌린지
    go언어
    mysql
    컨테이너
    js
    하이퍼레저
    React
    erc20
    html
    30일챌린지
    블록체인
    명령어
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
TeTedo.
[Spring Boot] Spring Security - OAuth2 (Google login)
상단으로

티스토리툴바