-> 차근차근 개발하는 과정을 올릴려고 했는데 중간 중간에 과정을 올리기가 힘들어서 전체 소스를 한번에 정리해서 이번장에 올리겠습니다.

 

게시판 생성에 앞서 테이블 생성
CREATE TABLE `tb_board` (
  `BOARD_IDX` int NOT NULL AUTO_INCREMENT COMMENT '인덱스',
  `BOARD_TITLE` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '타이틀',
  `BOARD_CONTENT` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '내용',
  `REG_ID` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '등록자',
  `VIEW_COUNT` int DEFAULT '0' COMMENT '조회수',
  `USE_YN` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'Y' COMMENT '사용여부',
  `REG_DATE` datetime DEFAULT NULL COMMENT '등록일자',
  PRIMARY KEY (`BOARD_IDX`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COMMENT='게시판'

 

admin 프로젝트 java 파일들

 

AdminApllication.java
package com.board.admin;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

import com.board.common.Constants;

@SpringBootApplication
@ComponentScan(basePackages = Constants.APP_DEFAULT_PACKAGE_NAME)
@MapperScan(basePackages = Constants.MAPPER_PACKAGE)
public class AdminApplication {

	public static void main(String[] args) {
		SpringApplication.run(AdminApplication.class, args);
	}
}

-> 해당 파일은 admin run파일입니다.

 

ServletInitializer.java
package com.board.admin;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

   @Override
   protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
      return application.sources(AdminApplication.class);
   }

}

-> 해당파일은 war파일로 배포를 진행할경우 사용합니다. Spring Boot의 전통방식인 jar를 사용할경우에는 사용하지 않습니다.

 

Constants.java
package com.board.common;

/** 서비스에 사용되는 공통변수 */

public final class Constants {
	
	//다국어 Cookie 이름
	public static final String APP_LOCALE_COOKIE = "APP_LOCALE";
	
	//프로젝트 패키지 이름
	public final static String APP_DEFAULT_PACKAGE_NAME = "com.board";
	
	//dao 패키지 경로
	public final static String MAPPER_PACKAGE = Constants.APP_DEFAULT_PACKAGE_NAME+".dao";
	
	//Tiles xml 경로
	public final static String[] TILES_LAYOUT_XML_PATH = {
			"WEB-INF/tiles.xml"
	};
	
	//Runtime에서 JSP의 refresh 적용 여부
	public final static boolean REFRESH_JSP_ON_RUNTIME = true;
	
	/** 정적 리소스 종류 */
	private final static String[] STATIC_RES = {
			 "/js"
			,"/css"
			,"/images"
			,"/favicon"
			,"/template"
			,"/font"
			,"/robots.txt"
			,"/favicon.ico"
	};
	
	/** 정적 리소스 매핑 URL 패턴 (위에꺼랑 순서 맞아야함) */
	public final static String[] STATIC_RESOURCES_URL_PATTERNS = {
			 STATIC_RES[0]+"/**"
			,STATIC_RES[1]+"/**"
			,STATIC_RES[2]+"/**"
			,STATIC_RES[3]+"/**"
			,STATIC_RES[4]+"/**"
			,STATIC_RES[5]+"/**"
			,STATIC_RES[6]
			,STATIC_RES[7]+"/**"
	};
	
	/** 정적 리소스 기본 페이지 classpath */
	private static final String STATIC_PATH = "classpath:/static";
	
	/** 정적 리스스 위치 */
	public final static String[] CLASSPATH_RESOURCE_LOCATIONS = {
			STATIC_PATH+STATIC_RES[0]+"/"
			,STATIC_PATH+STATIC_RES[1]+"/"
			,STATIC_PATH+STATIC_RES[2]+"/"
			,STATIC_PATH+STATIC_RES[3]+"/"
			,STATIC_PATH+STATIC_RES[4]+"/"
			,STATIC_PATH+STATIC_RES[5]+"/"
			,STATIC_PATH+STATIC_RES[6]
			,STATIC_PATH+STATIC_RES[7]+"/"
	};
	
	
	public static final int DEFAULT_PAGE_NUMBER = 1;
	public static final int DEFAULT_PAGE_SIZE = 10;
	
	// server health check url
	public static final String HEALTH_CHECK_URL = "/healthCheck";
	
	
}

-> 해당 파일은 서비스에서 사용할 공통 변수를 설정해놓은 파일입니다.

 

CoTopComponent.java
package com.board.common;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;

import com.google.gson.JsonObject;

/* 최상위 컴포넌트 */

public abstract class CoTopComponent {
	protected void writeResponse(HttpServletResponse res, JsonObject jsonObject) throws IOException{
		writeResponse(res, jsonObject.toString());
	}
	
	protected void writeResponse(HttpServletResponse res, String message) throws IOException{
		res.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE);
		
		PrintWriter pw = res.getWriter();
		pw.write(message);
		pw.close();
	}
	
	protected String makePageDispatcherUrl(String requestUrl, String dispatcherPath) {
		return Url.TILES_AJAX + dispatcherPath + requestUrl.substring(requestUrl.lastIndexOf("/"));
	}
	
}

-> 해당 파일은 공통 메소드를 설정한 파일입니다.

 

Paginator.java
package com.board.common;

import java.io.Writer;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import lombok.Setter;

/**
 * 
 */
@Setter
public class Paginator extends SimpleTagSupport {
   
   // ******************************************************************
   // *                                           페이지 네비게이터 템플릿 (Debult)                                     *
   // ******************************************************************
    private String MainSB   ="";
    private String FistB   ="<li><a class='#$class' href='javascript:;' onclick='#$goPage(##);'>#$title</a></li>";
    private String PrevB   ="<li><a class='#$class' href='javascript:;' onclick='#$goPage(##);'>#$title</a></li>";
    private String CurB      ="<li><a class='#$class' href='javascript:;'>#$title</a></li>";
    private String OthB      ="<li><a class='#$class' href='javascript:;' onclick='#$goPage(##);'>#$title</a></li>";
    private String NextB   ="<li><a class='#$class' href='javascript:;' onclick='#$goPage(##);'>#$title</a><li>";
    private String LastB   ="<li><a class='#$class' href='javascript:;' onclick='#$goPage(##);'>#$title</a></li>";
    private String MainEB   ="";
    
   String goPageScript = "goPage";                     // 이동 이벤트 Script Function Name (해당 스크립트의 인자는 페이지 번호만 받아야함, 추가 검색조건은 함수내에서 처리할 것)   (설정은 JSTL 속성으로 입력)
    int curPage;                  // 현재 페이지                                 (설정은 JSTL 속성으로 입력)
    int totPages;                  // 전체 페이지                                 (설정은 JSTL 속성으로 입력)
    int blockSize = 10;               // 블록 크기                                    (설정은 JSTL 속성으로 입력)
    int skipSize = 1;               // 이전/다음 선택시 이동할 페이지 갯수            (설정은 JSTL 속성으로 입력)
    
    // Options
    boolean goFirstUse = false;                     // 처음 페이지 사용 여부      (설정은 JSTL 속성으로 입력)
    boolean goLastUse = false;                     // 마지막 페이지 사용 여부      (설정은 JSTL 속성으로 입력)
    
    // CSS Class
    private String paginatorBlockClass = "";         // paginator 전체 메인 블록   (설정은 JSTL 속성으로 입력)
    private String goFirstClass = "";                  // 처음 페이지               (설정은 JSTL 속성으로 입력)
    private String prevPageClass = "prev";               // 이전 페이지               (설정은 JSTL 속성으로 입력)
    private String curPageClass = "on";               // 현재 페이지               (설정은 JSTL 속성으로 입력)
    private String defaultPageClass = "";            // 다른 페이지               (설정은 JSTL 속성으로 입력)
    private String nextPageClass = "next";               // 다음 페이지                (설정은 JSTL 속성으로 입력)
    private String goLastClass = "";                  // 마지막 페이지               (설정은 JSTL 속성으로 입력)
    
    // Label
    private String goFirstLabel = "처음";         // 처음 페이지 타이틀            (설정은 JSTL 속성으로 입력)
    private String prevPageLabel = "이전";         // 이전 페이지 타이틀            (설정은 JSTL 속성으로 입력)
    private String nextPageLabel = "다음";         // 다음 페이지 타이틀            (설정은 JSTL 속성으로 입력)
    private String goLastLabel = "마지막";         // 마지막 페이지 타이틀         (설정은 JSTL 속성으로 입력)
    
    private Writer getWriter() {
        JspWriter out = getJspContext().getOut();
        return out;
    }
 
