20220518 복습

2022 - 0518 - 복습


package com.jslhrd.sample;

import org.springframework.stereotype.Component;

import lombok.Data;

@Component //스프링에게 해당 클래스가 스프링에서 관리해야 하는 대상임을 표시
@Data //Lombok(setter,생성자,toString()등)을 자동 생성하는 어노테이션
public class Chef {
     
}


package com.jslhrd.sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.Setter;

@Component //스프링에게 해당 클래스가 스프링에서 관리해야 하는 대상임을 표시
@Data //Lombok(setter,생성자,toString()등)을 자동 생성하는 어노테이션
public class Restaurant {

     @Setter(onMethod_ = @Autowired) // 의존성
     private Chef chef;
}


package com.jslhrd.sample;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class) //현재 테스트 코드가 스프링을 실행하는 역할을 할 것이다.
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml") //필요한 객체들을 스프링 내에 객체로 등록(Spring's Bean)
@Log4j //Lombok을 이용하여 로그기록하는 Logger를 변수로 생성.
public class SampleTests {

     @Setter(onMethod_ = @Autowired) //@Autowired = 해당 인스턴스 변수가 스프링으로부터 자동으로 주입해 달라는 표시.
     private Restaurant restaurant;

     @Test
     public void testExist() {

          assertNotNull(restaurant); //restaurant변수가 null이 아니어야만 테스트가 성공한다는 뜻.

          log.info(restaurant);
          log.info("------------------");
          log.info(restaurant.getChef());
     }
}


package com.jslhrd.persistence;

import static org.junit.Assert.fail;
import java.sql.Connection;
import java.sql.DriverManager;

import org.junit.Test;

import lombok.extern.log4j.Log4j;

@Log4j
public class JDBCTests {

     static {
          try {
               Class.forName("oracle.jdbc.driver.OracleDriver");
          } catch (Exception e) {
               e.printStackTrace();
          }
     }

     @Test
     public void testConnection() {
          try {
               Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE","system","123456");
               log.info(con);
               log.info("성공");
               Class.forName("oracle.jdbc.driver.OracleDriver");
          } catch (Exception e) {
               log.info("실패");
               e.printStackTrace(); //
               fail(e.getMessage());
          }
     }

}


import static org.junit.Assert.fail;

import java.sql.Connection;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class DataSourceTests {

     @Setter(onMethod_ = @Autowired)
     private DataSource ds;

     @Test
     public void testConnection(){

          try(Connection con = ds.getConnection()) {

               log.info(con);
               log.info("success!!!");

          } catch (Exception e) {
               e.printStackTrace();
               fail(e.getMessage());
          }
     }
}


package com.jslhrd.domain;

import java.sql.Date;

import lombok.Data;

@Data
//Lombok(setter,생성자,toString()등)을 자동 생성하는 어노테이션
public class BoardVO {

     private long bno;
     private String title;
     private String content;
     private String writer;
     private Long hit;
     private Date regdate;
     private Date updatedate;
}


package com.jslhrd.mapper;
import java.util.List;
import com.jslhrd.domain.BoardVO;

public interface BoardMapper {

     public List<BoardVO> getList();

     public void insert(BoardVO board);

     public void insertSelectKey(BoardVO board);

     public BoardVO read(Long bno);

     public int update(BoardVO board);

     public int delete(Long bno);

}


package com.jslhrd.mapper;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.jslhrd.domain.BoardVO;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {

     @Setter(onMethod_ = @Autowired)
     private BoardMapper mapper;

     @Test
     public void testGetList() {
          mapper.getList().forEach(board -> log.info(board));
     }

     @Test
     public void testInsert() {
          BoardVO board = new BoardVO();
           board.setTitle("insert");
          board.setContent("insert");
           board.setWriter("insert");
          mapper.insert(board);
           log.info("-------------------insert result-----------------------");
          log.info(board);
          log.info("------------------------------------------");
     }

     @Test
     public void testInsertSelectKey() {
          BoardVO board = new BoardVO();
          board.setTitle("insert K");
          board.setContent("insert K");
          board.setWriter("insert K");
          mapper.insertSelectKey(board);
          log.info("-------------------insert Select Key result-----------------------");
          log.info(board);
          log.info("------------------------------------------");
     }

     @Test
     public void testRead() {
          BoardVO board = mapper.read(16L);
          log.info("-------------------read result-----------------------");
          log.info(board);
          log.info("------------------------------------------");
     }

     @Test
     public void testUpdate() {
          BoardVO board = new BoardVO();
          board.setBno(16L);
          board.setTitle("update");
          board.setContent("update");
          board.setWriter("update");
          log.info("UPDATE RESULT !!! : " + mapper.update(board));

     }
     @Test
     public void testDelete() {
          int count = mapper.delete(31L);
          log.info("DELETE RESULT !!! : "+count);
     }


}


package com.jslhrd.service;
import java.util.List;
import com.jslhrd.domain.BoardVO;
public interface BoardService {

     public List<BoardVO> getList();

     public BoardVO get(Long bno);

     public void insert(BoardVO board);

     public boolean update(BoardVO board);

     public boolean delete(Long bno);

}


package com.jslhrd.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.jslhrd.domain.BoardVO;
import com.jslhrd.mapper.BoardMapper;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Service // 계층 구조상 주로 비즈니스 영역을 담당하는 객체임을 표시하기 위해 사용함.
@Log4j
@AllArgsConstructor // 생성자를 만들고 자동으로 주입함. (생성자를 만들지 않을 경우에는 @setter()를 이용해서 처리함)
public class BoardServiceImpl implements BoardService{

