====== 업그레이드 ====== ===== 개요 ===== 표준프레임워크 2.7(Spring Security 2.0.4)에서 3.0(Spring Security 3.2.3)로 업그레이드 Server security의 경우 설정 변경뿐만 아니라 소스 상의 변경 작업이 필요하다. ===== 주요 변경내용 (Spring Security 부분) ===== ==== dependencies 및 패키지 변경 ==== * spring-security-core (org.springframework.security.core, org.springframework.security.access, etc.) * spring-security-web (org.springframework.security.web) * spring-security-config (org.springframework.security.config) ==== API 변경 ==== * SpringSecurityException 삭제 * ConfigAttributeDefinition => Collection * SavedRequest : class => interface (DefaultSavedRequest 대체) ==== 기타 ==== * 다중 http elements 지원 * stateless 인증 지원 * DebugFilter 추가 (debugging용) * hasPermission 표현식 지원 (authorize JSP tag) * 등등 ===== 실행환경 부분 업그레이드 절차 ===== ==== 1. dependency 수정 ==== egovframework.rte egovframework.rte.fdl.security 3.0.0 * 적용 버전은 최신 버전 확인 후 적용(patch 버전 등) ==== 2. web.xml 수정 ==== 접속 제한을 사용하는 경우 web.xml 상에 HttpSessionEventPublisher listener의 패키지 변경 필요 * 기존 : org.springframework.security.ui.session.HttpSessionEventPublisher * 변경 : org.springframework.security.web.session.HttpSessionEventPublisher ==== 3. Spring Security 설정 수정 ==== 다음 설정을 참조하여 관련 설정을 변경한다. (Spring Security쪽 패키지 등) ==== 4. 불필요 class 삭제 ==== 자체 적용된 server security에 대한 소스 삭제 정리 \\ Ex: * org.springframework.security.intercept.web.EgovReloadableDefaultFilterInvocationDefinitionSource : 패키지 변경으로 인하여 불필요 (egovframework.rte.fdl.security.intercept.EgovReloadableFilterInvocationSecurityMetadataSource) * ...security.intercept.*, ...security.securedobject.securedobject.*, ...security.securedobject.userdetails.* 등 : 업그레이된 실행환경쪽 패키지(egovframework.rte.fdl.security.*)를 참조하기 때문에 불필요 ==== 5. Mapping 클래스 mapRow 메소드 변경 ==== jdbcUserService(egovframework.rte.fdl.security.userdetails.jdbc.EgovJdbcUserDetailsManager)에 의해 지정된 mapClass는 EgovUsersByUsernameMapping 클래스를 extend 하도록 되어 있는데, 해당 EgovUsersByUsernameMapping의 mapRow() 메소드의 return 타입이 Object에서 EgovUserDtails로 변경되었다. * 기존 : public class EgovSessionMapping extends EgovUsersByUsernameMapping { ... @Override protected Object mapRow(ResultSet rs, int rownum) throws SQLException { ... } } * 변경 : public class EgovSessionMapping extends EgovUsersByUsernameMapping { ... @Override protected EgovUserDetails mapRow(ResultSet rs, int rownum) throws SQLException { ... } } ==== 6. 참조 패키지 및 클래스 변경 ==== Spring security 관련 패키지 변경 등에 따라 일부 참조 클래스에 대한 패키지 변경 필요 * org.springframework.security.Authentication -> org.springframework.security.core.Authentication * org.springframework.security.GrantedAuthority -> org.springframework.security.core.GrantedAuthority * org.springframework.security.context.SecurityContext -> org.springframework.security.core.context.SecurityContext * org.springframework.security.context.SecurityContextHolder -> org.springframework.security.core.context.SecurityContextHolder * 등등 ==== 7. GrantedAuthority 방식 변경 (array -> collection 타입) ==== GrantedAuthority[] -> Collection 변경 적용 * 이전 코드 : GrantedAuthority[] authorities = authentication.getAuthorities(); for (int i = 0; i < authorities.length; i++) { listAuth.add(authorities[i].getAuthority()); } * 변경 코드 : Collection authorities = (Collection) authentication.getAuthorities(); for (GrantedAuthority authority : authorities) { listAuth.add(authority.getAuthority()); } ==== 8. SecurityContext의 getAuthentication() 방식 변경 ==== 기존의 경우 로그인되지 않은 경우 SecurityContext의 getAuthentication()의 리턴 값이 null이었으나, SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); if (EgovObjectUtil.isNull(authentication)) { return null; } 신규 버전의 경우는 null이 아닌 값으로 처리된다. SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); if (EgovObjectUtil.isNull(authentication)) { log.debug("## authentication object is null!!"); return null; } if (authentication.getPrincipal() instanceof EgovUserDetails) { EgovUserDetails details = (EgovUserDetails) authentication.getPrincipal(); log.debug("## EgovUserDetailsHelper.getAuthenticatedUser : AuthenticatedUser is {}", details.getUsername()); return details.getEgovUserVO(); } else { return authentication.getPrincipal(); } 따라서 사용자 정보를 취득하는 부분은 자체 적용 부분이 아닌 실행환경 제공 부분(egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper)을 사용한다. ==== 9. GET 방식 인증 불가 처리 ==== 신규 버전의 경우는 GET 방식으로 j_spring_security_check를 호출할 수 없게 되었다.(j_spring_security_check URL을 내부적으로 redirect 호출하는 경우에만 해당)\\ 오류 메시지 : Authentication request failed: org.springframework.security.authentication.AuthenticationServiceException: Authentication method not supported: GET 이 경우 다음 코드와 같이 GET 방식이 아닌 filter chain 호출 방식으로 변경해주어야 한다. * 기존 코드 : return "redirect:/j_spring_security_check?j_username=" + resultVO.getUserSe() + resultVO.getId() + "&j_password=" + resultVO.getUniqId(); * 변경 코드 (일반 Controller인 경우) : @RequestMapping(value="/uat/uia/actionSecurityLogin.do") public String actionSecurityLogin(@ModelAttribute("loginVO") LoginVO loginVO, HttpServletRequest request, HttpServletResponse response, ModelMap model) throws Exception { ... UsernamePasswordAuthenticationFilter springSecurity = null; ApplicationContext act = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext()); @SuppressWarnings("rawtypes") Map beans = act.getBeansOfType(UsernamePasswordAuthenticationFilter.class); if (beans.size() > 0) { springSecurity = (UsernamePasswordAuthenticationFilter)beans.values().toArray()[0]; } else { throw new IllegalStateException("No AuthenticationProcessingFilter"); } springSecurity.setContinueChainBeforeSuccessfulAuthentication(false); // false 이면 chain 처리 되지 않음.. (filter가 아닌 경우 false로...) springSecurity.doFilter( new RequestWrapperForSecurity(request, resultVO.getUserSe() + resultVO.getId() , resultVO.getUniqId()), response, null); return "forward:/cmm/main/mainPage.do"; // 성공 시 페이지.. (redirect 불가) ... } ... class RequestWrapperForSecurity extends HttpServletRequestWrapper { private String username = null; private String password = null; public RequestWrapperForSecurity(HttpServletRequest request, String username, String password) { super(request); this.username = username; this.password = password; } @Override public String getRequestURI() { return ((HttpServletRequest)super.getRequest()).getContextPath() + "/j_spring_security_check"; } @Override public String getParameter(String name) { if (name.equals("j_username")) { return username; } if (name.equals("j_password")) { return password; } return super.getParameter(name); } } * 변경 코드 (Filter인 경우) : public class EgovSpringSecurityLoginFilter implements Filter{ private FilterConfig config; public void init(FilterConfig filterConfig) throws ServletException { this.config = filterConfig; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ... HttpServletRequest httpRequest = (HttpServletRequest)request; HttpServletResponse httpResponse = (HttpServletResponse)response; ... UsernamePasswordAuthenticationFilter springSecurity = null; ApplicationContext act = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()); @SuppressWarnings("rawtypes") Map beans = act.getBeansOfType(UsernamePasswordAuthenticationFilter.class); if (beans.size() > 0) { springSecurity = (UsernamePasswordAuthenticationFilter)beans.values().toArray()[0]; } else { throw new IllegalStateException("No AuthenticationProcessingFilter"); } //springSecurity.setContinueChainBeforeSuccessfulAuthentication(false); // false 이면 chain 처리 되지 않음.. (filter가 아닌 경우 false로...) springSecurity.doFilter( new RequestWrapperForSecurity(request, resultVO.getUserSe() + resultVO.getId() , resultVO.getUniqId()), response, chain); ... } ... } class RequestWrapperForSecurity extends HttpServletRequestWrapper { private String username = null; private String password = null; public RequestWrapperForSecurity(HttpServletRequest request, String username, String password) { super(request); this.username = username; this.password = password; } @Override public String getRequestURI() { return ((HttpServletRequest)super.getRequest()).getContextPath() + "/j_spring_security_check"; } @Override public String getParameter(String name) { if (name.equals("j_username")) { return username; } if (name.equals("j_password")) { return password; } return super.getParameter(name); } }