    @Override
    public void doTag() throws JspException {
        Writer out = getWriter();
        
        // Spring pageable의 page 번호가 0부터 시작하기 때문에 +1하고
        // 실제로 페이지 링크를 생성하는 부분에서 다시 -1 한다.
        curPage ++;
        
        boolean lastPage = curPage == totPages;
        int pgStart = Math.max(curPage - blockSize / 2, 1);
        int pgEnd = pgStart + blockSize;
        if (pgEnd > totPages + 1) {
            int diff = pgEnd - totPages;
            pgStart -= diff - 1;
            if (pgStart < 1)
                pgStart = 1;
            pgEnd = totPages + 1;
        }
        
        if(totPages > 0) {
            try {
                out.write(MainSB.replace("#$class", paginatorBlockClass));
                
                // 처음
                if (goFirstUse) out.write( replacePageStr(FistB, goFirstClass, goPageScript, 1, goFirstLabel) );
     
                // 이전
                if (curPage > 1){
                   int moveSize = curPage-skipSize;
                   if (moveSize < 1) moveSize = 1;
                   out.write( replacePageStr(PrevB, prevPageClass, goPageScript, moveSize, prevPageLabel) );
                }
     
                
                for (int i = pgStart; i < pgEnd; i++) {
                   // 현재 페이지
                    if (i == curPage) out.write( replacePageStr(CurB, curPageClass, goPageScript, curPage, String.valueOf(curPage)) );
                    // 기타 페이지
                    else out.write( replacePageStr(OthB, defaultPageClass, goPageScript, i, String.valueOf(i)) );
                }
                
     
                // 다음
                if (!lastPage){
                   int moveSize = curPage + skipSize;
                   if (moveSize > totPages) moveSize = totPages;
                   out.write( replacePageStr(NextB, nextPageClass, goPageScript, moveSize, nextPageLabel) );
                }
                
                // 마지막
                if (goLastUse) out.write( replacePageStr(LastB, goLastClass, goPageScript, totPages, goLastLabel) );
     
               out.write(MainEB);
     
            } catch (java.io.IOException ex) {
                throw new JspException("Error in Paginator tag", ex);
            }           
        }

    }

   private String replacePageStr(String srcStr, String classStr, String scriptStr, int pageNum, String labelStr) {
      pageNum --;
      return srcStr.replace("#$class", classStr).replace("#$goPage", scriptStr).replace("##", String.valueOf(pageNum)).replace("#$title", labelStr);
   }
   
   
}

-> 해당파일은 페이징 처리 관련된 java 파일입니다. 이파일에서 버튼의 html 형식을 수정할수 있습니다.

 

Url.java
package com.board.common;

/* api url 정의 */
public final class Url {
	public static final String TILES_ROOT = "/tiles/view";
	public static final String TILES_SINGLE = "/tiles/single";
	public static final String TILES_AJAX = "/tiles/ajax";
	
	/* 로그인 */
	public static final class AUTH {
		
		/* 로그인 url */
		public static final String LOGIN = "/auth/login";
		
		/* 로그인 jsp */
		public static final String LOGIN_JSP = TILES_SINGLE + "/auth/login";
		
		/* 회원가입 url */
		public static final String JOIN = "/auth/join";
		
		/* 회원가입 jsp */
		public static final String JOIN_JSP = TILES_ROOT + "/auth/join";
	
		/* 사용자 등록 */
		public static final String INSERT_USER = "/auth/insertUser";
		
		/* 로그인 인증 요청 */
		public static final String LOGIN_PROC = "/auth/login-proc";
		
		/* 로그아웃 요청 */
		public static final String LOGOUT_PROC = "/auth/logout-proc";
		
	}
	
	/* 메인 화면 */
	public static final class MAIN {
		
		public static final String _MAIN_AJAX_ROOT_PATH = "/main/ajax";
		
		/* 메인 url */
		public static final String MAIN = "/";
		
		/* 메인 jsp */
		public static final String MAIN_JSP = TILES_ROOT + "/main/list";
		
		/* 메인 리스트 ajax */
		public static final String MAIN_LIST_AJAX = _MAIN_AJAX_ROOT_PATH + "/list-view";
		
		/* 메인 글쓰기 */
		public static final String MAIN_WRITE = "/board/write";
		
		/* 메인 글쓰기 jsp */
		public static final String MAIN_WRITE_JSP = TILES_ROOT + "/main/write";
		
		/* 메인 수정화면 */
		public static final String MAIN_UPDATE = "/board/update";
		
		/* 메인 글쓰기 jsp */
		public static final String MAIN_UPDATE_JSP = TILES_ROOT + "/main/update";
		
		
	}
	
}

-> 해당 파일은 Controller에 사용될 url을 모아놓은 class 파일입니다.

 

AdminAuthenticationProvider.java
package com.board.config;

import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import com.board.dao.UserMapper;
import com.board.entity.UserAuthority;
import com.board.entity.UserInfo;
import com.board.exception.UserAuthException;
import com.board.exception.UserIdException;
import org.springframework.security.crypto.password.PasswordEncoder;

/** 로그인 provider */
@Component
public class AdminAuthenticationProvider implements AuthenticationProvider{
	
	@Autowired 
	UserMapper userMapper;
	
	@Autowired
	PasswordEncoder passwordEncoder;
	
	@SuppressWarnings("unchecked")
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
	UsernamePasswordAuthenticationToken authToken = (UsernamePasswordAuthenticationToken) authentication;
		
		//아이디
		String id = (authToken.getName()).toUpperCase();
		
		//비밀번호
		String pwd = (String) authToken.getCredentials();
		
		//해당 사용자 정보 조회
		UserInfo userInfo = userMapper.getUserInfo(id);
		
		//해당 사용자가 있을경우
		if(userInfo != null ) {	
			//입력한 비밀번호가 현재 비밀번호와 같지 않으면
			if(!(passwordEncoder.matches(pwd, userInfo.getPassword()))) {
				throw new UserIdException("접속 할 수 없습니다. \n아이디 또는 비밀번호를 확인해주세요.");
			}
			
			//입력한 비밀번호가 현재 비밀번호와 같으면
			else {
				List<UserAuthority> authorities = new ArrayList<>();
				
				//권한 조회
				UserAuthority getUserAuthorities = userMapper.getUserAuthorities(authToken.getName());
				
				//권한이 있을경우
				if(getUserAuthorities != null) {
					UserAuthority auth = new UserAuthority();
					auth.setAuthority(getUserAuthorities.getAuthority());
					auth.setAuthorityNm(getUserAuthorities.getAuthorityNm());
					authorities.add(auth);
					userInfo.setAuthority(getUserAuthorities.getAuthority());
					userInfo.setAuthorityNm(getUserAuthorities.getAuthorityNm());
				}
				
				//권한이 없을 경우
				else {
					throw new UserAuthException("접속권한이 없습니다. \n관리자에게 권한을 요청해주세요.");
				}
			}
		}
		
		//해당 사용자가 없을경우
		else {
			throw new UserIdException("접속 할 수 없습니다. \n아이디 또는 비밀번호를 확인해주세요.");
		}
		
		List<GrantedAuthority> authorities = (List<GrantedAuthority>) userInfo.getAuthorities();
		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userInfo, "1", authorities);
		
		return token;
	}



	@Override
	public boolean supports(Class<?> authentication) {
		return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
	}
	
}

-> 해당 파일은 로그인을 시도하였을때 provider에서 로그인이 정상적인지 체크하는 부분입니다.

 

AjaxInterceptor.java
package com.board.config;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AjaxInterceptor implements HandlerInterceptor {

   /* (non-Javadoc)
    * @see org.springframework.web.servlet.HandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
    */
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {
      boolean result = true;
      //tmptmp
//      if(false) {
         try {
            SecurityContext sc = SecurityContextHolder.getContext();
            Authentication auth = sc.getAuthentication();
            if(auth == null || auth.getPrincipal() == null || "anonymousUser".equals(auth.getPrincipal())) {
               //Ajax 콜인지 아닌지 판단
               if(isAjaxRequest(request)){
                  response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                  return false;
               }
            }
         } catch (Exception e) {
            e.printStackTrace();
            log.debug(e.getMessage());
            return false;
         }
//      }
      return result;
   }

   /**
    * Checks if is ajax request.
    *
    * @param req the req
    * @return true, if is ajax request
    */
   private boolean isAjaxRequest(HttpServletRequest req) {
      /*
      // 사용자 인증이 필요 없는 URL 체크
      String requestUri = RequestUtil.getRequestURI(req);
      if(requestUri.startsWith("/auth/")) {
         return false;
      }
      */
      String ajaxHeader = "AJAX";
      return req.getHeader(ajaxHeader) != null && req.getHeader(ajaxHeader).equals(Boolean.TRUE.toString());
   }

   /* (non-Javadoc)
    * @see org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.web.servlet.ModelAndView)
    */
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
         ModelAndView modelAndView) throws Exception {
   }

   /* (non-Javadoc)
    * @see org.springframework.web.servlet.HandlerInterceptor#afterCompletion(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
    */
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
         throws Exception {
      // TODO Auto-generated method stub
   }
}

-> 로그인을 하지 않았을경우에는 통신을 할수없게 제어하는 인터셉터입니다.

 