     // @Setter(onMethod_ = @Autowired)
     private BoardMapper mapper;

     @Override
     public List<BoardVO> getList(){
          log.info("GET LIST ----------------------------");
          return mapper.getList();
     }

     @Override
     public BoardVO get(Long bno) {
          log.info("GET : " + bno);
          return mapper.read(bno);
     }

     @Override
     public void insert(BoardVO board) {
          log.info("INSERT : " + board);
          mapper.insertSelectKey(board);
     }

     @Override
     public boolean update(BoardVO board) {
          log.info("UPDATE : " + board);
          return mapper.update(board) == 1;
     }

     @Override
     public boolean delete(Long bno) {
          log.info("DELETE : " + bno);
          return mapper.delete(bno) == 1;
     }

}


package com.jslhrd.service;

import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.jslhrd.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardServiceTests {

     @Setter(onMethod_ = @Autowired)
     private BoardService service;

     @Test
     public void testExist() {
          log.info(service);
          assertNotNull(service);
     }

     @Test
     public void testGetList() {
          service.getList().forEach(board -> log.info(board));
     }

     @Test
     public void testGet() {
          log.info("GET ---------------------------------------");
          log.info(service.get(17L));
     }
     @Test
     public void testInsert() {
          log.info("INSERT --------------------------------------");
          BoardVO board = new BoardVO();
          board.setTitle("insert");
          board.setContent("insert");
          board.setWriter("insert");
          service.insert(board);
     }

     @Test
     public void testUpdate() {
          BoardVO board = service.get(17L);
          if(board == null) {
               return;
          }
          board.setTitle("update");
          board.setContent("update");
          board.setWriter("update");
          log.info("UPDATE RESULT : " + service.update(board));
     }

     @Test
     public void testDelete() {
          log.info("DELETE RESULT : " + service.delete(30L));
     }

}


package com.jslhrd.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.jslhrd.domain.BoardVO;
import com.jslhrd.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller //스프링의 빈으로 인식할 수 있게 함.
@Log4j
@RequestMapping("/board/*") //'/board'로 시작하는 모든 처리를 BoardController가 하도록 지정.
@AllArgsConstructor
public class BoardController {

     private BoardService service;

     @GetMapping("/t1")
     public void t1() {

     }

     @GetMapping("/t2")
     public void t2() {

     }

     @GetMapping("/list")
     public void list(Model model) {
          log.info("list --------------------------------------------------");
          model.addAttribute("list",service.getList());
     }

     @GetMapping("/insert")
     public void insert() {

     }

     @PostMapping("/insert") //새롭게 등록된 게시물의 번호를 같이 전달(RedirectAttributes)
     public String insert(BoardVO board, RedirectAttributes rttr) {
          log.info("INSERT : " + board);
          service.insert(board);
          rttr.addFlashAttribute("result", board.getBno());
          return "redirect:/board/list";
     }

     @GetMapping({"/get","/update"}) // get방식의 get(),update()는 bno값을 받아 BoardVO를 전달하는 동일한 처리방식
     public void get(@RequestParam("bno") Long bno, Model model) { //bno를 좀 더 명시적으로 처리(@RequestParam), 화면쪽으로 게시물 전달(Model)
          log.info("get --------------------------------------------------");
          model.addAttribute("board",service.get(bno));
     }

     @PostMapping("/update")
     public String update(BoardVO board, RedirectAttributes rttr) {
          log.info("update --------------------------------------------------");
          if(service.update(board)) {
               rttr.addFlashAttribute("result", "update_success");
          }
          return "redirect:/board/list";
     }

     @PostMapping("/delete")
     public String delete(@RequestParam("bno") Long bno, RedirectAttributes rttr) {
          log.info("delete --------------------------------------------------");
          if(service.delete(bno)) {
               rttr.addFlashAttribute("result","delete_success");
          }
          return "redirect:/board/list";
     }


}


package com.jslhrd.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration //Servlet의 ServletContext 이용. 스프링에서는 WebApplicationContext라는 존재를 이용.
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {

     @Setter(onMethod_ = @Autowired)
     private WebApplicationContext ctx;
     private MockMvc mockMvc;

     @Before //@Before가 적용된 메서드는 모든 테스트 전에 매번 실행되는 메서드.
     public void setup() {
          this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
     } // MockMvc = 가짜 MVC (URL,파라미터 등을 가짜로 브라우저에서 사용하는 것처럼 하여 Controller를 실행 가능.)
     @Test
     public void testList() throws Exception {
          log.info("test");
          log.info(
               mockMvc.perform(MockMvcRequestBuilders.get("/board/list"))
                    .andReturn()
                    .getModelAndView()
                    .getModelMap()
          );
     }

     @Test
     public void testInsert() throws Exception {
          log.info("INSERT----------------------------------------------");
          String resultPage = mockMvc.perform(MockMvcRequestBuilders.post("/board/insert")
               .param("title","test new")
               .param("content","test new")
               .param("writer","test new")
               ).andReturn().getModelAndView().getViewName();

          log.info(resultPage);
     }

     @Test
     public void testGet() throws Exception {
          log.info("GET----------------------------------------------");
          mockMvc.perform(MockMvcRequestBuilders.get("/board/get")
               .param("bno","36")
               ).andReturn().getModelAndView().getViewName();
     }

     @Test
     public void testUpdate() throws Exception {
          log.info("UPDATE----------------------------------------------");
          String resultPage = mockMvc.perform(MockMvcRequestBuilders.post("/board/update")
               .param("bno","34")
               .param("title","new update")
               .param("content","new update")
               .param("writer","new update")
               ).andReturn().getModelAndView().getViewName();
          log.info(resultPage);
     }

     @Test
     public void testDelete() throws Exception {
          log.info("DELETE----------------------------------------------");
          String resultPage = mockMvc.perform(MockMvcRequestBuilders.post("/board/delete")
               .param("bno","33")
               ).andReturn().getModelAndView().getViewName();
          log.info(resultPage);
     }


}


