상세 컨텐츠

본문 제목

프로그래머 도전기 106일차

프로그래머가 될거야!

by Choyee 2024. 1. 23. 21:11

본문

오늘은

 

오늘은 어제에 이어서 정말 무척 추운 날이었습니다

한동안 따뜻하더니 겨울인걸 까먹을까 걱정이 되었는지

이번주는 내내 추울 예정인 것 같습니다

저도 제 본분을 잊지말고 다시금 되새겨 공부에 더 집중하고

프로젝트 아이디어를 내는데에 생각을 몰두하여

잡념이 들지 않도록 노력해야겠습니다

 

 

Spring 공부

 

* bootboard v1
starter project + JPA(ORM) + thymeleaf + 인증(session)

* bootboard v2
- starter project + JPA(ORM) + thymeleaf + 인증 및 권한(Security)
- 관계 : 외래키, 일대다, 다대일
   = 연관 관계 매핑
Member - @OneToMany - 일대다
Board - @ManyToOne - 다대일
=> 주인 = 다쪽

* RDBMS - 외래키만 설정
  ORM - 양방향 관계 설정(양쪽 모두 관계 설정)

* 순환 참조(exclude 해야함)



게시판 - 글쓰기, 글목록
회원 - 로그인(session) -> 로그인(security)
pom.xml - spring-boot-starter-security

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>




로그인 - 시큐리티 제공 이용(id = username, password-password)
회원가입, 목록 - 개발자
=> [user, Using generated security password:  비밀번호 제공됨]

=> input의 name을 username/password로 해준다

<li>
    <label for="username">ID</label>
    <input type="text" name="username" id="username"
           placeholder="ID">
</li>
<li>
    <label for="password">Password</label>
    <input type="password" name="password" id="password"
           placeholder="password">
</li>



security 패키지 - 파일 3개 (설정)
SecurityConfig.java, SecurityUser.java, CustomUserDetailsService

* 인증 -> 권한 설정
인덱스("/") - 로그인 불요 = permitAll()
인증메인("/auth/main") - 로그인 불요 = permitAll()
글 목록 ("/board/list") - 로그인 필요 = authenticated()


시큐리티 설정O
- 회원 : 회원 가입, 회원 목록(관리자만 가능), 회원 상세보기, 회원 수정, 회원 삭제
- 게시판 : 글쓰기, 글 목록, 글 상세보기, 글 수정, 글 삭제 -> 댓글(ajax)

1. 의존성 주입
   = 메이븐 : thymeleaf-extra-springsecurity6
* pom.xml

<!-- 타임리프로 시큐리티 적용 --> 
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>



2. 네임스페이스 명시

<html xmlns:th="http://www.thymeleaf.org"
  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6" >



3. principal 객체로 사용
* main.html

<div sec:authorize="isAuthenticated()" class="auth">
	<h3 sec:authentication="principal.member.name"></h3>
</div>







* SecurityUser

package cohttp://m.khit.board.config;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;

import cohttp://m.khit.board.entity.Member;

public class SecurityUser extends User{
    private static final long serialVersionUID = 1L;

    private Member member;
    // 3가지 파라미터 = username, password, authorities(role)
    public SecurityUser(Member member) {
        // username -> memberId로 바꿈
        // 암호화 안된 상태는 "{noob}" + member.getPassword()을 사용함
        super(member.getMemberId(), member.getPassword(), 
          // 권한 설정
          AuthorityUtils.createAuthorityList(member.getRole().toString()));
        this.member = member;
    }

    public Member getMember() {
    	return member;
    }
}






* SecurityConfig

package cohttp://m.khit.board.config;

import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService customService;

    // @Bean은 프로젝트에서 관리가 안되는 클래스를 빈으로 사용할 때 필요함
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    // 인증 설정 -> 권한 설정
    http.userDetailsService(customService);

    http.authorizeHttpRequests(authorize -> authorize
                                            // 로그인이 필요없음
                                            .requestMatchers("/", "/css/**", "/img/**", "/member/**", "/auth/main").permitAll()
                                            // 로그인이 필요함
                                            .anyRequest().authenticated()
                                            ).formLogin(form -> form.loginPage("/member/login"));
    	return http.build();
    }

    // 암호화 설정
    // PasswordEncoder를 상속받은 BCryptPasswordEncoder를 반환
    @Bean
    PasswordEncoder passwordEncoder() {
    	return new BCryptPasswordEncoder();
    }
}





 

 

 

