본문 바로가기
개발/egovframework

[전자정부프레임워크] 스프링시큐리티 로그인 적용(2/2)

by 아크투어 2023. 3. 31.
반응형

1. 개요

 

[전자정부프레임워크] 스프링시큐리티 로그인 적용(1/2)

1. 개요 이번포스팅에서는 전자정부프레임워크에서 제공하는 스프링 시큐리티를 적용해본다. 기본적으로 전자정부프레임워크에서 제공하는 시큐리티는 DB 권한관리/역할관리를 사용해야한다.

arckwon.tistory.com

 


spring security

 

2. 개발환경 및 소스

  • 개발환경은 egovframework 3.10 / jdk1.8 / tomcat8 / mariadb or mysql
  • 공통컴포넌트에서 제공하는 기능을 웬만하면 바꾸지않고 최소한으로 하여 적용된 소스이다.
  • 지금부터 변경해야 할부분 / 추가해야 할부분을 천천히 알아보자.
  • 일부 파일의 경로가 기존과 다를수도있다. java 파일명은 그대로이기 때문에 package 경로는 본인들 원하는 위치로 하길 바란다.

 

  • pom.xml 스프링시큐리티 관련 rte버전

pom.xml

 

  • globals.properties
# 권한 인증방식(dummy, session, security) - 사용자의 로그인시 인증 방식을 결정함
Globals.Auth = security

 

  • context-egovuserdetailshelper.xml
  • 위 1,2번은 주석으로 처리하고 시큐리티(Security) 인증을 사용
<!-- 1.더미(Dummy) 인증 
<beans profile="dummy"> 
	<bean id="egovUserDetailsHelper" class="egovframework.com.cmm.util.EgovUserDetailsHelper" 
			p:egovUserDetailsService="#{new egovframework.com.cmm.service.impl.EgoDummyUserDetailsServiceImpl()}"/>
</beans>
 -->
	
<!-- 2.세션(Session) 인증  
<beans profile="session">  
	<bean id="egovUserDetailsHelper" class="egovframework.com.cmm.util.EgovUserDetailsHelper" 
			p:egovUserDetailsService="#{new egovframework.com.cmm.service.impl.EgovUserDetailsSessionServiceImpl()}"/>
</beans>
-->
	
<!-- 3.시큐리티(Security) 인증  -->
<beans profile="security">  
	<bean id="egovUserDetailsHelper" class="egovframework.com.cmm.helper.EgovUserDetailsHelper">
        <property name="egovUserDetailsService" ref="egovUserDetailsSecurityService" />
    </bean>
    <!-- 스프링 시큐리티를 이용한 인증을 사용할 빈 -->
    <bean id="egovUserDetailsSecurityService" class="egovframework.com.cmm.service.EgovUserDetailsSecurityServiceImpl"/>