LoginFailureHandler.java
package com.board.config;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import com.board.common.CoTopComponent;
import com.board.exception.UserAuthException;
import com.board.exception.UserIdException;
import com.google.gson.JsonObject;

import lombok.extern.slf4j.Slf4j;

/* 로그인 실패시 타는 핸들러 */

@Slf4j
@Component
public class LoginFailureHandler extends CoTopComponent implements AuthenticationFailureHandler {

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		writeResponse(response, parseException(request.getParameter("un"), exception));
	}
	
	private JsonObject parseException(String userName, AuthenticationException exception) {
		String errCode = "99";
		String errMsg = exception.getMessage();

		//존재하지 않는 아이디
		if( exception instanceof UserIdException) {
			log.error("존재하지않는 아이디입니다.");
		}
		
		//권한이 없을경우
		else if( exception instanceof UserAuthException) {
			log.error("권한이 없는 아이디입니다.");
		}
		
		JsonObject result = new JsonObject();
		result.addProperty("resultCode", errCode);
		result.addProperty("resultMessage", errMsg);
		return result;
	}
}

-> 로그인 실패시 타는 핸들러

 

LoginSuccessHandler.java
package com.board.config;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.board.common.CoTopComponent;
import com.board.entity.UserInfo;
import com.google.gson.JsonObject;

/* 로그인 성공시 타는 handler */
@Component
public class LoginSuccessHandler extends CoTopComponent implements AuthenticationSuccessHandler {
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication auth) throws IOException, ServletException {
		
		//default 성공
		String resultCode = "00";	
		HttpSession session = request.getSession(true);
		session.setMaxInactiveInterval(60 * 60 * 3);
		session.setAttribute("sessUserInfo",((UserInfo) auth.getPrincipal()));
		
	    //Response 결과 값을 넣어줌
	    JsonObject loginResult = new JsonObject();
	    loginResult.addProperty("resultCode", resultCode);
	    loginResult.addProperty("targetUrl", request.getContextPath()+"/");
	    
	    //응답 전송
	    writeResponse(response, loginResult);
	}
	
}

-> 로그인 성공시 타는 핸들러

 

SecurityConfig.java
package com.board.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.board.common.Constants;
import com.board.common.Url;

@Configuration
@EnableWebSecurity
@ComponentScan(value = Constants.APP_DEFAULT_PACKAGE_NAME)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired private LoginSuccessHandler loginSuccessHandler;
   @Autowired private LoginFailureHandler loginFailureHandler;
//   @Autowired private CustomerUserDetailsService customUserDetailsService;

   /**
    * Configure.
    *
    * @param auth the auth
    * @throws Exception the exception
    */
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {

//      auth.userDetailsService(customUserDetailsService);

      auth.authenticationProvider(new AdminAuthenticationProvider());
      super.configure(auth);
   }

   /**
    * http 요청 검사.
    *
    * @param http the http
    * @throws Exception the exception
    */
   @Override
   protected void configure(HttpSecurity http) throws Exception {

      http
      // replay 어택을 막기 위한 csrf 토큰의 생성을 비활성화(disabled) 처리
      .csrf().disable()
      // pdf viewer 에서 'X-Frame-Options' to 'DENY' 대응
      .headers().frameOptions().disable().and()
      // 요청에 대한 권한 매핑
      .authorizeRequests()
         .antMatchers( "/auth/**" ).permitAll()                // 패스워드찾기,회원가입
         .antMatchers( "/" ).permitAll()
         .antMatchers( "/**/ajax/**" ).permitAll()
         .antMatchers( "/board/**" ).permitAll()
         .antMatchers( "/resource/**/images/**" ).permitAll()   // image
         .anyRequest().authenticated()                     // 모든 요청에 대해 권한 확인이 필요
//         .anyRequest().permitAll()
      .and()
      // 로그인 화면 설정
      .formLogin()
         .permitAll()
         .loginPage( Url.AUTH.LOGIN )
         .loginProcessingUrl( Url.AUTH.LOGIN_PROC )
         .successHandler( loginSuccessHandler )
         .failureHandler( loginFailureHandler )
         .usernameParameter( USERNAME_PARAM )
         .passwordParameter( PASSWORD_PARAM )
      .and()
      .logout()
         .logoutUrl( Url.AUTH.LOGOUT_PROC )
         .invalidateHttpSession(true)
         .deleteCookies("JSESSIONID")
      .and()
      // 세션 관리
      .sessionManagement()
         .maximumSessions(200) /* session 허용 갯수 */
         .expiredUrl(Url.AUTH.LOGIN) /* session 만료시 이동 페이지*/
         .sessionRegistry(sessionRegistry())                     // 세션을 목록에 담아둠
         .maxSessionsPreventsLogin(true) /* 동일한 사용자 로그인시 x, false 일 경우 기존 사용자 */
   ;
   }

   /**
    * web요청 검사.
    *
    * @param web the web
    * @throws Exception the exception
    */
   @Override
   public void configure(WebSecurity web) throws Exception {
      // Security Debug
//      web.debug(true);

      web
         .ignoring()
            // static 리소스 경로는 webSecurity 검사 제외
            .antMatchers( Constants.STATIC_RESOURCES_URL_PATTERNS )
            .antMatchers( Constants.HEALTH_CHECK_URL )
            .antMatchers(HttpMethod.GET, "/exception/**")
      ;
      super.configure(web);
   }

   /**
    * PasswordEncoder.
    *
    * @return the password encoder
    */
   @Bean
   public PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
   }

   @Bean
   public SessionRegistry sessionRegistry() {
      return new SpringSecuritySessionRegistImpl();
   }

   /**
    * AuthenticationProvider
    * <br>관리자의 계정정보를 통해 로그인 인증을 처리합니다.
    *
    * @return the authentication provider
    * @see kr.mediaflow.fdwm.config.DatabaseConfig
    */
//   @Bean
//   public AuthenticationProvider daoAuthenticationProvider() {
//      DaoAuthenticationProvider impl = new DaoAuthenticationProvider();
//      impl.setUserDetailsService(customUserDetailsService);
//      impl.setPasswordEncoder(new BCryptPasswordEncoder());
//      impl.setHideUserNotFoundExceptions(false);
//      return impl;
//   }

   /**  관리자 아이디 파라미터 이름 : {@value #USERNAME_PARAM}. */
   public static final String USERNAME_PARAM = "un";

   /**  관리자 비밀번호 파라미터 이름 : {@value #PASSWORD_PARAK}. */
   public static final String PASSWORD_PARAM = "up";
}

-> web에서 해당 url은 특정한 보안 절차를 거친 사용자들만 접속할수 있게 설정하는 config파일입니다.

 

SpringSecuritySessionRegistImpl.java
package com.board.config;

import org.springframework.security.core.session.SessionRegistryImpl;

public class SpringSecuritySessionRegistImpl extends SessionRegistryImpl {
}

 

TilesConfig.java
package com.board.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import com.board.common.Constants;

@Configuration
public class TilesConfig {
	
	 @Bean
	 public TilesConfigurer tilesConfigurer() {
		 final TilesConfigurer configurer = new TilesConfigurer();
		 configurer.setDefinitions(Constants.TILES_LAYOUT_XML_PATH);
		 configurer.setCheckRefresh(true);
		 return configurer;
	 }

	   /**
	    * UrlBased 뷰 리졸버.
	    *
	    * @return urlViewResolver
	    */
	   @Bean
	   public UrlBasedViewResolver urlViewResolver() {
	      UrlBasedViewResolver tilesViewResolver = new UrlBasedViewResolver();
	      tilesViewResolver.setViewClass(TilesView.class);
	      return tilesViewResolver;
	   }
}

-> tiles 설정파일입니다.

 

WebApplicationContextLocator.java
package com.board.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

@Configuration
public class WebApplicationContextLocator implements ServletContextInitializer {
	private static WebApplicationContext webApplicationContext;

	public static WebApplicationContext getCurrentWebApplicationContext() {
		return webApplicationContext;
	}
	
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
	}
}

 

WebConfig.java
package com.board.config;

import java.util.List;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.resource.PathResourceResolver;
import com.board.common.Constants;
import com.navercorp.lucy.security.xss.servletfilter.XssEscapeServletFilter;

/**
 * The Class WebConfig.
 */
@Configuration
@EnableWebMvc
@EnableCaching
@ComponentScan
public class WebConfig implements WebMvcConfigurer {