React 공부

 

<<기초 공사 항목>>
1. 폰트 셋팅
=> Google Web Fonts를 이용한 프로젝트에 사용되는 폰트 세팅
* App.css

/* font를 web 요청으로 가져옴 */
@import url('https://fonts.googleapis.com/css2?family=Dokdo&family=Nanum+Pen+Script&display=swap');

.App {
  padding: 20px;

  /* font-family속성은 제일 뒤에 있는 속성을 따른다 */
  font-family: 'Nanum Pen Script', cursive;
  /* 같은 라인에서는 제일 왼쪽에 있는 폰트가 적용된다 */
  font-family: 'Dokdo', system-ui;
}




2. 레이아웃 세팅
=> 모든 페이지에 반영되는 레이아웃 스타일링, 셋팅

* App.css

/* font를 web 요청으로 가져옴 */
@import url('https://fonts.googleapis.com/css2?family=Dokdo&family=Nanum+Pen+Script&display=swap');

/* 공통 스타일 */
body {
  background-color: #f6f6f6;
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: 'Nanum Pen Script', cursive;
  /* vh=viewport-height 현재 웹 스크린의 실제크기의 100%를 최소 높이로 갖겠다라는 의미 */
  min-height: 100vh;
  margin: 0px;
}

/* media query 기술 */
/* min-width 화면이 650px 이상일 때에만 { } 안의 규칙이 적용된다 */
/* = 반응형 웹을 작성할 수 있도록 도와주는 css 도구 */
@media (min-width : 650px){
  .App {
    width: 640px;
  }
}

/* min-width 화면이 650px 이하일 때 */
@media (max-width: 650px) {
  /* App class component -> 90 viewport width = 90%
     = 지금 화면의 90%만 차지하도록 해줌 */
  .App {
    width: 90vw;
  }
}

#root {
  background-color: white;
  box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
}

.App {
  min-height: 100vh;
  padding-left: 20px;
  padding-right: 20px;
}





3. 이미지 에셋 세팅
=> 감정 이미지들을 프로젝트에서 불러와 사용할 수 있는 환경 세팅

* App.js

function App() {
  // process~URL이 작동하지 않는 경우
  const env = process.env;
  env.PUBLIC_URL = env.PUBLIC_URL || "";
  // = env.PUBLIC_URL이 존재한다면 그냥 담고 아니라면 비워라

  return (
    // BrowserRouter로 감싸준다
    // = 감싸져있는 부분은 브라우저 url과 매핑될 수 있다
    <BrowserRouter>
      <div className="App">
        <h2>App.js</h2>

        {/* process.env.PUBLIC_URL = public directory를 바로 쓸 수 있는 명령어 */}
        <img src={process.env.PUBLIC_URL + "/assets/emotion1.png"} />
        <img src={process.env.PUBLIC_URL + "/assets/emotion2.png"} />
        <img src={process.env.PUBLIC_URL + "/assets/emotion3.png"} />
        <img src={process.env.PUBLIC_URL + "/assets/emotion4.png"} />
        <img src={process.env.PUBLIC_URL + "/assets/emotion5.png"} />
      </div>
    </BrowserRouter>
  );
}

export default App;






4. 공통 컴포넌트 세팅
=> 모든 페이지에 공통으로 사용되는 버튼, 헤더 컴포넌트 세팅

#Button component
작성완료 - type: POSITIVE
            - text: "작성완료"
            - onClick: ?
            - color: green

수정하기 - type: DEFAULT(or undefined)
            - text: "수정하기"
            - onClick: ?
            - color: gray

수정하기 - type: NEGATIVE
            - text: "삭제하기"
            - onClick: ?
- color: red

=> Button component의 type이라는 prop이 가질 수 있는 값
     = 3가지의 값(POSITIVE, DEFAULT, NEGATIVE)을 갖는다