</beans>

 

  • context-security.xml
  • 여기서 주의할점은 아래역할관리 관련 5개 SQL문은 필수적으로 있어야하고 쿼리 실행시 오류가 발생해도 안된다.
  • 그리고 css html 같은 파일은 시큐리티 사용안함으로 한다.(security="none")
  • loginUrl / logoutSuccessUrl을 잘기억해두자. 나중에 EgovWebApplicationInitializer.java 파일에서 매핑해야한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:egov-security="http://maven.egovframe.go.kr/schema/egov-security"
	xmlns:security="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
		http://maven.egovframe.go.kr/schema/egov-security http://maven.egovframe.go.kr/schema/egov-security/egov-security-3.10.0.xsd">

	<security:http pattern="/css/**" security="none"/>
	<security:http pattern="/html/**" security="none"/>
    <security:http pattern="/images/**" security="none"/>
 	<security:http pattern="/js/**" security="none"/>
 	<security:http pattern="/resource/**" security="none"/>
 	<security:http pattern="\A/WEB-INF/jsp/.*\Z" request-matcher="regex" security="none"/>
	
    <egov-security:config id="securityConfig"
		loginUrl="/login/index"
		logoutSuccessUrl="/login/index"
		loginFailureUrl="/login/index?login_error=1"
		accessDeniedUrl="/common/access"
		
		dataSource="egov.dataSource"
		jdbcUsersByUsernameQuery="SELECT user_id
		                               , user_id AS password
		                               , 1 enabled
		                               , user_nm
		                               , email_adres
		                               , mbtl_no
                                   FROM cms.tb_user
                                  WHERE user_id = ?"
		jdbcAuthoritiesByUsernameQuery="SELECT a.scrty_dtrmn_trget_id as user_id, a.author_cd as authority
                                          FROM cms.tb_cms_author_user a, cms.tb_user b
                                         WHERE a.scrty_dtrmn_trget_id = b.user_id
                                           AND b.user_id = ?"
		jdbcMapClass="egovframework.com.sec.security.common.EgovSessionMapping"

		requestMatcherType="regex"
		hash="plaintext"
		hashBase64="false"

		concurrentMaxSessons="10"
		concurrentExpiredUrl="/main/index"

		errorIfMaximumExceeded="false"

		defaultTargetUrl="/main/index"
		alwaysUseDefaultTargetUrl="true"

		sniff="true"
		xframeOptions="SAMEORIGIN" 
		xssProtection="true" 
		cacheControl="false"
		csrf="false"
		csrfAccessDeniedUrl="/common/csrf"
	/>
	
	<egov-security:secured-object-config id="securedObjectConfig"
		sqlHierarchicalRoles="
			select a.chldrn_role child, a.parnts_role parent
			from tb_cms_role_hierarchy a left join tb_cms_role_hierarchy b
			on a.chldrn_role = b.parnts_role" 
		sqlRolesAndUrl="
			select a.role_pttrn url, b.author_cd authority
			from tb_cms_role_inf a, tb_cms_author_role_ref b
			where a.role_cd = b.role_cd
			and a.role_ty = 'URL' order by a.role_sort"
		sqlRolesAndMethod="
			select a.role_pttrn method, b.author_cd authority
			from tb_cms_role_inf a, tb_cms_author_role_ref b
			where a.role_cd = b.role_cd
			and a.role_ty = 'METHOD' order by a.role_sort"
		sqlRolesAndPointcut="
			select a.role_pttrn pointcut, b.author_cd authority
			from tb_cms_role_inf a, tb_cms_author_role_ref b
			where a.role_cd = b.role_cd
			and a.role_ty = 'POINTCUT' order by a.role_sort"
		sqlRegexMatchedRequestMapping="
			select a.role_pttrn uri, b.author_cd authority
			from tb_cms_role_inf a, tb_cms_author_role_ref b
			where a.role_cd = b.role_cd
			and a.role_ty = 'URL'"	    
	/>

	<egov-security:initializer id="initializer" supportMethod="true" supportPointcut="false" />

</beans>

 

 

  • sec > security 3가지 파일작업을 해야한다.
  • 하나씩 알아보자

spring security

 

 

  • EgovSessionMapping.java
  • 아래사진과 같이 LoginVO와 사용자테이블을 매핑한다.
  • 아래에 매핑된 변수는 나중에 세션정보로 사용할수 있다.
  • password는 실제 패스워드를 값이 아닌 user_id값이다. 프레임워크에서 default로 이렇게 설정해놓았기때문에 그대로사용한다. enabled는 지우면 안된다.

 

  • EgovSpringSecurityLoginFilter.java
  • 불필요한 부분은 삭제하였다.
  • 주의할점은 session.setAttribute("loginVO", loginVO); 할때 빨간색 loginVO명명규칙은 변경하지 않는다.
  • egov.rte 내장함수에 loginVO로 fix되어있다.
  • loginVO = loginService.actionLogin(loginVO); 이부분만 본인소스에 맞게 작업하면 된다.
package egovframework.com.sec.security.filter;

import java.io.IOException;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.context.support.WebApplicationContextUtils;

import egovframework.com.cmm.helper.EgovUserDetailsHelper;
import egovframework.com.cmm.util.EgovMessageSource;
import egovframework.com.common.login.service.LoginService;
import egovframework.com.common.login.service.LoginVO;

public class EgovSpringSecurityLoginFilter implements Filter {

	private FilterConfig config;