   /**
    * 인터셉터 관리.
    *
    * @param registry the registry
    */
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
//      registry.addInterceptor(new LogInterceptor()); // 로그 인터셉터
      registry.addInterceptor(localeChangeInterceptor()); // 로케일 변경 인터셉터
      registry.addInterceptor(new AjaxInterceptor()).excludePathPatterns("/error", "/error/**","/viewer","/viewer/**", "/css/**", "/js/**", "/images/**");
   }

   /**
    * Resource 핸들러.
    *
    * @param registry the registry
    */
   @Override
   public void addResourceHandlers(ResourceHandlerRegistry registry) {
      registry.addResourceHandler( Constants.STATIC_RESOURCES_URL_PATTERNS)
      .addResourceLocations(Constants.CLASSPATH_RESOURCE_LOCATIONS)
      .setCachePeriod(60*60*24*7)// 60*60*24*7 => 일주일
      .resourceChain(true)
      .addResolver(new PathResourceResolver());
   }
   
   /**
    * Adds the argument resolvers.
    *
    * @param argumentResolvers the argument resolvers
    */
   @Override
   public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
        resolver.setFallbackPageable(PageRequest.of(Constants.DEFAULT_PAGE_NUMBER, Constants.DEFAULT_PAGE_SIZE));
        argumentResolvers.add(resolver);
        WebMvcConfigurer.super.addArgumentResolvers(argumentResolvers);
   }
   
   /**
    * Active profile.
    *
    * @param profile the profile
    * @return the string
    */
   @Bean
   @Value("${spring.profiles.active}")
   public String activeProfile(String profile) {
      return profile;
   }
   
   /**
    * Locale Resolver.
    *
    * @return the locale resolver
    */
   @Bean
   public LocaleResolver localeResolver() {
//      SessionLocaleResolver slr = new SessionLocaleResolver();
//      slr.setDefaultLocale(Locale.KOREAN);
      CookieLocaleResolver slr = new CookieLocaleResolver();
      slr.setDefaultLocale(Locale.KOREAN);
      slr.setCookieName(Constants.APP_LOCALE_COOKIE);
      return slr;
   }

   /**
    * Locale Change Interceptor.
    *
    * @return the locale change interceptor
    */
   @Bean
   public LocaleChangeInterceptor localeChangeInterceptor() {
      LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
      lci.setParamName("lang");
      return lci;
   }
   
   
    @Bean
    public FilterRegistrationBean<XssEscapeServletFilter> filterRegistrationBean() {
        FilterRegistrationBean<XssEscapeServletFilter> filterRegistration = new FilterRegistrationBean<>();
        filterRegistration.setFilter(new XssEscapeServletFilter());
        filterRegistration.setOrder(1);
        filterRegistration.addUrlPatterns("/*");
        return filterRegistration;
    }

//   /**
//    * Message source.
//    *
//    * @return the message source
//    */
//   @Bean
//   public MessageSource messageSource() {
//      ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//      // WEB-INF 밑에 해당 폴더에서 properties를 찾는다.
//      messageSource.setBasename("message/messages");
//      messageSource.setDefaultEncoding("UTF-8");
//      return messageSource;
//   }

}

 

LoginController.java
package com.board.controller;

import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.board.common.Url;
import com.board.entity.UserInfo;
import com.board.service.LoginService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class LoginController {

	@Autowired
	LoginService loginService;
	
	//로그인 화면
	@GetMapping(value= {Url.AUTH.LOGIN})
	public String login() {
		
		return Url.AUTH.LOGIN_JSP;
	}
	
	//회원가입 화면
	@GetMapping(Url.AUTH.JOIN)
	public String join() {
		return Url.AUTH.JOIN_JSP;
	}
	
	//사용자 등록
	@PostMapping(Url.AUTH.INSERT_USER)
	@ResponseBody
	public Map<String, Object> insertUser(@ModelAttribute UserInfo userInfo) {
		
		//회원 등록
		return loginService.checkLoginInsert(userInfo);
	}
	
}

-> 로그인 controller입니다.

 

MainController.java
package com.board.controller;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.board.common.CoTopComponent;
import com.board.common.Constants;
import com.board.common.Url;
import com.board.common.Url.MAIN;
import com.board.dao.MainMapper;
import com.board.service.MainService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class MainController extends CoTopComponent {

	@Autowired MainService mainService;
	@Autowired MainMapper mainMapper;
	
	//profile
	@Value("${spring.profile.active")
	private String profile;
	
	//메인화면
	@GetMapping(Url.MAIN.MAIN)
	public String main() {
		
		return Url.MAIN.MAIN_JSP;
	}
	
	//메인화면 리스트 ajax
	@GetMapping(Url.MAIN.MAIN_LIST_AJAX)
	public String mainListAjax(@RequestParam Map<String, Object> params
								,Pageable pageable
								,Model model
			) {
		
		model.addAttribute("boardList", mainService.selectBoardList(params, pageable, Integer.parseInt(params.get("size").toString())));
		model.addAttribute("resultDataTotal", mainMapper.selectBoardListCnt(params));
		
		return makePageDispatcherUrl(MAIN.MAIN_LIST_AJAX, MAIN._MAIN_AJAX_ROOT_PATH);
	}
	
	
	
	//글쓰기화면
	@GetMapping(Url.MAIN.MAIN_WRITE)
	public String write() {
		return Url.MAIN.MAIN_WRITE_JSP;
	}
	
	//글등록
	@ResponseBody
	@PostMapping(Url.MAIN.MAIN_WRITE)
	public Map<String, Object> writeSubmit(@RequestBody Map<String, Object> params) {
		log.info("params={}", params);
		mainMapper.insertBoard(params);
		
		return params;
	}
	
	//수정화면
	@GetMapping(Url.MAIN.MAIN_UPDATE+"/{boardIdx}")
	public String update(@PathVariable("boardIdx") String boardIdx, Model model) {
		log.info("boardIdx={}", boardIdx);
		model.addAttribute("boardInfo", mainMapper.selectBoard(boardIdx));
		model.addAttribute("boardIdx", boardIdx);
		
		int viewCount = (int) mainMapper.selectBoard(boardIdx).get("viewCount");
		
		log.info("viewCount={}", viewCount);
		
		Map<String, Object> params = new HashMap<String, Object>();
		viewCount++;
		params.put("boardIdx", boardIdx);
		params.put("viewCount", viewCount);
		
 		//조회수 업데이트
		mainMapper.updateViewCount(params);
		
		return Url.MAIN.MAIN_UPDATE_JSP;
	}
	
	//글수정
	@ResponseBody
	@PostMapping(Url.MAIN.MAIN_UPDATE)
	public Map<String, Object> updateSubmit(@RequestBody Map<String, Object> params) {
		log.info("params={}", params);
		mainMapper.updateBoard(params);
		return params;
	}
	
	//server health check
	@RequestMapping(value= { Constants.HEALTH_CHECK_URL }, produces=MediaType.TEXT_HTML_VALUE)
	   public void healthCheck( HttpServletRequest req, HttpServletResponse res ) throws IOException {

	      String ip = req.getHeader("X-FORWARDED-FOR");
	      if (ip == null) ip = req.getRemoteAddr();

	      PrintWriter pw = res.getWriter();
	      pw.write(" - Active Profile : " + profile + "\n");
	      pw.write(" - Client IP : " + ip);
	      pw.close();
	   }
	
}

-> 메인 Controller입니다.

 

UserAuthException.java
package com.board.exception;

import org.springframework.security.core.AuthenticationException;

/**
 * The Class CUserNotFoundException.
 */
public class UserAuthException extends AuthenticationException {
   
   /** The Constant serialVersionUID. */
   private static final long serialVersionUID = 1L;
    
    /**
     * Instantiates a new c user not found exception.
     *
     * @param msg the msg
     * @param t the t
     */
    public UserAuthException(String msg, Throwable t) {
        super(msg, t);
    }

    /**
     * Instantiates a new c user not found exception.
     *
     * @param msg the msg
     */
    public UserAuthException(String msg) {
        super(msg);
    }
}

-> 사용자 로그인 권한 관련된 exception

 

UserIdException.java
package com.board.exception;

import org.springframework.security.core.AuthenticationException;

/**
 * The Class CUserNotFoundException.
 */
public class UserIdException extends AuthenticationException {
   
   /** The Constant serialVersionUID. */
   private static final long serialVersionUID = 1L;
    
    /**
     * Instantiates a new c user not found exception.
     *
     * @param msg the msg
     * @param t the t
     */
    public UserIdException(String msg, Throwable t) {
        super(msg, t);
    }

    /**
     * Instantiates a new c user not found exception.
     *
     * @param msg the msg
     */
    public UserIdException(String msg) {
        super(msg);
    }
}

-> 사용자 로그인 아이디 접속 관련 exception

 

java 부분은 정리가 끝났습니다. 아래는 jsp와 tld, xml 정리한 부분입니다.

 

