/blog

๐Ÿ“— SpringBoot ๊ณต๋ถ€๋‚ด์šฉ ์ •๋ฆฌ ๐Ÿ“—

Primary LanguageHTML

๐Ÿงค Blog

๐Ÿ“ application.yml

server:
  port: 8000 # ์„œ๋ฒ„ ํฌํŠธ์„ค์ •
  servlet:
    context-path: /blog # ์ง„์ž…์  : localhost:8000/blog/

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver # mysql ์„ค์ •
    url: jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Seoul
    username: root
    password: 

  jpa: # jpa ์„ค์ •
    open-in-view: true # lazy Loading
    hibernate:
      ddl-auto: update # create:์ƒ์„ฑ๋ชจ๋“œ, update:์—…๋ฐ์ดํŠธ๋ชจ๋“œ, none: ์ƒ์„ฑ,์—…๋ฐ์ดํŠธ (x)
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl # ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค๋•Œ ๋ณ€์ˆ˜๋ช… ๊ทธ๋Œ€๋กœ ํ…Œ์ด๋ธ”์— ๋„ฃ์–ด์คŒ.
      use-new-id-generator-mappings: false # false: jpa๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ๋„˜๋ฒ„๋ง ์ „๋žต์„ ๋”ฐ๋ผ๊ฐ€์ง€ ์•Š๋Š”๋‹ค.
    show-sql: true # ์ฟผ๋ฆฌ ๋ณด์—ฌ์ฃผ๊ธฐ
    properties:
      hibernate.format_sql: true # ์ฟผ๋ฆฌ ์˜ˆ์˜๊ฒŒ ๋ณด์—ฌ์ฃผ๊ธฐ

  jackson:
    serialization:
      fail-on-empty-beans: false

๐Ÿ“ Http ํ†ต์‹ 

  • ํŒจํ‚ท ์Šค์œ„์นญ

    A๊ฐ€ B์—๊ฒŒ '๊ฐ€๋‚˜๋‹ค๋ผ'๋ผ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ '๊ฐ€','๋‚˜','๋‹ค','๋ผ ์ด๋ ‡๊ฒŒ ํŒจํ‚ท๋‹จ์œ„๋กœ ์ชผ๊ฐœ์„œ ๋ณด๋‚ด์„œ ์ „์†กํ•œ๋‹ค.

    C๋ผ๋Š” ์• ๊ฐ€ B์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋Š” A์™€ ์„ ์„ ๊ณต์œ ํ•œ๋‹ค.

    ๋‹จ์  : ๋™์‹œ์— ๋ณด๋‚ด๊ฒŒ ๋  ๊ฒฝ์šฐ, ํ•˜๋‚˜์˜ ์„ ์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ A์™€ C์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์„ž์ด๊ฒŒ ๋œ๋‹ค.

    ๊ทธ๋ž˜์„œ ํ•ด๊ฒฐ์ฑ…์œผ๋กœ ํŒจํ‚ท์— ํ—ค๋”๋ฅผ ์ถ”๊ฐ€์‹œ์ผœ ์–ด๋–ค ๊ณณ์—์„œ ์˜จ ๋…€์„์ธ์ง€ ๊ตฌ๋ถ„ํ•œ๋‹ค.

  • ์„œํ‚ท ์Šค์œ„์นญ

    A์™€ B๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตํ™˜ํ• ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฐฉ์— ์‹ค์–ด์„œ ์ „์†กํ•œ๋‹ค.

    ๋‹จ์  : C๋ผ๋Š” ์• ๊ฐ€ B์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์„ ์„ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•ด์•ผํ•œ๋‹ค.

    ์†๋„๋Š” ๋น ๋ฅด์ง€๋งŒ, ๋น„์šฉ์ด ๋งŽ์ด ๋“ฌ.


๐Ÿ“ MIME ํƒ€์ž…

MIME ํƒ€์ž…์ด๋ž€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†ก๋œ ๋ฌธ์„œ์˜ ๋‹ค์–‘์„ฑ์„ ์•Œ๋ ค์ฃผ๊ธฐ ์œ„ํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.

๋ฌธ๋ฒ•

type/subtype

'/'๋กœ ๊ตฌ๋ถ„๋œ ๋‘๊ฐœ์˜ ๋ฌธ์ž์—ด์ธ ํƒ€์ž…๊ณผ ์„œ๋ธŒํƒ€์ž…์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค. (์ŠคํŽ˜์ด์Šค๋Š” ํ—ˆ์šฉ X)

๊ฐœ๋ณ„ํƒ€์ž…

