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였기 때문에 디버그를 사용할 수 있어 매우 편리했다!
자세한 자료가 많지 않고 그나마 있는 자료는 이전 버전인 경우가 많아 개인적으로 헤맸었는데,
이 자료가 기존 자료와 함께 쌓여 누군가에게 도움이 됐으면 좋겠다.
틀린 내용이 있을 경우 언제든지 알려주시면 감사하겠습니다!