티스토리 뷰

https://while-programming.tistory.com/150에서 만들었던 권한서버를 이용하는 클라이언트 서버를 만드는 과정은 비교적 간단하다

 

대략적인 OAuth의 흐름은 아래와 같다

 

위의 flow를 보면 클라이언트 서버에 필요한 기능은

  1. user의 로그인 요청을 받을 수 있어야 한다
  2. user의 로그인 요청에 대해 권한 서버로 요청을 할 수 있게 해야 한다
  3. 권한서버에 user가 로그인을 완료하면 권한서버에게 토큰을 요청해야 한다
  4. 전달받은 토큰으로부터 사용자 정보를 가져와야 한다

 

 

이제 클라이언트 서버에 필요한 기능이 정리됐으니 구현을 시작해 보자

 

1. 일단 스프링으로 서버를 시작할 것이기 때문에 스프링 서버 의존성을 추가한다

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
}

 

 

2. 위의 기능 명세의 1번과 2번을 합쳐 user가 로그인 요청을 하면 권한서버로 이동하게끔 API를 개발한다

@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {

    @GetMapping(value = "/oauth2/login")
    public ResponseEntity<Void> oauthLogin(@RequestParam String state) {
        return ResponseEntity
            .status(HttpStatus.FOUND)
            .header(HttpHeaders.LOCATION, "http://localhost:8084/oauth2/authorize?response_type=code&client_id=user-service&redirect_uri=http://localhost:8081/users/login&scope=profile&state=" + state)
            .build();
    }
}
  • 권한서버로 이동할 수 있도록 응답코드를 302로 설정하고 Location 헤더에 권한서버의 주소를 입력한다
    • 권한 서버 주소를 보면 여러 요청 파라미터가 있다
      • response_type=code
        • 권한서버에서 승인코드(AUTHORIZATION_CODE) 유형으로 인증을 진행하겠다고 등록했었다
        • 따라서 권한서버부터 코드를 받아와야 한다
      • client_id=user-service
        • 권한서버에 등록했던 client_id를 요청 파라미터에 추가한다
      • redirect_uri=http://localhost:8081/users/login
        • 권한서버에 로그인이 성공했을 시 돌아갈 url을 등록했었다. 그 값을 넣어주면 된다
      • scope=profile%20openid
        • 권한서버에 등록했던 스코프를 전달한다
        • 여러 값을 전달할 경우 urlEncoding한 값을 넣으면 된다
      • state
        • 필수값은 아니고 user가 처음에 접속한 UI 화면으로 돌아가기 위해 사용된다
        • 즉, 서버의 url이 아니라 Web UI(Front)의 Url을 받아서 나중에 사용할 예정이다

 

 

이제 권한서버에서 로그인이 성공한 후 돌아올 API를 개발하면 되는데 그전에 기능 3번인 권한서버에 토큰을 요청하는 기능부터 개발해 보겠다

3. 클라이언트서버에서 권한서버를 호출할 수 있어야 한다

@FeignClient(name = "authFeignClient", url = "http://localhost:8084")
public interface AuthClient {
    @PostMapping(value = "/oauth2/token", consumes = "application/x-www-form-urlencoded")
    LoginResponseDto login(@RequestHeader("Authorization") String authorization, @RequestBody Map<String, ?> form);
}
  • 권한서버를 호출하는 데는 간단하게 FeignClient를 사용하겠다
 

RFC 6749: The OAuth 2.0 Authorization Framework

The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowi

datatracker.ietf.org

 

 

4. 이제 위의 공식문서에 나와있는 대로 request body값을 넣어 호출하는 코드를 작성한다

public LoginResponseDto login(String code) {
    return authClient.login(
        getBasicAuth(),
        Map.of("grant_type", "authorization_code",
            "code", code,
            "redirect_uri", "http://localhost:8081/users/login"
        ));
}

private String getBasicAuth() {
    return "Basic " + Base64.getEncoder()
        .encodeToString((
                "user-service"
                    + ":"
                    + "secret"
            ).getBytes()
        );
}

@Builder
public record LoginResponseDto(
    String access_token,
    String refresh_token,
    String scope,
    String token_type,
    String expires_in
) {
}
  • grant_type, code, redirect_uri를 입력했고 클라이언트 인증은 basic 인증을 사용하기로 되어있으므로 Authorization 헤더에 넣어줬다

 

5. 이제 리소스 서버를 호출해서 사용자 정보를 얻어오는 API를 개발하면 된다. 하지만 약간 다르게 권한서버가 발급해 준 토큰으로 정보를 얻어오는 것이 아닌 클라이언트 서버에서 토큰자체를 이용해서 사용자 인증을 진행하려고 한다

즉, 클라이언트서버를 리소스서버로 운영하려고 한다

따라서 권한서버에서 토큰을 받은 뒤 리소스서버로 사용자 정보를 요청하는 부분을 생략하고 바로 로그인 완료 처리를 한다

@GetMapping(value = "/login")
public ResponseEntity<Void> login(@RequestParam String code, @RequestParam String state) {
    LoginResponseDto loginInfo = login(code);

    return ResponseEntity
        .status(HttpStatus.FOUND)
        .header(HttpHeaders.LOCATION, state)
        .header(HttpHeaders.SET_COOKIE, ResponseCookie.from("accessToken", loginInfo.access_token())
            .httpOnly(true)
            .sameSite("Lax")
            .path("/")
            .maxAge(60)
            .domain("localhost")
            .build().toString())
        .header(HttpHeaders.SET_COOKIE, ResponseCookie.from("refreshToken", loginInfo.refresh_token())
            .httpOnly(true)
            .sameSite("Lax")
            .path("/")
            .maxAge(1800)
            .domain("localhost")
            .build().toString())
        .build();
}
  • 토큰은 쿠키에 저장해서 처음 user가 요청한 곳으로 리다이렉트 하도록 302 응답을 한다

 

이렇게 해서 권한서버를 이용해 로그인처리를 하는 클라이언트 서버 구현을 완료했다

 

클라이언트서버가 리소스서버처럼 토큰 인증처리를 하는 부분은 다음에 이어서 알아보도록 하자

 


클라이언트서버 구현 공식문서

https://docs.spring.io/spring-security/reference/servlet/oauth2/login/core.html

 

Core Configuration :: Spring Security

CommonOAuth2Provider pre-defines a set of default client properties for a number of well known providers: Google, GitHub, Facebook, X, and Okta. For example, the authorization-uri, token-uri, and user-info-uri do not change often for a provider. Therefore,

docs.spring.io

클라이언트서버 구현 sample(yaml설정)

https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/oauth2/login

 

spring-security-samples/servlet/spring-boot/java/oauth2/login at main · spring-projects/spring-security-samples

Contribute to spring-projects/spring-security-samples development by creating an account on GitHub.

github.com

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30