WebSecurityConfig.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. package com.its.op.security;
  2. import com.its.op.security.interceptor.UserLogoutHandler;
  3. import lombok.RequiredArgsConstructor;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.http.HttpMethod;
  9. import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
  10. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  11. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  12. import org.springframework.security.config.annotation.web.builders.WebSecurity;
  13. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  14. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  15. import org.springframework.security.config.http.SessionCreationPolicy;
  16. import org.springframework.security.core.session.SessionRegistry;
  17. import org.springframework.security.core.session.SessionRegistryImpl;
  18. import org.springframework.security.crypto.password.PasswordEncoder;
  19. import org.springframework.security.web.session.HttpSessionEventPublisher;
  20. import java.io.IOException;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. @Slf4j
  25. @Configuration
  26. @EnableWebSecurity
  27. @RequiredArgsConstructor
  28. //@EnableGlobalMethodSecurity(securedEnabled = true)
  29. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  30. private final WebLoginService loginService;
  31. private final WebLoginSuccessHandler webLoginSuccessHandler;
  32. private final WebLoginFailureHandler webLoginFailureHandler;
  33. @Override
  34. public void configure(WebSecurity web) {
  35. web.ignoring().antMatchers("/favicon.ico");
  36. // static 디렉터리의 하위 파일 목록은 인증 무시 ( = 항상통과 )
  37. web.ignoring().antMatchers("/js/**", "/images/**", "/libs/**", "/css/**", "/application/fonts/**");
  38. //web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); // 정적 리소스 접근 가능하게
  39. web.ignoring().antMatchers(HttpMethod.GET, "/api/**"); // GET Method 는 모두 통과
  40. // cs-api
  41. web.ignoring().antMatchers(HttpMethod.GET, "/cs-api/**"); // GET Method 는 모두 통과
  42. web.ignoring().antMatchers(HttpMethod.POST, "/cs-api/**"); // GET Method 는 모두 통과
  43. web.ignoring().antMatchers(HttpMethod.PUT, "/cs-api/**"); // GET Method 는 모두 통과
  44. web.ignoring().antMatchers(HttpMethod.DELETE, "/cs-api/**"); // GET Method 는 모두 통과
  45. }
  46. @Override
  47. protected void configure(HttpSecurity http) {
  48. // URL 권한 설정
  49. //setAntMatchers(http, "ROLE_");
  50. try {
  51. http.csrf()
  52. .disable()
  53. ; // REST API 호출 유효하게(POST...)
  54. http
  55. .authorizeRequests()
  56. // SWAGGER 권한 설정
  57. .antMatchers("/swagger-ui.html", "/swagger/**", "/swagger-resources/**", "/webjars/**", "/v2/api-docs").permitAll()
  58. // 웹소켓 권한 설정하지
  59. .antMatchers("/ws/**").permitAll()
  60. .antMatchers("/api/**").permitAll()
  61. // API 권한 설정하지
  62. //.antMatchers("/api/**").permitAll()
  63. // 지도 URI 권한 설정하지
  64. .antMatchers("/MAPDATA/**").permitAll()
  65. .antMatchers("/download/**").permitAll()
  66. // 페이지 권한 설정
  67. // .antMatchers("/application/facility/**", "/facility/**").permitAll()
  68. .antMatchers("/application/**", "/facility/**").permitAll()
  69. .antMatchers("/application/wall/**", "/wall/**").permitAll()
  70. .antMatchers("/application/login/**").permitAll()
  71. .antMatchers("/api/auth/**").permitAll()
  72. // .antMatchers("/api/**").permitAll()
  73. .anyRequest().authenticated()
  74. .and()
  75. .formLogin()
  76. // .loginPage("/application/login/login.html")
  77. .loginPage("/application/op/00.main/main.html")
  78. .loginProcessingUrl("/api/auth/login.do")
  79. .defaultSuccessUrl("/application/op/00.main/main.html", true)
  80. .usernameParameter("username")
  81. .passwordParameter("password")
  82. .successHandler(this.webLoginSuccessHandler)
  83. .failureHandler(this.webLoginFailureHandler)
  84. .permitAll()
  85. .and()
  86. .logout()
  87. //.logoutUrl("/api/auth/logout.do")
  88. //.logoutRequestMatcher(new AntPathRequestMatcher("/api/auth/logout.do"))
  89. .addLogoutHandler(new UserLogoutHandler()).permitAll()
  90. .logoutSuccessUrl("/application/login/login.html").permitAll()
  91. //.logoutSuccessUrl("/api/auth/login.do").permitAll()
  92. .invalidateHttpSession(true)
  93. .deleteCookies("JSESSIONID")
  94. .deleteCookies(WebMvcConfig.USER_UUID)
  95. .deleteCookies(WebMvcConfig.USER_TIME)
  96. .and()
  97. .sessionManagement()
  98. .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 스프링 시큐리티가 필요 시 생성 (default)
  99. // 인증에 성공할 때 마다 세션 ID나 세션을 변경해서 발급해줌으로써
  100. // 세션을 중간에서 가로채더라도 해당 세션이 유효하지 않게 하는 기능
  101. .invalidSessionUrl("/application/login/login.html") // 세션이 유효하지 않을 경우 이동 할 페이지
  102. //.invalidSessionUrl("/api/auth/login.do") // 세션이 유효하지 않을 경우 이동 할 페이지
  103. .sessionFixation().changeSessionId() // changeSessionId : 새로운 세션 ID를 발급해서 전달(default)
  104. // none : 아무 동작 안함
  105. // migrateSession : 새로운 세션을 생성해서 전달 (속성값 유지)
  106. // newSession : 새로운 세션 전달 (속성값 유지 안됨)
  107. .maximumSessions(20) // 최대 허용 가능 세션 수, -1인 경우 무제한 세션 허용
  108. .maxSessionsPreventsLogin(true) // 동시 로그인 차단, false 인 경우 기존 세션 만료(default)
  109. .expiredUrl("/application/login/login.html") // 세션이 만료된 경우 이동 할 페이지
  110. //.expiredUrl("/api/auth/login.do") // 세션이 만료된 경우 이동 할 페이지
  111. .sessionRegistry(sessionRegistry())
  112. // .and()
  113. // .exceptionHandling()
  114. // .accessDeniedPage("/login.do")
  115. // .and()
  116. // .headers()
  117. // .defaultsDisabled()
  118. // .frameOptions()
  119. // .sameOrigin()
  120. // .cacheControl();
  121. // .and() // 로그아웃 설정
  122. // .logout()
  123. // .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
  124. // .logoutSuccessUrl("/login")
  125. // .invalidateHttpSession(true)
  126. // .deleteCookies("JSESSIONID")
  127. // .and()
  128. // // 403 예외처리 핸들링
  129. // .exceptionHandling().accessDeniedPage("/login");
  130. ;
  131. } catch (IOException e) {
  132. // FOR KISA Secure Coding pass
  133. log.error("{configure: IOException}");
  134. } catch (Exception e) {
  135. log.error("{configure: Exception}");
  136. }
  137. }
  138. public DaoAuthenticationProvider daoAuthenticationProvider() {
  139. DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
  140. authenticationProvider.setUserDetailsService(this.loginService);
  141. authenticationProvider.setPasswordEncoder(passwordEncoder());
  142. // loadUserByUsername 의 UsernameNotFoundException 이 BadCredentialsException 로 발생함.
  143. // Exception 을 catch 하기 위해서는 아래를 false 로 설정하면 됨.
  144. authenticationProvider.setHideUserNotFoundExceptions(true);
  145. return authenticationProvider;
  146. }
  147. @Override
  148. public void configure(AuthenticationManagerBuilder auth) {
  149. // loadUserByUsername 의 UsernameNotFoundException 를 처리하기 위해
  150. // AuthenticationProvider 를 빈으로 등록해서 사용자 로그인 처리를 수행한다.
  151. //auth.userDetailsService(this.loginService).passwordEncoder(passwordEncoder());
  152. auth.authenticationProvider(daoAuthenticationProvider());
  153. }
  154. @Bean
  155. public PasswordEncoder passwordEncoder() {
  156. return new WebPasswordEncoder();
  157. }
  158. @Bean
  159. public SessionRegistry sessionRegistry() {
  160. return new SessionRegistryImpl();
  161. }
  162. /**
  163. * 로그아웃을 했기 때문에 세션의 개수가 0이라 생각했는데 로그인이 안되었다.
  164. * 이에 대한 해결책으로 SessionRegistry 빈을 생성 후 sessionManagement 에 DI 시킨다.
  165. * @return
  166. */
  167. @Bean
  168. public static ServletListenerRegistrationBean httpSessionEventPublisher() {
  169. return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
  170. }
  171. protected List<Map<String, Object>> getAuthReq() {
  172. Map<String, Object> roll = new HashMap<>();
  173. // roll.put("id", "id");
  174. // roll.put("url", "url");
  175. // roll.put("hasAuthority", "auth");
  176. // roll.put("date", "date");
  177. return (List<Map<String, Object>>) roll;
  178. }
  179. protected void setAntMatchers(HttpSecurity http, String rolePrefix) {
  180. List<Map<String, Object>> list = getAuthReq();
  181. for(Map<String, Object> m : list) {
  182. // 쉼표(,)로 구분된 권한 정보를 분리 후 배열로 저장
  183. String[] roles = m.get("hasAuthority").toString().split(",");
  184. // 권한 앞에 접두사(rolePrefix) 붙임
  185. for(int ii = 0; ii < roles.length; ii++) {
  186. roles[ii] = rolePrefix + roles[ii].toUpperCase();
  187. }
  188. String url = m.get("url").toString();
  189. if(url.charAt(0) != '/') {
  190. url = "/" + url;
  191. }
  192. // url, 권한 정보를 넣는다.
  193. try {
  194. http.authorizeRequests()
  195. .antMatchers(url)
  196. .hasAnyAuthority(roles);
  197. } catch (IOException ie) {
  198. // FOR KISA Secure Coding pass
  199. log.error("setAntMatchers: IOException");
  200. } catch (Exception e) {
  201. log.error("setAntMatchers: Exception");
  202. }
  203. }
  204. try {
  205. http.authorizeRequests()
  206. .antMatchers("/**").permitAll()
  207. .anyRequest().authenticated();
  208. } catch (IOException ie) {
  209. // FOR KISA Secure Coding pass
  210. log.error("setAntMatchers: IOException, permitAll");
  211. } catch (Exception e) {
  212. log.error("setAntMatchers: Exception, permitAll");
  213. }
  214. }
  215. }