1. 배경

요즘은 설정파일을 xml보다 java config를 사용하는 것이 추세라고 한다.

더불어 나는 이번에 Spring Security를 처음으로 사용해보며, 에러를 잡을 때 디버그 기능이 아주 아주 유용했다!

이 디버그 기능도 java config로 설정을 했을 때 사용할 수 있으니, xml에서 java config로 수정하는 것은 좋은 시도라고 생각한다.

하지만 자료가 많이 없었으며 헷갈린 부분이 있었다. 그런 분들에게 이 글이 도움이 되었으면 좋겠다(기본적인 Security 개념은 알고 있는 것으로 전제하고 작성하겠지만, 나도 Security를 공부한지 얼마 안되어 잘못된 부분이 있을 수 있다.).

 

2. 기술 배경

사용된 기술은 전자 정부 프레임워크 4 버전(Spring Framework 4 버전)과 Spring Security 4 버전이다.

내가 변경한 코드에 모든 Security 설정들이 사용되진 않았으니, 아쉽지만 없는 부분도 많을 수 있다.

 

3. 수정 내용

먼저 WebSecurityConfigurerAdapter를 상속하는 SecurityConfig.java 파일을 만들고 그 안에 커스텀 설정들을 넣어주면 된다(Security 5부터는 이러한 방식은 deprecated 되었다.). 

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 내용
    // 아래 메서드를 여기에 추가해주면 됩니다.
}

 

3.1. WebSecurity 부분

<!-- xml 부분 -->
<security:http pattern="/나의url/**" security="none"/>

위와 같은 xml 코드는 java config에서 WebSecurity 부분에 해당한다. 아래 메서드를 SecurityConfig.java 안에 넣어주자.

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
        .antMatchers("/나의url/**")
        .antMatchers("/기타등등")
    ;
}

 

3.2. HttpSecurity 부분

HttpSecurity 부분은 여러 설정들을 사용했기 때문에 나눠서 설명하겠다(인증과 인가 개념은 아는 것으로 가정). 

앞보다 살짝 복잡하지만 어렵지 않다.

먼저 HttpSecurity 설정에 사용되는 메서드 틀은 아래와 같다.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        //다른 api 메서드
        //기타등등
        ;
}

이 틀 안에서 필요한 설정들은, http의 api 메서드를 사용하여 뒤에 이어 붙여줄 것이다.

 

  • (1) url 리소스 인가 설정
<!-- xml 부분 -->
<security:intercept-url pattern="/login" access="isAnonymous()"/>
<security:intercept-url pattern="/**" access="isAuthenticated()"/>

url 리소스별로 인가를 설정하는 xml 코드이다. 아래 java config로 고쳐주자.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/login").anonymous()
        .antMatchers("/**").authenticated()
        //추가
        ;
}

 

  • (2) form 로그인 설정
<!-- xml 부분 -->
<security:form-login
    login-page="/login"
    authentication-success-handler-ref="로그인성공핸들러객체이름"
    authentication-failure-handler-ref=""
    authentication-failure-url="/실패url" />

form 로그인을 사용하는 xml 코드이다(이 부분은 보안상 생략한 부분이 많다. 아쉽지만 따로 찾아보시길 바란다).

중간에 로그인성공핸들러객체가 사용되었다. 이 객체 설정은 나중에 이어진다.

우선 java config를 보자.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin()
        .loginPage("/login")
        .defaultSuccessUrl("/")
        .failureUrl("/실패url")
        .successHandler(로그인성공핸들러객체)
        ;
}

 

  • (3) 로그아웃 설정
<!-- xml 부분 -->
<security:logout
    logout-success-url="/login"/>

로그아웃 부분은 간단하다.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .logout()
        .logoutSuccessUrl("/login")
        ;
}

 

  • (4) i-frame 설정
<!-- xml 부분 -->
<security:headers>
    <security:frame-options policy="SAMEORIGIN"/>
</security:headers>

i-frame에서 cors 설정을 하는 부분이다. 처음 설정하는 거라면 sameorigin이 무난할 것이다. 물론 각자 상황에 맞출 것.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .headers()
        .frameOptions().sameOrigin()
        ;
}

 

  • (5) csrf 설정
<!-- xml 부분 -->
<security:csrf disabled="true"/>

csrf 설정을 꺼두었다.

(참고: csrf 설정은 보안에 중요한 부분이다. Spring Security 4 버전부터 csrf 설정이 디폴트로 변경됨에 따라, 3에서 4로 업그레이드하는 과정에서 이 부분에 에러가 생길 수 있다. 그 경우 위처럼 일단 disabled="true"로 설정을 하여 해결할 수 있긴 하지만, 권장되는 방법은 아니다. 나도 이후 수정하여 포스팅할 예정이다.)

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        ;
}

 

3.3. 로그인성공핸들러(사용하지 않았을 수도 있음)

이 부분은 나도 확실하지 않은 부분이다. 또한 이 객체를 사용하지 않은 프로젝트가 있을 수 있다. 사용하지 않았다면 넘어가자.

<!-- xml 부분 -->
<bean name="로그인성공핸들러" class="패키지.위치.로그인성공핸들러">
    <property name="설정" value="/설정url"/>
</bean>

위 로그인성공핸들러에 해당하는 파일을 찾자. 나의 경우 java 파일이었다.

