06. 스프링시큐리티 설정

스프링시큐리티는 인증/인가 담당하는 모듈 하나에 불과하지만 이것을 적용하기 위해서는 손대야할 것이 많다. 여기서는 웹보안에 대한 것만 간단하게 다룬다.  아래의 소스는 스프링시큐리티를 적용하기 위해 작성한 설정 클래스이다. 


1. SpringSecurityConfig 설정

30번 라인의 @EnableWebSecurity은 웹보안을 활성화 시키는 역하을 하고 31번 라인의 WebSecurityConfigurerAdapter은  웹보안을 위한 메소드를 확장시키고 구현을 재정의할 수 있도록 한다.
44번 라인의 configure(WebSecurity web)은 스프링시큐리티의 보안 설정에 적용받지 않을 URL을 등록한다. 주로 이미지 파일이나, CSS, JavaScript 라이브러리가 대상이다. 
49번 라인의 configure(HttpSecurity http)에서는 본격적인 보안 사항을 설정한다. http.formLogin() 에서는 로그인과 관련된 사항을 설정한다. loginPage("/login_form.html").permitAll()은 로그인 페이지가 어떤 URL 인지 지정하고, 로그인을 처리할 loginProcessingUrl("/auth") URL이 무엇인지 지정한다. 본인은 /login_f
orm.html URL을 처리할 컨트롤러를 controller.common.CommonController에 설정했다. usernameParameter("loginid").passwordParameter("passwd")는 로그인에 사용할 파라메터를 지정하는 부분인다. 마지막으로 successHandler()와 failureHandler()는 성공시 처리할 모듈과 실패시 처리할 모듈을 설정했다.
http.logout()에서는 로그아웃과 관련된 사항을 설정한다. logoutRequestMatcher()는 어떤 URL을 입력했을 경우 로그아웃 처리를 수행할 것인지를 지정하는 부분이며, logoutSuccessHandler()는 로그아웃시 처리할 모듈을 설정하는 부분이다. clearAuthentication(true).invalidateHttpSession(true)는 로그 아웃시 인증정보를 지우하고 세션을 무효화 시킨다는 설정이다.
http.exceptionHandling()은 예외사항을 설정한다. 여기서는 권한이 없는 URL에 접근했을 때 동작을 설정하는 부분으로 accessDeniedHandler() 통해 처리할 모듈을 설정했다.
http.authorizeRequests()는 URL별 권한 사항을 설정한다. 이 부분은 userService.findAllComMenuMngtAndAuthGrpMngt() 통해 메뉴정보와 권한 그룹정보를 조회한 후 URL별 권한 정보를 만들어 설정한다. 스프링시큐리티는 ROLE_PREFIX가 'ROLE_'로 설정되어 있는데 이것을 그대로 사용하기 위해 권한 그룹 정보의 시퀀스 앞에 'ROLE_G'라는 문자열을 붙여 설정하였다.
http.sessionManagement() 세션 정책을 설정한다. maximumSessions()에 1을 넣어 같은 아이디로 1명만 접속하도록 설정하였다. maxSessionsPreventsLogin(false)는 기존에 접속한 사용자가 있을 경우 기존 사용자의 접속을 끊고, 나중에 접속한 사용자가 접속되도록 한다. expiredSessionStrategy()을 통해 세션이 종료되었을 경우의 처리를 할 수 있도록 하였다.

82번 라인의 configure(AuthenticationManagerBuilder auth) 메소드는 로그인 처리를 위한 구성을 수행한다. 자세한 내용은 아래서 설명할 것이다.




2. configure(AuthenticationManagerBuilder auth)

configure(AuthenticationManagerBuilder auth)에서는 인증 처리를 위한 AuthenticationProvider를 구현한 구현체와 UserDetailsService를 구현한 구현체를 조합한다. AuthenticationProvider의 구현체의 주요 역할은 사용자 정보를 조회하고, 로그인시 입력 받은 패스워드를 확인하고, 사용자의 권한을 확인하여 인증토큰을 발행하는 역할을 한다. UserDetailsService의 구현체는 사용자 정보를 조회하는 역할을 하며, UserDetails 구현체를 반환 한다.


여기서는 AuthenticationProvider의 구현체로 SecurityAuthenticationProvider를 만들어 사용한다. 아래는 SecurityAuthenticationProvider의 소스이다. 25번 라인과 같이 AuthenticationProvider를 implements 하면서 시작한다. 44번 라인에서는 사용자 정보를 조회한다. 여기서는 UserDetails 구현체를 받는데 본인 나름대로 편리하다고 생각되는 방향으로 수정해서 사용했다. 46번 라인은 패스워드를 비교하는 부분으로 현재의 passwordEncoder는 사실상 패스워드를 암호화 하거나 하지는 않는다. 이것을 암호화 모듈로 바꿔주려면 SpringSecurityConfig.java의 90번 라인의 NoOpPasswordEncoder.getInstance() 수정하면 된다. 51번 라인은 본인이 추가한 부분으로 사용자에게 지정된 IP에서 접속했는가를 확인하는 부분이다. 사용가능 IP 목록이 없으면 이 부분은 그냥 넘어가도록 했다. 사용가능 IP 목록은 관리자와 같은 특수한 사용자 위한 것으로 특정한 PC에서만 접속할 권한을 부여할 경우 사용한다. 68번 라인은 사용자가 사용할 수 있는 권한(ROLE)을 부여하는 부분으로 사용자 정보에서 가져왔다. 마지막으로 80번 라인에서는 스프링에서 사용할 수 있도록 사용자 정보와 권한 정보 등으로 Authentication 구현체를 반한다.



