[Spring Security] Google Login with Spring Security & JWT (2)

반응형

11. Security Configuration

다음으로 Security Configuration을 생성하겠습니다.

SecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private CustomOAuth2UserService customOAuth2UserService;

    @Autowired
    private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;

    @Autowired
    private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;

    @Autowired
    private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() {
        return new TokenAuthenticationFilter();
    }

    /*
      By default, Spring OAuth2 uses HttpSessionOAuth2AuthorizationRequestRepository to save
      the authorization request. But, since our service is stateless, we can't save it in
      the session. We'll save the request in a Base64 encoded cookie instead.
    */
    @Bean
    public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() {
        return new HttpCookieOAuth2AuthorizationRequestRepository();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(customUserDetailsService)
                .passwordEncoder(passwordEncoder());
    }

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


    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                    .and()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                .csrf()
                    .disable()
                .formLogin()
                    .disable()
                .httpBasic()
                    .disable()
                .exceptionHandling()
                    .authenticationEntryPoint(new RestAuthenticationEntryPoint())
                    .and()
                .authorizeRequests()
                    .antMatchers("/",
                        "/error",
                        "/favicon.ico",
                        "/**/*.png",
                        "/**/*.gif",
                        "/**/*.svg",
                        "/**/*.jpg",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js")
                        .permitAll()
                    .antMatchers("/auth/**", "/oauth2/**")
                        .permitAll()
                    .anyRequest()
                        .authenticated()
                    .and()
                .oauth2Login()
                    .authorizationEndpoint()
                        .baseUri("/oauth2/authorize")
                        .authorizationRequestRepository(cookieAuthorizationRequestRepository())
                        .and()
                    .userInfoEndpoint()
                        .userService(customOAuth2UserService)
                        .and()
                    .successHandler(oAuth2AuthenticationSuccessHandler)
                    .failureHandler(oAuth2AuthenticationFailureHandler);

        // Add our custom Token based authentication filter
        http.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

• WebSecurityConfigureAdapter를 extends하고 @EnableWebSecurity 어노테이션을 선언해 Security Configuration을 정의합니다.

• CustomUserDetailService : 인증시 사용할 custom User Service 입니다. 자세한 내용은 이 글을 참고합니다.

• TokenAuthenticationFilter : 로그인시 JWT Token을 확인해 인가된 사용자 유무를 판별하고 내부 process를 수행합니다. 자세한 내용은 이 글을 참고합니다.

• HttpCookieOAuth2AuthorizationRequestRepository : Spring OAuth2는 기본적으로 HttpSessionOAuth2AuthorizationRequestRepository를 사용해 Authorization Request를 저장합니다.

우리는 JWT를 사용하므로, Session에 이를 저장할 필요가 없습니다. 따라서 custom으로 구현한 HttpCookieOAuth2AuthorizationRequestRepository를 사용해 Authorization Request를 Based64 encoded cookie에 저장합니다. 자세한 내용은 이 글을 참고합니다.

• configure (AuthenticationManagerBuilder) : Authorization에서 사용할 userDetailService와 password Encoder를 정의합니다.

• passwordEncoder : password를 저장할때 사용할 encoding algorithm을 정의합니다. 가장 많이사용되는 BCrypt 방식을 사용하겠습니다.

11-1) configure (HttpSecurity)

이 부분을 해석해보면 다음과 같습니다.

• cors( ) : cors을 허용합니다.

• sessionManagement( ) : session Creation Policy를 STATELESS로 정의해 session을 사용하지 않겠다 선언합니다.

• csrf( ) : 사용하지 않습니다.

• formLogin ( ) : 사용하지 않습니다.

• httpBasic ( ) : 사용하지 않습니다.

• authenticationEntryPoint(RestAuthenticationEntryPoint) : 사용자가 authentication 없이 protected resource에 접근하는 경우에 invoked 되는 entry point를 정의합니다.