header.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/WEB-INF/template/constants.jsp"%>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 
<nav class="navbar navbar-default navbar-static-top">
	<div class="container">
		<div class="navbar-header">
			<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar" aria-expanded="true" aria-controls="navbar">
				<span class="sr-only">Toggle navigation</span>
	            <span class="icon-bar"></span>
	            <span class="icon-bar"></span>
	            <span class="icon-bar"></span>
			</button>
			<a class="navbar-brand" href="#">게시판 예제</a>
        </div>
		<div id="navbar" class="navbar-collapse collapse in" aria-expanded="true" style="">
			<ul class="nav navbar-nav">
				<li class="active"><a href="#">게시판</a></li>
          	</ul>
          	
			<ul class="nav navbar-nav navbar-right">
				<c:choose>
					<c:when test="${empty sessUserInfo.authority}">
						<li><a href="#">회원가입</a></li>
						<li><a href="/auth/login">로그인</a></li>
					</c:when>
					<c:otherwise>
						<li><a href="/auth/logout-proc">로그아웃</a></li>
					</c:otherwise>
				</c:choose>
				
			</ul>
		</div><!--/.nav-collapse -->
	</div>
</nav>

 

meta.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>게시판예제</title>

 

scripts.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

<!-- 합쳐지고 최소화된 최신 자바스크립트 -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://code.jquery.com/jquery-migrate-1.4.1.min.js"></script>
<script type="text/javascript" src="${ctxPath}/js/util.js?latest=${version}"></script>

 

styles.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">

<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">

 

error-template.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html>
<html lang="ko">
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   </head>
   <body>
      <tiles:insertAttribute name="contents"/>
   </body>
</html>

 

constants.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="paginator" uri="/WEB-INF/tlds/paginator.tld" %>

<c:set var="version" value="<%=new java.util.Date()%>" />
<c:set var="ctxPath" value="${pageContext.request.contextPath eq '/' ? '' : pageContext.request.contextPath}" />
<c:set var="remoteURI" value="${ctxPath}${requestScope['javax.servlet.forward.servlet_path']}" />
<c:set var="orgRemoteURI" value="${requestScope['javax.servlet.forward.servlet_path']}" />
<sec:authentication property="principal" var="principal" />

 

template.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>

<!DOCTYPE html>
<html lang="ko">
	<head>
		<tiles:insertAttribute name="header"/>
		<tiles:insertAttribute name="meta"/>
		<tiles:insertAttribute name="styles"/>
	</head>
	<body>
		<tiles:insertAttribute name="scripts"/>	
		<tiles:insertAttribute name="contents"/>
		<tiles:insertAttribute name="contents-js"/>
	</body>
</html>

 

template-simple.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<tiles:insertAttribute name="contents"/>

 

paginator.tld
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
   
   <tlib-version>1.0</tlib-version>
   <short-name>paginator</short-name>
   <uri>/WEB-INF/tlds/paginator</uri>

   <tag>
      <name>print</name>
      <tag-class>com.board.common.Paginator</tag-class>
      <body-content>empty</body-content>
      <attribute>
         <name>goPageScript</name>
         <required>false</required>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>curPage</name>
         <required>true</required>
         <rtexprvalue>true</rtexprvalue>
         <type>int</type>
      </attribute>
      <attribute>
         <name>totPages</name>
         <required>true</required>
         <rtexprvalue>true</rtexprvalue>
         <type>int</type>
      </attribute>
      <attribute>
         <name>blockSize</name>
         <rtexprvalue>true</rtexprvalue>
         <type>int</type>
      </attribute>
      <attribute>
         <name>skipSize</name>
         <rtexprvalue>true</rtexprvalue>
         <type>int</type>
      </attribute>
      <attribute>
         <name>paginatorBlockClass</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>prevPageClass</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>prevPageLabel</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>nextPageClass</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>nextPageLabel</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>curPageClass</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>defaultPageClass</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>goFirstUse</name>
         <rtexprvalue>true</rtexprvalue>
         <type>bool</type>
      </attribute>
      <attribute>
         <name>goFirstLabel</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>goFirstClass</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>goLastUse</name>
         <rtexprvalue>true</rtexprvalue>
         <type>bool</type>
      </attribute>
      <attribute>
         <name>goLastLabel</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
      <attribute>
         <name>goLastClass</name>
         <rtexprvalue>true</rtexprvalue>
         <type>java.lang.String</type>
      </attribute>
   </tag>
</taglib>

 

join.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/WEB-INF/template/constants.jsp"%>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<link rel="stylesheet" type="text/css" href="${ctxPath}/css/loginForm.css"/>

<div class="wrapper">
  <div id="formContent">
    <!-- Tabs Titles -->

    <!-- Icon -->
    <div class="fadeIn first">
      회원가입
    </div>

    <!-- Login Form -->
    <form>
      <input type="text" id="userId" class="fadeIn second" name="login" placeholder="아이디를 입력해주세요." required>
      <input type="text" id="password" class="fadeIn third" name="login" placeholder="비밀번호를 입력해주세요." required>
      <input type="text" id="userName" class="fadeIn third" name="login" placeholder="이름을 입력해주세요.">
      <input type="text" id="email" class="fadeIn third" name="login" placeholder="이메일을 입력해주세요.">
      <input type="text" id="handPhoneNo" class="fadeIn third" name="login" placeholder="핸드폰번호를 입력해주세요.">
      <input type="button" class="fadeIn fourth" value="Join" onclick="join()">
    </form>

    <!-- Remind Passowrd -->
    <div id="formFooter">
      <a class="underlineHover" href="/auth/login">뒤로가기</a>
    </div>

  </div>
</div>

 

join.js.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<script>

	//로그인 submit
	function join(){
		
		var params = {
			  'userId' : $("#userId").val()
			 ,'password' : $("#password").val()
			 ,'userName' : $("#userName").val()
			 ,'email' : $("#email").val()
			 ,'handPhoneNo' : $("#handPhoneNo").val() 
			 
		}
		
		console.log(params);
		
		$.ajax({
	         type : 'POST'
	        ,url : '/auth/insertUser'
	        ,dataType : 'json'
	        ,data : params 
	        ,success : function(result) {
	        	alert(result.resultMsg);
	        	if(result.resultCode == '00'){
	        		location.href="/auth/login";
	        	}
	        },
	        error: function(request, status, error) {
	          
	        }
	    }) 
	}
</script>

 

login.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/WEB-INF/template/constants.jsp"%>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<link rel="stylesheet" type="text/css" href="${ctxPath}/css/loginForm.css"/>

<div class="wrapper fadeInDown">
    <div id="formContent">
        <!-- Tabs Titles -->

        <!-- Icon -->
        <div class="fadeIn first">
            로그인
        </div>

        <!-- Login Form -->
        <form>
            <input type="text" id="loginId" class="fadeIn second" name="un" placeholder="아이디를 입력해주세요" onkeyup="enterkey()">
            <input type="text" id="loginPw" class="fadeIn third" name="up" placeholder="비밀번호를 입력해주세요" onkeyup="enterkey()">
            <input type="button" class="fadeIn fourth" value="Log In" onclick="loginSubmit()">
        </form>

        <!-- Remind Passowrd -->
        <div id="formFooter">
            <a class="underlineHover" href="/auth/join">회원가입</a>
        </div>

    </div>
</div>

	<script>

	//로그인 submit
	function loginSubmit(){
		
		var params = {
			 'un' : $.trim($("#loginId").val())
			,'up' : $("#loginPw").val()
		}
		
		console.log(params);
		
		if(params.un == ""){
			alert("아이디를 입력해주새요");
			return false;
		}
		
		else if(params.up == ""){
			alert("비밀번호를 입력해주새요");
			return false;
		}
		
		$.ajax({
	         type : 'POST'
	        ,url : '/auth/login-proc'
	        ,dataType : 'json'
	        ,data : params 
	        ,success : function(result) {
				console.log(result);
				if(result.resultCode != "00"){
					alert(result.resultMessage);
				}
				
				else{
					location.href=result.targetUrl;
				}
				
	        },
	        error: function(request, status, error) {
	          
	        }
	    }) 
	}
	
	function enterkey(){
		if (window.event.keyCode == 13) { 
			// 엔터키가 눌렸을 때
			loginSubmit(); 
		}
	}

</script>

 

list-view.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ include file="/WEB-INF/template/constants.jsp"%>