text/plain
text/html
image/jpeg
image/png
audio/mpeg
audio/ogg
audio/*
video/mp4
application/octet-stream
...

MIME ํƒ€์ž… ์ „์ฒด๋ชฉ๋ก : https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_type


๐Ÿ“ Http ์š”์ฒญ ์‹ค์Šต

@Controller // ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•˜๋ฉด HTMLํŒŒ์ผ์„ ์‘๋‹ต
@RestController // ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•˜๋ฉด Data๋ฅผ ์‘๋‹ต
  // ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญ -> ์‘๋‹ต(HTMLํŒŒ์ผ)
// @Controller

// ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญ -> ์‘๋‹ต(Data)
@RestController
public class HttpController {

    // ์ธํ„ฐ๋„ท ์š”์ฒญ์€ ๋ฌด์กฐ๊ฑด get๋งŒ ๊ฐ€๋Šฅ
    // http://localhost:8080/http/get (select)
    @GetMapping("/http/get")
    public String getTest(){
        return "get ์š”์ฒญ";
    }

    // http://localhost:8080/http/post (insert)
    @PostMapping("/http/post")
    public String postTest(){
        return "post ์š”์ฒญ";
    }

    // http://localhost:8080/http/put (update)
    @PutMapping("/http/put")
    public String putTest(){
        return "put ์š”์ฒญ";
    }

    // http://localhost:8080/http/delete (delete)
    @DeleteMapping("/http/delete")
    public String deleteTest(){
        return "delete ์š”์ฒญ";
    }
}

์ž๋ฐ”์—์„œ ๋ณ€์ˆ˜๋Š” ๋‹ค private์œผ๋กœ ๋งŒ๋“ ๋‹ค.

๊ทธ ์ด์œ ๋Š” ๋ณ€์ˆ˜์— ์ง์ ‘ ์ ‘๊ทผํ•ด์„œ ๊ฐ’์„ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์€ ๊ฐ์ฒด์ง€ํ–ฅ์— ๋งž์ง€ ์•Š์œผ๋ฏ€๋กœ

๋ฉ”์†Œ๋“œ๋ฅผ public์œผ๋กœ ๋งŒ๋“ค์–ด ๋ฉ”์†Œ๋“œ๋กœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋„๋ก ํ•ด์ค€๋‹ค.


@GetMapping

@RequestParam("๋ณ€์ˆ˜๋ช…") : get์š”์ฒญ์œผ๋กœ ๋“ค์–ด์˜จ ๋ณ€์ˆ˜๋ฅผ ์ฝ์–ด๋“ค์ผ ์ˆ˜ ์žˆ๋‹ค.

๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ์ฒด๋กœ ๋ฐ›์œผ๋ฉด ํ•œ๊บผ๋ฒˆ์— ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. (setter ํ•„์š”ํ•จ)

...
  @GetMapping("/http/get")
  public String get(Member m){
    return m.getId() + m.getUsername();
  }
...

@PostMapping

PostMapping ๋˜ํ•œ ๊ฐ์ฒด๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. (html์—์„œ formํƒœ๊ทธ๋กœ ๋ณด๋ƒˆ์„๋•Œ)

JSON ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด์„œ๋Š” @ResponseBody ํƒœ๊ทธ๋ฅผ ์จ์ค˜์•ผํ•œ๋‹ค.

// http://localhost:8080/http/post (insert)
  @PostMapping("/http/post")
  public String postTest(@RequestBody Member m){
      return "post ์š”์ฒญ" + m.getId();
  }
  ...

์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด์„œ JSON ํ˜•์‹์œผ๋กœ POST์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด JSON์ด ์ž๋ฐ”์˜ Member ๊ฐ์ฒด๋กœ ์ž˜ ๋“ค์–ด๊ฐ„๋‹ค.

์ด ๊ณผ์ • ์ฆ‰, JSON => ์ž๋ฐ” ๊ฐ์ฒด ๋กœ ๋ฐ”๊พธ์–ด์ฃผ๋Š” ๊ณผ์ •์„ ์Šคํ”„๋ง ๋ถ€ํŠธ์˜ MessageConverter๊ฐ€ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๋‚˜๋จธ์ง€ @PutMapping๊ณผ @DeleteMapping๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ @PostMapping๊ณผ ๋น„์Šทํ•œ ์ผ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.


๐Ÿ“ AJAX ์š”์ฒญ

์ผ๋‹จ, ajax ์š”์ฒญ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋„์›€์ด ํ•„์š”ํ•˜๋‹ค.

staticํด๋”์— js ํด๋”๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด user.js๋ฅผ ๋งŒ๋“ค์ž.

// ํด๋” ๊ฒฝ๋กœ : /resources/static/js/user.js
let index = {
    init:function(){
        // btn-save ๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜๋ฉด, saveํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ
        document.querySelector("#btn-save").addEventListener('click',()=>{
            this.save();
        });
    },

    save:function(){
        let data = {
            username: document.querySelector("#username").value,
            password: document.querySelector("#password").value,
            email: document.querySelector("#email").value
        }

        // console.log(data);

        // ajax ์š”์ฒญ
        fetch('/blog/api/user',{
            method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(data)
        })
            .then(response => response.json())
            .then(data=>{
                alert("ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ");
                console.log(data);
                // location.href="/blog";
            })
            .catch(error=>{alert(error.message)});
    }
};

index.init();

์œ„์™€ ๊ฐ™์ด jQuery ๋Œ€์‹ ์— Javascript๋กœ ajax ์š”์ฒญ์„ ํ•˜๊ธฐ์œ„ํ•ด์„œ๋Š” fetch ํ•จ์ˆ˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

fetchํ•จ์ˆ˜๋Š” Promise๊ฐ์ฒด๋กœ ๋ฆฌํ„ด์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ณ„๋„์˜ response.json()๊ณผ ๊ฐ™์ด Promise๊ฐ์ฒด => json ์œผ๋กœ์˜ ๋ณ€ํ™˜์ด ํ•„์š”ํ•˜๋‹ค.

ajax ์‚ฌ์šฉ ์‹œ ์žฅ์ 

  • ์›นํŽ˜์ด์ง€์˜ ์†๋„๊ฐ€ ํ–ฅ์ƒ๋œ๋‹ค.
  • ์„œ๋ฒ„์˜ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์„œ๋ฒ„์—์„œ Data๋งŒ ์ „์†กํ•˜๋ฉด ๋˜๋ฏ€๋กœ ์ „์ฒด์ ์ธ ์ฝ”๋”ฉ์˜ ์–‘์ด ์ค„์–ด๋“ ๋‹ค.
  • ๊ธฐ์กด ์›น์—์„œ๋Š” ๋ถˆ๊ฐ€๋Šฅํ–ˆ๋˜ ๋‹ค์–‘ํ•œ UI๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.

ajax ์‚ฌ์šฉ ์‹œ ๋‹จ์ 

  • ํžˆ์Šคํ† ๋ฆฌ ๊ด€๋ฆฌ๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค.
  • ํŽ˜์ด์ง€ ์ด๋™ ์—†๋Š” ํ†ต์‹ ์œผ๋กœ ์ธํ•œ ๋ณด์•ˆ์ƒ์˜ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.
  • ์—ฐ์†์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋ฉด ์„œ๋ฒ„ ๋ถ€ํ•˜๊ฐ€ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ajax๋ฅผ ์“ธ ์ˆ˜ ์—†๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
  • HTTP ํด๋ผ์ด์–ธํŠธ์˜ ๊ธฐ๋Šฅ์ด ํ•œ์ •๋˜์–ด ์žˆ๋‹ค.
  • ์ง€์›ํ•˜๋Š” Charset์ด ํ•œ์ •๋˜์–ด ์žˆ๋‹ค.
  • script๋กœ ์ž‘์„ฑ๋˜๋ฏ€๋กœ ๋””๋ฒ„๊น…์ด ์šฉ์ดํ•˜์ง€ ์•Š๋‹ค.
  • ๋™์ผ-์ถœ์ฒ˜ ์ •์ฑ…์œผ๋กœ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ๊ณผ๋Š” ํ†ต์‹ ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿ“ ์ŠคํŠธ๋ง ๋ถ€ํŠธ์˜ ํŠธ๋žœ์žญ์…˜

์ „ํ†ต์ ์ธ ๋ฐฉ์‹์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ, JDBC, ํŠธ๋žœ์žญ์…˜์„ ๋ชจ๋‘ ๊ฐ™์€ ๊ตฌ๊ฐ„์—์„œ ์—ฐ๊ฒฐํ–ˆ์ง€๋งŒ,

  • lazy Loading์„ ์œ„ํ•ด, ์„ธ์…˜์˜ ์‹œ์ž‘์€ ์„œ๋ธ”๋ฆฟ์ด ์‹œ์ž‘๋˜๋Š” ์‹œ์ ๋ถ€ํ„ฐ (์„ธ์…˜์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ํฌํ•จ)

  • ํŠธ๋žœ์žญ์…˜๊ณผ JDBC์˜ ์‹œ์ž‘์€ Service ๋ ˆ์ด์–ด๋ถ€ํ„ฐ

  • ํŠธ๋žœ์žญ์…˜๊ณผ JDBC์˜ ์ข…๋ฃŒ๋Š” Service ๋ ˆ์ด์–ด์—์„œ ์ข…๋ฃŒ

  • ์„ธ์…˜(์˜์†์„ฑ ์ปจํ…์ŠคํŠธ)์€ ์ปจํŠธ๋กค๋Ÿฌ ์˜์—ญ๊นŒ์ง€ ๋Œ๊ณ ๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์˜์†์„ฑ์ด ๋ณด์žฅ๋˜์–ด select๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง€๊ณ , lazy Loading์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-03 แ„‹แ…ฉแ„’แ…ฎ 6 06 27


- ์ „ํ†ต์ ์ธ ๋ฐฉ์‹

์ „ํ†ต์ ์ธ ๋ฐฉ์‹์€ ์•„๋ž˜์™€๊ฐ™์ด ์„ธ์…˜ ์‹œ์ž‘์‹œ์ ์— ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ, JDBC, Transaction ์—ฐ๊ฒฐ์„ ํ•œ๋ฒˆ์— ์‹คํ–‰ํ•˜๊ณ  ํ•œ๋ฒˆ์— ์ข…๋ฃŒํ•œ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-03 แ„‹แ…ฉแ„’แ…ฎ 6 30 45


- lazy Loading ๋ฐฉ์‹

lazy Loading ๋ฐฉ์‹์€ ์œ„์—์„œ ์„ค๋ช…ํ•œ๊ฒƒ์ฒ˜๋Ÿผ ์„ธ์…˜์ด ์‹œ์ž‘๋ ๋•Œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ณ , Service ์‹œ์ ๋ถ€ํ„ฐ JDBC์™€ Transaction์˜ ์—ฐ๊ฒฐ์ด ์—ฐ๊ฒฐ๋˜๊ณ , ๋Š๊ธด๋‹ค.

์•„๋ž˜์˜ ๊ทธ๋ฆผ์€ ์ตœ์ดˆ ์„ ์ˆ˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™”์„๋•Œ์ธ๋ฐ, ์„ ์ˆ˜ ์ •๋ณด๋Š” ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์™”์ง€๋งŒ, ์„ ์ˆ˜ ์ •๋ณด์˜ ์™ธ๋ž˜ํ‚ค์ธ ํŒ€ ์ •๋ณด๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๊ฐ€์ ธ์˜จ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-03 แ„‹แ…ฉแ„’แ…ฎ 6 35 04


์ด๋ ‡๊ฒŒ ์„ ์ˆ˜ ์ •๋ณด๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” Controller์—์„œ ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๋œ๋‹ค.

๋งŒ์•ฝ, ํŒ€ ์ •๋ณด๋ฅผ ์“ธ์ผ์ด ์žˆ์–ด ํŒ€ ์ •๋ณด์˜ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด, ๋‹ค์‹œ JDBC ์—ฐ๊ฒฐ์„ ํ†ตํ•ด select๋ฌธ์„ ์‹ค์‹œํ•ด ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ ์ง„์งœ ํŒ€ ์ •๋ณด ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์™€์ค€๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-03 แ„‹แ…ฉแ„’แ…ฎ 6 35 52

์‚ฌ์šฉ๋ฒ•

Lazy Loading ๋ฐฉ์‹์„ ์“ฐ๊ธฐ ์œ„ํ•ด์„œ๋Š” application.yml์˜ jpa ์„ค์ •์—์„œ open-in-view: true ์„ค์ •์„ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ( ๋งจ์œ„์— ๋‚˜์™€์žˆ์Œ. ๊ทธ๋ฆฌ๊ณ  Default๋Š” true์ž„ )

๋งŒ์•ฝ, open-in-view: false๋ฅผ ์“ฐ๊ฒŒ๋œ๋‹ค๋ฉด, Service ๊ณ„์ธต์—์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ JDBC, Transaction ์—ฐ๊ฒฐ์ด ์—ฐ๊ฒฐ๋˜๊ณ  ๋Š๊ธฐ๊ฒŒ ๋œ๋‹ค.

์ฆ‰, Controller์—์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋กœ ์ ‘๊ทผ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค.

๐Ÿ“ ์ „ํ†ต์ ์ธ ๋กœ๊ทธ์ธ ๋ฐฉ์‹

์ „ํ†ต์ ์ธ ๋กœ๊ทธ์ธ ๊ตฌํ˜„

userService์—์„œ ๋กœ๊ทธ์ธ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ , ( ๋กœ๊ทธ์ธ ํ•จ์ˆ˜๋Š” @RequestBody๋กœ ๋ฐ›์€ User์˜ ์•„์ด๋””์™€ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ )

์•„๋ž˜์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ HttpSession ์„ ํ†ตํ•ด ์„ธ์…˜์„ ์ƒ์„ฑํ•œ๋‹ค.

    @PostMapping("/api/user/login")
    public ResponseDto<Integer> login(@RequestBody User user, HttpSession session){
        User principal = userService.๋กœ๊ทธ์ธ(user);
        if(principal != null){
            session.setAttribute("principle",principal);
        }
        return new ResponseDto<Integer>(HttpStatus.OK.value(),1);
    }

thymeleaf์—์„œ ์„ธ์…˜๊ฐ’์„ ํ†ตํ•ด ์„ธ์…˜๋ณ„ ํ‘œ์‹œํ•  ์ •๋ณด๋ฅผ ๊ตฌ๋ถ„ํ•ด์ค€๋‹ค.

<!-- fragment bodyHeader ํŒŒ์ผ -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div th:fragment="bodyHeader">
    <nav class="navbar navbar-expand-md bg-dark navbar-dark">
        <a class="navbar-brand" href="/blog">ํ™ˆ</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="collapsibleNavbar">
            <ul th:if="${session.principle == null}" class="navbar-nav">
                <li class="nav-item">
                    <a class="nav-link" href="/blog/user/loginForm">๋กœ๊ทธ์ธ</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/blog/user/joinForm">ํšŒ์›๊ฐ€์ž…</a>
                </li>
            </ul>
            <ul th:if="${session.principle != null}" class="navbar-nav">
                <li class="nav-item">
                    <a class="nav-link" href="/blog/board/writeForm">๊ธ€์“ฐ๊ธฐ</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/blog/user/userForm">๋‚ด ์ •๋ณด</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/blog/user/logout">๋กœ๊ทธ์•„์›ƒ</a>
                </li>
            </ul>
        </div>
    </nav>
</div>

๐Ÿ“ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ

์ฐธ๊ณ ์ž๋ฃŒ

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ์Šคํ”„๋ง ๊ธฐ๋ฐ˜์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณด์•ˆ(์ธ์ฆ๊ณผ ๊ถŒํ•œ)์„ ๋‹ด๋‹นํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.

๋งŒ์•ฝ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ์œ„์˜ ์ „ํ†ต์ ์ธ ๋ฐฉ๋ฒ•์ฒ˜๋Ÿผ ์ž์ฒด์ ์œผ๋กœ ์„ธ์…˜์„ ์ฒดํฌํ•˜๊ณ , redirect๋“ฑ์„ ํ•ด์•ผํ•œ๋‹ค.

spring security๋Š” filter ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— spring MVC์™€ ๋ถ„๋ฆฌ๋˜์–ด ๊ด€๋ฆฌ ๋ฐ ๋™์ž‘ํ•œ๋‹ค.

๋ณด์•ˆ ๊ด€๋ จ ์šฉ์–ด

  • ์ ‘๊ทผ ์ฃผ์ฒด(Principal) : ๋ณดํ˜ธ๋œ ๋Œ€์ƒ์— ์ ‘๊ทผํ•˜๋Š” ์œ ์ €
  • ์ธ์ฆ (Authenticate) : ํ˜„์žฌ ์œ ์ €๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ํ™•์ธ ex) ๋กœ๊ทธ์ธ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์ฒด์ž„์„ ์ฆ๋ช…ํ•œ๋‹ค.
  • ์ธ๊ฐ€ (Authorize) : ํ˜„์žฌ ์œ ์ €๊ฐ€ ์–ด๋–ค ์„œ๋น„์Šค, ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌ
  • ๊ถŒํ•œ : ์ธ์ฆ๋œ ์ฃผ์ฒด๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ๋ฝ๋˜์–ด์žˆ๋Š”์ง€๋ฅผ ๊ฒฐ์ •
    • ๊ถŒํ•œ ์Šน์ธ์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„์œผ๋กœ ์ ‘๊ทผํ•˜๋ ค๋ฉด ์ธ์ฆ ๊ณผ์ •์„ ํ†ตํ•ด ์ฃผ์ฒด๊ฐ€ ์ฆ๋ช… ๋˜์–ด์•ผ๋งŒ ํ•œ๋‹ค.
    • ๊ถŒํ•œ ๋ถ€์—ฌ์—๋„ ๋‘๊ฐ€์ง€ ์˜์—ญ์ด ์กด์žฌํ•˜๋Š”๋ฐ ์›น ์š”์ฒญ ๊ถŒํ•œ, ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ ๋ฐ ๋„๋ฉ”์ธ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ ๋ถ€์—ฌ

ํƒ€์ž„๋ฆฌํ”„์—์„œ ์ž๋ฐ” ์‹œํ๋ฆฌํ‹ฐ ์‚ฌ์šฉํ•˜๊ธฐ

// build.gradle
  implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
<!-- html ํŒŒ์ผ -->
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

๊ธฐ์กด์˜ ํƒœ๊ทธ์—์„œ xmlns:sec ๊ฐ€ ์ถ”๊ฐ€ ๋œ ํ˜•ํƒœ์ด๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  <!--ROLE_USER ๊ถŒํ•œ์„ ๊ฐ–๋Š”๋‹ค๋ฉด ์ด ๊ธ€์ด ๋ณด์ž„-->
  <h1 sec:authorize="hasRole('ADMIN')">Has admin Role</h1>

  <!--ROLE_ADMIN ๊ถŒํ•œ์„ ๊ฐ–๋Š”๋‹ค๋ฉด ์ด ๊ธ€์ด ๋ณด์ž„-->
  <h1 sec:authorize="hasRole('USER')">Has user Role</h1> 

  <!--์–ด๋–ค ๊ถŒํ•œ์ด๊ฑด ์ƒ๊ด€์—†์ด ์ธ์ฆ์ด ๋˜์—ˆ๋‹ค๋ฉด ์ด ๊ธ€์ด ๋ณด์ž„-->
  <div sec:authorize="isAuthenticated()">
      Only Authenticated user can see this Text
  </div>
  
  <!--์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž์˜ ๊ฒฝ์šฐ ์ด ๊ธ€์ด ๋ณด์ž„-->
  <div sec:authorize="isAnonymous()">
      Only Authenticated user can see this Text
  </div>

  <!--์ธ์ฆ์‹œ ์‚ฌ์šฉ๋œ ๊ฐ์ฒด์— ๋Œ€ํ•œ ์ •๋ณด-->
  <b>Authenticated DTO:</b>
  <div sec:authentication="principal"></div>

  <!--์ธ์ฆ์‹œ ์‚ฌ์šฉ๋œ ๊ฐ์ฒด์˜ Username (ID)-->
  <b>Authenticated username:</b>
  <div sec:authentication="name"></div>

  <!--๊ฐ์ฒด์˜ ๊ถŒํ•œ-->
  <b>Authenticated user role:</b>
  <div sec:authentication="principal.authorities"></div>

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•˜๊ธฐ

blog ํŒจํ‚ค์ง€์— config ํŒจํ‚ค์ง€๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•˜๊ณ , SecurityConfig ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค.

package com.cos.blog.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
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.configuration.WebSecurityConfigurerAdapter;

// ๋นˆ ๋“ฑ๋ก: ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์—์„œ ๊ฐ์ฒด๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๊ฒƒ
@Configuration // ๋นˆ ๋“ฑ๋ก: IoC
@EnableWebSecurity // ํ•„ํ„ฐ ์ถ”๊ฐ€: ์‹œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ๋ฅผ ๊ฑฐ๋Š” ๊ฒƒ
@EnableGlobalAuthentication // ํŠน์ • ์ฃผ์†Œ๋กœ ์ ‘๊ทผ์„ํ•˜๋ฉด ๊ถŒํ•œ ๋ฐ ์ธ์ฆ์„ ๋ฏธ๋ฆฌ ์ฒดํฌํ•˜๋Š” ๊ฒƒ
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http
                .authorizeRequests()
                    .antMatchers("/auth/**")// /auth/ ์ดํ•˜์˜ ๋ชจ๋“  ๊ฒฝ๋กœ๋Š”
                    .permitAll() // ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค
                    .anyRequest() // ๊ทธ๊ฒŒ ์•„๋‹ˆ๊ณ ๋Š”
                    .authenticated() // ํ—ˆ๋ฝ๋œ ์‚ฌ๋žŒ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.
                .and()
                    .formLogin()
                    .loginPage("/auth/loginForm");
    }
}

์œ„์™€ ๊ฐ™์ด WebSecurityConfigurerAdapter๋ฅผ ์ƒ์†ํ•˜๊ณ , configure๋ฅผ Override ํ•˜๋ฉด, login ํŽ˜์ด์ง€ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.


๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ

๊ฐ์‚ฌํ•˜๊ฒŒ๋„, ๋ฌธ์ž์—ด์„ ๋„ฃ์œผ๋ฉด ํ•ด์‹œ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” ํด๋ž˜์Šค๋ฅผ Spring Security์—์„œ ์ง€์›ํ•ด์ค€๋‹ค.

์šฐ๋ฆฌ๋Š” BCryptPasswordEncoder๋ฅผ ์“ธ ๊ฒƒ์ด๋‹ค.

package com.cos.blog.config;

  @Bean // ์Šคํ”„๋ง์ด ๊ด€๋ฆฌํ•˜๋Š” IoC๊ฐ€ ๋œ๋‹ค.
  BCryptPasswordEncoder encodePWD(){ return new BCryptPasswordEncoder(); }

์œ„์™€๊ฐ™์ด SecurityConfig ํด๋ž˜์Šค์— ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. @Bean ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์Šคํ”„๋ง IoC์—์„œ ๊ด€๋ฆฌํ•˜๋„๋ก ํ•œ๋‹ค.

BCryptPasswordEncoder์˜ encode( ) ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด String Wrapper Class ๋กœ ํ•ด์‹œ๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

๊ธฐ์กด์— ํšŒ์›๊ฐ€์ž…์„ ํ•  ๋•Œ password ๊ทธ๋Œ€๋กœ DB์— ์ €์žฅํ–ˆ์ง€๋งŒ, ์ด์ œ๋Š” ํ•ด์‹œ๋กœ ๋ณ€ํ™˜๋œ ๊ฐ’์„ ๋„ฃ์–ด์ค€๋‹ค.

๋ฐ”๋€ ํšŒ์›๊ฐ€์ž… Service ๊ฐ์ฒด๋ฅผ ๋ด๋ณด์Ÿˆ.

package com.cos.blog.service;

import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository; // ์˜์กด์„ฑ ์ฃผ์ž…

    private final BCryptPasswordEncoder encoder; // BCryptPasswordEncoder ํด๋ž˜์Šค ์˜์กด์„ฑ ์ฃผ์ž…

    @Transactional
    public void ํšŒ์›๊ฐ€์ž…(User user){
        String rawPassword = user.getPassword(); // ์›๋ž˜ password
        String encPassword = encoder.encode(rawPassword); // ํ•ด์‹œ
        user.setPassword(encPassword);
        user.setRole(RoleType.USER);
        userRepository.save(user);
    }

}

์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์›๋ž˜ User ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ rawPassword๋ฅผ ํ•ด์‹œ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ๊ณ , ๋ณ€๊ฒฝ๋œ ํ•ด์‹œ๊ฐ’์„ User ๊ฐ์ฒด์˜ password๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.

๊ทธ ๋’ค์— userRepository.save() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด DB์— ํšŒ์›์ •๋ณด๋ฅผ ์ €์žฅํ•œ๋‹ค.


XSS์™€ CSRF ๊ณต๊ฒฉ

XSS๋Š” (Cross Site Scripting) ์˜ ์•ฝ์ž๋กœ ์ฃผ๋กœ ๋‹ค๋ฅธ ์›น์‚ฌ์ดํŠธ์™€ ์ •๋ณด๋ฅผ ๊ตํ™˜ํ•˜๋Š” ์‹์œผ๋กœ ์ž‘๋™ํ•˜๋ฏ€๋กœ ์‚ฌ์ดํŠธ ๊ฐ„ ์ŠคํŠธ๋ฆฝํŒ…์ด๋ผ๊ณ  ํ•œ๋‹ค.

ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฝํŒ…์€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณต๊ฒฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. ๊ฒŒ์‹œํŒ ๊ฐ™์€ ๊ณต๊ฐ„์— <script> </script> ํƒœ๊ทธ๋ฅผ ์ €์žฅํ–ˆ์„๋•Œ, ์ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์„œ๋ฒ„์— ์ €์žฅ๋ผ ์‹คํ–‰์ด ๋˜์–ด

์„œ๋ฒ„์˜ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ๋นผ์˜ฌ์ˆ˜๊ฐ€ ์žˆ๊ฒŒ๋œ๋‹ค.


CSRF๋Š” (Cross Site Request Fogery)์˜ ์•ฝ์ž๋กœ ์‚ฌ์ดํŠธ๊ฐ„ ์š”์ฒญ์„ ์œ„์กฐํ•˜๋Š” ๊ณต๊ฒฉ์ด๋‹ค.

์„ ๋Ÿ‰ํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ์˜์ง€์™€๋Š” ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ณต๊ฒฉ์ž๊ฐ€ ์˜๋„ํ•œ ํ–‰์œ„๋ฅผ ์›น์‚ฌ์ดํŠธ์— ์š”์ฒญํ•˜๊ฒŒํ•˜๋Š” ๊ณต๊ฒฉ์„ ๋งํ•œ๋‹ค.

ex) ์˜ˆ๋ฅผ๋“ค์–ด, ์šด์˜์ž๊ฐ€ http://www.example.com/point?100&username?ghdcksgml ํ•ด๋‹น GET์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด

ghdcksgml๋ผ๋Š” ์œ ์ €์—๊ฒŒ 100ํฌ์ธํŠธ๊ฐ€ ์ง€๊ธ‰๋œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

์œ„๋Š” ์šด์˜์ž๋งŒ์ด ๊ถŒํ•œ์ด ์žˆ๊ธฐ๋•Œ๋ฌธ์—, ์ผ๋ฐ˜์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น GET์š”์ฒญ์„ ๋ณด๋‚ด๋„ ์•„๋ฌด๋Ÿฐ ์‘๋‹ต์ด ์—†๊ณ , ์˜ค์ง ์šด์˜์ž๋งŒ์ด ํ•ด๋‹น GET์š”์ฒญ์„ ํ–ˆ์„๋•Œ ๋™์ž‘ํ•œ๋‹ค.

์ด ์ ์„ ์ด์šฉํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚š์‹œ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-08 แ„‹แ…ฉแ„’แ…ฎ 4 57 21

์šด์˜์ž๊ฐ€ ๋‚š์—ฌ์„œ ํ•ด๋‹น ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ghdcksgml๋ผ๋Š” ์œ ์ €์—๊ฒŒ 100ํฌ์ธํŠธ๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด๊ฒƒ์ด ๋ฐ”๋กœ CSRF ๊ณต๊ฒฉ์ด๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-08 แ„‹แ…ฉแ„’แ…ฎ 4 59 35

์ถœ์ฒ˜: https://lucete1230-cyberpolice.tistory.com/23


์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๋กœ๊ทธ์ธ

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ฒŒ ๋งŒ๋“ ๋‹ค.

@Override
    protected void configure(HttpSecurity http) throws Exception{
        http
                .csrf().disable() // csrf ํ† ํฐ ๋น„ํ™œ์„ฑํ™” (ํ…Œ์ŠคํŠธ์‹œ ๊ฑธ์–ด๋‘๋Š” ๊ฒŒ ์ข‹์Œ)
                .authorizeRequests()
                    .antMatchers("/","/auth/**","/js/**","/css/**","/image/**")// /auth/ ์ดํ•˜์˜ ๋ชจ๋“  ๊ฒฝ๋กœ๋Š”
                    .permitAll() // ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค
                .anyRequest() // ๊ทธ๊ฒŒ ์•„๋‹ˆ๊ณ ๋Š”
                    .authenticated() // ํ—ˆ๋ฝ๋œ ์‚ฌ๋žŒ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.
                .and()
                    .formLogin()
                    .loginPage("/auth/loginForm")
                    .loginProcessingUrl("/auth/loginProc") // ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ํ•ด๋‹น ์ฃผ์†Œ๋กœ ์š”์ฒญ์˜ค๋Š” ๋กœ๊ทธ์ธ์„ ๊ฐ€๋กœ์ฑ„์„œ ๋Œ€์‹  ๋กœ๊ทธ์ธ ํ•ด์ค€๋‹ค.
                    .defaultSuccessUrl("/");
    }

์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ "/auth/loginFrom" ์ด๋ผ๋Š” ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด, ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ํ•ด๋‹น ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„ ๋Œ€์‹  ๋กœ๊ทธ์ธ ํ•ด์ค€๋‹ค.

antMatchers์— ์„ค์ •๋œ ํŽ˜์ด์ง€๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ํŽ˜์ด์ง€์˜ ์š”์ฒญ์€ loginPage๋กœ ๋„˜์–ด์˜จ๋‹ค.

defaultSuccessUrl์€ ์ •์ƒ์ ์œผ๋กœ ์™„๋ฃŒ๋˜๋ฉด, ํ•ด๋‹น URL๋กœ ์ด๋™ํ•œ๋‹ค.


๊ฐ€๋กœ์ฑ„์„œ ๋กœ๊ทธ์ธ์„ ํ•  ๋•Œ ๊ทธ๋•Œ ๋‚ด๊ฐ€ ๋งŒ๋“ค์–ด์•ผ๋  ํด๋ž˜์Šค๊ฐ€ ํ•˜๋‚˜ ์žˆ๋‹ค.

๋ฐ”๋กœ, UserDetails๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” User Object๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค. (๋กœ๊ทธ์ธ ์š”์ฒญ์„ ํ•˜๊ณ  ์„ธ์…˜์— ๋“ฑ๋ก์„ ํ•ด์ค˜์•ผํ•˜๋Š”๋ฐ ๊ทธ๋ƒฅ User ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฆฌํ„ดํ•˜๋ฉด ํƒ€์ž…์ด ๋งž์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ์—)

package com.cos.blog.config.auth;

import com.cos.blog.model.User;
import lombok.AllArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

// ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„์„œ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•˜๊ณ  ์™„๋ฃŒ๊ฐ€ ๋˜๋ฉด, UserDetails ํƒ€์ž…์˜ ์˜ค๋ธŒ์ ํŠธ๋ฅผ
// ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ๊ณ ์œ ํ•œ ์„ธ์…˜์ €์žฅ์†Œ์— ์ €์žฅ์„ ํ•ด์ค€๋‹ค.
@AllArgsConstructor
public class PrincipalDetail implements UserDetails {
    
    private User user; // composition

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    // ๊ณ„์ •์ด ๋งŒ๋ฃŒ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ๋ฆฌํ„ดํ•œ๋‹ค. (true:๋งŒ๋ฃŒ์•ˆ๋จ)
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // ๊ณ„์ •์ด ์ž ๊ฒจ์žˆ๋Š”์ง€ ์•Š์•˜๋Š”์ง€ ๋ฆฌํ„ดํ•œ๋‹ค. (true:์ž ๊ธฐ์ง€ ์•Š์Œ)
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งŒ๋ฃŒ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ๋ฆฌํ„ดํ•œ๋‹ค. (true:๋งŒ๋ฃŒ์•ˆ๋จ)
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // ๊ณ„์ •์ด ํ™œ์„ฑํ™”(์‚ฌ์šฉ๊ฐ€๋Šฅ)์ธ์ง€ ๋ฆฌํ„ดํ•œ๋‹ค. (true:ํ™œ์„ฑํ™”)
    @Override
    public boolean isEnabled() {
        return true;
    }

    // ๊ณ„์ •์ด ๊ฐ–๊ณ ์žˆ๋Š” ๊ถŒํ•œ ๋ชฉ๋ก์„ ๋ฆฌํ„ดํ•œ๋‹ค. (๊ถŒํ•œ์ด ์—ฌ๋Ÿฌ๊ฐœ ์žˆ์„ ์ˆ˜ ์žˆ์–ด์„œ ๋ฃจํ”„๋ฅผ ๋Œ์•„์•ผ ํ•˜๋Š”๋ฐ ์šฐ๋ฆฌ๋Š” 1๊ฐœ๋ฐ–์— ์—†์Œ.)
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        Collection<GrantedAuthority> collectors = new ArrayList<>();

        collectors.add(()->{ return "ROLE_"+user.getRole();}); // ์•ž์— ROLE_์„ ๋ถ™์ด๋Š”๊ฑด ์ž๋ฐ” ์‹œํ๋ฆฌํ‹ฐ ๊ทœ์น™์ž„.

        return collectors;
    }
}
package com.cos.blog.config.auth;

import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import lombok.RequiredArgsConstructor;
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;

@Service // Bean ๋“ฑ๋ก
@RequiredArgsConstructor
public class PrincipalDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    // ์Šคํ”„๋ง์ด ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑŒ๋•Œ, username,password ๋ณ€์ˆ˜ 2๊ฐœ๋ฅผ ๊ฐ€๋กœ์ฑ„๋Š”๋ฐ
    // password ๋ถ€๋ถ„ ์ฒ˜๋ฆฌ๋Š” ์•Œ์•„์„œ ํ•จ.
    // username์ด DB์— ์žˆ๋Š”์ง€๋งŒ ํ™•์ธํ•ด์ฃผ๋ฉด ๋จ.
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User principal = userRepository.findByUsername(username) // username์ด ์ผ์น˜ํ•˜๋Š” ์‚ฌ์šฉ์ž ์ฐพ๊ธฐ
                .orElseThrow(()->{
                    return new UsernameNotFoundException("ํ•ด๋‹น ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
                });
        return new PrincipalDetail(principal); // ์‹œํ๋ฆฌํ‹ฐ์˜ ์„ธ์…˜์— ์œ ์ € ์ •๋ณด๊ฐ€ ์ €์žฅ์ด ๋จ.
    }
}
package com.cos.blog.config;

import com.cos.blog.config.auth.PrincipalDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

// ๋นˆ ๋“ฑ๋ก: ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์—์„œ ๊ฐ์ฒด๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๊ฒƒ
@Configuration // ๋นˆ ๋“ฑ๋ก: IoC
@EnableWebSecurity // ํ•„ํ„ฐ ์ถ”๊ฐ€: ์‹œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ๋ฅผ ๊ฑฐ๋Š” ๊ฒƒ
@EnableGlobalAuthentication // ํŠน์ • ์ฃผ์†Œ๋กœ ์ ‘๊ทผ์„ํ•˜๋ฉด ๊ถŒํ•œ ๋ฐ ์ธ์ฆ์„ ๋ฏธ๋ฆฌ ์ฒดํฌํ•˜๋Š” ๊ฒƒ
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final PrincipalDetailService principalDetailService; // DI

    @Bean // ์Šคํ”„๋ง์ด ๊ด€๋ฆฌํ•˜๋Š” IoC๊ฐ€ ๋œ๋‹ค.
    public BCryptPasswordEncoder encodePWD(){
        return new BCryptPasswordEncoder();
    }

    // ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋Œ€์‹  ๋กœ๊ทธ์ธํ•ด์ฃผ๋Š”๋ฐ password๋ฅผ ๊ฐ€๋กœ์ฑ„๊ธฐ๋ฅผ ํ•˜๋Š”๋ฐ
    // ํ•ด๋‹น password๊ฐ€ ๋ญ˜๋กœ ํ•ด์‰ฌ๊ฐ€ ๋˜์–ด ํšŒ์›๊ฐ€์ž…์ด ๋˜์—ˆ๋Š”์ง€ ์•Œ์•„์•ผ
    // ๊ฐ™์€ ํ•ด์‰ฌ๋กœ ์•”ํ˜ธํ™”ํ•ด์„œ DB์— ์žˆ๋Š” ํ•ด์‰ฌ๋ž‘ ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Œ.
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
        // principalDetailService๋ฅผ ํ†ตํ•ด์„œ ๋กœ๊ทธ์ธ์„ํ•  ๋•Œ password๋ฅผ encodePWD๋กœ ์ธ์ฝ”๋“œํ•ด์„œ ๋น„๊ต๋ฅผ ์•Œ์•„์„œ ํ•ด์ค€๋‹ค.
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http
                .csrf().disable() // csrf ํ† ํฐ ๋น„ํ™œ์„ฑํ™” (ํ…Œ์ŠคํŠธ์‹œ ๊ฑธ์–ด๋‘๋Š” ๊ฒŒ ์ข‹์Œ)
                .authorizeRequests()
                    .antMatchers("/","/auth/**","/js/**","/css/**","/image/**")// /auth/ ์ดํ•˜์˜ ๋ชจ๋“  ๊ฒฝ๋กœ๋Š”
                    .permitAll() // ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค
                .anyRequest() // ๊ทธ๊ฒŒ ์•„๋‹ˆ๊ณ ๋Š”
                    .authenticated() // ํ—ˆ๋ฝ๋œ ์‚ฌ๋žŒ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.
                .and()
                    .formLogin()
                    .loginPage("/auth/loginForm")
                    .loginProcessingUrl("/auth/loginProc") // ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ํ•ด๋‹น ์ฃผ์†Œ๋กœ ์š”์ฒญ์˜ค๋Š” ๋กœ๊ทธ์ธ์„ ๊ฐ€๋กœ์ฑ„์„œ ๋Œ€์‹  ๋กœ๊ทธ์ธ ํ•ด์ค€๋‹ค.
                    .defaultSuccessUrl("/");
    }
}

ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

๋กœ๊ทธ์ธ์š”์ฒญ์ด ์˜ค๋Š” ์ˆœ๊ฐ„, loginProcessingUrl์ด ๊ฐ€๋กœ์ฑˆ๋‹ค. => username๊ณผ password ์ •๋ณด๋ฅผ PrincipalDetailService์— ์žˆ๋Š” loadUserByUsername์œผ๋กœ ๋ณด๋‚ธ๋‹ค.

=> username์„ ๋น„๊ตํ•ด์„œ PrincipalDetail์„ ๋ฆฌํ„ดํ•ด์ค€๋‹ค. => ๋ฆฌํ„ดํ• ๋•Œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฒดํฌ๋ฅผ ํ•œ๋‹ค. SecurityConfig์˜ configure์„ ํ†ตํ•ด์„œ principalDetailService๊ฐ€ ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ํ•˜๊ณ 

=> auth.userDetailsService(principalDetailService) ์ด ๋ฆฌํ„ด์ด ๋˜๋ฉด, passwordEncoder๋ฅผ ํ†ตํ•ด encodePWD๋กœ ๋‹ค์‹œ ์•”ํ˜ธํ™”๋ฅผ ํ•˜๊ณ , ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์™€ ๋น„๊ตํ•œ๋‹ค.

=> ๋น„๊ต๊ฐ€ ๋๋‚˜๋ฉด Spring Security ์˜์—ญ์— PrincipalDetail๋กœ ๊ฐ์‹ธ์ ธ์„œ ์ €์žฅ์ด ๋œ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-08 แ„‹แ…ฉแ„’แ…ฎ 10 29 04

๐Ÿ“ ๊ธ€์“ฐ๊ธฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

๊ธ€์“ฐ๊ธฐ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ฐ€์žฅ๋จผ์ € ๊ธ€์“ฐ๊ธฐ ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•ด์คฌ๋‹ค. (/resources/templates/board/saveForm.html)

๊ทธ ๋‹ค์Œ ํ™ˆ์—์„œ ๊ธ€์“ฐ๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„๋•Œ, ๊ธ€์“ฐ๊ธฐ ํŽ˜์ด์ง€๋กœ ๋งคํ•‘๋˜๋„๋ก BoardController์— (/board/saveForm) GET ์š”์ฒญ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก GetMapping์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

๊ธ€์„ ์“ธ ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€์ธ saveForm.html์—๋Š” ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋กํ–ˆ๊ณ , ๊ธ€์“ฐ๊ธฐ ์™„๋ฃŒ๋ฒ„ํŠผ์€ ajaxํ†ต์‹ ์„ ์œ„ํ•ด formํƒœ๊ทธ ๋ฐ–์œผ๋กœ ๊บผ๋ƒˆ๋‹ค.

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="/layout/fragments/header::header"/>
<body>

<div class="container">
    <div th:replace="/layout/fragments/bodyHeader::bodyHeader"/>

    <form>
        <div class="form-group">
            <label for="title">Username</label>
            <input type="text" class="form-control" placeholder="Enter title" id="title">
        </div>
        <div class="form-group">
            <label for="content">Content:</label>
            <textarea class="form-control summernote" rows="5" id="content"></textarea>
        </div>
    </form>
    <button id="save" class="btn btn-primary">๊ธ€์“ฐ๊ธฐ ์™„๋ฃŒ</button>
    <script>
        $('.summernote').summernote({
            placeholder: 'Hello Bootstrap 4',
            tabsize: 2,
            height: 300
        });
    </script>
    <div th:replace="/layout/fragments/footer::footer"/>
</div>
</body>
<script src="/js/board.js"></script>
</html>

๋‚ด์šฉ ๋ถ€๋ถ„์— ํ…์ŠคํŠธ ์ƒ์ž๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€๋งŒ, ๋„ˆ๋ฌด ํ—ˆ์ ‘ํ•ด์„œ summer note๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. summer note

ํ•ด๋‹น ์ฝ”๋“œ์—์„œ ์œ„์—์„œ๋ถ€ํ„ฐ scriptํƒœ๊ทธ 2๊ฐœ๊นŒ์ง€๋Š” ๊ฐ๊ฐ jQuery, boostrap4์— ๊ด€๋ จ๋œ ๋‚ด์šฉ์ด๋ฏ€๋กœ ์ด๋ฏธ ์ถ”๊ฐ€ํ–ˆ๋‹ค๋ฉด ์ง€์›Œ์ฃผ์ž.

์ด์ œ ajax ํ†ต์‹ ์„ ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ž‘์„ฑํ•œ ๊ธ€์„ ๋„ฃ์–ด์ค˜์•ผํ•˜๋ฏ€๋กœ, (resources/static/js/board.js) ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

user.js์™€ ๋น„์Šทํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ๋งž์ถฐ์ฃผ๊ณ  ํ†ต์‹ ~

let index = {
    init:function(){
        // btn-save ๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜๋ฉด, saveํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ
        document.querySelector("#save").addEventListener('click',()=>{
            this.save();
        });
    },

    save:function(){
        let data = {
            title: document.querySelector("#title").value,
            content: document.querySelector("#content").value
        }

        // ajax ์š”์ฒญ
        fetch("/api/board",{
            method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(data)
        })
            .then(response => response.json())
            .then(data=>{
                alert("๊ธ€์“ฐ๊ธฐ ์™„๋ฃŒ");
                console.log(data);
                location.href="/";
            })
            .catch(error=>{alert(error.message)});
    }
};

index.init();

์ด๋ ‡๊ฒŒ ๋˜๋ฉด, ๊ธ€์„ ์ž‘์„ฑํ•˜๊ณ  ๊ธ€์“ฐ๊ธฐ ์™„๋ฃŒ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด, /api/board ๋กœ post์š”์ฒญ์ด ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

์šฐ๋ฆฌ๋Š” ์•„์ง board์— ๋Œ€ํ•œ api ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์—

BoardApiController(ํด๋ž˜์Šค), BoardService(ํด๋ž˜์Šค), BoardRepository(์ธํ„ฐํŽ˜์ด์Šค) ๋ฅผ ๊ฐ๊ฐ ์ƒ์„ฑํ•ด์ค€๋‹ค.

BoardApiController๋ฅผ ํ†ตํ•ด BoardService์— Board,User ๊ฐ์ฒด๋ฅผ ๋„˜๊ธฐ๋ฉด

BoardService๋Š” Board์— User๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด DB์— ์ €์žฅํ•ด์ค€๋‹ค. ( Board์— ๋ˆ„๊ฐ€์“ด๊ฑด์ง€ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ Userํ…Œ์ด๋ธ”์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— )

// BoardApiController.class
package com.cos.blog.controller.api;

import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.controller.dto.ResponseDto;
import com.cos.blog.model.Board;
import com.cos.blog.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class BoardApiController {

    private final BoardService boardService;

    @PostMapping("/api/board")
    public ResponseDto<Integer> save(@RequestBody Board board, @AuthenticationPrincipal PrincipalDetail principal){
        boardService.save(board,principal.getUser());
        return new ResponseDto<>(HttpStatus.OK.value(), 1);
    }
}
// BoardService.class
package com.cos.blog.service;

import com.cos.blog.model.Board;
import com.cos.blog.model.User;
import com.cos.blog.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class BoardService {

    private final BoardRepository boardRepository;

    @Transactional
    public void save(Board board, User user){
        board.setCount(0L);
        board.setUser(user);
        boardRepository.save(board);
    }

}
// BoardRepository.class
package com.cos.blog.repository;

import com.cos.blog.model.Board;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BoardRepository extends JpaRepository<Board,Long> {

}

์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•ด์„œ ๊ธ€์“ฐ๊ธฐ ์™„๋ฃŒ๋ฅผ ๋ˆŒ๋Ÿฌ๋ณด๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ DB์— ์ž˜ ๋“ค์–ด๊ฐ„ ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-10 แ„‹แ…ฉแ„’แ…ฎ 8 39 31


๐Ÿ“ ๊ธ€ ๋ชฉ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

์ง€๊ธˆ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š” ๊ฒŒ์‹œํŒ์€ ๋ชจ๋“  ์œ ์ €์˜ ๊ธ€ ๋ชฉ๋ก๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ธ€ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š”๊ฒƒ์€ ์•„์ฃผ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ƒฅ BoardRepository์˜ findAll() ๋ฉ”์†Œ๋“œ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด Listํ˜•ํƒœ๋กœ ์ „์ฒด ๊ธ€ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

BoardService๋Š” contentList๋ฉ”์†Œ๋“œ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ  ๊ธ€ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜จ ๋’ค List๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

package com.cos.blog.service;

import com.cos.blog.model.Board;
import com.cos.blog.model.User;
import com.cos.blog.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class BoardService {

    private final BoardRepository boardRepository;

    @Transactional
    public void save(Board board, User user){
        board.setCount(0L);
        board.setUser(user);
        boardRepository.save(board);
    }

    // ์•„๋žซ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹น.
    @Transactional(readOnly = true)
    public List<Board> contentList(){
        return boardRepository.findAll();
    }
}

์ด๋ ‡๊ฒŒ Listํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ๋˜๋ฉด, BoardController์—์„œ๋Š” index.html(๊ฒŒ์‹œํŒ)์œผ๋กœ ํ•ด๋‹น ๋ฆฌ์ŠคํŠธ๋ฅผ ๋„˜๊ฒจ์ฃผ๋ฉด ๋˜๋Š”๋ฐ

์Šคํ”„๋ง์—์„œ๋Š” Model ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

package com.cos.blog.controller;

import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
@RequiredArgsConstructor
public class BoardController {

    private final BoardService boardService;

    // ์š”๋ถ€๋ถ„ ์ž…๋‹ˆ๋‹น.
    @GetMapping({"","/"})
    public String index(Model model){
        model.addAttribute("boards",boardService.contentList());
        return "index"; // viewResolver ์ž‘๋™
    }

    @GetMapping("/board/saveForm")
    public String saveForm(){
        return "board/saveForm";
    }
}

model.addAttribute("key","value") ์ด๋‹ค.

index.html์—์„œ model์— ์ถ”๊ฐ€ํ•œ ๊ฒƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="/layout/fragments/header::header"/>
<body>

<div class="container">
    <div th:replace="/layout/fragments/bodyHeader::bodyHeader"/>

    <div th:each="board : ${boards}">
        <div class="card m-2">
            <div class="card-body">
                <h4 class="card-title" th:text="${board.getTitle()}">์ œ๋ชฉ ์ ๋Š” ๋ถ€๋ถ„</h4>
                <a href="#" class="btn btn-primary">์ƒ์„ธ ๋ณด๊ธฐ</a>
            </div>
        </div>
    </div>

    <div th:replace="/layout/fragments/footer::footer"/>
</div>
</body>
</html>

th:each ๋Š” foreach์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•œ๋‹ค. boards๊ฐ€ List๋กœ ๋˜์–ด์žˆ๊ธฐ๋•Œ๋ฌธ์—, boards๋ฅผ ํ•œ๊ฐœ์”ฉ ๊ฐ€์ ธ์™€ ์ œ๋ชฉ์„ ๋„ฃ์–ด์ค€๋‹ค.


๐Ÿ“ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ๊ตฌ์กฐ

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-15 แ„‹แ…ฉแ„’แ…ฎ 4 59 31