[8/11 TIL] SPRING SECURITY(OAuth2.0 ์ ์šฉ)

yumyeonghanยท2023๋…„ 8์›” 13์ผ
1

๐Ÿƒํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ๋ฐฑ์—”๋“œ ๋ฐ๋ธŒ์ฝ”์Šค 4๊ธฐ ๊ต์œก๊ณผ์ •์„ ๋“ฃ๊ณ  ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.๐Ÿƒ

OAuth

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ํ•˜๋‚˜์˜ ๊ณ„์ •์œผ๋กœ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค์— ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š๊ณ ๋„ ์„œ๋กœ ๋‹ค๋ฅธ ์„œ๋น„์Šค๋“ค ๊ฐ„์— ๋ฐ์ดํ„ฐ ๋ฐ ๊ธฐ๋Šฅ์„ ๊ณต์œ ํ•˜๊ณ  ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์คŒ

์ฃผ์š” ์šฉ์–ด 4๊ฐ€์ง€

  • Resource Owner: ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž์ด์ž, ๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž
  • Client: ๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์•ก์„ธ์Šคํ•˜๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ
    • ์นด์นด์˜ค, ๋„ค์ด๋ฒ„๋ฅผ ์ด์šฉํ•˜๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค ์ œ 3์˜ ์„œ๋น„์Šค
  • Resource Server: ๋ณดํ˜ธ๋ฐ›๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ํ˜ธ์ŠคํŒ…ํ•˜๊ณ  ์•ก์„ธ์Šค ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ์ˆ˜๋ฝํ•˜๊ณ  ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„
    • ์นด์นด์˜ค, ๋„ค์ด๋ฒ„ ๋“ฑ์˜ ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„
  • Authorization Server: ํด๋ผ์ด์–ธํŠธ ๋ฐ ๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์ธ์ฆํ•œ ํ›„ ์•ก์„ธ์Šค ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๋Š” ์„œ๋ฒ„
    • ์นด์นด์˜ค, ๋„ค์ด๋ฒ„ ๋“ฑ์˜ ์ธ์ฆ ์„œ๋ฒ„

Authorization Server์—๊ฒŒ ํ† ํฐ์„ ์š”์ฒญํ•˜๋Š” 4๊ฐ€์ง€ ๋ฐฉ๋ฒ•

Authorization Code Grant(๊ฐ€์žฅ ์ค‘์š”)

๊ทธ๋ฆผ ์ฐธ๊ณ 

  • Oauth2.0์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•˜๊ณ , ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์ด๋ฉฐ, ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ ์กด์žฌํ•˜๋Š” ์›น/๋ชจ๋ฐ”์ผ ์„œ๋น„์Šค์— ์ ํ•ฉํ•จ
  • ์‚ฌ์šฉ์ž ์ธ์ฆ ํ›„ Callback์„ ํ†ตํ•ด authorization ์ฝ”๋“œ๋ฅผ ๋ฐ›๊ณ , ์ด๋ฅผ client-id, client-secret๊ณผ ํ•จ๊ป˜ Access-Token์œผ๋กœ ๊ตํ™˜ํ•จ
  • Callback ์ฒ˜๋ฆฌ๋Š” ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์—์„œ ์ด๋ฃจ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์—, Access-Token์ด ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜์ง€ ์•Š์•„์„œ ๋ณด์•ˆ์ƒ ์•ˆ์ „

Implicit Grant

  • Authorization Code Grant ๋ฐฉ์‹๊ณผ ๋น„๊ตํ–ˆ์„ ๋•Œ, Authorization Response ๋‹จ๊ณ„์—์„œ Access-Token์ด ์ „๋‹ฌ๋˜๊ณ  Token Request ๋‹จ๊ณ„๊ฐ€ ์ƒ๋žต๋จ
  • Access-Token์ด URL์— ๋…ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณด์•ˆ์ƒ ๋ฆฌ์Šคํฌ๊ฐ€ ์žˆ์Œ
  • ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ ์—†๋Š” ์ œํ•œ์ ์ธ ํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•จ
    • ๋ธŒ๋ผ์šฐ์ €์—์„œย ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์™€ย ๊ฐ™์€ย ์Šคํฌ๋ฆฝํŠธย ์–ธ์–ด๋กœย ๋™์ž‘ํ•˜๋Š”ย ํด๋ผ์ด์–ธํŠธ