>>> web.xml 인코딩 & Security 로그인

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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 https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml /WEB-INF/spring/security-context.xml <!-- security-context.xml을 로딩하도록 빈 설정 --> </param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 한글깨짐 현상 처리 시작 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 한글깨짐 현상 처리 끝 --> <!-- Spring Security 시작 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Spring Security 끝 --> </web-app>


>>> footer.jsp

<script> $(document).ready(function() { $('#dataTables-example').DataTable({ responsive: true }); $(".sidebar-nav") .attr("class","sidebar-nav navbar-collapse collapse") .attr("aria-expanded",'flase') .attr("style","height:1px") }); </script>

>>> list.jsp


<script type="text/javascript">
$(document).ready(function() {

     var result = '<c:out value="${result}"/>';
     console.log(result);
     checkModal(result);
     history.replaceState({},null,null);

          function checkModal(result) {
               if (result === '' || history.state) {
                    return;
               }
               if (parseInt(result) > 0) {
                    $(".modal-body").html(
                         "게시글 " + parseInt(result) + " 번이 등록되었습니다.");
               }else {
                    $(".modal-body").html("값이 전달되지 않습니다.");
               }
               $("#myModal").modal("show");
          }
     $("#regBtn").on("click",function() {
          self.location="/board/insert";
     });

});
</script>

>>> update.jsp