<form>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>번호</th>
                <th>제목</th>
                <th>작성자</th>
                <th>날짜</th>
                <th>조회수</th>
            </tr>
        </thead>
        <tbody>
        	<c:forEach var="board" items="${boardList.content}" varStatus="vs">
				<tr>
				    <td>${resultDataTotal - (boardList.size * boardList.number) - vs.index}</td>
				    <td><a href='/board/update/${board.boardIdx}'>${board.boardTitle}</a></td>
				    <td>${board.regName}</td>
				    <td>
				    	<fmt:parseDate value="${board.regDate}" var="regDate" pattern="yyyy-MM-dd HH:mm:ss.s"/>
						<fmt:formatDate value="${board.regDate}" pattern="yyyy-MM-dd"/>
					</td>
				    <td>${board.viewCount}</td>
				</tr>
			</c:forEach>
			
			<c:if test="${resultDataTotal == 0}">
				<tr>
					<center><td colspan="6" style="text-align: center;">등록된 게시판 리스트가 없습니다.</td></center>
				</tr>
			</c:if>
				
        </tbody>
    </table>
   	
   	<!-- ADMIN 권한일경우에만 글쓰기 권한있음 -->
   	<c:if test="${sessUserInfo.authority == 'ADMIN'}">
		<div class="text-right">            
            <a href='/board/write' class="btn btn-primary">글쓰기</a>            
		</div>
    </c:if>
   	 
    <div class="text-center">
    	<c:if test="${resultDataTotal != 0}">
         <ul class="pagination">
            <paginator:print goPageScript="goPage" curPage="${boardList.number}" totPages="${boardList.totalPages}"/>
         </ul>
      </c:if>
    
    	<!--
    	<ul class="pagination">
    		<li><a href="#">prev</a></li>
    		<li><a href="#">1</a></li>
    		<li><a href="#">2</a></li>
    		<li><a href="#">3</a></li>
    		<li><a href="#">4</a></li>
    		<li><a href="#">5</a></li>
    		<li><a href="#">next</a></li>
    	</ul>
    	-->
    </div>	
</form>

 

list.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ include file="/WEB-INF/template/constants.jsp"%>

<style>
	/* paginate */
	.pagination a.on {color:#fff;font-weight:600;background:#a0afbf;}
</style>

<body>
	<div class="container">
	
	<br>
	<br>
	<br>
	<h1 class="page-header">게시판 목록</h1>
	</br>
	</br>
	
	<div id="list-div">
	</div>
</div>
</body>

-> 글 목록 jsp

 

list-js.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<script>

$(document).ready(function(){
	boardCtrl.init();
});

var boardCtrl = {
	
	init : function(){
		this.bindData();
		this.bindEvent();
	},
	
	bindData : function(){
		goPage(0);
	},
	
	bindEvent : function(){
	
	}
}

//리스트 가져오기
function goPage(pageNum){
	var size = "10";
	
	var params = {
		 page : pageNum
		,size : size
	}
	
	ajaxUtil.coGetAjaxPage(
      "/main/ajax/list-view"
      , params
      , 'list-div'
      , function(result){
      }
   );
}

var ajaxUtil = {
	coGetAjaxPage : function(url, params, targetId, onLoadEvent) {
      var request = $.ajax({
         url : url,
         async : false,
         one : true,
         type : "get",
         traditional: true,
         contentsType : "html",
         data : params ? params : {}
      });
      request.done(function(result){
         $("#" + targetId).empty().append($(result));
         // 공통 UI 이벤트
         util.bindUIEvent(targetId);
         // custom UI 이벤트
         if(onLoadEvent) {
            onLoadEvent();
         }
      });
      request.always(function(result){
         
      });

      request.fail(function(request,status,error){
         
         if(request.status == 410) {
            var node = document.createElement("div");
            node.innerHTML = request.responseText;
            var navigationNode = $("#err_msg", node);
            var navigationHTML = navigationNode.val();
            common.alert(navigationHTML);
            // dimm 또는 팝업이 표시중인 경우 처리
            if($(".popDim:visible").length > 0) {
               $(".popDim").hide();
            }
         } else {
            //console.log("페이지호출 실패")
         }
      });
   }
}

</script>

 

update.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/WEB-INF/template/constants.jsp"%>

<body>
	<article>
		<div class="container" role="main">
			<h2>게시판 상세</h2>
			<form name="form" id="form" role="form" method="post" action="${pageContext.request.contextPath}/board/saveBoard">
				<div class="mb-3">
					<label for="title">제목</label>
					<input type="text" class="form-control" id="boardTitle" name="boardTitle" placeholder="제목을 입력해 주세요" value="${boardInfo.boardTitle}" <c:if test="${sessUserInfo.authority != 'ADMIN'}">readonly</c:if>>
					<input type="hidden" id="boardIdx" value="${boardIdx}" />
				</div>
			
				<div class="mb-3">
					<label for="reg_id">작성자</label>
					<input type="text" class="form-control" id="regId" name="regId"  value="${boardInfo.regName}" readonly>
				</div>

				
				<div class="mb-3">
					<label for="content">내용</label>
					<textarea class="form-control" rows="5" id="boardContent" name="boardContent" placeholder="내용을 입력해 주세요" <c:if test="${sessUserInfo.authority != 'ADMIN'}">readonly</c:if>>${boardInfo.boardContent}</textarea>
				</div>	
			</form>
			
			<br>
			<c:if test="${sessUserInfo.authority == 'ADMIN'}">
				<div>
					<button onclick="writeSubmit()" type="button" class="btn btn-sm btn-primary" id="btnSave">저장</button>
					<button onclick="location.href='/'" type="button" class="btn btn-sm btn-primary" id="btnList">목록</button>
				</div>
			</c:if>
		</div>
	</article>
</body>

-> 글 수정 jsp

 

update-js.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<script>
	
	function writeSubmit(){
		
		var params = {
			 boardTitle : $.trim($("#boardTitle").val())
			,boardContent : $.trim($("#boardContent").val())
			,userId : "${sessUserInfo.userId}"
			,boardIdx : $("#boardIdx").val()
		}
		
		console.log(params);
		
		if(params.boardTitle == ""){
			alert("제목을 입력해주세요.");
			return false;
		}
		
		else if(params.boardContent == ""){
			alert("내용을 입력해주세요.");
			return false;
		} 
		
		$.ajax({
	         type : 'POST'
	        ,url : '/board/update'
	        ,dataType : 'json'
	        ,data : JSON.stringify(params) 
	        ,contentType: 'application/json'
	        ,success : function(result) {
				alert("해당글이 정상적으로 수정되었습니다.");
				location.href="/";
	        },
	        error: function(request, status, error) {
	          
	        }
	    }) 
	
	}
	
</script>

 

write.jsp
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/WEB-INF/template/constants.jsp"%>

<body>
	<article>
		<div class="container" role="main">
			<h2>게시판 글쓰기</h2>
			<form name="form" id="form" role="form" method="post" action="${pageContext.request.contextPath}/board/saveBoard">
				<div class="mb-3">
					<label for="title">제목</label>
					<input type="text" class="form-control" id="boardTitle" name="boardTitle" placeholder="제목을 입력해 주세요">
				</div>

			
				<div class="mb-3">
					<label for="reg_id">작성자</label>
					<input type="text" class="form-control" id="regId" name="regId"  value="${sessUserInfo.authorityNm}" readonly>
				</div>

				
				<div class="mb-3">
					<label for="content">내용</label>
					<textarea class="form-control" rows="5" id="boardContent" name="boardContent" placeholder="내용을 입력해 주세요"></textarea>
				</div>	
			</form>
			
			<br>
			<div>
				<button onclick="writeSubmit()" type="button" class="btn btn-sm btn-primary" id="btnSave">저장</button>
				<button onclick="location.href='/'" type="button" class="btn btn-sm btn-primary" id="btnList">목록</button>
			</div>
		</div>
	</article>
</body>

-> 글 등록 jsp

 

write-js.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<script>
	
	function writeSubmit(){
		
		var params = {
			 boardTitle : $.trim($("#boardTitle").val())
			,boardContent : $.trim($("#boardContent").val())
			,userId : "${sessUserInfo.userId}"
		}
		
		console.log(params);
		
		if(params.boardTitle == ""){
			alert("제목을 입력해주세요.");
			return false;
		}
		
		else if(params.boardContent == ""){
			alert("내용을 입력해주세요.");
			return false;
		} 
		
		$.ajax({
	         type : 'POST'
	        ,url : '/board/write'
	        ,dataType : 'json'
	        ,data : JSON.stringify(params) 
	        ,contentType: 'application/json'
	        ,success : function(result) {
				alert("해당글이 정상적으로 등록되었습니다.");
				location.href="/";
	        },
	        error: function(request, status, error) {
	          
	        }
	    }) 
	
	}
	
</script>

 

tiles.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
	<!-- 레이아웃 템플릿 정의 -->
	<definition name="tiles/default/template"    template="/WEB-INF/template/template.jsp">
		<put-attribute name="meta"               value="/WEB-INF/template/common/meta.jsp"/>
		<put-attribute name="styles"             value="/WEB-INF/template/common/styles.jsp"/>
		<put-attribute name="scripts"            value="/WEB-INF/template/common/scripts.jsp"/>
		<put-attribute name="header"             value="/WEB-INF/template/common/header.jsp"/>
		<put-attribute name="snb"                value="/WEB-INF/template/common/snb.jsp"/>
	</definition>

	<!-- Depth(4) : / A / B / C / D / E.jsp -->
	<definition name="/tiles/view/*/*/*/*/*"      extends="tiles/default/template">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}/{3}/{4}/{5}.jsp"/>
		<put-attribute name="contents-js"        value="/WEB-INF/views/{1}/{2}/{3}/{4}/{5}-js.jsp"/>
	</definition>

	<!-- Depth(3) : / A / B / C / D.jsp -->
	<definition name="/tiles/view/*/*/*/*"        extends="tiles/default/template">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}/{3}/{4}.jsp"/>
		<put-attribute name="contents-js"        value="/WEB-INF/views/{1}/{2}/{3}/{4}-js.jsp"/>
	</definition>

	<!-- Depth(2) : / A / B / C.jsp -->
	<definition name="/tiles/view/*/*/*"          extends="tiles/default/template">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}/{3}.jsp"/>
		<put-attribute name="contents-js"        value="/WEB-INF/views/{1}/{2}/{3}-js.jsp"/>
	</definition>

	<!-- Depth(1) : / A / B.jsp -->
	<definition name="/tiles/view/*/*"            extends="tiles/default/template">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}.jsp"/>
		<put-attribute name="contents-js"        value="/WEB-INF/views/{1}/{2}-js.jsp"/>
	</definition>

	<!-- Depth(0) : / A.jsp -->
	<definition name="/tiles/view/*"              extends="tiles/default/template">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}.jsp"/>
		<put-attribute name="contents-js"        value="/WEB-INF/views/{1}-js.jsp"/>
	</definition>
	
	<!-- 화면 영역 분할 등에서 사용함 -->
	<definition name="tiles/default/template-simple" template="/WEB-INF/template/template-simple.jsp">
	</definition>

	<!-- Depth(4) : / A / B / C / D / E.jsp -->
	<definition name="/tiles/ajax/*/*/*/*/*"      extends="tiles/default/template-simple">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}/{3}/{4}/{5}.jsp"/>
	</definition>

  	<!-- Depth(3) : / A / B / C / D.jsp -->
	<definition name="/tiles/ajax/*/*/*/*"        extends="tiles/default/template-simple">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}/{3}/{4}.jsp"/>
	</definition>

	<!-- Depth(2) : / A / B / C.jsp -->
	<definition name="/tiles/ajax/*/*/*"          extends="tiles/default/template-simple">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}/{3}.jsp"/>
	</definition>

	<!-- Depth(1) : / A / B.jsp -->
	<definition name="/tiles/ajax/*/*"            extends="tiles/default/template-simple">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}/{2}.jsp"/>
	</definition>

	<!-- Depth(0) : / A.jsp -->
	<definition name="/tiles/ajax/*"              extends="tiles/default/template-simple">
		<put-attribute name="contents"           value="/WEB-INF/views/{1}.jsp"/>
	</definition>

	<!-- 에러 페이지 레이아웃 -->
	<definition name="tiles/error/template"     template="/WEB-INF/template/error/error-template.jsp"/>

	<definition name="/errors/*"                   extends="tiles/error/template">
		<put-attribute name="contents"           value="/WEB-INF/errors/{1}.jsp"/>
	</definition>
	
	<!-- login -->
	<definition name="/tiles/single/auth/login" template="/WEB-INF/views/auth/login.jsp">
		<put-attribute name="meta"			value="/WEB-INF/template/common/meta.jsp" />
	</definition>
   