Client Credentials Grant

  • client_id, client_secret ํŒŒ๋ฆฌ๋ฏธํ„ฐ๋งŒ ๊ฐ€์ง€๊ณ  Access-Token์„ ๋ฐœ๊ธ‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž๋Š” ์ „ํ˜€ ๊ด€์—ฌํ•˜์ง€ ์•Š์Œ
  • ์‚ฌ์šฉ์ž์˜ ์ง์ ‘์ ์ธ ์ƒํ˜ธ ์ž‘์šฉ ์—†์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ์„œ๋ฒ„ ๊ฐ„ ์ƒํ˜ธ ์ž‘์šฉ์— ์‚ฌ์šฉ

Resource Owner Password Credentials Grant

  • Client Credentials Grant ๋ฐฉ์‹๊ณผ ๋งค์œ  ์œ ์‚ฌํ•˜์ง€๋งŒ, client_id, client_secret ๋Œ€์‹  ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์ธ์ฆ๋จ
  • ์ฆ‰, ์ผ๋ฐ˜ ๋กœ๊ทธ์ธ ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฆ
  • ํด๋ผ์ด์–ธํŠธ๋ฅผ ์™„์ „ํžˆ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์„ ๋•Œ ์‚ฌ์šฉ

Spring Security OAuth2.0 Client (์นด์นด์˜ค ์ธ์ฆ ์—ฐ๋™)

์นด์นด์˜ค Application ์ƒ์„ฑ

  1. ์นด์นด์˜ค ๊ฐœ๋ฐœ์ž ์‚ฌ์ดํŠธ์—์„œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ•˜๋‚˜ ๋“ฑ๋กํ•จ

    • ์š”์•ฝ ์ •๋ณด์˜ REST API ํ‚ค ๊ฐ’์„ OAuth2.0์—์„œ client_id ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋จ
  2. ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์„ค์ •์„ ํ™œ์„ฑํ™”ํ•˜๊ณ , Redirect URI ๋ถ€๋ถ„์— http://localhost:8080/login/oauth2/code/kakao ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅ

  3. ๋™์˜ ํ•ญ๋ชฉ ์„ค์ •์—์„œ profile_nickname, profile_image ํ•„๋“œ๋ฅผ ํ•„์ˆ˜ ๋™์˜๋กœ ์„ค์ •

    • ํ•ด๋‹น ๊ฐ’์€ scope ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋จ
  4. ๋ณด์•ˆ ์„ค์ •์—์„œ Client Secret์„ ํ™œ์„ฑํ™”ํ•˜๊ณ , ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑ

    • ํ•ด๋‹น ๊ฐ’์€ client_secret ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋จ

Spring Security OAuth2.0 ์˜์กด์„ฑ ์ถ”๊ฐ€ ๋ฐ ์„ค์ •

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
  • gradle ํŒŒ์ผ์— oauth2 ์˜์กด์„ฑ ์ถ”๊ฐ€

