[From Hello To QueryDSL] Google/Naver Login (10/12)

By | 2020년 3월 21일
Table of Contents

Google/Naver Login

개발환경

  • Spring Boot 2.1.x
  • Gradle 4.10.2

build.gradle 수정

build.gradle

......
dependencies {
    // ......
    compile('org.springframework.boot:spring-boot-starter-oauth2-client')
    // ......
}
......

파일 수정 및 추가

src/main/java/kr/co/episode/example/config/oauth2/dto/OAuthAttributes.java

@Getter
public class OAuthAttributes {

    private 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) {
        //System.out.println(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"))
                .picture((String) attributes.get("picture"))
                .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"))
                .picture((String) response.get("profile_image"))
                .attributes(response)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

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

src/main/java/kr/co/episode/example/config/oauth2/CustomOAuth2UserService.java

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

    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
        OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);

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

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

        User user = saveOrUpdate(attributes);

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

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

    private User saveOrUpdate(OAuthAttributes attributes) {
        User user = userRepository.findByEmail(attributes.getEmail())
                .map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
                .orElse(attributes.toEntity());

        return userRepository.save(user);
    }
}

src/main/java/kr/co/episode/example/config/SecurityConfig.java

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
                .headers().frameOptions().disable()
                .and()
                    .authorizeRequests()
                    .antMatchers("/", "/css/**", "/images/**", "/js/**", "/oauth_login", "/profile").permitAll()
                    .antMatchers("/api/v1/**", "/posts/save").hasRole(Role.USER.name())
                    .anyRequest().authenticated()
                .and()
                    .logout()
                    .logoutSuccessUrl("/")
                .and()
                    .oauth2Login()
                    .loginPage("/oauth_login")
                    .userInfoEndpoint()
                    .userService(customOAuth2UserService);
    }
}

src/main/resources/application.properties

spring.security.oauth2.client.registration.google.client-id=
spring.security.oauth2.client.registration.google.client-secret=
spring.security.oauth2.client.registration.google.scope=profile,email

# registration
spring.security.oauth2.client.registration.naver.client-id=
spring.security.oauth2.client.registration.naver.client-secret=
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.client-name=Naver

# provider
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response

src/main/resources/templates/oauth_login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>게시판</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
    <style>
        .container p { display: inline }
    </style>
</head>
<body class="container">

<h3>로그인:</h3>

<div class="col-md-12">
    <div class="row">
        <div class="col-md-6">
            <a th:each="url : ${urls}" th:href="${url.link}" class="btn btn-success active" role="button" th:text="${url.client}"></a>
        </div>
    </div>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

<script src="/js/app/index.js"></script>

</body>
</html>

src/main/java/kr/co/episode/example/web/dto/LoginUrlDto.java

@Getter
public class LoginUrlDto {

    private String client;
    private String url;

    public String getLink() {
        return "/" + url;
    }

    public LoginUrlDto(String client, String url) {
        this.client = client;
        this.url = url;
    }
}

src/main/resources/templates/index.html

<h1>스프링 부트 게시판</h1>

<div class="col-md-12">
    <div class="row">
        <div class="col-md-6">
            <a href="/posts/save" role="button" class="btn" btn-primary>글 등록</a>
            <div th:if="${userName != null}" th:inline="text">
                [[${userName}]] 님, 안녕하세요.
                <a href="/logout" class="btn btn-info active" role="button">로그아웃</a>
            </div>
            <div th:if="${userName == null}">
                <a href="/oauth2/authorization/google" class="btn btn-success active" role="button">구글 로그인</a>
                <a href="/oauth2/authorization/naver" class="btn btn-success active" role="button">네이버 로그인</a>
            </div>
        </div>
    </div>
</div>

<div style="height: 80px;">

</div>

src/main/java/kr/co/episode/example/web/LoginController.java

@Controller
public class LoginController {

    private static String authorizationRequestBaseUri = "oauth2/authorization";
    List<LoginUrlDto> oauth2AuthenticationUrls;

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @GetMapping("/oauth_login")
    public String getLoginPage(Model model) {
        if (oauth2AuthenticationUrls == null) {
            oauth2AuthenticationUrls = new ArrayList();

            Iterable<ClientRegistration> clientRegistrations = null;
            ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
            if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
                clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;

                clientRegistrations.forEach(registration ->
                        oauth2AuthenticationUrls.add(new LoginUrlDto(registration.getClientName(), authorizationRequestBaseUri + "/" + registration.getRegistrationId()))
                );
            }
        }

        model.addAttribute("urls", oauth2AuthenticationUrls);

        return "oauth_login";
    }
}

답글 남기기