<script> $(document).ready(function(){ var formObj = $("form"); $('button').on("click",function(e){ e.preventDefault(); var operation = $(this).data("oper"); console.log(operation); if(operation==='delete'){ formObj.attr("action","/board/delete"); }else if(operation==='list'){ //move to list self.location="/board/list"; return; }formObj.submit(); }); $("p").click(function(){ $(this).hide(); }); }); </script>


>>> WEB-INF/spring 폴더 내에 생성

<?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:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 5.0버전은 에러가 발생 버그 때문에 지우기 --> <bean id="customAccessDenied" class="com.jslhrd.security.CustomAccessDeniedHandler"></bean> <bean id="customLoginSuccess" class="com.jslhrd.security.CustomLoginSuccessHandler"></bean> <!-- <bean id="customPasswordEncoder" class="com.jslhrd.security.CustomNoOpPasswordEncoder"></bean> --> <bean id="bcryptPasswordEndcoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean> <bean id="customUserDetailsService" class="com.jslhrd.security.CustomUserDetailsService"></bean> <security:http auto-config="true" use-expressions="true"> <security:intercept-url pattern="/sample/all" access="permitAll" /> <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')" /> <security:intercept-url pattern="/board/list" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/board/insert" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/board/update" access="hasRole('ROLE_MEMBER')" /> <!-- <security:access-denied-handler error-page="/accessError" /> error-page 나 ref 둘 중 하나만 !!! --> <security:access-denied-handler ref="customAccessDenied" /> <!-- <security:form-login /> --> <!-- 로그인창으로 강제 이동 시 해당하는 페이지 --> <security:form-login login-page="/customLogin" authentication-success-handler-ref="customLoginSuccess"/> </security:http> <security:authentication-manager> <security:authentication-provider user-service-ref="customUserDetailsService"> <!-- 최종적으로 customUserDetailsService 사용 --> <!--<security:jdbc-user-service data-source-ref="dataSource" users-by-username-query="select userid,userpw,enabled from tbl_member where userid=?" authorities-by-username-query="select userid,auth from tbl_member_auth where userid=?" /> change to Bcrypt <security:password-encoder ref="customPasswordEncoder" /> --> <security:password-encoder ref="bcryptPasswordEndcoder" /> </security:authentication-provider> <!-- ********************************************************************************************* <security:authentication-provider> <security:user-service> <security:user name="member" password="{noop}member" authorities="ROLE_MEMBER" /> <security:user name="admin" password="{noop}admin" authorities="ROLE_MEMBER, ROLE_ADMIN" /> </security:user-service> </security:authentication-provider> *********************************************************************************************** --> </security:authentication-manager> </beans>


>>> src/main/java -> com.jslhrd.controller 에 생성.

package com.jslhrd.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import lombok.extern.log4j.Log4j; @Log4j @RequestMapping("/sample/*") @Controller public class SampleController { @GetMapping("/all") public void doAll() { log.info("DO ALL -----------------------"); } @GetMapping("/member") public void doMember() { log.info("logined Member --------------------"); } @GetMapping("/admin") public void doAdmin() { log.info("Only Admin--------------------------"); } }


>>> src/main/java -> com.jslhrd.controller 에 생성.

package com.jslhrd.controller; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import lombok.extern.log4j.Log4j; @Controller @Log4j public class CommonController { @GetMapping("/customLogin") public void loginInput(String error, String logout, Model model) { log.info("error : " + error); log.info("logout : " + logout); if(error != null) { model.addAttribute("error", "Login Error Check your Account !"); } if(logout != null) { model.addAttribute("logout", "Logout !"); } } @GetMapping("/accessError") public void accessDenied(Authentication auth, Model model) { log.info("ACCESS DENIED ->" + auth + "------------------------------------------------"); model.addAttribute("msg","Access Denied"); } }


>>> src/main/java -> com.jslhrd.security 에 생성.
package com.jslhrd.security; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import lombok.extern.log4j.Log4j; @Log4j public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessException) throws IOException, ServletException { log.error("Access Denied Handler !!!"); log.error("Redirect . . . "); response.sendRedirect("/accessError"); } }


>>> src/main/java -> com.jslhrd.security 에 생성.
package com.jslhrd.security; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import lombok.extern.log4j.Log4j; @Log4j public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler{ // 일반, 맴버, 관리자 /******************************** <security:authentication-manager> <security:authentication-provider> <security:user-service> <security:user name="member" password="{noop}member" authorities="ROLE_MEMBER" /> <security:user name="admin" password="{noop}admin" authorities="ROLE_MEMBER, ROLE_ADMIN" /> </security:user-service> </security:authentication-provider> </security:authentication-manager> 의 역할을 대신 해주는 클래스이다. ********************************/ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException { log.warn("LOGIN SUCCESS !!!"); List<String> roleNames = new ArrayList<>(); auth.getAuthorities().forEach(authority -> { roleNames.add(authority.getAuthority()); }); log.warn("ROLE NAMES : " + roleNames); if(roleNames.contains("ROLE_ADMIN")) { response.sendRedirect("/sample/admin"); return; //만약 사용자가 ROLE_ADMIN 권한을 가졌다면 로그인 후에 바로 '/sample/admin'으로 이동하게 하는 방식입니다. } if(roleNames.contains("ROLE_MEMBER")) { response.sendRedirect("/sample/member"); return; } response.sendRedirect("/"); } }


>>> src/main/java -> com.jslhrd.security 에 생성.
package com.jslhrd.security; import org.springframework.security.crypto.password.PasswordEncoder; import lombok.extern.log4j.Log4j; @Log4j public class CustomNoOpPasswordEncoder implements PasswordEncoder { //bcrypt를 사용하기 때문에, 사용하지 않는 클래스. public String encode(CharSequence rawPassword) { log.warn("before encode : " + rawPassword); return rawPassword.toString(); } public boolean matches(CharSequence rawPassword, String encodedPassword) { log.warn("matches : " + rawPassword + " : " + encodedPassword); return rawPassword.toString().equals(encodedPassword); } }


>>> src/main/java -> com.jslhrd.domain 에 생성.
package com.jslhrd.domain; import java.sql.Date; import java.util.List; import lombok.Data; @Data public class MemberVO { private String userid; private String userpw; private String userName; private boolean enabled; private Date regDate; private Date updateDate; private List<AuthVO> authList; }


>>> src/main/java -> com.jslhrd.domain 에 생성.
package com.jslhrd.domain; import lombok.Data; @Data public class AuthVO { private String userid; private String auth; }


>>> src/main/java -> com.jslhrd.security 에 생성.
package com.jslhrd.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.jslhrd.domain.MemberVO; import com.jslhrd.mapper.MemberMapper; import com.jslhrd.security.domain.CustomUser; import lombok.Setter; import lombok.extern.log4j.Log4j; @Log4j public class CustomUserDetailsService implements UserDetailsService { @Setter(onMethod_ = @Autowired) private MemberMapper memberMapper; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { log.warn("Load User By UserName : " + userName); //userName means userid MemberVO vo = memberMapper.read(userName); log.warn("queried by member mapper : " + vo); return vo == null ? null : new CustomUser(vo); } }


>>> src/main/java -> com.jslhrd.security.domain 에 생성.
package com.jslhrd.security.domain; import java.util.Collection; import java.util.stream.Collectors; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import com.jslhrd.domain.MemberVO; import lombok.Getter; @Getter public class CustomUser extends User { private static final long serialVersionUID = 1L; private MemberVO member; public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) { super(username, password, authorities); } public CustomUser(MemberVO vo) { super(vo.getUserid(), vo.getUserpw(), vo.getAuthList().stream() .map(auth -> new SimpleGrantedAuthority(auth.getAuth())).collect(Collectors.toList())); // AuthVO 인스턴스를 GrantedAuthority 객체로 변환하는 과정 this.member = vo; // 변견 후, CustomUserDetailsService에서 CustomUser를 반환하도록 함. } }


>>> src/main/java -> com.jslhrd.mapper 에 생성.
package com.jslhrd.mapper; import com.jslhrd.domain.MemberVO; public interface MemberMapper { public MemberVO read(String userid); }


>>> src/test/java -> com.jslhrd.mapper 에 생성.
package com.jslhrd.mapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.jslhrd.domain.MemberVO; import lombok.Setter; import lombok.extern.log4j.Log4j; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml") @Log4j public class MemberMapperTests { @Setter(onMethod_ = @Autowired) private MemberMapper mapper; @Test public void testRead() { MemberVO vo = mapper.read("admin99"); log.info(vo); vo.getAuthList().forEach(authVO -> log.info(authVO)); } }


>>> src/main/resources -> com.jslhrd.mapper 폴더에 생성.

<?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">
<mapper namespace="com.jslhrd.mapper.MemberMapper">

     <resultMap type="com.jslhrd.domain.MemberVO" id="memberMap">
          <id property="userid" column="userid" />
          <result property="userid" column="userid" />
          <result property="userpw" column="userpw" />
          <result property="userName" column="username" />
          <result property="regDate" column="regdate" />
          <result property="updateDate" column="updatedate" />
          <collection property="authList" resultMap="authMap">
          </collection>
     </resultMap> <!-- 여러개의 데이터를 처리하는 경우 1:N의 결과를 처리할 수 있는 resultMap 태그 사용 -->

     <resultMap type="com.jslhrd.domain.AuthVO" id="authMap">
          <result property="userid" column="userid" />
          <result property="auth" column="auth" />
     </resultMap>

     <select id="read" resultMap="memberMap"> <!-- mem 테이블과 auth 테이블을 쪼인하여 userid에 해당하는 항목들을 가져옴. -->
          SELECT
               mem.userid, userpw, username, enabled, regdate, updatedate, auth
          FROM
               tbl_member mem LEFT OUTER JOIN tbl_member_auth auth on mem.userid = auth.userid where mem.userid = #{userid}
     </select>

</mapper>


< Spring 기초 >




>>> pom.xml
12라인 -> 5.0.7
64라인 -> 1.2.17
116라인 -> 4.12
139라인 -> 3.5.1
141라인 -> 1.8
142라인 -> 1.8
-----------------------------------------------------------------------------------------------------
>>> root-context.xml

<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">

     <property name="driverClassName"
          value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
     <property name="jdbcUrl"
          value="jdbc:log4jdbc:oracle:thin:@localhost:1521:XE"></property>
     <property name="username" value="db53"></property>
     <property name="password" value="123456"></property>

</bean>

<!-- HikariCP configuration -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
     destroy-method="close">
          <constructor-arg ref="hikariConfig" />
</bean>

<bean id="sqlSessionFactory"
     class="org.mybatis.spring.SqlSessionFactoryBean">
          <property name="dataSource" ref="dataSource">
</bean>

<mybatis-spring:scan
     base-package="com.jslhrd.mapper" />

<context:component-scan
     base-package="com.jslhrd.service">

-----------------------------------------------------------------------------------------------------
>>> buildpath
mysql-connector-java-8.0.28.jar
ojdbc8.jar
-----------------------------------------------------------------------------------------------------
>>>src/main/resources
log4jdbc.log4j2.properties 파일 생성
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator 내용입력
-----------------------------------------------------------------------------------------------------
>>>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j

@Setter(onMethod_ = @Autowired)
@Select("select sysdate from dual")

@Service // 계층 구조상 주로 비즈니스 영역을 담당하는 객체임을 표시하기 위해 사용함.
@Log4j
@AllArgsConstructor // 생성자를 만들고 자동으로 주입함.

<BoardController.java>
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor

<BoardControllerTests.java>
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml",
               "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j

>>>Tomcat 더블클릭 - Modules - Edit - controller 부분 지우기
-----------------------------------------------------------------------------------------------------
P.173
>>>Oracle 테이블

사용자 추가 후

system/123456

db53/123456


create user db53 identified by 123456;
grant connect,resource to db53;
grant create view to db53;

create sequence seq_board;

create table tbl_board(
bno number(10,0),
title varchar2(200) not null,
content varchar2(2000) not null,
writer varchar2(50) not null,
regdate date default sysdate,
updatedate date default sysdate
);

create table tbl_member(
userid varchar2(50) not null primary key,
userpw varchar2(100) not null,
username varchar2(100) not null,
regdate date default sysdate,
updatedate date default sysdate,
enabled char(1) default '1');

create table tbl_member_auth(
userid varchar2(50) not null,
auth varchar2(50) not null,
constraint fk_member_auth foreign key(userid) references tbl_member(userid)
);

alter table tbl_board add constraint pk_board primary key(bno);

insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user00');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user01');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user02');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user03');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user04');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user05');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user06');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user07');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user08');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user09');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user10');

-----------------------------------------------------------------------------------------------------
>>> BoardMapper.xml (@Select 어노테이션을 써도 되지만, 쿼리문이 복잡해질 경우 xml 파일로 관리하는게 유리하다 !!!)

src/main/resources -> com 폴더 -> jslhrd 폴더 -> mapper 폴더 생성 후, 그 안에 BoardMapper.xml 파일 생성
(com.jslhrd.mapper 패키지 명과 동일한 틀로)

<?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">
<mapper namespace="com.jslhrd.mapper.BoardMapper">

     <!-- 부등호 들어가면 CDATA 처리 !!! -->

     <select id="getList" resultType="com.jslhrd.domain.BoardVO">
          <![CDATA[
               select * from tbl_board where bno > 0
          ]]>
     </select>

     <insert id="insert">
          insert into tbl_board(bno,title,content,writer) values(seq_board.nextval, #{title}, #{content}, #{writer})
     </insert>

     <insert id="insertSelectKey">
          <selectKey keyProperty="bno" order="BEFORE" resultType="long">
               select seq_board.nextval from dual
          </selectKey>
          insert into tbl_board(bno,title,content,writer) values(#{bno}, #{title}, #{content}, #{writer})
     </insert>

     <select id="read" resultType="com.jslhrd.domain.BoardVO">
          select * from tbl_board where bno = #{bno}
     </select>

     <update id="update">
          update tbl_board
          set title=#{title},
          content=#{content},
          writer=#{writer},
           updateDate=sysdate
          where bno=#{bno}
     </update>

     <delete id="delete">
          delete from tbl_board where bno=#{bno}
     </delete>

</mapper>

-----------------------------------------------------------------------------------------------------
src/main/resources -> com 폴더 -> jslhrd 폴더 -> mapper 폴더 생성 후, 그 안에 TimeMapper.xml 파일 생성
(com.jslhrd.mapper 패키지 명과 동일한 틀로)

<?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">
<mapper namespace="com.jslhrd.mapper.TimeMapper">
     <select id="getTime2" resultType="string">
          select sysdate from dual
     </select>
</mapper>

-------------------------------------------------------------------------------------- src/main/resources -> com 폴더 -> jslhrd 폴더 -> mapper 폴더 생성 후, 그 안에 MemberMapper.xml 파일 생성
(com.jslhrd.mapper 패키지 명과 동일한 틀로)

<?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">
<mapper namespace="com.jslhrd.mapper.MemberMapper">

     <resultMap type="com.jslhrd.domain.MemberVO" id="memberMap">
          <id property="userid" column="userid" />
          <result property="userid" column="userid" />
          <result property="userpw" column="userpw" />
          <result property="userName" column="username" />
          <result property="regDate" column="regdate" />
          <result property="updateDate" column="updatedate" />
          <collection property="authList" resultMap="authMap">
          </collection>
     </resultMap> <!-- 여러개의 데이터를 처리하는 경우 1:N의 결과를 처리할 수 있는 resultMap 태그 사용 -->

     <resultMap type="com.jslhrd.domain.AuthVO" id="authMap">
          <result property="userid" column="userid" />
          <result property="auth" column="auth" />
     </resultMap>

     <select id="read" resultMap="memberMap"> <!-- mem 테이블과 auth 테이블을 쪼인하여 userid에 해당하는 항목들을 가져옴. -->
          SELECT
               mem.userid, userpw, username, enabled, regdate, updatedate, auth
          FROM
               tbl_member mem LEFT OUTER JOIN tbl_member_auth auth on mem.userid = auth.userid where mem.userid = #{userid}
     </select>

</mapper>

--------------------------------------------------------------------------------------
>>> web.xml (인코딩 & Security)

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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 https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml /WEB-INF/spring/security-context.xml <!-- security-context.xml을 로딩하도록 빈 설정 --> </param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 한글깨짐 현상 처리 시작 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 한글깨짐 현상 처리 끝 --> <!-- Spring Security 시작 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Spring Security 끝 --> </web-app>
--------------------------------------------------------------------------------------
>>> Modal 코드

<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
     <div class="modal-dialog">
          <div class="modal-content">
               <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                    <h4 class="modal-title" id="myModalLabel">Modal title</h4>
               </div>
               <div class="modal-body">
(modal)
               </div>
               <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                    <button type="button" class="btn btn-primary">Save changes</button>
               </div>
          </div>
          <!-- /.modal-content -->
     </div>
     <!-- /.modal-dialog -->
</div>
<!-- /.modal -->


--------------------------------------------------------------------------------------
>>> security-context.xml
(WEB-INF / spring 폴더 내에 생성)
(Namespaces 에서 security 항목 체크 !!!)
<?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:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 5.0버전은 에러가 발생 버그 때문에 지우기 --> <bean id="customAccessDenied" class="com.jslhrd.security.CustomAccessDeniedHandler"></bean> <bean id="customLoginSuccess" class="com.jslhrd.security.CustomLoginSuccessHandler"></bean> <!-- <bean id="customPasswordEncoder" class="com.jslhrd.security.CustomNoOpPasswordEncoder"></bean> --> <bean id="bcryptPasswordEndcoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean> <bean id="customUserDetailsService" class="com.jslhrd.security.CustomUserDetailsService"></bean> <security:http auto-config="true" use-expressions="true"> <security:intercept-url pattern="/sample/all" access="permitAll" /> <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')" /> <security:intercept-url pattern="/board/list" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/board/insert" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/board/update" access="hasRole('ROLE_MEMBER')" /> <!-- <security:access-denied-handler error-page="/accessError" /> error-page 나 ref 둘 중 하나만 !!! --> <security:access-denied-handler ref="customAccessDenied" /> <!-- <security:form-login /> --> <!-- 로그인창으로 강제 이동 시 해당하는 페이지 --> <security:form-login login-page="/customLogin" authentication-success-handler-ref="customLoginSuccess"/> </security:http> <security:authentication-manager> <security:authentication-provider user-service-ref="customUserDetailsService"> <!-- 최종적으로 customUserDetailsService 사용 --> <!--<security:jdbc-user-service data-source-ref="dataSource" users-by-username-query="select userid,userpw,enabled from tbl_member where userid=?" authorities-by-username-query="select userid,auth from tbl_auth where userid=?" /> change to Bcrypt <security:password-encoder ref="customPasswordEncoder" /> --> <security:password-encoder ref="bcryptPasswordEndcoder" /> </security:authentication-provider> <!-- ********************************************************************************************* <security:authentication-provider> <security:user-service> <security:user name="member" password="{noop}member" authorities="ROLE_MEMBER" /> <security:user name="admin" password="{noop}admin" authorities="ROLE_MEMBER, ROLE_ADMIN" /> </security:user-service> </security:authentication-provider> *********************************************************************************************** --> </security:authentication-manager> </beans>

https://mvnrepository.com/ <- dependency 검색
https://start.spring.io/ <- Spring Start Page

- 세팅이 반이다 !
- Test 중요 !
- build path (mysql-connector-java-8.0.28.jar , ojdbc8.jar)
- pom.xml 계속 수정

MVC

   (Model View Controller)

   Presentation - Client의 요청을 받아 Business에게 처리를 전달.
   Business - 실제 요청에 대한 처리를 담당. (Service)
   Persistence - Business의 처리에 따라 데이터베이스에 접근. 저장,조회,삭제 등 수행.

Layered Architecture


   각 계층은 자신의 계층이 갖는 책임에만 충실하도록 개발해야 한다.

View

Model Model

Presentation
@Controller

Model Model

Business
@Service

DTO Model

Persistence

DTO Model

DB
(Oracle)



초기 설정/관리해야 할 .XML

p.608
    pom.xml
    web.xml
    root-context.xml
    servlet-context.xml
    security-context.xml (로그인 처리를 위해 생성)


/sample/

p.612
    /sample/all -> 로그인을 하지 않은 사용자도 접근 가능한 URL
    /sample/member -> 로그인 한 사용자들만이 접근할 수 있는 URL
    /sample/admin -> 로그인 한 사용자들 중에서 관리자 권한을 가진 사용자만이 접근할 수 있는 URL


Interceptor

p.606
    스프링 시큐리티의 기본 동작 방식은 서블릿의 여러 필터 + 인터셉터를 이용.
    필터 = 스프링과 무관하게 서블릿 자원.
    인터셉터 = 스프링의 빈으로 관리되며 스프링의 컨텍스트 내에 속함
    ( 스프링 내부에서 컨트롤러를 호출할 때 관여하기 때문에 스프링의 컨텍스트 내의 모든 자원을 활용 가능. )
    + web.xml에 필터&bean 설정 !


Authentication(인증) & Authorization(인가)

p.615
    Authentication = '자신을 증명'
    Authorization = '권한 부여'

    AuthenticationManager = 가장 중요한 역할. 다양한 방식의 인증을 처리.
    ProviderManager = 인증에 대한 처리를 AuthenticationProvider라는 타입의 객체를 이용해서 처리.
    AuthenticationProvider = 실제 인증 작업을 진행. 이때, 인증된 정보에는 권한에 대한 정보를 같이 전달.
    UserDetailService = 인터페이스의 구현체는 실제로 사용자의 정보와 사용자가 가진 권한의 정보를 처리해서 반환하게 됨.

    -스프링 시큐리티를 커스터마이징 하는 방식-
    1. AuthenticationProvider를 직접 구현함.
    2. 실제 처리를 담당하는 UserDetailService를 구현하는 방식
    대부분은 2번으로 충분. 하지만, 새로운 프로토콜&인증구현방식을 직접 구현할 때에는 1번을 직접 구현.


security-context.xml


<bean id="customAccessDenied" class="com.jslhrd.security.CustomAccessDeniedHandler"></bean> 접근 제한.
<bean id="customLoginSuccess" class="com.jslhrd.security.CustomLoginSuccessHandler"></bean> 로그인 성공.
<bean id="customPasswordEncoder" class="com.jslhrd.security.CustomNoOpPasswordEncoder"></bean> 밑의 bcrypt 사용.
<bean id="bcryptPasswordEndcoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
<bean id="customUserDetailsService" class="com.jslhrd.security.CustomUserDetailsService"> DB에서 Member와 Auth 내용을 가져옴.

<security:http auto-config="true" use-expressions="true">
     <security:intercept-url pattern="/sample/all" access="permitAll" /> 모든 사용자 접근 가능.
     <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')" /> MEMBER(로그인 성공 유저)만 접근 가능.

     <security:access-denied-handler error-page="/accessError" /> denied-handler는 error-page / ref 둘 중 하나만 사용.
     <security:access-denied-handler ref="customAccessDenied" />

     <security:form-login /> 로그인창으로 강제 이동 시 해당하는 페이지.
     <security:form-login login-page="/customLogin" authentication-success-handler-ref="customLoginSuccess"/> 로그인 성공 핸들러
</security:http>

<security:authentication-manager> 찐찐찐 최종 manager
     <security:authentication-provider user-service-ref="customUserDetailsService"> MemberMapper를 통해 유저 정보 사용
          <security:password-encoder ref="bcryptPasswordEndcoder" />
     </security:authentication-provider>
</security:authentication-manager>

<security:authentication-manager> 쿼리를 이용하는 인증.
     <security:authentication-provider>
          <security:jdbc-user-service data-source-ref="dataSource" 작성하기 전 root-context.xml에 dataSource 확인.
          users-by-username-query="select userid,userpw,enabled from tbl_member where userid=?"
          authorities-by-username-query="select userid,auth from tbl_auth where userid=?" />
          <security:password-encoder ref="customPasswordEncoder" /> 밑의 bcrypt 사용.
          <security:password-encoder ref="bcryptPasswordEndcoder" />
     </security:authentication-provider>
</security:authentication-manager>

<security:authentication-manager> 사용자 정보를 직접 등록시켜 인메모리로 인증.
     <security:authentication-provider>
          <security:user-service>
               <security:user name="member" password="{noop}member" authorities="ROLE_MEMBER" />
               <security:user name="admin" password="{noop}admin" authorities="ROLE_MEMBER, ROLE_ADMIN" />
          </security:user-service>
     </security:authentication-provider>
</security:authentication-manager>


username

p.620
    일반적인 경우 '사용자의 아이디 = userid' 를 의미하지만,
    스프링 시큐리티에서는 '사용자의 아이디 = username' 에 해당함에 주의.

    스프링 시큐리티의 User는 인증 정보와 권한을 가진 객체.
    ( 본 프로젝트는 MemberVO 라는 클래스 사용 )

    인증과 권한은 security-context.xml에서 UserDetailService를 이용하여 실제 처리함.


CSRF

p.634
    Cross-site request forgery
    스프링 시큐리티에서 POST방식을 이용하는 경우, 기본적으로 CSRF 토큰 사용.
    CSRF 토큰은 사용자가 임의로 변하는 난수의 토큰값을 서버에서 체크하는 방식.
    POST 전송 시, 토큰의 값이 다르면 작업을 처리하지 않는 방식.
    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />


resultMap & 테이블 JOIN

p.663
    MemberMapper.xml 중 id가 'read'인 <Select>태그는 resultMap 속성을 지정.
    회원정보인 MemberVO는 하나이지만, AuthVO는 2개가 되어야 하는 결과를 저장함.
    Mybatis에서는 이처럼 1:N (하나의 결과 + 부가적 데이터) 의 결과를 <resultMap> 태그로 처리한다.

    SELECT mem.userid,userpw,username,enabled,regdate,updatedate,auth
    FROM tbl_member mem LEFT OUT JOIN tbl_member_auth auth on mem.userid = auth.userid
    WHERE mem.userid = #{userid}


UserDetailsService

p.668
    -JDBC를 이용하는 방식으로 DB를 처리할 수 있지만, 사용자의 정보 중 제한적인 내용만 이용하는 것이 아쉽.
    고로 UserDetailsService를 구현하여 원하는 객체를 인증과 권한체크에 활용할 수 있도록 함.

    -UserDetailsService는 laodUserByUsername()라는 하나의 추상메서드만을 가지고 있다.
    return 타입은 org.springframework.security.core.userdetails.UserDetails라는 타입.
    본 프로젝트에서는 MemberVO의 인스턴스를 스프링 시큐리티의 UserDetails 타입으로 변환하는 작업을 처리.

    -CustomUser 클래스에서 슈퍼클래스(User)의 생성자를 호출하는 과정에서
    stream(), map()을 이용하여 AuthVO -> GrantedAuthority 객체로 변환.
    -CustomUserDetailsService 클래스에서 MemberVO의 인스턴스를 얻어, CustomUser 타입의 객체로 변환해서 반환.
    -웹페이지에서 'principal.member', 'principal.username' 등을 활용하여
    반환된 CustomUser 객체의 getMember()등의 메서드를 호출할 수 있다.



상위 폴더 패키지 클래스








src/main/java
com.jslhrd.sample Chef.java
Restaurant.java


com.jslhrd.controller
BoardController.java (C)(Pre)
HomeController.java
SampleController.java
CommonController.java
LoginController.java
com.jslhrd.mapper BoardMapper.java ( I )(Per)
TimeMapper.java
com.jslhrd.domain BoardVO.java (C)(Per)
MemberVO.java
AuthVO.java
com.jslhrd.service BoardService.java (I)(Ser)
BoardServiceImpl.java (C)(Ser)

com.jslhrd.security
CustomAccessDeniedHandler.java
CustomLoginSuccessHandler.java
CustomUserDetailsService.java
CustomNoOpPasswordEncoder.java
com.jslhrd.security.domain CustomUser.java

src/main/resources
com/jslhrd/mapper BoardMapper.xml
TimeMapper.xml
MemberMapper.xml
. log4jdbc.log4j2.properties




src/test/java
com.jslhrd.sample SmapleTests.java
com.jslhrd.controller BoardControllerTests.java (C)(Pre)

com.jslhrd.persistence
DataSourceTests.java
JDBCTests.java
TimeMapperTests.java
com.jslhrd.mapper BoardMapperTests.java (C)(Per)
MemberMapperTests.java
com.jslhrd.service BoardServiceTests.java (C)(Ser)
com.jslhrd.security MemberTests.java





webapp/WEB-INF/views

board
list.jsp (전체 목록)
insert.jsp (게시글 등록)
get.jsp (상세 조회)
update.jsp (수정 및 삭제)
includes headr.jsp
footer.jsp

sample
all.jsp (모든 유저)
member.jsp (로그인한 유저만)
admin.jsp (관리자만)
. home.jsp
customLogin.jsp
accessError.jsp

tbl_board
필드명 타입 설명
BNO NUMBER(10) 게시글 번호
TITLE VARCHAR(200) 제목
CONTENT VARCHAR(2000) 내용
WRITER VARCHAR(50) 작성자
HIT NUMBER(10) 조회수
REGDATE DATE 작성일
UPDATEDATE DATE 수정일
tbl_member
필드명 타입
USERID VARCHAR2(50)
USERPW VARCHAR2(100)
USERNAME VARCHAR2(100)
REGDATE DATE
UPDATEDATE DATE
ENABLED CHAR
tbl_member_auth
필드명 타입
USERID (FK) VARCHAR2(50)
AUTH VARCHAR2(50)