spring:
  security:
    oauth2:
      client:
        registration:
          kakao:
            client-name: kakao
            client-id: e77125b43bb728c04d2f3a40bc2db4a0
            client-secret: xyqgyjDW66KF0FAYxjGxZlJGVAg3Jwtz
            scope: profile_nickname, profile_image
            redirect-uri: "http://localhost:8080/login/oauth2/code/{registrationId}"
            authorization-grant-type: authorization_code
            client-authentication-method: client_secret_post
        provider:
          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize
            token-uri: https://kauth.kakao.com/oauth/token
            user-info-uri: https://kapi.kakao.com/v2/user/me
            user-name-attribute: id
  • redirect-uri: ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์„ค์ •์—์„œ ์ž…๋ ฅํ•œ Redirect URI ์ฃผ์†Œ์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์€ kakao ๋Œ€์‹ , {registrationId} ๋ณ€์ˆ˜๋กœ ์ฒ˜๋ฆฌ
  • authorization-uri: ์•ก์„ธ์Šค ํ† ํฐ ๋ฐœ๊ธ‰์„ ์œ„ํ•ด์„œ ์‚ฌ์ „์— 1ํšŒ์„ฑ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•œ api
  • token-uri: authorization-uri๋ฅผ ํ†ตํ•ด์„œ ๋ฐ›์€ 1ํšŒ์„ฑ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด ์—‘์„ธ์Šค ํ† ํฐ์„ ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ ์œ„ํ•œ api
  • user-info-uri: ์•ก์„ธ์Šค ํ† ํฐ์„ ์ด์šฉํ•ด์„œ ์นด์นด์˜ค์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ api
  • user-name-attribute: user-info-uri๋ฅผ ํ†ตํ•ด์„œ ์นด์นด์˜ค ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™”์„๋•Œ ์‚ฌ์šฉ์ž์˜ ๊ณ ์œ  ์‹๋ณ„ํ‚ค๋ฅผ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•œ ์‹๋ณ„ํ‚ค ํ•„๋“œ๋ช…
  • ์นด์นด์˜ค OAuth ๊ณต์‹ ๋ฌธ์„œ

public class OAuth2AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    //ํ•„๋“œ ์ƒ๋žต
    
	public OAuth2AuthenticationSuccessHandler(Jwt jwt, UserService userService) {
        this.jwt = jwt;
        this.userService = userService;
    }

    @Override
  	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
      if (authentication instanceof OAuth2AuthenticationToken) {
        OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
        OAuth2User principal = oauth2Token.getPrincipal();
        String registrationId = oauth2Token.getAuthorizedClientRegistrationId();

        User user = processUserOAuth2UserJoin(principal, registrationId);
        String loginSuccessJson = generateLoginSuccessJson(user);
        response.setContentType("application/json;charset=UTF-8");
        response.setContentLength(loginSuccessJson.getBytes(StandardCharsets.UTF_8).length);
        response.getWriter().write(loginSuccessJson);
      } else {
        super.onAuthenticationSuccess(request, response, authentication);
        }
 	}
  
  	//private ๋ฉ”์„œ๋“œ ์ƒ๋žต
}
  • ์นด์นด์˜ค ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ํ›„์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•  AuthenticationSuccessHandler ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด ์ถ”๊ฐ€
  • ์นด์นด์˜ค ์ธ์ฆ์ด ์™„๋ฃŒ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์‹ ๊ทœ ์‚ฌ์šฉ์ž๋ผ๋ฉด ์‚ฌ์šฉ์ž๋ฅผ ๊ฐ€์ž… ์‹œํ‚ด
  • ์„œ๋น„์Šค ์ ‘๊ทผ์„ ์œ„ํ•œ JWT ํ† ํฐ ์ƒ์„ฑ ๋ฐ ์‘๋‹ต
    • ์œ„์˜ ์ฝ”๋“œ์—์„œ๋Š” ๋‹จ์ˆœํžˆ JSON ํฌ๋งท์œผ๋กœ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜์ง€๋งŒ, ์•ฑ ์—ฐ๋™์„ ์œ„ํ•ด ์•ฑ ์ „์šฉ ์Šคํ‚ด์„ ์„ค๊ณ„ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Œ

@Service
public class UserService {

  // ... ์ƒ๋žต ...