	private static final Logger LOGGER = LoggerFactory.getLogger(EgovSpringSecurityLoginFilter.class);

	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

		LOGGER.info("SpringSecurityLoginFilter called...");
		
		String loginURL = config.getInitParameter("loginURL");
		loginURL = loginURL.replaceAll("\r", "").replaceAll("\n", "");

		String loginProcessURL = config.getInitParameter("loginProcessURL");
		loginProcessURL = loginProcessURL.replaceAll("\r", "").replaceAll("\n", "");

		ApplicationContext act = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext());
		LoginService loginService = (LoginService) act.getBean("loginService");
		EgovMessageSource egovMessageSource = (EgovMessageSource) act.getBean("egovMessageSource");

		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;
		HttpSession session = httpRequest.getSession();
		String requestURL = ((HttpServletRequest) request).getRequestURI();
		
		if (EgovUserDetailsHelper.getAuthenticatedUser() == null || requestURL.contains(loginProcessURL)) {

			if (requestURL.contains(loginProcessURL)) {

				String id = httpRequest.getParameter("userId");
				String password = httpRequest.getParameter("userPw");
					
				if ((id == null || "".equals(id)) && (password == null || "".equals(password))) {
					RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginURL);
					httpRequest.setAttribute("loginMessage", "");
					dispatcher.forward(httpRequest, httpResponse);
					return;
				}else if (password == null || password.equals("") || password.length() < 8 || password.length() > 20) {
					httpRequest.setAttribute("loginMessage", egovMessageSource.getMessage("fail.common.login.password",request.getLocale()));
					RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginURL);
					dispatcher.forward(httpRequest, httpResponse);
					return;
				}

				LoginVO loginVO = new LoginVO();
				 
				loginVO.setUserId(id);
				loginVO.setUserPw(password);
					
				try {
					loginVO = loginService.actionLogin(loginVO);
					
					if (loginVO != null && loginVO.getUserId() != null && !loginVO.getUserId().equals("")) {

						session.setAttribute("loginVO", loginVO);
						
						UsernamePasswordAuthenticationFilter springSecurity = null;

						Map<String, UsernamePasswordAuthenticationFilter> beans = act.getBeansOfType(UsernamePasswordAuthenticationFilter.class);
						if (beans.size() > 0) {
							springSecurity = (UsernamePasswordAuthenticationFilter) beans.values().toArray()[0];
							springSecurity.setUsernameParameter("egov_security_username");
							springSecurity.setPasswordParameter("egov_security_password");
							springSecurity.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(request.getServletContext().getContextPath() +"/egov_security_login", "POST"));
						} else {
							throw new IllegalStateException("No AuthenticationProcessingFilter");
						}
						springSecurity.doFilter(new RequestWrapperForSecurity(httpRequest, loginVO.getUserId(), loginVO.getUserId()), httpResponse, chain);
					} else {
						httpRequest.setAttribute("loginMessage", egovMessageSource.getMessage("fail.common.login",request.getLocale()));
						RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginURL);
						dispatcher.forward(httpRequest, httpResponse);
						return;
					}
		        } catch(IllegalArgumentException e) {
		            LOGGER.error("[IllegalArgumentException] : "+ e.getMessage());
				} catch (Exception ex) {
					LOGGER.error("Login Exception : {}", ex.getCause(), ex);
					httpRequest.setAttribute("loginMessage", egovMessageSource.getMessage("fail.common.login",request.getLocale()));
					RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginURL);
					dispatcher.forward(httpRequest, httpResponse);
					return;
				}
				return;
			}
		}
		chain.doFilter(request, response);
	}

	public void init(FilterConfig filterConfig) throws ServletException {
		this.config = filterConfig;
	}
}

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 getServletPath() {
		return ((HttpServletRequest) super.getRequest()).getContextPath() + "/egov_security_login";
	}

	@Override
	public String getRequestURI() {
		return ((HttpServletRequest) super.getRequest()).getContextPath() + "/egov_security_login";
	}

	@Override
	public String getParameter(String name) {
		if (name.equals("egov_security_username")) {
			return username;
		}

		if (name.equals("egov_security_password")) {
			return password;
		}

		return super.getParameter(name);
	}
}

 

  • EgovSpringSecurityLogoutFilter.java
  • 이부분은 크게 변경할것이 없다.