</tiles-definitions>

 

build.gradle
apply plugin: 'war'
def timestamp = new Date().format('yyMMddHHmm')
bootWar.enabled = false // (1)
war.enabled = true  // (2)
war {
   archiveName = "admin-${version}-"+timestamp+".war"
}

dependencies {
	implementation project(':core')

	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	testImplementation('org.springframework.security:spring-security-test')
	
	//db
	runtimeOnly 'mysql:mysql-connector-java'
   
	// MyBatis
	implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2")
   
	testImplementation('org.springframework.boot:spring-boot-starter-test')
    
	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools
	implementation group: 'org.springframework.boot', name: 'spring-boot-devtools', version: '2.2.6.RELEASE'

	providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
	
	implementation group: 'org.springframework.security', name: 'spring-security-core', version: '5.3.0.RELEASE'
	implementation group: 'org.springframework.security', name: 'spring-security-taglibs', version: '5.3.0.RELEASE'
   
	// Apache Http Requests
	implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.12'         // https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient
	
	// Tiles Setting
	implementation group: 'org.apache.tiles', name: 'tiles-jsp', version: '3.0.8'      // https://mvnrepository.com/artifact/org.apache.tiles/tiles-jsp
	implementation group: 'org.apache.tiles', name: 'tiles-core', version: '3.0.8'      // https://mvnrepository.com/artifact/org.apache.tiles/tiles-core
   
	/* Jsp Setting */
	implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '9.0.31'   // https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper
	implementation group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: '1.2.5'      // https://mvnrepository.com/artifact/org.apache.taglibs/taglibs-standard-impl
	providedRuntime group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'      // https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
	implementation group: 'javax.servlet', name: 'jstl'                                    // https://mvnrepository.com/artifact/javax.servlet/jstl
	
	/* gson */
	implementation 'com.google.code.gson:gson:2.8.6'
	
	// https://mvnrepository.com/artifact/com.navercorp.lucy/lucy-xss-servlet
   	implementation group: 'com.navercorp.lucy', name: 'lucy-xss-servlet', version: '2.0.1'
}

 

다음으로는 core쪽 java 파일입니다.

 

MainMapper.java
package com.board.dao;


import java.util.List;
import java.util.Map;
import org.springframework.data.domain.Pageable;
import org.apache.ibatis.annotations.Param;

public interface MainMapper {

	//글쓰기 리스트 조회
	public List<Map<String, Object>> selectBoardList(
			 @Param(value = "params") Map<String, Object> params
			,@Param(value = "pageable") Pageable pageable
			,@Param(value = "size") int size);
	
	//글쓰기 리스트 count
	int selectBoardListCnt(@Param(value = "params") Map<String, Object> params);
	
	//글쓰기 등록
	public int insertBoard(Map<String, Object> params);
	
	//글 수정
	public int updateBoard(Map<String, Object> params);
	
	//글쓰기 상세조회
	public Map<String, Object> selectBoard(String boardIdx);
	
	//조회수 업데이트
	public int updateViewCount(Map<String, Object> params);
	
}

-> 메인관련 dao입니다.

 

UserMapper.java
package com.board.dao;

import com.board.entity.UserInfo;
import com.board.entity.UserAuthority;

public interface UserMapper {

	//사용자 정보 조회
	UserInfo getUserInfo(String userId);
	
	//사용자 권한 조회
	UserAuthority getUserAuthorities(String userId);
	
	//사용자 등록
	public int insertUser(UserInfo userinfo);
	
	//사용자 권한 등록
	public int insertUserAuth(UserInfo userinfo);
	
	//사용자 중복체크
	int duplicateUserCheck(String userId);
	
}

-> 사용자 관련 dao입니다.

 

CamelMap.java
package com.board.entity;

import org.apache.commons.collections4.map.ListOrderedMap;
import org.springframework.jdbc.support.JdbcUtils;

public class CamelMap extends ListOrderedMap<String, Object> {

   private static final long serialVersionUID = 1L;

   @Override
   public Object put(String key, Object value) {
      if (key.indexOf('_') < 0 && Character.isLowerCase(key.charAt(0)))
         return super.put(key, value);

      return super.put(JdbcUtils.convertUnderscoreNameToPropertyName((String) key), value);
   }
}

-> xml에서 resultType를 camelMap으로 받기위해서 만들었습니다.

 

UserAuthority.java
package com.board.entity;

import org.springframework.security.core.GrantedAuthority;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserAuthority implements GrantedAuthority {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private String authority;
	private String authorityNm;

	@Override
	public String getAuthority() {
		// TODO Auto-generated method stub
		return this.authority;
	}
}

-> 사용자 권한 관련 vo

 

UserInfo.java
package com.board.entity;

import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserInfo implements UserDetails {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	//사용자 id
	private String userId;
	
	//사용자 이름
	private String userName;
	
	//패스워드
	private String password;
	
	//이메일
	private String email;
	
	//핸드폰 반호
	private String handPhoneNo;
	
	//사용여부
	private String useYn;

	//권한 list
	public List<UserAuthority> authorities;
	
	//권한
	public String authority;
	
	//권한 이름
	public String authorityNm;
	
	public String auth;
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}

	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return userName;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}
}

-> 사용자 정보 관련 vo

 

LoginService.java
package com.board.service;

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.board.dao.UserMapper;
import com.board.entity.UserInfo;

@Service
public class LoginService {
	
	@Autowired
	UserMapper userMapper;
	
	@Autowired
	PasswordEncoder passwordEncoder;
	