이 클래스에 @Component를 붙여 빈으로 등록해주고, 원하는 설정(property 부분)을 추가해주자.

내가 한 설정 부분은 보안상 구체적으로 언급하지 않겠다.

 

그리고 SecurityConfig.java에 이 로그인성공핸들러 객체를 의존주입해주면 완료!

(@Autowired private final 로그인성공핸들러 로그인성공핸들러; 를 추가!)

 

3.4. AuthenticationManagerBuilder 설정

개인적으로 가장 어려웠던 부분이다.

이 부분은 비록 개발이 아니라 수정을 하는 것일지라도,

AuthenticationManager와 AuthenticationProvider, UserDetailsService의 흐름과 역할을 알아야 수월하게 진행할 수 있다.

 

나의 경우, 해당 부분만 빠르고 공부하고 설정 수정에 들어갔는데, 지금 생각해보면 잘 한 행동인 것 같다.

해당되는 내용 강의는 30분도 안됐는데, 덕분에 덜 헤매며 많은 시간을 아껴 설정을 성공적으로 마무리할 수 있었다.

(혹시 궁금해하실 수도 있으니 적어두자면, 정수원님의 Spring Security 강의의  "스프링 시큐리티 주요 아키텍처 이해"에서 "7) 인증 관리자: AuthenticationManager"와 "8) 인증 처리자 - AuthenticationProvider" 각각의 앞 이론 부분만 들었다. 이 부분만 알아도 훨씬 할만 해진다. Security에 대한 기본적인 이해가 있다는 전제 하에 해당.)

 

<!-- xml 부분 -->
<security:authentication-manager>
    <security:authentication-provider user-service-ref="jdbcUserService">
        <security:password-encoder hash="bcrypt"/>
    </security:authentication-provider>
</security:authentication-manager>

<security:jdbc-user-service id="jdbcUserService"
    data-source-ref="dataSource" 
    users-by-username-query="쿼리"
    authorities-by-username-query="쿼리"
/>

위 xml을 보면 두 부분으로 나뉘어진다. AuthenticationManager와 JdbaUserService를 각각 설정해주어야 하는 건지, 각각 알맞은 구현체는 어떻게 찾을지 난감했었다.

우선 나의 경우, jdbc-user-service 만 설정해주어도 되었다(잘 보면 authentication-manager 부분에서는, jdbcUserService를 주입하고, 인코딩 설정만 해주고 끝난다.).

@Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.jdbcAuthentication()
        .dataSource(dataSource)
        .usersByUsernameQuery(쿼리)
        .authoritiesByUsernameQuery(쿼리)
        .passwordEncoder(new MessageDigestPasswordEncoder("SHA-256"))
        ;    
}

 

 

(참고: 아무래도 jdbc는 많이 쓰지 않기 때문에 알맞은 구현체를 찾기 어려웠다. UserDetailsService 구현 객체를 다 뒤져봐야하나 심란해하던 차에, 좋은 자료를 찾았다! Spring Security에서 Jdbc를 사용하는 방법에 대해 자세하게 적힌 좋은 글이다. 링크를 남겨두니 자세히 보고 싶은 분은 참고하길 바란다.)

https://github.com/Djunnni/Springboot-Summary/blob/master/2019-10-05.md

 

GitHub - Djunnni/Springboot-Summary: (MAC OS X)스프링부트 2.x 버전 공부내용 정리 + 상황별 해결법

(MAC OS X)스프링부트 2.x 버전 공부내용 정리 + 상황별 해결법. Contribute to Djunnni/Springboot-Summary development by creating an account on GitHub.

github.com

 

4. 최종

최종적으로 SecurityConfig.java의 모습은 아래와 같다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private BasicDataSource dataSource;
    //@Autowired
    //private 로그인성공핸들러 로그인성공핸들러; //필요한 경우
    
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
            .antMatchers("/나의url/**")
            .antMatchers("/기타등등")
        ;
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/login").anonymous()
            .antMatchers("/**").authenticated()
            ;
            
        http
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/")
            .failureUrl("/실패url")
            //.successHandler(로그인성공핸들러객체)
            ;

        http
            .logout()
            .logoutSuccessUrl("/login")
            ;

        http
            .headers()
            .frameOptions().sameOrigin()
            ;

        http
            .csrf().disable()
            ;
    }
    
    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
            .dataSource(dataSource)
            .usersByUsernameQuery(쿼리)
            .authoritiesByUsernameQuery(쿼리)
            .passwordEncoder(new MessageDigestPasswordEncoder("SHA-256"))
            ;    
    }
    
}

 

5. 마무리

xml에서 java config로 수정하는 것이 필수인 것도 아니고 항상 옳은 것도 아니지만,

나는 수정을 해주는 것이 프로젝트에 장기적으로 도움이 될 것이라고 판단했고, 실제로 수정하고나니 무척 만족스러웠다.

실제로 Security 4에서 5로 버전을 업그레이드할 때, java config였기 때문에 디버그를 사용할 수 있어 매우 편리했다!

자세한 자료가 많지 않고 그나마 있는 자료는 이전 버전인 경우가 많아 개인적으로 헤맸었는데,

이 자료가 기존 자료와 함께 쌓여 누군가에게 도움이 됐으면 좋겠다.

 

틀린 내용이 있을 경우 언제든지 알려주시면 감사하겠습니다!

 

+ Recent posts