  @Transactional
  public User join(OAuth2User oauth2User, String provider) {
    checkArgument(oauth2User != null, "oauth2User must be provided.");
    checkArgument(isNotEmpty(provider), "authorizedClientRegistrationId must be provided.");

    String providerId = oauth2User.getName();
    return findByProviderAndProviderId(provider, providerId)
      .map(user -> {
        log.warn("Already exists: {} for (provider: {}, providerId: {})", user, provider, providerId);
        return user;
      })
      .orElseGet(() -> {
        Map<String, Object> attributes = oauth2User.getAttributes();
        @SuppressWarnings("unchecked")
        Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");
        checkArgument(properties != null, "OAuth2User properties is empty");

        String nickname = (String) properties.get("nickname");
        String profileImage = (String) properties.get("profile_image");
        Group group = groupRepository.findByName("USER_GROUP")
          .orElseThrow(() -> new IllegalStateException("Could not found group for USER_GROUP"));
        return userRepository.save(
          new User(nickname, provider, providerId, profileImage, group)
        );
      });
  }
}
  • OAuth 2.0์„ ํ†ตํ•œ ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ๊ฐ€์ž…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋น„์Šค
  • ์ด๋ฏธ ๊ฐ€์ž…๋œ ์‚ฌ์šฉ์ž์ผ ๊ฒฝ์šฐ
    • findByProviderAndProviderId ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ•ด๋‹น providerId์™€ authorizedClientRegistrationId๋กœ ์ด๋ฏธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ์‚ฌ์šฉ์ž๋ฅผ ๊ฒ€์ƒ‰
    • ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅ
  • ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž ๋“ฑ๋ก
    • ์ด๋ฏธ ๊ฐ€์ž…๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์„ ๊ฒฝ์šฐ, oauth2User ๊ฐ์ฒด์—์„œ ์‚ฌ์šฉ์ž ์†์„ฑ์„ ์ถ”์ถœํ•˜์—ฌ ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑ ๋ฐ ์ €์žฅ
    • ์‚ฌ์šฉ์ž์˜ ๋‹‰๋„ค์ž„, ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๋“ฑ์„ ์ถ”์ถœํ•˜์—ฌ ์ƒˆ๋กœ์šด User ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
    • groupRepository๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ทธ๋ฃน์„ ๊ฒ€์ƒ‰ํ•˜๊ณ , ํ•ด๋‹น ๊ทธ๋ฃน์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ• ๋‹น
    • userRepository๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ฅผ ์ €์žฅ

	@Bean
    public OAuth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler(Jwt jwt) {
        return new OAuth2AuthenticationSuccessHandler(jwt, userService);
    }
  • ์œ„์—์„œ ๋งŒ๋“  ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋ก
	@Bean
    public SecurityFilterChain filterChain(HttpSecurity http, Jwt jwt) throws Exception {
        return http
                .oauth2Login(auth -> auth
                        .successHandler(oauth2AuthenticationSuccessHandler(jwt)))
                .addFilterAfter(jwtAuthenticationFilter(jwt), SecurityContextHolderFilter.class)
                .build();
    }            
  • SecurityFilterChain์— OAuth2AuthenticationSuccessHandler๊ฐ€ ์„ค์ •๋œ oauth2Login์„ ๋“ฑ๋ก

Spring Security OAuth2.0 ๋™์ž‘

  • ์ง€๊ธˆ๊นŒ์ง€ ์„ค์ •ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋””๋ฒ„๊น…ํ•˜๋ฉด, filterChainProxy๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์ด๋ ‡๊ฒŒ 3๊ฐœ์˜ ํ•„ํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜๋Š”๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Œ

DefaultLoginPageGeneratingFilter

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ƒ์„ฑ ํ•„ํ„ฐ

  • ๋กœ๊ทธ์ธ ์ „๋žต์— ๋”ฐ๋ผ Form ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€, OAuth2.0 ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๋“ฑ์ด ์ƒ์„ฑ๋จ
  • /oauth2/authorization/kakao: ์นด์นด์˜ค OAuth ์ธ์ฆ ์š”์ฒญ ๋งํฌ
    • OAuth2AuthorizationRequestRedirectFilter์—์„œ ํ•ด๋‹น ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ฒŒ๋จ

OAuth2AuthorizationRequestRedirectFilter