• authorizeRequests ( ) : permitAll( )을 선언한 경로의 resource는 모든 사용자에게 접근을 허용합니다. 이외의 anyRequest( )는 authenticated( ) 즉 인증된 사용자에게만 접근을 허용합니다.

• authorizationEndpoint( ) : oauth 로그인시 접근할 end point를 정의합니다. baseUri에 "/ouath2/authorize"라고 선언했으므로, react Client App에서는 다음과 같이 login을 수행합니다.

# server base uri
API_BASE_URL = 'http://localhost:8080';

# oauth2 redirect uri
OAUTH2_REDIRECT_URI = 'http://localhost:3000/oauth2/redirect'

# google login uri
GOOGLE_AUTH_URL = API_BASE_URL + '/oauth2/authorize/google?redirect_uri=' + OAUTH2_REDIRECT_URI;

• userInfoEndpoint( ) : 로그인시 사용할 User Service를 정의합니다.

• successHandler ( ) : 로그인 성공시 invoke 할 Handler를 정의합니다.

• failureHandler ( ) : 로그인 실패시 invoke 할 Handler를 정의합니다.

• addFilterBefore(TokenAuthenticationFilter) : reqeust 요청이 올때마다 UsernamePasswordAuthenticationFilter 이전에 tokenAuthenticaitonFilter를 수행하도록 정의합니다.

12. OAuth2 Login Flow

다시한번.. OAuth2 login flow를 살펴보겠습니다. 😅

• OAuth2 login flow는 client에서 "http://localhost:8080/oauth2/authorize/{provider}?redirect_uri="로 user를 send 하면서 시작됩니다.

• 이 때 위의 provider는 google/naver와 같은 oauth provider가 됩니다.

• 는 user가 oauth provider의 login에 성공하면 redirect 될 uri 입니다. 예를 들어 "http://localhost:3000/oauth2/redirect"와 같이 작성합니다.

• authorization request를 receive하게 되면, Spring Security's OAuth2 client (google/naver)는 그들의 AuthorizaitonUri로 사용자를 redirect 합니다. 즉, 아래와 같은 google login 창으로 이동하게 됩니다.

• 모든 Authorization 관련 state(allow/deny)는 authorizationRequestRepository를 사용해 저장됩니다.

• 위의 login창으로 이동하게된 경우, 이제 user는 해당 도메인에 대한 permission을 allow/denies 할 수 있습니다.

• 예를 들어 위와 같이 velog에 로그인시 이름/이메일주소/사진에 대한 permission을 허용할 것인지를 물어보게 됩니다.

• user가 허용할 경우 authorization code와 함께 "http://localhost:8080/login/oauth2/code/google" 로 redirect 됩니다.

• 만약 user가 허용하지 않을 경우 동일하게 "http://localhost:8080/login/oauth2/code/google" 로 redirect 되지만, error가 발생합니다.

• 만약 OAuth2 callback이 error를 발생시킨경우, Spring Security는 oAuth2AuthenticationFailureHandler를 invoke합니다.

• 만약 OAuth2 callback이 success한 경우, Spring Security는 자동으로 획득한 authorization_code를 사용해 access_token을 발급받는 절차를 수행하게 됩니다. 또한, customOAuth2UserService 를 invoke 하게 됩니다.

• customOAuth2UserService는 DB에서 login한 사용자에 대한 detail 정보를 retrieve 하며, 신규 사용자를 등록하거나 기존의 사용자 정보를 update 하는 절차를 수행합니다.

• 최종적으로 oAuth2AuthenticationSuccessHandler가 invoke 되며, 이때 JWT Token이 생성됩니다. 이 JWT Token은 client의 redirect_uri로 Query String을 통해 전달됩니다.

JWT Token 발급 흐름은 다음과 같습니다.


참고 자료 : https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-2/


추천서적

 

스프링5 레시피:스프링 애플리케이션 개발에 유용한 161가지 문제 해결 기법

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음


반응형

댓글

Designed by JB FACTORY