* MyButton.js

const MyButton = ({ text, type, onClick }) => {
  // 예외처리 => btnType 배열에 없는 값이 들어오면
  // 강제로 default type으로 btnType을 바꿔버림
  const btnType = ["positive", "negative"].includes(type) ? type : "default";

  return (
    // className에 배열을 넣어줌
    // 배열을 join() 메서드를 활용하여 문자열로 합쳐준다
    // MyButton_${type}를 통해 type를 받아와 동적으로 className을 지정해줄 수 있다
    <button
      className={["MyButton", `MyButton_${btnType}`].join(" ")}
      onClick={onClick}
    >
      {text}
    </button>
  );
};

// type prop을 전달받지 않은 경우 = default
MyButton.defaultProps = {
  type: "default",
};

export default MyButton;






* App.js

import MyButton from "./components/MyButton.js";

function App() {
  return (
    <BrowserRouter>
      <div className="App">

        {/* MyButton import */}
        {/* positive */}
        <MyButton
          text={"버튼"}
          onClick={() => alert("버튼 클릭")}
          type={"positive"}
        />
        {/* negativ */}
        <MyButton
          text={"버튼"}
          onClick={() => alert("버튼 클릭")}
          type={"negative"}
        />
        {/* default */}
        <MyButton text={"버튼"} onClick={() => alert("버튼 클릭")} />

      </div>
    </BrowserRouter>
  );
}

export default App;






* App.css

/* MyButton style */
.MyButton {
  cursor: pointer;
  border: none;
  border-radius: 5px;
  
  padding-top: 10px;
  padding-bottom: 10px;
  padding-left: 20px;
  padding-right: 20px;

  font-size: 18px;

  /* 버튼안에 있는 글자가 짤려서 2줄이 되지 않도록 방지해주는 속성 */
  white-space: nowrap;

  font-family: 'Nanum Pen Script';
}

.MyButton_default{
  background-color: #ececec;
  color: block;
}

.MyButton_positive{
  background-color: #64c964;
  color: white;
}

.MyButton_negative{
  background-color: #fd565f;
  color: white;
}





# Header component
뒤로가기      -      일기 수정하기      -      삭제하기
= 왼쪽 자식          = 헤드 텍스즈             = 오른쪽 자식
  leftChild               headText                   rightChild


* MyHeader.js

const MyHeader = ({ headText, leftChild, rightChild }) => {
  return (
    <header>
      <div className="head_btn_left">{leftChild}</div>
      <div className="head_text">{headText}</div>
      <div className="head_btn_right">{rightChild}</div>
    </header>
  );
};

export default MyHeader;





* App.js

import MyHeader from "./components/MyHeader.js";

function App() {
  return (
    <BrowserRouter>
      <div className="App">

        <MyHeader
          headText={"App"}
          leftChild={
            // MyHeader에 leftChild에 MyButton component를 전달해서 그대로 사용한다
            <MyButton text={"왼쪽 버튼"} onClick={() => alert("왼쪽 클릭")} />
          }
          rightChild={
            <MyButton
              text={"오른쪽쪽 버튼"}
              onClick={() => alert("오른쪽 클릭")}
            />
          }
        />
        {/* component 자체를 prop으로 전달하게 되면
            onClick이나 text를 따로따로 전달할 필요가 없어지기 때문에
            전달되는 prop의 갯수를 줄일 수 있는 좋은 방법이다 */}

      </div>
    </BrowserRouter>
  );
}





* App.css 

/* MyHeader style */
header {
  padding-top: 20px;
  padding-bottom: 20px;

  display: flex;
  align-items: center;
  border-bottom: 1px solid #e2e2e2;
}

header > div {
  display: flex;
}

header .head_text {
  width: 50%;
  font-size: 25px;
  justify-content: center;
}

header .head_btn_left {
  width: 25%;
  justify-content: start;
}

header .head_btn_right {
  width: 25%;
  justify-content: end;
}

header button {
  font-family: "Nanum pen Script";
}

 

 

 

 

 

 

 

2023. 01. 23 (화)

 

 

관련글 더보기