์นด์นด์˜ค ์ธ์ฆ์„œ๋ฒ„๋กœ ์‚ฌ์šฉ์ž๋ฅผ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‹œํ‚ด

  • /oauth2/authorization/{registrationId} ํŒจํ„ด์˜ URL ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•จ (๊ธฐ๋ณธ๊ฐ’)
    • {registrationId} ๋ถ€๋ถ„์—๋Š” ์ธ์ฆ Provider ์‹๋ณ„ํ‚ค(kakako ๊ฐ™์€)๊ฐ€ ์ž…๋ ฅ๋จ
  • AuthorizationRequestRepository ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด์—๋Š” application.yml ํŒŒ์ผ์— ์„ค์ •ํ•œ OAuth ์—ฐ๋™ ์ •๋ณด๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ์Œ
  • ์ธ์ฆ Provider ์‹๋ณ„ํ‚ค๋กœ AuthorizationRequestRepository ์ธํ„ฐํŽ˜์ด์Šค์—์„œ OAuth ์—ฐ๋™ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ด
  • authorization-uri ์ฃผ์†Œ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‹œํ‚ด

OAuth2LoginAuthenticationFilter

Authorization Response๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ , Token Request๋ฅผ ์ธ์ฆ ์„œ๋ฒ„๋กœ ์š”์ฒญ

  • OAuth2LoginAuthenticationToken
    • OAuth2.0 ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋Š” Authentication ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด
  • OAuth2LoginAuthenticationProvider
    • OAuth2LoginAuthenticationToken ํƒ€์ž… ์ธ์ฆ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” AuthenticationProvider ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด
    • Authorization Server์—์„œ Access-Token ๋ฐ Refresh-Token์„ ๊ฐ€์ ธ์˜ด
    • ๋ฐœ๊ธ‰ ๋ฐ›์€ Access-Token ์„ ์ด์šฉํ•ด, ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ด์˜ด: OAuth2User ๊ฐ์ฒด๋กœ ํ‘œํ˜„๋จ

์ถ”๊ฐ€์ ์ธ ๊ฐœ์„ 

๋™์ž‘ ๊ณผ์ •

  1. OAuth2AuthorizationRequestRedirectFilter
    • authorizationRequestRepository๋ฅผ ํ†ตํ•ด authorizationRequest ์ €์žฅ
  2. OAuth2LoginAuthenticationFilter
    • authorizationRequestRepository๋ฅผ ํ†ตํ•ด authorizationRequest ์กฐํšŒ
    • authorizationRequest ์กฐํšŒ๊ฐ€ ์•ˆ๋˜๋ฉด ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

authorizationRequestRepository ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด๊ฐ€ HttpSessionOAuth2AuthorizationRequestRepository ํด๋ž˜์Šค๋กœ Session์„ ์‚ฌ์šฉํ•˜์ง€๋งŒ, API ์„œ๋ฒ„๋Š” Session์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— HttpCookieOAuth2AuthorizationRequestRepository ๊ตฌํ˜„์„ ์ถ”๊ฐ€ํ•˜์—ฌ, Session ๋Œ€์‹  Cookie์„ ์‚ฌ์šฉํ•˜๋„๋กํ•จ

public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {

    private static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "OAUTH2_AUTHORIZATION_REQUEST";

    private final String cookieName;

    private final int cookieExpireSeconds;

    public HttpCookieOAuth2AuthorizationRequestRepository() {
        this(OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, 180);
    }

    public HttpCookieOAuth2AuthorizationRequestRepository(String cookieName, int cookieExpireSeconds) {
        this.cookieName = cookieName;
        this.cookieExpireSeconds = cookieExpireSeconds;
    }

    @Override
    public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
        return getCookie(request)
                .map(this::getOAuth2AuthorizationRequest)
                .orElse(null);
    }

    @Override
    public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
        if (authorizationRequest == null) {
            getCookie(request).ifPresent(cookie -> clear(cookie, response));
        } else {
            String value = Base64.getUrlEncoder().encodeToString(SerializationUtils.serialize(authorizationRequest));
            Cookie cookie = new Cookie(cookieName, value);
            cookie.setPath("/");
            cookie.setHttpOnly(true);
            cookie.setMaxAge(cookieExpireSeconds);
            response.addCookie(cookie);
        }
    }

    @Override
    public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) {
        return getCookie(request)
                .map(cookie -> {
                    OAuth2AuthorizationRequest oauth2Request = getOAuth2AuthorizationRequest(cookie);
                    clear(cookie, response);
                    return oauth2Request;
                })
                .orElse(null);
    }

    private Optional<jakarta.servlet.http.Cookie> getCookie(HttpServletRequest request) {
        return ofNullable(WebUtils.getCookie(request, cookieName));
    }

    private void clear(Cookie cookie, HttpServletResponse response) {
        cookie.setValue("");
        cookie.setPath("/");
        cookie.setMaxAge(0);
        response.addCookie(cookie);
    }

    private OAuth2AuthorizationRequest getOAuth2AuthorizationRequest(Cookie cookie) {
        return SerializationUtils.deserialize(
                Base64.getUrlDecoder().decode(cookie.getValue())
        );
    }

}

