티스토리 뷰

전체적인 과정은 공식문서를 참고해서 만들어 보았다

(https://docs.spring.io/spring-authorization-server/reference/1.3/getting-started.html)

 

 

1. 의존성을 추가한다

  • 전체 application.yaml 파일이 아닌 필수적인 부분만 작성
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation "org.springframework.boot:spring-boot-starter-oauth2-authorization-server"
}

 

 

2. OAuth2 엔드포인트 설정용 SecurityFilterChain 빈을 정의

@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
    throws Exception {
    
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
        .oidc(Customizer.withDefaults());

    http
        .exceptionHandling((exceptions) -> exceptions
            .defaultAuthenticationEntryPointFor(
                new LoginUrlAuthenticationEntryPoint("/login"),
                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
            )
        );

    return http.build();
}
  • Spring Security 권한서버는 몇 가지 엔드포인트를 자동으로 제공해 주는데(/oauth2/authorize, /oauth2/token 등) 이러한 기본 설정에 대한 security 기본 설정을 사용하기 위해 아래 코드를 작성한다. OIDC 기능 또한 기본 기능을 사용한다
    • OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
      http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
             .oidc(Customizer.withDefaults());
      • 아래 applyDefaultSecurity 코드를 보면 인증설정(autenticated()), csrf 설정 등을 하는 걸 볼 수 있다
public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
    RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
    http.securityMatcher(endpointsMatcher).authorizeHttpRequests((authorize) -> ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)authorize.anyRequest()).authenticated()).csrf((csrf) -> csrf.ignoringRequestMatchers(new RequestMatcher[]{endpointsMatcher})).apply(authorizationServerConfigurer);
}

 

  • HTML 요청일 경우에 /login 페이지로(/login 페이지도 Spring Security가 제공한다)로 보내기 위해 exceptionHandling을 추가한다

3. 인증에 대한 처리를 정의하기 위해 SecurityFilterChain을 정의한다

  - 위의 SecurityFilterChain 빈보다 나중에 호출되기 위해 @Order를 적용했다

@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
    throws Exception {
    
    http.csrf(AbstractHttpConfigurer::disable)
        .httpBasic(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated()
        )
        .formLogin(Customizer.withDefaults());

    return http.build();
}
  • 모든 요청에 대해 인증이 필요하도록 설정
    • .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
  • Spring Security의 FormLogin을 사용하도록 설정
    • .formLogin(Customizer.withDefaults());
  • csrf와 Basic 인증은 사용하지 않기 위해 비활성화
    • .csrf(AbstractHttpConfigurer::disable)
      .httpBasic(AbstractHttpConfigurer::disable)

 

4. User 정보를 조회하기 위해 UserDetailsService와 PasswordEncoder 빈 정의

@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
    final String usersQuery = "select email, password, true from user where email = ?";
    final String authsQuery = "select email, null from user where email = ?";

    var userDetailsManager = new JdbcUserDetailsManager(dataSource);

    userDetailsManager.setUsersByUsernameQuery(usersQuery);
    userDetailsManager.setAuthoritiesByUsernameQuery(authsQuery);

    return userDetailsManager;
}

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

 

5. 인증서버를 이용할 클라이언트 서버 정보를 등록한다

@Bean 
public RegisteredClientRepository registeredClientRepository() {
    RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("user-service")
            .clientSecret("secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .redirectUri("http://localhost:8081/users/login")
            .scope(OidcScopes.OPENID)
            .scope(OidcScopes.PROFILE)
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
            .build();

    return new InMemoryRegisteredClientRepository(oidcClient);
}
  • 편의를 위해 메모리에 클라이언트 정보를 등록
    • InMemoryRegisteredClientRepository(oidcClient);
  • 메모리에 클라이언트를 등록하므로 id는 UUID 사용
    • .withId(UUID.randomUUID().toString())
  • 클라이언트 id, secret을 등록
    • .clientId("user-service")
      .clientSecret(passwordEncoder().encode("secret"))
  • 권한서버에 클라이언트를 인증할 때 기본인증(basic)을 사용
    • .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
  • 토큰을 얻는 방식으로 승인코드(AUTHORIZATION_CODE)와 갱신토큰(REFRESH_TOKEN) 유형을 사용
    • .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
      .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
  • 인증 성공 후 리다이렉트 될 클라이언트 uri를 등록
    • .redirectUri("http://localhost:8081/users/login")
  • 허용 스코프를 지정
    • .scope(OidcScopes.OPENID)
      .scope(OidcScopes.PROFILE)
  • 공식문서에서 생략한 부분
    • .postLogoutRedirectUri("http://localhost:8080/")
      • 클라이언트 로그아웃을 권한서버까지 연동하지 않을 것이므로 생략했다
    • .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
      • 권한서버로부터 권한접근 허용여부를 확인하는 단계를 생략했다

 

6. JWT 인증에 사용할 빈 정의

@Bean
public JWKSource<SecurityContext> jwkSource() {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAKey rsaKey = new RSAKey.Builder(publicKey)
        .privateKey(privateKey)
        .keyID(UUID.randomUUID().toString())
        .build();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
}

private static KeyPair generateRsaKey() {
    KeyPair keyPair;
    try {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        keyPair = keyPairGenerator.generateKeyPair();
    } catch (Exception ex) {
        throw new IllegalStateException(ex);
    }
    return keyPair;
}

@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
  • JWT 인증에 사용할 키를 생성하고 해당 키 정보로 Decoder 빈도 정의

 

7. Security의 기본 권한 서버 endpoint 설정을 그대로 사용하기 위해 AuthorizationServerSettings 빈 정의

  - 따로 커스텀하는 내용 X

@Bean
public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder().build();
}

 

 

 


+ 권한서버의 /.well-known/openid-configuration 경로로 접속해 보면 권한 서버의 인증 관련 정보를 볼 수 있다

 

'Spring > Security' 카테고리의 다른 글

[Security] hasIpAddress 사용하기  (0) 2024.03.09
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/05   »
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
31