考核筆記 == ###### tags: `promotion note` {%hackmd BJrTq20hE %} ## 一、建立Maven專案 step 1 : Download GitLab > window -> show view -> other -> Git -> Git Repository -> Copy Clone專案路徑 -> Paste(Git Repository) -> 右鍵Import -> team ->切分支 step 2 : 調整專案結構 java檔 > 方法一 : 新增src資料夾 > main > java > 方法二 : 專案右鍵properties java complier level 選 1.8 > resources file、webapp file > ![](https://i.imgur.com/plgRiVG.png) ## 二、 建立SpringBoot專案 - maven 設定檔 加入所需要的Repository 1. Spring MVC 2. Oracle Driver 目前使用Oracle driver 4. JSP View 使用JSP 6. Devtool 熱部屬 7. lombok 可以用簡單的注解省略Java的code,像是 setter、getter、logger…等,目的在比較簡潔 9. JPA Java Persistence API 是一個標準的規範及接口(API)來實現ORM(object-relational mapping)框架,就直接通過註解(annotation)如:@Entity、@Table、@Column等註解或XML-Java been 和資料表的對映關係,並將執行期的實體物件持久化到資料庫中。 [JPA Persistence LifeCircle](https://) ![](https://i.imgur.com/y74mU3X.png) 10. JSTl (JSP Standard Tag Library) JSTL是jsp裡面的標準標籤庫,而這些能夠讓我們的jsp頁面只關注與View的部份,同時因為Tag可以被reuse,讓我們在維護上面更加容易。 之前在JSP中參雜java的寫法稱作ScriptLet,不好重複使用和維護,所以才有tag,可以對集合做iteration或者對輸出做format比較容易。 主要這次只會用到核心標籤庫 12. SpringSecurity (先註解最後在加) ```== <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.7</version> </parent> <dependencies> <!-- 加入依賴,方可使用 Spring MVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 加入依賴,方可使用 JSP --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!-- 加入依賴,載入 Oracle Driver --> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> </dependency> <!-- 加入依賴,開發時熱部署機制 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- orm 加入依賴,方可使用 JPA--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> </dependency> <!-- orm --> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!-- 加入依賴,方可使用 Spring Security 最後使用--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ``` ## 三、建立application.properties - Spring Boot 設定檔 也可以使用.yml (封包小) 直接建立在resources底下 > application.properties 1. 設定port號 2. jsp檔案位置 3. 資料庫連線資訊1534123456 4. 印出查詢 5. logging.level.org.hibernate.SQL=DEBUG 印出sql語句; logging.level.org.hibernate.type.descriptor.sql=TRACE 印出sql參數。 ```== server.port=8081 spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp spring.datasource.url=jdbc:oracle:thin:@61.216.84.220::XE spring.datasource.username=DEMO spring.datasource.password= spring.datasource.driver-class-name=oracle.jdbc.OracleDriver logging.level.org.hibernate.SQL=debug logging.level.org.hibernate.type=TRACE logging.level.org.hibernate.type.descriptor.sql=trace ``` 印出SQL結果 Console log ``` 2020-10-23 10:56:07.607 DEBUG 10804 --- [ main] o.h.SQL : insert into employee (id, age, create_date, email, name) values (null, ?, ?, ?, ?) 2020-10-23 10:56:07.612 TRACE 10804 --- [ main] o.h.t.d.s.BasicBinder : binding parameter [1] as [INTEGER] - [29] 2020-10-23 10:56:07.613 TRACE 10804 --- [ main] o.h.t.d.s.BasicBinder : binding parameter [2] as [TIMESTAMP] - [Fri Oct 23 10:56:07 CST 2020] 2020-10-23 10:56:07.621 TRACE 10804 --- [ main] o.h.t.d.s.BasicBinder : binding parameter [3] as [VARCHAR] - [john@abc.com] 2020-10-23 10:56:07.621 TRACE 10804 --- [ main] o.h.t.d.s.BasicBinder : binding parameter [4] as [VARCHAR] - [John] ``` ## 四、建立SpringBoot程式進入點Application step1. 在java package 底下<span style="color:yellow">必須</span>新建 package。 step2. 建立 java Class 作為程式的進入點 >PromotionApplication ```== import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PromotionApplication { public static void main (String[] args) { SpringApplication.run(PromotionApplication.class, args); } } ``` <span style="color:yellow">@SpringBootApplication 說明 會觸發自動配置、組件的掃描。是SprnigBoot的核心的註解。 然後他其實是由三個註解組成,分別是:@ComponentScan、@EnableAutoConfiguration、@SpringBootConfiguration。</span> 1. @ComponentScan為自動掃描註解,掃描那些被@Component和@Repository等註解修飾的組件或者@Bean。將他們加載到Spring的IoC容器中,讓後面去使用。 3. @EnableAutoConfiguration 就是開起程式後,會自動配置, (會根據你配置的classpath以及已經定義的一些Bean,會猜並且去配置我們想要的一些Bean。) @AutoConfigurationPackage:自動配置package @Import: 導入自動配置的組件 3. @SpringBootConfiguration的介紹 表示這class加上了@Configuration註解。就可以通過@Bean註解生成IOC容器管理的bean。這個註解應該在我們整個應用中只被使用一次,通常是從@SpringBootApplication註解中繼承過來。 ## 五、 建立Entity ![](https://i.imgur.com/w7DyjaN.png) 建立三個Entity <span style="color:yellow">@Entity</span>的Bean是告訴Spring這是數據模型Model層的宣告 <span style="color:yellow">@Table</span> : Table的name對映到資料庫中的資料表名稱 <span style="color:yellow">@Column</span> : 對應到Table的欄位中的欄位名稱,如果不寫,就要和資料庫名稱一樣且駝峰式命名 <span style="color:yellow">@Id</span> : 代表資料表的Primary Key <span style="color:yellow">@GeneratedValue</span> : 告訴此Column的生成方式 ,如果設定成GenerationType.AUTO讓容器來自動產生 <span style="color:yellow">@Data </span>: [lombok 是一種語法糖](https://kucw.github.io/blog/2020/3/java-lombok/), 同時加了以下4種注解 @Getter/@Setter @ToString @EqualsAndHashCode @RequiredArgsConstructor --- >Contact.java ``` == import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.annotations.GenericGenerator; import lombok.Data; @Entity @Table(name = "PM_CONTACT") @Data public class Contact { @Id @Column(name = "ID") private Integer id; @Column(name = "NAME") private String name; @Column(name = "EMAIL") private String email; @Column(name = "PHONE") private String phone; @Column(name = "CITY") private String city; @Column(name = "STATUS") private String status; } ``` --- > Cv.java ```== import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import org.springframework.format.annotation.DateTimeFormat; import lombok.Data; @Entity @Table(name = "PM_CV") @Data public class Cv { @Id @Column(name = "IDEN") private String iden; @Column(name = "NAME") private String name; @Column(name = "RATE") private Integer rate; @Column(name = "SKILL") private String skill; @Column(name = "ARRIVAL_NDATE") @DateTimeFormat(pattern = "yyyy-MM-dd") private Date arrivalNdate; @Column(name = "JOB_TYPE") private String jobType; @Column(name = "LOCATION") private String location; } ``` --- > Employee.java ```== import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import lombok.Data; @Entity @Table(name = "PM_EMPLOYEE") @Data public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") private Integer id; @Column(name="iden") private String iden; @Column(name="cname") private String cname; @Column(name="ename") private String ename; @Column(name="pword") private String pword; @Column(name="enabled") private String enabled; } ``` ## 六、 建立Repository > ContactRepository.java 模糊查詢 @Repository 可加可不加 ```== import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import promotion.dao.entity.Contact; @Repository public interface ContactRepository extends JpaRepository<Contact, String>{ @Query(value = "select * from PM_CONTACT WHERE 1=1 " + "AND UPPER(NAME) like UPPER(CONCAT(CONCAT('%', :name),'%'))" , nativeQuery = true) Page<Contact> findByName(@Param("name") String name, Pageable pageable); } ``` --- > CvRespository.java 模糊查詢 @Repository 可加可不加 ```== import java.util.Date; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import promotion.dao.entity.Cv; @Repository public interface CvRespository extends JpaRepository<Cv, String>{ @Query(value = "select * from PM_CV WHERE 1=1 " + "AND UPPER(NAME) LIKE UPPER(CONCAT(CONCAT('%', :name),'%')) " + "OR NAME IS NULL " + "AND SKILL LIKE '%:skill%' " + "OR SKILL IS NULL " + "AND JOB_TYPE LIKE '%:jobType%' " + "OR JOB_TYPE IS NULL " + "AND ARRIVAL_NDATE = TO_DATE('%:arrivalNdate%','yyyy-mm-dd HH24:mi:ss') " + "OR ARRIVAL_NDATE IS NULL " , nativeQuery = true) public Page<Cv> findEmployeeCvPage(@Param("name")String name, @Param("skill") String skill, @Param("jobType") String jobType, @Param("arrivalNdate")String arrivalNdate, Pageable pageable); } ``` > EmployeeRepository ``` == import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import promotion.dao.entity.Employee; @Repository public interface EmployeeRepository extends JpaRepository<Employee, Integer>{ @Query(value = "SELECT * FROM PM_EMPLOYEE WHERE IDEN = :iden",nativeQuery = true) public Optional<Employee> findByIden(@Param("iden") String iden); } ``` ## 七、 製作Table頁面 ![](https://i.imgur.com/GKFBXSE.png) Introduce : Controller(not All) -> ServiceInfo -> ServiceImpl -> Controller(解釋) -> JSP ### Step 1 - 建立 TableController ```== @Slf4j @RestController public class TableController { } ``` ### Step 2 - 建立 CvServiceInf ```== public interface CvServiceInf { Page<Cv> findAllCvForTablePage(int currentPage); Page<Cv> queryAllCvForTablePage(int currentPage, String name, String skill, String date, String jobType); } ``` <span style="color:yellow">說明 CvServiceInf</span> 我會先建立 Service 的 Interface,這個專案的情境可能用不到,可能之後會有切換還境的需求,會去使用profile去切換,用interface就不會去互相影響,例如三星專案的mock 和 !mock。 --- ### Step 3 - 實作 CvServiceImpl ```== @Slf4j @Service public class CvServiceImpl implements CvServiceInf { @Autowired CvRespository cvRespository; /** * table加入分頁的參數 **/ @Override public Page<Cv> findAllCvForTablePage(int currentPage) { Page<Cv> page = cvRespository.findAll(PageRequest.of(currentPage, 5)); return page ; } /** * table 分頁查詢 */ @Override public Page<Cv> queryAllCvForTablePage(int currentPage, String name, String skill, String date, String jobType) { Pageable pageable = PageRequest.of(currentPage, 5); Page<Cv> page = cvRespository.findEmployeeCvPage(name , skill, jobType, date ,pageable); log.info("查詢 employee cv 結果 ===>{} ", page); return page; } } ``` ### Step 4 - 說明TableController ```== import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import lombok.extern.slf4j.Slf4j; import promotion.dao.entity.Cv; import promotion.domain.service.CvServiceInf; @Slf4j @RestController public class TableController { @Autowired CvServiceInf cvServiceInf; @GetMapping("/index") public ModelAndView tableView(@RequestParam(value="page",defaultValue = "0") Integer currentPage) { log.info("<===== Start#TableView =====>"); ModelAndView mv = new ModelAndView("table"); Page<Cv> findAllCv = cvServiceInf.findAllCvForTablePage(currentPage); mv.addObject("page", findAllCv); mv.addObject("currentPage",currentPage); log.info("data pages => {}", findAllCv); log.info("currentPage pages => {}", currentPage); return mv; } @PostMapping("/search") public ModelAndView tableSearch(@RequestParam(value="page",defaultValue = "0") Integer currentPage, @RequestParam(name = "name" ,required = false)String name, @RequestParam(name = "skill",required = false) String skill, @RequestParam(name = "arrivalNdate",required = false) String date, @RequestParam(name = "jobType",required = false) String jobType) { log.info("查詢條件name: " + name); log.info("查詢條件skill: " + skill); log.info("查詢條件date: " + date); log.info("查詢條件jobType: " + jobType); System.err.println("============> currentPager" + currentPage); ModelAndView mv = new ModelAndView("table"); System.out.println("CurrentPage ========>" + currentPage); Page<Cv> queryEmployeeCv = cvServiceInf.queryAllCvForTablePage(currentPage, name, skill, date, jobType); mv.addObject("page", queryEmployeeCv); mv.addObject("currentPage",currentPage); log.info("data pages => {}", queryEmployeeCv); return mv; } } ``` ### 說明 :8ball: :8ball: :8ball: <span style="color:yellow">1. @Slf4j</span> Slf4j是一個框架,它是對所有log框架制定的一種規範、像是interface,並不是一個具體的實現, 也因為interface不能獨立使用,它需要和具體的log框架來配合使用; Log4j2是一個具體實現;在Java中具體的log實現有多種,比如Log4j、Log4j2、Slf4j、JDKLog、Logback等等; 最後,你要知道,框架和實現的這種搭配使用方式,是為了以後如果有其它要求而需要更換log框架時可以不改動代碼,只需要把依賴的jar包換掉就可以了。 <span style="color:yellow">2. @RestController</span> 與一般@Controller的差別,只是@RestController 會等於@Controller搭配@ResponseBody。 如果用@RestController他會回傳json格式,那要傳畫面就要使用ModelAndView <!-- 在撰寫RESTful API時,因為通常都是回傳json,所以回傳無須交由View Resolver處理來返回頁面,所以會在Controller的API方法前加上@Responsebody來達成, --> <span style="color:yellow">3. Table 解釋</span> 我在jsp table 頁面會需要做分頁,所以currentPage參數的是當前頁數,我這邊預設為0,jsp那邊就要+1 讓分頁頁籤顯示第一頁,url回傳時會帶1 FindAll這個方法會回傳page物件,在用ModelAndView Object的方式帶給JSP做分頁處理。 再來會把currentPage再丟回JSP做頁籤上一頁、下一頁的處理。 <span style="color:yellow">3. Search 解說</span> 這邊也有針對Table做上方搜尋欄位簡單模糊查詢, 跟做分頁的原理一樣 <span style="color:yellow">說明 CvServiceImpl</span> 1. 先加@Autowired依賴注入 2. 這邊兩個方法,一個是table載入時去做分頁,另一個是去做模糊查詢後的分頁。 --- ### Step 5 - Table JSP頁面 <span style="color:Red">異動區塊</span> 1. 加入JSTL標籤庫 2. 區塊S - 調整CSS路徑 /assets...... 3. sideBar href 導頁 4. form 表單調整、一樣改href路徑 改去找這支controller 5. EL語法ForEach帶入資料,items: page.content代表取的這個page的集合然後去迭帶,var表示給他一個變數 6. cv.屬性,其實是這個物件的getter 7. 分頁區塊的部分 > <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> ``` == html <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>IMSoftware</title> <!--------------------------CSS 區塊 S----------------------------> <link type="text/css" rel="stylesheet" href="/assets/css/main/app.css" /> <link type="text/css" rel="stylesheet" href="/assets/css/main/app-dark.css" /> <link type="text/css" rel="stylesheet" href="/assets/images/logo/favicon.svg" /> <link type="text/css" rel="stylesheet" href="/assets/images/logo/favicon.png" /> <link type="text/css" rel="stylesheet" href="/assets/css/shared/iconly.css" /> <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" /> <!--------------------------CSS 區塊 E----------------------------> </head> <body> <div id="app"> <div id="sidebar" class="active"> <div class="sidebar-wrapper active"> <div class="sidebar-header position-relative"> <div class="d-flex justify-content-between align-items-center"> <!--------------------------LOGO 區塊 S----------------------------> <div class="logo"> <a href="/index"><img src="assets/images/logo/logo.png" style="height:120px; width:120px;" alt="Logo" srcset=""></a> </div> <!--------------------------LOGO 區塊 E----------------------------> <!--------------------------亮/暗色系切換按鈕 區塊 S----------------------------> <div class="theme-toggle d-flex gap-2 align-items-center mt-2"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--system-uicons" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 21 21"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M10.5 14.5c2.219 0 4-1.763 4-3.982a4.003 4.003 0 0 0-4-4.018c-2.219 0-4 1.781-4 4c0 2.219 1.781 4 4 4zM4.136 4.136L5.55 5.55m9.9 9.9l1.414 1.414M1.5 10.5h2m14 0h2M4.135 16.863L5.55 15.45m9.899-9.9l1.414-1.415M10.5 19.5v-2m0-14v-2" opacity=".3"></path><g transform="translate(-210 -1)"><path d="M220.5 2.5v2m6.5.5l-1.5 1.5"></path><circle cx="220.5" cy="11.5" r="4"></circle><path d="m214 5l1.5 1.5m5 14v-2m6.5-.5l-1.5-1.5M214 18l1.5-1.5m-4-5h2m14 0h2"></path></g></g></svg> <div class="form-check form-switch fs-6"> <input class="form-check-input me-0" type="checkbox" id="toggle-dark" > <label class="form-check-label" ></label> </div> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="currentColor" d="m17.75 4.09l-2.53 1.94l.91 3.06l-2.63-1.81l-2.63 1.81l.91-3.06l-2.53-1.94L12.44 4l1.06-3l1.06 3l3.19.09m3.5 6.91l-1.64 1.25l.59 1.98l-1.7-1.17l-1.7 1.17l.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95l2.06.05m-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85c-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14c.4-.4.82-.76 1.27-1.08c.75-.53 1.93.36 1.85 1.19c-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82c-2.81 3.14-2.7 7.96.31 10.98c3.02 3.01 7.84 3.12 10.98.31Z"></path></svg> </div> <div class="sidebar-toggler x"> <a href="#" class="sidebar-hide d-xl-none d-block"><i class="bi bi-x bi-middle"></i></a> </div> <!--------------------------亮/暗色系切換按鈕 區塊 E----------------------------> </div> </div> <!--------------------------Sidebar 區塊 S----------------------------> <div class="sidebar-menu"> <ul class="menu"> <li class="sidebar-item active "> <a href="/index" class='sidebar-link'> <i class="bi bi-grid-1x2-fill"></i> <span>Table</span> </a> </li> <li class="sidebar-item "> <a href="/datatable" class='sidebar-link'> <i class="bi bi-grid-1x2-fill"></i> <span>Datatable</span> </a> </li> </ul> </div> <!--------------------------Sidebar 區塊 E----------------------------> </div> </div> <div id="main"> <header class="mb-3"> <a href="#" class="burger-btn d-block d-xl-none"> <i class="bi bi-justify fs-3"></i> </a> </header> <div class="page-heading"> <!--------------------------登出按鈕 區塊 S----------------------------> <div class="page-title"> <div class="row"> <div class="col-12 col-md-6 order-md-1 order-last"> <h3>Table</h3> </div> <div class="col-12 col-md-6 order-md-2 order-first"> <nav aria-label="breadcrumb" class="breadcrumb-header float-start float-lg-end"> <a href="/logout" class="btn icon icon-left"><i data-feather="user"></i>登出</a> </nav> </div> </div> </div> <!--------------------------登出按鈕 區塊 E----------------------------> <section class="section"> <div class="row" id="table-head"> <div class="col-12"> <div class="card"> <div class="card-header"> <h4 class="card-title">Table Name</h4> </div> <div class="card-content"> <!--------------------------表單元件 區塊 S----------------------------> <form action="/search" method="POST" > <div class="card-body"> <div class="row"> <div class="col-2"> <input type="text" class="form-control" placeholder="NAME" name="name"/> </div> <div class="col-2"> <div class="col-2 col-2"></div> <div class="col-2 col-10"> <select class="form-select" name="skill"> <option disabled selected>SKILL</option> <option>UI/UX</option> <option>Graphic concepts</option> <option>Animation</option> </select> </div> </div> <div class="col-2"> <input class="form-control" type="date" name="arrivalNdate"/> </div> <div class="col-3 row "> <div class="col-4 col-6"> <input class="form-check-input" type="radio" value="remote" name="jobType" id="remote" /> <label class="form-check-label" for="radio" >remote</label> </div> <div class="col-4 col-6"> <input class="form-check-input" type="radio" name="jobType" id="near" value= "near"/> <label class="form-check-label" for="radio">near</label> </div> </div> <div class="col-3"> <button class="btn btn-primary rounded-pill">查詢</button> <button class="btn btn-success rounded-pill" data-bs-toggle="modal" data-bs-target="#exampleModalCenter">新增</button> <button class="btn btn-danger rounded-pill">刪除</button> </div> </div> </div> </form> <!--------------------------表單元件 區塊 E----------------------------> <!---------------------------table 區塊 S-----------------------------> <div class="table-responsive"> <!--***** 這裡就是要作業的地方 *****--> <table class="table mb-0"> <thead class="thead-dark"> <tr> <th></th> <th>NAME</th> <th>RATE</th> <th>SKILL</th> <th>DATE</th> <th>TYPE</th> <th>LOCATION</th> <th>ACTION</th> </tr> </thead> <tbody> <c:forEach items ="${page.content}" var="cv" varStatus="status"> <tr> <td><input type="checkbox" class="form-check-input"></td> <td class="text-bold-500">${cv.name}</td> <td>${cv.rate}/hr</td> <td class="text-bold-500">${cv.skill}</td> <td><fmt:formatDate pattern="yyyy/MM/dd" value="${cv.arrivalNdate}"/></td> <td>${cv.jobType}</td> <td>${cv.location}</td> <td> <button class="btn btn-sm icon icon-left btn-warning rounded-pill" data-bs-toggle="modal" data-bs-target="#exampleModalCenter" value="${status.index + 1}"><i data-feather="edit"></i>編輯</button> </td> </tr> </c:forEach> </tbody> </table> </div> <!---------------------------table 區塊 E-----------------------------> <!----------------------------分頁 區塊 S------------------------------> <nav aria-label="Page navigation example" style="margin-top: 1rem;"> <ul class="pagination pagination-primary justify-content-center"> <c:if test="${currentPage > 1}"> <li class="page-item"> <a class="page-link" href="/index?page=<c:out value="${currentPage-1}"/>"><span aria-hidden="true"><i class="bi bi-chevron-left"></i></span></a> </li> </c:if> <c:choose> <c:when test = "${page.totalPages > 0}"> <c:forEach var="index" begin="0" end="${page.totalPages-2}"> <li class="page-item"> <a class="page-link" href="/index?page=<c:out value="${index+1}"/>"><c:out value="${index+1}"/></a> </li> </c:forEach> </c:when> <c:otherwise> <li class="page-item"> <a class="page-link" href="/index?page=1"><c:out value="1"/></a> </li> </c:otherwise> </c:choose> <c:if test="${currentPage < page.totalPages-1}"> <li class="page-item"> <a class="page-link" href="/index?page=<c:out value="${currentPage+1}"/>"><span aria-hidden="true"><i class="bi bi-chevron-right"></i></span></a> </li> </c:if> </ul> </nav> <!----------------------------分頁 區塊 E------------------------------> </div> </div> </div> </div> </section> <footer> <div class="footer clearfix mb-0 text-muted"> <div class="float-start"> <p>2022 © IMSOFTWARE</p> </div> </div> </footer> </div> </div> </div> <!--------------------------modal 彈出視窗 區塊 S----------------------------> <div class="modal fade" id="exampleModalCenter" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered modal-dialog-centered modal-dialog-scrollable" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalCenterTitle">新增/修改資料 </h5> <button type="button" class="close" data-bs-dismiss="modal" aria-label="Close"> <i data-feather="x"></i> </button> </div> <form class="form form-vertical"> <div class="modal-body"> <div class="form-body"> <div class="row"> <div class="col-12"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" class="form-control" name="name" placeholder="Name"> </div> </div> <div class="col-12"> <div class="form-group"> <label for="rate">Rate</label> <div class="input-group mb-3"> <span class="input-group-text">$</span> <input type="text" class="form-control" id="rate" placeholder="Rate"> <span class="input-group-text">/hr</span> </div> </div> </div> <div class="col-12"> <div class="form-group"> <label for="skill">Skill</label> <select class="form-select" id="skill"> <option selected>Skill</option> <option>UI/UX</option> <option>Graphic concepts</option> <option>Animation</option> </select> </div> </div> <div class="col-12"> <div class="form-group"> <label for="date">Date</label> <input class="form-control" type="date" id="date"> </div> </div> <div class="col-12"> <div class="form-group"> <label for="type">Type</label> <div id="type"></div> <div class="row"> <div class="col-3"> <input class="form-check-input" type="radio" name="radio" id="Remote"> <label class="form-check-label" for="radio">Remote</label> </div> <div class="col-3"> <input class="form-check-input" type="radio" name="radio" id="near"> <label class="form-check-label" for="radio">near</label> </div> </div> </div> </div> <div class="col-12"> <div class="form-group"> <label for="location">Location</label> <input type="text" id="location" class="form-control" name="location" placeholder="Location"> </div> </div> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-light-secondary" data-bs-dismiss="modal"> <i class="bx bx-x d-block d-sm-none"></i> <span class="d-none d-sm-block">取消</span> </button> <button type="submit" class="btn btn-primary ml-1" data-bs-dismiss="modal"> <i class="bx bx-check d-block d-sm-none"></i> <span class="d-none d-sm-block">儲存</span> </button> </div> </form> </div> </div> </div> <!--------------------------modal 彈出視窗 區塊 E----------------------------> <!--------------------------js 區塊 S----------------------------> <script src="/assets/js/bootstrap.js"></script> <script src="/assets/js/app.js"></script> <script src="/assets/extensions/jquery/jquery.min.js"></script> <script src="/assets/extensions/apexcharts/apexcharts.min.js"></script> <script src="/assets/js/pages/dashboard.js"></script> <!--------------------------js 區塊 E----------------------------> </body> </html> ``` --- ## 八、製作DataTable頁面 製作之前: [DataTable](https://datatables.net/) ### step1 JSP 頁面說明 重點 1. 引入Datatable bootstrap css js 2. DataTable說明 欄位數據的處理我是直接把title寫在dataTable,html中表頭的就可以刪掉 最後一個status 我用columns 的 render屬性,返回需要顯示的內容,也就是在這裡寫了判斷,icon顯示才不同 ```== <script> $(document).ready(function() { $('#example').DataTable({ processing: true, serverSide: true, ajax: 'paging', columns: [ { data: 'name', title:'Name' }, { data: 'email', title:'Email'}, { data: 'phone', title:'Phone'}, { data: 'city', title:'City'}, { data: 'status', title:'Status', render:function(data){ if (data === 'A'){return '<span class="badge bg-success">Active</span>'} else if (data ==='I'){return '<span class="badge bg-danger">Inactive</span>'} }} ], }); }); </script> ``` --- 完整code ```== <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <c:set var="root" value="${pageContext.request.contextPath}" /> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Table Datatable Jquery - Mazer Admin Dashboard</title> <!--------------------------CSS 區塊 S----------------------------> <link rel="stylesheet" href="/assets/css/main/app.css"> <link rel="stylesheet" href="/assets/css/main/app-dark.css"> <link rel="shortcut icon" href="/assets/images/logo/favicon.svg" type="image/x-icon"> <link rel="shortcut icon" href="/assets/images/logo/favicon.png" type="image/png"> <link rel="stylesheet" href="/assets/css/pages/fontawesome.css"> <link rel="stylesheet" href="/assets/extensions/datatables.net-bs5/css/dataTables.bootstrap5.min.css"> <link rel="stylesheet" href="/assets/css/pages/datatables.css"> <!--------------------------CSS 區塊 E----------------------------> </head> <body> <div id="app"> <div id="sidebar" class="active"> <div class="sidebar-wrapper active"> <div class="sidebar-header position-relative"> <div class="d-flex justify-content-between align-items-center"> <!--------------------------LOGO 區塊 S----------------------------> <div class="logo"> <a href="/index"><img src="assets/images/logo/logo.png" style="height: 120px; width: 120px;" alt="Logo" srcset=""></a> </div> <!--------------------------LOGO 區塊 E----------------------------> <!--------------------------亮/暗色系切換按鈕 區塊 E----------------------------> <div class="theme-toggle d-flex gap-2 align-items-center mt-2"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--system-uicons" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 21 21"> <g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"> <path d="M10.5 14.5c2.219 0 4-1.763 4-3.982a4.003 4.003 0 0 0-4-4.018c-2.219 0-4 1.781-4 4c0 2.219 1.781 4 4 4zM4.136 4.136L5.55 5.55m9.9 9.9l1.414 1.414M1.5 10.5h2m14 0h2M4.135 16.863L5.55 15.45m9.899-9.9l1.414-1.415M10.5 19.5v-2m0-14v-2" opacity=".3"></path> <g transform="translate(-210 -1)"> <path d="M220.5 2.5v2m6.5.5l-1.5 1.5"></path> <circle cx="220.5" cy="11.5" r="4"></circle> <path d="m214 5l1.5 1.5m5 14v-2m6.5-.5l-1.5-1.5M214 18l1.5-1.5m-4-5h2m14 0h2"></path></g></g></svg> <div class="form-check form-switch fs-6"> <input class="form-check-input me-0" type="checkbox" id="toggle-dark"> <label class="form-check-label"></label> </div> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"> <path fill="currentColor" d="m17.75 4.09l-2.53 1.94l.91 3.06l-2.63-1.81l-2.63 1.81l.91-3.06l-2.53-1.94L12.44 4l1.06-3l1.06 3l3.19.09m3.5 6.91l-1.64 1.25l.59 1.98l-1.7-1.17l-1.7 1.17l.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95l2.06.05m-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85c-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14c.4-.4.82-.76 1.27-1.08c.75-.53 1.93.36 1.85 1.19c-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82c-2.81 3.14-2.7 7.96.31 10.98c3.02 3.01 7.84 3.12 10.98.31Z"></path></svg> </div> <div class="sidebar-toggler x"> <a href="#" class="sidebar-hide d-xl-none d-block"><i class="bi bi-x bi-middle"></i></a> </div> <!--------------------------亮/暗色系切換按鈕 區塊 E----------------------------> </div> </div> <!--------------------------Sidebar 區塊 S----------------------------> <div class="sidebar-menu"> <ul class="menu"> <li class="sidebar-item"><a href="/index" class='sidebar-link'> <i class="bi bi-grid-1x2-fill"></i> <span>Table</span> </a></li> <li class="sidebar-item active"><a href="/datatable" class='sidebar-link'> <i class="bi bi-grid-1x2-fill"></i> <span>Datatable</span> </a></li> </ul> </div> <!--------------------------Sidebar 區塊 E----------------------------> </div> </div> <div id="main"> <header class="mb-3"> <a href="#" class="burger-btn d-block d-xl-none"> <i class="bi bi-justify fs-3"></i> </a> </header> <div class="page-heading"> <!--------------------------登出按鈕 區塊 S----------------------------> <div class="page-title"> <div class="row"> <div class="col-12 col-md-6 order-md-1 order-last"> <h3>Jquery Datatable</h3> </div> <div class="col-12 col-md-6 order-md-2 order-first"> <nav aria-label="breadcrumb" class="breadcrumb-header float-start float-lg-end"> <a href="/logout" class="btn icon icon-left"><i data-feather="user"></i>登出</a> </nav> </div> </div> </div> <!--------------------------登出按鈕 區塊 E----------------------------> <!---------------------------table 區塊 S-----------------------------> <section class="section"> <div class="card"> <div class="card-header"> <h4 class="card-title">Table Name</h4> </div> <div class="card-body"> <!--***** 這裡就是要作業的地方 *****--> <table id="example" class="display" style="width: 100%"></table> </div> </div> </section> <!---------------------------table 區塊 E-----------------------------> <footer> <div class="footer clearfix mb-0 text-muted"> <div class="float-start"> <p>2022 © IMSOFTWARE</p> </div> </div> </footer> </div> </div> </div> <!--------------------------js 區塊 S----------------------------> <script src="/assets/js/bootstrap.js"></script> <script src="/assets/js/app.js"></script> <script src="/assets/extensions/jquery/jquery.min.js"></script> <script src="https://cdn.datatables.net/v/bs5/dt-1.12.1/datatables.min.js"></script> <!--------------------------js 區塊 E----------------------------> <script> $(document).ready(function() { $('#example').DataTable({ processing: true, serverSide: true, ajax: 'paging', columns: [ { data: 'name', title:'Name' }, { data: 'email', title:'Email'}, { data: 'phone', title:'Phone'}, { data: 'city', title:'City'}, { data: 'status', title:'Status', render:function(data){ if (data === 'A'){return '<span class="badge bg-success">Active</span>'} else if (data ==='I'){return '<span class="badge bg-danger">Inactive</span>'} }} ], }); }); </script> </body> </html> ``` --- ### Step2 製作 ContactVo ```== import java.util.List; import lombok.Data; import promotion.dao.entity.Contact; @Data public class ContactVo { private Integer draw; private Long recordsFiltered; private Long recordsTotal; private List<Contact> data; } ``` <span style="color:yellow">說明 ContactVo</span> draw:表示請求次數 recordsTotal:總記錄數 recordsFiltered:過濾後的總記錄數 data:具體的數據對象數組 <span style="color:yellow">分頁製作必須符合格式</span> ```== { "draw": 2, "recordsTotal": 11, "recordsFiltered": 11, "data": [ { "id": 1, "firstName": "Troy", "lastName": "Young" }, { "id": 2, "firstName": "Alice", "lastName": "LL" }, { "id": 3, "firstName": "Larry", "lastName": "Bird" } // ...... ] } ``` --- ### Step3 製作 Controller >DataTableController ```== @Slf4j @RestController public class DataTableController { } ``` ---- ### Step4 製作ContactServiceInf ```== iimport org.springframework.data.domain.Page; import promotion.dao.entity.Contact; import promotion.dao.entity.Employee; public interface ContactServiceInf { Page<Contact> findAllCvForDataTablePage(int currentPage, int length, String search); } ``` ---- ### Step5 製作ContactServiceImpl ```== import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import lombok.extern.log4j.Log4j2; import promotion.dao.entity.Contact; import promotion.dao.repository.ContactRepository; @Log4j2 @Service public class ContactServiceImpl implements ContactServiceInf{ @Autowired ContactRepository contactRepository; @Override public Page<Contact> findAllCvForDataTablePage(int currentPage, int length, String search) { log.info("currentPage ==>{}, draw ==>{}", currentPage, length); // contactRepository.findAll(PageRequest.of(currentPage,length)); return contactRepository.findByName("%" + search + "%",PageRequest.of(currentPage, length)); } } ``` --- ### Step6 說明DataTableController ```== import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import lombok.extern.slf4j.Slf4j; import promotion.dao.entity.Contact; import promotion.domain.service.ContactServiceInf; import promotion.domain.vo.ContactVo; @Slf4j @RestController public class DataTableController { @Autowired ContactServiceInf employeeServiceInf; @GetMapping("/datatable") public ModelAndView dataTable() { log.info("<==== Start dataTable ====>"); ModelAndView mv = new ModelAndView("datatable"); return mv; } @GetMapping("/paging") public Object dataTablePaging(HttpServletRequest request, int draw, int start, int length, @RequestParam("search[value]") String search) { Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String names = parameterNames.nextElement(); String key= request.getParameter(names); log.info(names +" - " + key); } Page<Contact> page = employeeServiceInf.findAllCvForDataTablePage(start/length, length, search); ContactVo contactInfo = new ContactVo(); contactInfo.setDraw(draw); contactInfo.setRecordsFiltered(page.getTotalElements()); contactInfo.setRecordsTotal(page.getTotalElements()); contactInfo.setData(page.getContent()); return contactInfo; } } ``` <span style="color:yellow">說明 DataTableController</span> 第一個單純是進入Datatable頁面 第二個是做DataTable,有連同分頁、搜尋一起做,可以用HttpServletRequest看到datatable傳了那些參數過來。 參數解釋: * draw:表示請求次數 * recordsTotal:總記錄數 * recordsFiltered:過濾後的總記錄數 * data:具體的數據的object list * start: 起始記錄位置 * length:頁面顯示數量 * start/length : 就表示當前頁面 [參考資料-jQuery Datatable 實用簡單實例](https://www.twblogs.net/a/5c6fbf9fbd9eee7f07332f40) --- ## 九、SpringSecurity ![](https://i.imgur.com/cKHDQlp.png) ### Step1 先開啟SpringSecurity註解 ```== <!-- 加入依賴,方可使用 Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` ### Step2 login jsp 頁面 ```== <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login - Mazer Admin Dashboard</title> <!--------------------------CSS 區塊 S----------------------------> <link rel="stylesheet" href="/assets/css/main/app.css"> <link rel="stylesheet" href="/assets/css/pages/auth.css"> <link rel="shortcut icon" href="/assets/images/logo/favicon.svg" type="image/x-icon"> <link rel="shortcut icon" href="/assets/images/logo/favicon.png" type="image/png"> <!--------------------------CSS 區塊 E----------------------------> </head> <body> <div id="auth"> <div id="auth-center"> <div class="card"> <div class="card-content"> <div class="card-body"> <h4 class="card-title">登入</h4> <p class="card-subtitle mb-5">請輸入帳號密碼</p> <!--------------------------form S----------------------------> <form class="form" method="post" action="/login"> <div class="form-group position-relative has-icon-left mb-4"> <input type="text" class="form-control form-control-xl" placeholder="帳號" name="username"> <div class="form-control-icon"> <i class="bi bi-person"></i> </div> </div> <div class="form-group position-relative has-icon-left mb-4"> <input type="password" class="form-control form-control-xl" placeholder="密碼" name="password"> <div class="form-control-icon"> <i class="bi bi-shield-lock"></i> </div> </div> <button type=submit class="btn btn-primary btn-block btn-lg shadow-lg mt-5">登入</button> <span>${SPRING_SECURITY_LAST_EXCEPTION.message}</span> </form> <!--------------------------form E----------------------------> </div> </div> </div> </div> </div> </body> </html> ``` <span style="color:yellow">說明 login jsp</span> 增加 name="username" name="password" >csrf不關jsp form得要加token <input type="hidden" value="${_csrf.token}" name="${_csrf.parameterName}"> --- ### Step 3 login Controller ``` == import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import lombok.extern.slf4j.Slf4j; import promotion.framework.security.service.CustUserDetailService; @Slf4j @RestController public class LoginController { @GetMapping("/login") public ModelAndView tableView() { log.info("<===== Start#TableView =====>"); ModelAndView mv = new ModelAndView("login"); return mv; } } ``` <span style="color:yellow">說明</span> 進入 login 頁面 ### Step 4 CustUserDetails --- ``` == import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class CustUserDetails implements UserDetails{ private String username; private String password; // public CustUserDetails(String username, String password) { // this.username = username; // this.password = password; // } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub return null; } @Override public String getPassword() { // TODO Auto-generated method stub return this.password; } @Override public String getUsername() { // TODO Auto-generated method stub return this.username; } @Override public boolean isAccountNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isAccountNonLocked() { // TODO Auto-generated method stub return true; } @Override public boolean isCredentialsNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isEnabled() { // TODO Auto-generated method stub return true; } } ``` --- ### Step 5 SecurityConfig ```== import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean protected SecurityFilterChain configure(final HttpSecurity http) throws Exception { http.csrf().disable(); AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry = http.authorizeHttpRequests(); registry .antMatchers("/assets/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/index",true) .permitAll() .and() .logout() .permitAll(); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); return NoOpPasswordEncoder.getInstance(); } } ``` <span style="color:yellow">說明</span> 進入 login 頁面 <span style="color:pink">@Configuration 類別要成為Spring容器管理的bean</span> <span style="color:pink">@EnableWebSecurity 是用來啟用Spring Security所需的各項配置</span> > What is the reason to disable csrf in a Spring Boot application? 1. You are using another token mechanism. 使用其他token機制 2. You want to simplify interactions between a client and the server. 3. Spring recommend using it when serving browser clients, if not it may be disabled: <span style="color:yellow">達成 CSRF 攻擊流程三要素</span> * 有一個可以觸發惡意腳本的動作 * 只以單一條件驗證網站身份,例如:只驗證 cookie 或 token * 沒有驗證或是驗證的參數可以預測(固定的 cookie ) <span style="color:yellow">防範 CSRF 的重點在於打破 CSRF 攻擊流程三要素,</span> * 增加所有敏感動作的驗證方式,例如:金流、提交個資 等…多加一道驗證碼的機制 * 增加無法預測的參數,常見且有效的防範方式例如:CSRF token (在頁面的 form 或是 custom header 裡面放一個 token 然後要求 client request 要夾帶這個 token ) <span style="color:yellow">如果是csrf沒有關,就要在jsp頁面加入</span> > <input type="hidden" value="${_csrf.token}" name="${_csrf.parameterName}"> 有點類似取得令牌的token就可以通過csrf的保護機制 --- ### Step 5 CustUserDetailService ```== import java.util.Optional; import javax.servlet.http.HttpServletRequest; 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 org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import lombok.extern.slf4j.Slf4j; import promotion.dao.entity.Employee; import promotion.dao.repository.EmployeeRepository; import promotion.framework.security.userdetails.CustUserDetails; @Slf4j @Service public class CustUserDetailService implements UserDetailsService{ @Autowired EmployeeRepository employeeRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info("<====== Security Check Start ======>"); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); log.info("帳號: " + request.getParameter("username")); log.info("密碼: " + request.getParameter("password")); Optional<Employee> checkUser = employeeRepository.findByIden(username); if(checkUser.isPresent()) { Employee employee = checkUser.get(); CustUserDetails user = new CustUserDetails(); log.info("================>帳號: " + employee.getIden()); log.info("================>密碼: " + employee.getPword()); user.setPassword(employee.getPword()); user.setUsername(employee.getIden()); return user; }else { return null; } } } ``` <span style="color:yellow">說明</span> > 接受參數 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 從jsp接收帳號、密碼,去檢查有沒有這個登入者 ## 補充CSRF 攻擊流程 CSRF ( Cross Site Request Forgery ),翻成中文叫做跨站請求偽造,其實字面上把他猜成拆開成請求偽造和跨站之後就蠻好理解的,怎麼說呢? 我以前言的例子來說明一下跨站請求偽造是怎麼一回事 先講請求偽造,請求偽造的意思很好理解,指的是陌生人拿了一張有你桌號的菜單點自己想點的餐之後給老闆這件事( Hacker 用帶著你 cookie 的 Request 送給 web server ),那至於跨站跨在哪裡呢? 跨在陌生人在你不知情的情況下把有你桌號的菜單送給了老闆,所以跨過了本該知情的你(送出的人不同,所以送出 Request 的 Domain 註2) 也會不同),CSRF 的本質在於 web server 無條件信任 cookie 而沒有再確認或以其他方式驗證( 等於老闆問也不問無條件相信菜單上的桌號,也不看是誰送的),因此只能保證這個Request 發自某個 User ,卻不能保證請求本身是 User 自願發出的( 等於菜單上的桌號是你的,但不代表這個菜是你點的 )。 用簡單的圖帶你看一下 Hacker 的犯案過程 ![](https://i.imgur.com/s6VzPqm.png) * User 訪問並登入 A 網站 * User 獲得 Cookie 並存至 User 瀏覽器 * User 在未登出 A 網站的情況下瀏覽 B 網站,接著 Hacker 以 B 網站的 Domain 以 A 網站給 User 的 Cookie 對 A 網站發送請求,如果 A 網站沒發現的話就 GG 了。 *B網站會在自身的網站中加入含 A 網站 Domain 的 Javascript ,例如:上方圖中的 A/pay?to=B ,這裡的 A 就是指 A 網站 Domain ,然後去執行 Pay 這個動作給 B ,這個攻擊的破綻就是剛剛前言例子中提到的,送出資料的Domain 不同,另外,貼心小提醒,在惡意網站中就算只是滑鼠移過圖片也可能會執行惡意的 Javascript 千萬不要覺得我都不點就沒事。 通常 Hacker 發現網站有漏洞時,都會以金流及竊取隱私資料為主要攻擊面向,畢竟 Hacker 除了做興趣也是要吃飯,因此當網站在設計關於金流及隱私資料的架構時需要特別小心, ## 實作 CSRF Token 邏輯 建立:在 User 打開網頁時,Server 會根據 User 的身份生成一個 Token ,將 Token 存放在頁面中(通常生成的基礎是 User 名稱加上隨機亂數或是時間戳記的加密組合,另外需要注意的是 Token 需要額外放置,不能依然存放在 Cookie 中,不然一樣會被整包帶走 ,建議是存在 Server 的 Session中)。 <span style="color:yellow"> 傳送請求:之後只要有添加 Token 的頁面在提交請求時,都需要加上這個Token ,請求才會被允許,通常對於GET請求,Token會附在請求的網址之後,這樣 URL 會變成 http://url?csrftoken=tokenvalue而 POST 請求則會在 form 的最後加上:</span> ```== <input type=「hidden」 name=「csrftoken」 value=「tokenvalue」/> ``` 把Token以參數的形式加入請求了。 驗證:當 User 發送當有請求的 Token 給 Server 時,Server 為了要確定 Token 的時間有效性和正確性,會先解密 Token,對比加密過後的 User 名稱和當時的隨機亂數及時間戳記,如果加密的資料一致而且時間戳記沒有過期,Server 就會驗證通過,確認 Token 是有效的。 [零基礎資安系列(一)-認識 CSRF](https://tech-blog.cymetrics.io/posts/jo/zerobased-cross-site-request-forgery/)