본문 바로가기
Spring

Spring Boot - Oauth2 AuthorizationServer(authorization_code) 구축하기 (React 연동)

by 오늘부터개발시작 2022. 8. 5.

오늘은 스프링 부트에서 Oauth2 Authorization Server를 구축하는 방법에 대해서 알아보도록 하겠다. 

직접 만들어보지 않으면 도무지 어떻게 돌아가는지도 모르겠고 슬쩍보면 너무 복잡해보이는 Oauth2인데 초 간단하게 사용하는 방법에 대해서 알아보도록하겠다. 이제 Spring에서는 Oauth2를 공식적으로 지원하지 않기 때문에 deprecate 경고가 뜨는데 여전히 잘작동하고 있고 다른 것을 원한다면 keycloack 같은 라이브러리를 이용하면 된다.

 

인증하는 방법(Grant Type)이 4가지가 있는데 나는 2가지를 사용해봤다. password와 authorization_code인데 오늘은 authorization_code 방법을 사용하는 법을 알아보도록 하겠다. authorization_code가 작동하는 순서는 다음과 같다.

 

출처 - https://developer.ebay.com/api-docs/static/oauth-authorization-code-grant.html

 

인증 과정

1. 유저가 사이트에 접속

2. 로그인이 안되어 있기 때문에 로그인 페이지로 이동

3.아이디 패스워드를 입력해서 로그인에 성공하면 지정해둔 redirect_url로 authorization_code와 함께 이동

4. authorization_code를 사용해서 실제 access_token 요청

5. 받은 access_token을 사용해서 사이트 사용 

 

그렇다면 먼저 간단하게 우리가 작업할  순서를 정리해보겠다. 

 

작업 순서

1. 프로젝트 생성 

2. pom.xml에 디펜던시 추가

3. AuthorizataionServer config 설정

4. SecurityConfiguration 설정 

5. callback을 처리할 컨트롤러 생성

6. 테스트 

 

폴더 구조

1. 프로젝트 생성

나는 프로젝트 생성을 intellij에서 spring initializer를 사용했다. 포함한 라이브러리들은 아래 사진과 같다. + h2 DB

 

2. pom.xml에 디펜던시 추가

위에서 선택한 것들에 추가로 jwt와 oauth2 autoconfigure를 추가해준다.

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.6.7</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

3. AuthorizataionServer config 설정

우리는 Authorization Server를 구축하고 있기 때문에 먼저 AuthorizationServer Config를 설정해보도록하겠다. 아무나 이 서버에 인증을 받아서는 안되기 때문에 Oauth2 인증을 사용할 수 있는 클라이언트를 설정하는 부분이다. 나중에 authorization_code를 요청하는 부분에서 사용될 정보이다. 클라이언트 정보는 DB로도 관리할 수 있는데 여기서는 inMemory로 사용하도록 하겠다. 아래 코드를 설명하자면 client 이름, secret, redirect url, grant type, scope 그리고 access_token의 만료시간을 설정할 수 있다.

 

client와 secret은 추후에 authorization_code를 요청할 때 쓰이고

redirect_url은 authorization_code를 받아서 처리할 때 쓰인다.

 

import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("testClientId")
                .secret("$2a$10$.u2vx.E0To/EKPosPiOoEekGkt/5sO0zsRP/T.Vlk.ueX57wBbzQ6")
                .redirectUris("http://localhost:8081/oauth2/callback")
                .authorizedGrantTypes("authorization_code")
                .scopes("read", "write")
                .accessTokenValiditySeconds(5);
    }
}

 

4. SecurityConfiguration 설정 

다음으로는 스프링 시큐리티 관련 설정이다. 스프링 시큐리티를 쓰면 기본적으로 모든 경로에 대해서 인증을 요구한다. 그러면 로그인도 할 수 없게 되는데 이를 피하기 위해 인증을 통과할 수 있는 경로들을 설정하고 우리는 id, pw로 로그인할 것이기 때문에 스프링에서 제공하는 formLogin을 사용해보도록하겠다. 로그인 화면은 커스터마이징이 가능한데 .loginPage()메소드를 뒤에 붙이면 내가 만든 페이지로 이동할 수 있게 할 수 있다. 이 부분은 나중에 다른 포스팅에서 다뤄보도록하겠다. 

 

아래 코드에서 첫번째 configure는 가상의 유저를 만들어 놓은 것인데, id: user, pw: pass 정보를 갖고 있는 유저이다. formLogin 화면에서 user, pass로 로그인하면 된다. 비밀번호는 BcryptEncoder로 암호화된 것인데 스프링 oauth2에서는 기본으로 Bcryt 암호화를 사용하고 있다.

 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.client.RestTemplate;


@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password("$2a$10$PWfKbpypwiBSDcdmW7wOp.KXCOsCMXAWrTxqm0VYRNQPrA0VV4rHC")
                .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity security) throws Exception {
        security
                .csrf().disable()
                .headers().frameOptions().disable()
                .and()
                .authorizeRequests().antMatchers("/oauth/**", "/oauth2/callback", "/h2-console/*").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

5. callback을 처리할 컨트롤러 생성

4단계인 상태에서 서버를 실행하고 아래 경로로 들어가면 로그인 화면이 뜨게된다. 

http://localhost:8080/oauth/authorize?client_id=testClientId&redirect_uri=http://localhost:8080/oauth2/callback&response_type=code&scope=read

거기서 로그인을 하면 redirect_url로 authorization_code와 함께 리다이렉트되는데 그걸 처리하는 컨트롤러이다. 여기서 처음에 설정했던 클라이언트와 클라이언트 비밀번호를 사용하게 된다. 누가 클라이언트고 누가 서버인지 조금 헷갈릴 수 있는데 아래 코드는 클라이언트 입장이라고 보면 된다. 클라이언트에서 클라이언트 정보를 사용해서 AuthorizationServer에 요청을 하는 것이다. 이렇게 요청을 하면 AccessToken을 받아볼 수 있다. 

import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.binary.Base64;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RequiredArgsConstructor
@RestController
@RequestMapping("/oauth2")
public class Oauth2Controller {

    private final RestTemplate restTemplate;


    @GetMapping(value = "/callback")
    public Object callbackSocial(@RequestParam String code) {

        String credentials = "testClientId:testSecret";
        String encodedCredentials = new String(Base64.encodeBase64(credentials.getBytes()));

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add("Authorization", "Basic " + encodedCredentials);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("code", code);
        params.add("grant_type", "authorization_code");
        params.add("redirect_uri", "http://localhost:8081/oauth2/callback");
        
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:8081/oauth/token", request, String.class);
        return response;
    }
}

6. 테스트 

아까와 마찬가지로 아래로 들어가게 되면 로그인 화면이 뜨고 인증을 완료하면 accessToken을 받아볼 수 있을 것이다.

 

http://localhost:8080/oauth/authorize?client_id=testClientId&redirect_uri=http://localhost:8080/oauth2/callback&response_type=code&scope=read