사용자 정보는 UserDetailsService를 구현한 SecurityUserDetailsService로 하였다. UserDetailsService의 필수 조건은 사용자의 권한 정보를 포함한 사용자 정보이다. 하지만 SecurityUserDetailsService는 메뉴 정보까지 포함하여 받을 수 있도록 수정했다.


20번 라인21번 라인은 UserDetailsService 기능을 수행할 수 있도록 @Service 추가와 UserDetailsService 인터페이스를 추가하였다. 26번 라인의 loadUserByUserName() 메소드는 AuthenticationProvider 구현체에서 호출하는 부분으로 반드시 구현되어야 하는 부분이다. 이곳에서 사용자 정보 조회와, 사용자의 권한 그룹 조회, 사용자에게 할당된 메뉴 정보를 조회한다. 이 부분은 28번 라인, 35번 라인, 38번 라인에서 구현되어 있으며, 특히 38번 라인의 사용자 메뉴 목록 조회 부분에서는 내부에서 메소드(56번 라인)를 구현하여 각 메뉴의 부모와 자식 관계를 연결하였다. 

이렇게 한 이유는 나중에 화면을 구현할 때 각 화면별 화면 정보 조회와 화면 경로 표현을 위해 미리 만들어 둔 것이다.

40번 라인은 SecurityUser Object를 생성하는 부분으로 우리의 필요 용도에 맞게 UserDetails를 수정하여 구현 하였다.


마지막은 UserDetails를 구현한 SecurityUser다. SecurityUsers는 Spring Security에 사용자 정보를 제공하는 용도와 더불어 개인화된 화면을 제공하기 위한 부가 정보로 구성했다.

SecurityUser는 UserDetails의 구현체이므로 29라인 처럼 UserDetails을 추가해야 하며, Serializable도 추가해야 한다. 41번 라인은 생성자로써 사용자 정보과 권한 정보, 메뉴 목록 등을 받아 인스턴스 내에 저장하는 역할을 한다. 특히 50번 라인에서는 최상위 메뉴를 topMenuMaps에서 저장하며, 54번 라인에서는 사용자에게 할당된 메뉴를 authMenuUrlMap라는 인스턴스 변수에 url + menu 정보로 저장하여 둔다. 71번 라인84번 라인은 메뉴 목록을 출력하는 용도와 화면 정보를 표시하는 용도로 사용된다. 자세한 것은 interceptor(Logging 파트)를 다룰 때 설명할 것이다. 89번 라인과 103번 라인, 112번 라인은 UserDetails 인터페이스가 요구하는 항목으로 각각 사용자의 권한 정보와 패스워드, 로그인 아이디를 반환한다.

117번 라인, 126번 라인, 131번 라인, 149번 라인은 UserDetails 인터페이스가 요구하는 구현 항목으로 각각 계정의 만료 여부, (일정 수의 로그인 처리 오류의 경우 등으로)계정 잠김, 패스워드 만료, 유효 계정 여부 등을 묻는 기능으로 원칙적으로는 구현하는 것이 올바르지만, 이런 기능들을 사용하지 않을 true를 반환해도 된다. 





3. 인증과 처리에 대한 각종 핸들러

SpringSecurityConfig 설정에는 총 6종의 핸들러가 선언되어 있다. 이중에 SecurityAuthenticationSuccessHandler만 설명하고 나머지는 Logging 파트에서 설명할 것이다. SecurityAuthenticationSuccessHandler의 역할은 로그인 처리가 완료되었을 경우 로그인한 사용자가 보게될 첫번째 화면으로 안내하는 것이다.

67번 라인의 onAuthenticationSuccess() 메소드에서 사용자에게 보여질 첫번째 화면을 지정해 준다. 그러나 우선적으로 처리해 주어야할 일이 있다. 70번 라인에 호출된 clearAuthenticationAttributes() 메소드는 로그인 시 발생했던 세션에 저장된 오류를 지워주는 역할을 수행한다. 그리고 72번 라인의 decideRedirectStrategy() 메소드를 통해 어떤 화면으로 이동할 것인지를 지정한다. 판단 기준은 targetUrlParameter 값이 있을 경우 그것을 1순위로 하여  리다이렉션하며, 1순위 URL이 없을 경우 Spring Security가 세션에 저장한 URL을 2순위로 한다. 2순위 URL이 없을 경우 Request의 REFERER을 3순위로 하여 그 REFERER URL로 이동하며,  3순위도 없을 경우 Default URL을 4순위로 한다. (SecurityAuthenticationSuccessHandler의 소스 내용은 [http://zgundam.tistory.com/52]를 차용하였다. 보다 상세한 내용도 이곳에서 확인할 수 있다.)



지금까지의 코드로는 결과를 확인하기는 좀 어렵다. 다음 포스트에서 Apache Tiles의 사용과 더불어 결과를 확인해 보도록 하겠다.


demo.zip