	public Map<String, Object> checkLoginInsert(UserInfo userInfo) {
		
		Map<String, Object> result = new HashMap<String, Object>();
		
		//0이면 중복되는 아이디 없음
		int duplicateCheck = userMapper.duplicateUserCheck(userInfo.getUserId());
		
		//신규등록
		if(duplicateCheck == 0) {
			//패스워드 bcrypt 암호화
			String password = passwordEncoder.encode(userInfo.getPassword());
			userInfo.setPassword(password);
			
			//사용자 등록
			userMapper.insertUser(userInfo);
			result.put("resultCode", "00");
			result.put("resultMsg", "정상적으로 회원이 등록되었습니다.");
			
			//사용자 권한 등록
			userMapper.insertUserAuth(userInfo);
		}
		
		//중복된 아이디가 있으므로 에러
		else {
			result.put("resultCode", "99");
			result.put("resultMsg", "중복된 아이디가 있습니다. 아이디를 다시 입력해주세요.");
		}
		
	return result;
	}
	
}

-> 로그인 서비스

 

MainService.java
package com.board.service;

import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import com.board.dao.MainMapper;

@Service("mainService")
public class MainService {

	@Autowired MainMapper mainMapper;
	
	public Page<Map<String, Object>> selectBoardList(Map<String, Object> params, Pageable pageable, int size){
		return new PageImpl<>(mainMapper.selectBoardList(params, pageable, size), pageable, mainMapper.selectBoardListCnt(params));
	}
	
}

-> 메인 서비스

 

MainMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- FAQ SQL Mapper -->
<mapper namespace="com.board.dao.MainMapper">
	
	<!-- 게시판 리스트 조회  -->
	<select id="selectBoardList" resultType="CamelMap">
		SELECT
			 TB.BOARD_IDX
			,TB.BOARD_TITLE
			,TB.BOARD_CONTENT
			,TB.REG_ID
			,TB.VIEW_COUNT
			,TB.USE_YN
			,TB.REG_DATE
			,(SELECT TC.CODE_EXP FROM TB_CODE TC WHERE TC.CODE_NO = '200' AND TC.CODE_NAME = TA.AUTHORITY) AS REG_NAME
		FROM
			 TB_BOARD TB
			,TB_AUTHORITIES TA
		WHERE
			USE_YN = 'Y'
		AND
			TB.REG_ID = TA.USER_ID	
		<if test="pageable != null">
			LIMIT #{pageable.offset}, #{size}
		</if>			
	</select>
	
	<!-- 게시판 count -->
	<select id="selectBoardListCnt" resultType="int">
		SELECT
			COUNT(*)
		FROM
			TB_BOARD
		WHERE
			USE_YN = 'Y'
	</select>
	
	<!-- 게시판 상세 조회 -->
	<select id="selectBoard" resultType="CamelMap">
		SELECT
			 TB.BOARD_IDX
			,TB.BOARD_TITLE
			,TB.BOARD_CONTENT
			,TB.REG_ID
			,TB.VIEW_COUNT
			,TB.USE_YN
			,TB.REG_DATE
			,(SELECT TC.CODE_EXP FROM TB_CODE TC WHERE TC.CODE_NO = '200' AND TC.CODE_NAME = TA.AUTHORITY) AS REG_NAME
		FROM
			TB_BOARD TB
			,TB_AUTHORITIES TA
		WHERE
			USE_YN = 'Y'
		AND
			TB.REG_ID = TA.USER_ID
		AND
			TB.BOARD_IDX = #{boardIdx}		
	</select>
	

	<!-- 게시판 등록 -->
	<insert id="insertBoard">
		INSERT INTO
			TB_BOARD(
				  BOARD_TITLE
				 ,BOARD_CONTENT
				 ,REG_ID
				 ,REG_DATE
			)
			VALUES(
				  #{boardTitle}
				 ,#{boardContent}
				 ,#{userId}
				 ,NOW()
			)
	</insert>
	
	<!-- 게시판 수정 -->
	<update id="updateBoard">
		UPDATE 
			TB_BOARD
		SET
			 BOARD_TITLE = #{boardTitle}
			,BOARD_CONTENT = #{boardContent}
		WHERE
			BOARD_IDX = #{boardIdx}		
	</update>
	
	<!-- 조회수 업데이트 -->
	<update id="updateViewCount">
		UPDATE
			TB_BOARD T1
		SET
			T1.VIEW_COUNT = #{viewCount}
		WHERE
			T1.BOARD_IDX = #{boardIdx}		
	</update>
	
	
	
</mapper>

-> 메인 xml

 

UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- FAQ SQL Mapper -->
<mapper namespace="com.board.dao.UserMapper">

	<!-- 사용자 정보 조회 -->
	<select id="getUserInfo" parameterType="String" resultType="UserInfo">
		SELECT
             USER_ID
            ,USER_NAME
            ,PASSWORD
            ,EMAIL
            ,HAND_PHONE_NO
            ,USE_YN
        FROM
            TB_USERS
        WHERE
        	USER_ID = #{userId}    
    </select>
    
    <!-- 사용자 권한 조회 -->
	<select id="getUserAuthorities" parameterType="String" resultType="UserAuthority">
		SELECT
			TA.AUTHORITY
     		,(SELECT CODE_EXP FROM TB_CODE TCD WHERE TCD.CODE_NO = '200' AND TCD.CODE_NAME = TA.AUTHORITY) AS AUTHORITY_NM
		FROM
     		TB_AUTHORITIES TA
     	WHERE
     		TA.USER_ID = #{userId}	
     </select>

	<!-- 사용자 등록 -->
	<insert id="insertUser" parameterType="UserInfo">
		INSERT INTO
			TB_USERS(
				 USER_ID
				,PASSWORD
				,USER_NAME
				,EMAIL
				,HAND_PHONE_NO
				,USE_YN
			)
			VALUES(
				 #{userId}
				,#{password}
				,#{userName}
				,#{email}
				,#{handPhoneNo}
				,'1'
			)
	</insert>
	
	<!-- 사용자 중복체크 -->
	<select id="duplicateUserCheck" parameterType="String" resultType="int">
		SELECT
			COUNT(*)
		FROM
			TB_USERS
		WHERE
			USER_ID = #{userId}		
	</select>
	
	<!-- 사용자 권한 등록 -->
	<insert id="insertUserAuth" parameterType="UserInfo">
		INSERT INTO
			TB_AUTHORITIES(
				 USER_ID
				,AUTHORITY
			)
			VALUES(
				 #{userId}
				,'USER'
			)
	</insert>
	
</mapper>

-> 사용자 xml

 

lucy-xss-servlet-filter-rule.xml
<config xmlns="http://www.navercorp.com/lucy-xss-servlet">
    <defenders>
        <!-- XssPreventer 등록 -->
        <defender>
            <name>xssPreventerDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssPreventerDefender</class>
        </defender>
 
        <!-- XssSaxFilter 등록 -->
        <defender>
            <name>xssSaxFilterDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssSaxFilterDefender</class>
            <init-param>
                <param-value>lucy-xss-sax.xml</param-value>   <!-- lucy-xss-filter의 sax용 설정파일 -->
                <param-value>false</param-value>        <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 -->
            </init-param>
        </defender>
 
        <!-- XssFilter 등록 -->
        <defender>
            <name>xssFilterDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssFilterDefender</class>
            <init-param>
                <param-value>lucy-xss.xml</param-value>    <!-- lucy-xss-filter의 dom용 설정파일 -->
                <param-value>false</param-value>         <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 -->
            </init-param>
        </defender>
    </defenders>
 
    <!-- default defender 선언, 필터링 시 지정한 defender가 없으면 여기 정의된 default defender를 사용해 필터링 한다. -->
    <default>
        <defender>xssPreventerDefender</defender>
    </default>
</config>

-> xss 대응

 

build.gradle
bootJar{
    enabled = false;
}
jar {
    enabled = true;
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	
	//security core
	implementation group: 'org.springframework.security', name: 'spring-security-core', version: '5.3.1.RELEASE'
	
	// https://mvnrepository.com/artifact/org.springframework/spring-jdbc
	implementation 'org.springframework:spring-jdbc:5.3.9'
	
	// https://mvnrepository.com/artifact/org.apache.commons/commons-collections4
	implementation 'org.apache.commons:commons-collections4:4.4'
	
	// https://mvnrepository.com/artifact/org.springframework.data/spring-data-commons
	implementation group: 'org.springframework.data', name: 'spring-data-commons', version: '2.5.2'
	
	// https://mvnrepository.com/artifact/org.apache.ibatis/ibatis-core
	implementation group: 'org.apache.ibatis', name: 'ibatis-core', version: '3.0'

}

 

게시판 화면 (로그인 안하였을경우)

 

-> 로그인을 안하였을경우에는 게시판 글을 등록 및 수정을 할수없게 하였습니다.

 

게시판 화면 (로그인하였을경우)

-> 글쓰기 화면이 보임

-> 회원가입, 로그인 -> 로그아웃으로 바뀜

 

게시판 글쓰기화면

 

게시판 수정화면

 

-> 다음 포스팅에서는 logback 개발 및 적용을 해보겠습니다. 감사합니다.

복사했습니다!