끄적끄적 코딩일지

[Spring] Spring Security + OAuth2 (2) - 코드 구현하기 본문

Spring

[Spring] Spring Security + OAuth2 (2) - 코드 구현하기

BaekGyuHyeon 2022. 6. 8. 17:03

이 글은 저번글에서 Google Console에 OAuth 를 사용하기 위한 사전 준비를 하던 글에서 이어진다.

2022.06.08 - [Spring] - [Spring] Spring Security + OAuth2 (1) - 프로젝트 설정하기

 

[Spring] Spring Security + OAuth2 (1) - 프로젝트 설정하기

OAuth란? Open Authorization의 약자로써 사용자 인증을 위한 개방형 프로토콜이다. 사용자들이 Id와 Password등을 제공하지 않고도 다른 웹사이트의 자신의 정보를 조회하여 로그인 하고자 하는 어플리

blablacoding.tistory.com

 

위 글에서 최종적으로 Client Id와 Client 보안 비밀번호를 발급받았을 것이다.

 

그 정보를 application.properties, application.yml등에 넣으면된다. 이 글에서는 application.properties를 기준으로 설명을 하겠다.


Step 1. 설정하기

 

spring.security.oauth2.client.registration.google.client-id=클라이언트 ID
spring.security.oauth2.client.registration.google.client-secret=클라이언트 보안 비밀번호
spring.security.oauth2.client.registration.google.scope=profile,email # 가저올 정보

naver, kakao등을 사용할때는 provider 설정을 해주어야 하나 google은 oauth2-client라이브러리에서 기본 설정값이 있어 클라이언트 id, 비밀번호만 설정해도 가능하다.


Step 2. Service 만들기

@RequiredArgsConstructor
@Service
public class OAuthLoginService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final MemberRepo repo;
    private final HttpSession httpSession;
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        // OAuth2UserService에서 request으로 user 정보를 얻어온다.
 
        OAuth2UserService delegate = new DefaultOAuth2UserService();
        OAuth2User user = delegate.loadUser(userRequest); // 사용자 요청을 OAuth server으로 보내고 redirection한 값을 받아온다.
        // 로그인 진행중인 서비스 구분( google인지 Kakao인지 Naver인지 구분하는 코드 )
        String registrationId = userRequest.getClientRegistration().getRegistrationId();

        // OAuth2 로그인 진행시 키가 되는 필드값
        // 네이버, 카카오등은 지원하지 않는다.
        // OAuth2User의 정보를 생성할때 Key값으로 쓰인다.
        String userNameAttributeName = userRequest.getClientRegistration()
                .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

        // 사용자 정보 값
        // Google 기준으로 범위에서 설정한 값이 제공된다.
        // System.out.println을 통해 사이트별 들어오는 정보를 확인할 수 있다.
        Map<String,Object> attrs = user.getAttributes();
        for(String key : attrs.keySet()){
            System.out.println(key+":"+attrs.get(key));
        }
        
        // Email으로 사용자 정보를 검색하여 없으면 만들고 있으면 수정후 업데이트한다.
        Member member = saveOrUpdate(attributes);
        
        // 인증 객체를 만들어서 return 한다.
        // Collections.emptyList()는 권한 목록이다.
        return new DefaultOAuth2User(
                Collections.emptyList(),
                attrs,
                userNameAttributeName
                );
    }
    // 사용자 이름과 프로필 사진 url 정보가 수정되면 바로 업데이트를 하기위한 logic
    private Member saveOrUpdate(Map<String,Object> attrs){
        Member m = repo.findByEmail(attrs.get("email").toString());
        if(m == null){
            m = new Member();
            m.setEmail(attrs.get("email").toString());
        }
        m.setName(attrs.get("name").toString());
        m.setPicture(attrs.get("picture").toString());
        return repo.save(m);
    }
}

Member이나 MemberRepo등은 기본적인 사용자 정보라서 따로 언급은 하지 않겠다.

하지만 위에서 user.getAttributes()에서 들어오는 값은

OAuth를 제공하는 서비스마다 다르므로 따로 구분하는 로직을 만들어야 한다.


Step 3. Spring Security 설정

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    private final OAuthLoginService loginService;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
                .and()
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/h2-console/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .logout() // 로그아웃 로직에서 쿠키 정보 삭제하도록 구현
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .deleteCookies("JSESSIONID").permitAll()
                .and()
                .oauth2Login() // oauth2 로그인 설정
                .userInfoEndpoint()
                .userService(loginService); // 헤당 서비스로 로그인이 진행되도록 설정

        return http.build();
    }


}

최근 Spring Security에서 Bean 등록만 가지고 설정이 가능하도록 수정되면서 WebSecurityConfiguraterAdapter가 deprecate 되었다. 

만약 Adapter을 사용한다면 HttpSecurity를 설정하는 부분에서 구현하면 된다.


Step 4. 사용하기

thymeleaf에서 사용

<!DOCTYPE html>
<html lang="en" xmlns:th=“https://www.thymeleaf.org” xmlns:sec="https://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="//unpkg.com/bootstrap@4/dist/css/bootstrap.min.css">
    <script src='//unpkg.com/jquery@3/dist/jquery.min.js'></script>
    <script src='//unpkg.com/bootstrap@4/dist/js/bootstrap.min.js'></script>
</head>
<body>
  <div class="container">
      <div class="row" sec:authorize="!isAuthenticated()" >
          <a role="button" href="/oauth2/authorization/google" class="btn btn-outline-primary">Google Login</a>
      </div>
      <div class="row" sec:authorize="isAuthenticated()">
          <a role="button" href="/logout"  class="btn btn-outline-danger">logout</a>
      </div>

      <div class="row" sec:authorize="isAuthenticated()">
          <b>Authenticated DTO:</b>
          <p th:text="${#authentication}"></p>
      </div>
      <div class="row" sec:authorize="isAuthenticated()">
          <b>Authenticated username:</b>
          <p sec:authentication="name"></p>
      </div>
  </div>
</body>
</html>

위에서 참고해야할것은 button에서 href를 /oauth2/authorization/google으로 설정하는것이다.

해당 url으로 요청이 오면 Oauth2-Client의 대한 기본설정으로 google oauth server으로 요청을 보내게 된다.

밑의 하단정보는 인증을 테스트하기위한 정보이다.

사용을 위해서는 thymeleaf와 thymeleaf-extras-springsecurity5를 사용해야 한다.

dependencies{
    ....
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
    ....
}

결과 확인

위처럼 로그인한 결과를 볼 수 있다.

 

contoller에서 사용

@Contoller
public class TestContoller{
    @GetMapping("/principal")
    public String getPrincipal(@AuthenticationPrincipal OAuth2User user){
        return user.toString();
    }
}