package egovframework.com.sec.security.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EgovSpringSecurityLogoutFilter implements Filter{

	@SuppressWarnings("unused")
	private FilterConfig config;

	private static final Logger LOGGER = LoggerFactory.getLogger(EgovSpringSecurityLogoutFilter.class);

	public void destroy() {}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		String requestURL = ((HttpServletRequest)request).getRequestURI();
		LOGGER.debug(requestURL);
		
		((HttpServletRequest)request).getSession().setAttribute("loginVO", null);
		((HttpServletResponse)response).sendRedirect(((HttpServletRequest)request).getContextPath() + "/egov_security_logout");
	}

	public void init(FilterConfig filterConfig) throws ServletException {
		this.config = filterConfig;
	}
}

 

  • EgovWebApplicationInitializer.java
  • 마지막으로 제일중요한 작업이다. 서블릿 2.0대 버전 에서는 시큐리티 필터를 web.xml에서 작성을 하였으나 3점대 버전에서는 java파일인 해당파일에서 설정을 한다.
  • 시큐리티 영역에서 불필요한 부분은 삭제하고 아래코드만 유지한다.
  • context-security.xml 파일에서 작성한 loginURL / loginProcessURL / 로그아웃 URL 등을 아래코드에 넣는다.
/** 스프링 시큐리티 START **/
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
springSecurityFilterChain.addMappingForUrlPatterns(null, false, "*");
servletContext.addListener(new org.springframework.security.web.session.HttpSessionEventPublisher());
		
FilterRegistration.Dynamic egovSpringSecurityLoginFilter = servletContext.addFilter("egovSpringSecurityLoginFilter", new EgovSpringSecurityLoginFilter());
egovSpringSecurityLoginFilter.setInitParameter("loginURL", "/login/index");
egovSpringSecurityLoginFilter.setInitParameter("loginProcessURL", "/login/login_process");
egovSpringSecurityLoginFilter.addMappingForUrlPatterns(null, false, "/login/*");
		
FilterRegistration.Dynamic egovSpringSecurityLogoutFilter = servletContext.addFilter("egovSpringSecurityLogoutFilter", new EgovSpringSecurityLogoutFilter());
egovSpringSecurityLogoutFilter.addMappingForUrlPatterns(null, false, "/login/logout");
/** 스프링 시큐리티 END **/

 

 

3. 테스트

  • 테스트관련 소스이다. 분량이 많아서 부분적으로만 작성한다.
# login.jsp
<input type="text" name="userId" id="userId" placeholder="아이디" value=""/>
<input type="password" name="userPw" id="userPw" placeholder="비밀번호" value="">
document.loginForm.action="/login/login_process";
document.loginForm.submit();



# main.jsp
${userId}님 환영합니다.
로그아웃시 스크립트 : document.location.href="/login/logout";



# MainController.java
# /main/index URL 호출시
try {
	boolean isAuthenticated = EgovUserDetailsHelper.isAuthenticated();	
	LoginVO user = (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser();
			
	if(!isAuthenticated) {
		ModelAndView modelAndView = new ModelAndView("redirect:/login/index");
		throw new ModelAndViewDefiningException(modelAndView);
	}
	model.addAttribute("userId", user.getUserId());
			
} catch (Exception e) {
	e.getMessage();
}
return "main/index";


# 로그인SQL
SELECT a.user_id
     , a.user_pw
     , a.user_nm
     , a.email_adres
     , a.mbtl_no
  FROM cms.tb_user a
 WHERE a.user_id = #{userId}
   AND a.user_pw = #{userPw}

 

4. 마치며

  • egovframework에서 제공하는 스프링 시큐리티 자체가 포스팅하기 사실 어렵다. 확인해야할부분, 데이터베이스 부분도 필수적으로 사용해야되고, 그러하다.
  • 위내용대로 따라해도 안되는 경우가 발생할수 있다. 개개인의 개발환경이 다 다르다. 최대한 도움이 되었으면 좋겠다.

 

반응형