OAuth2LoginAuthenticationFilter ๊ตฌํ˜„์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์—์„œ OAuth2AuthorizedClient (OAuth2.0์ธ์ฆ ์™„๋ฃŒ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด) ์ €์žฅ

  • authorizedClientRepository ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด๊ฐ€ AuthenticatedPrincipalOAuth2AuthorizedClientRepository ํด๋ž˜์Šค์ด๋ฉฐ ๋‚ด๋ถ€์ ์œผ๋กœ InMemoryOAuth2AuthorizedClientService ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด OAuth2AuthorizedClient ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•จ
    • ๋”ฐ๋ผ์„œ, OAuth2.0 ์œผ๋กœ ์ธ์ฆ๋˜๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋งŽ์•„์ง€๋ฉด OOME ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ
    • ๋˜ํ•œ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ํŠน์ • ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์ €์žฅ๋˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŠน์ • ์„œ๋ฒ„ ์žฅ์•  ๋ฐœ์ƒ ์‹œ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ
  • ๋‹คํ–‰ํžˆ InMemoryOAuth2AuthorizedClientService ํด๋ž˜์Šค๋Š” OAuth2AuthorizedClientService ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด์ด๋ฉฐ, InMemoryOAuth2AuthorizedClientService ๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋Š” JdbcOAuth2AuthorizedClientService ํด๋ž˜์Šค๊ฐ€ ์žˆ์Œ

์ตœ์ข… Configuration ์„ค์ •

์„ค์ • ๋ณ€๊ฒฝ ๋ถ€๋ถ„

  • HttpCookieOAuth2AuthorizationRequestRepository -> HttpSessionOAuth2AuthorizationRequestRepository Bean์„ ๋Œ€์ฒด
  • JdbcOAuth2AuthorizedClientService -> InMemoryOAuth2AuthorizedClientService Bean์„ ๋Œ€์ฒด
  • AuthenticatedPrincipalOAuth2AuthorizedClientRepository -> JdbcOAuth2AuthorizedClientService ์˜์กด์„ฑ ์ฃผ์ž…

์ฝ”๋“œ

	@Bean
    public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
        return new HttpCookieOAuth2AuthorizationRequestRepository();
    }

    @Bean
    public OAuth2AuthorizedClientService authorizedClientService(JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository) {
        return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);
    }

    @Bean
    public OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository(OAuth2AuthorizedClientService authorizedClientService) {
        return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, Jwt jwt, OAuth2AuthorizedClientService authorizedClientService) throws Exception {
        return http
                //์ƒ๋žต
                .oauth2Login(auth -> auth
                        .successHandler(oauth2AuthenticationSuccessHandler(jwt))
                        .authorizationEndpoint(authorizationEndpointConfig -> authorizationEndpointConfig.authorizationRequestRepository(authorizationRequestRepository()))
                        .authorizedClientRepository(oAuth2AuthorizedClientRepository(authorizedClientService)))
                .addFilterAfter(jwtAuthenticationFilter(jwt), SecurityContextHolderFilter.class)
                .build();
    }
profile
์›น ๊ฐœ๋ฐœ์— ๊ด€์‹ฌ ์žˆ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€