오늘은
오늘은 어제에 이어서 정말 무척 추운 날이었습니다
한동안 따뜻하더니 겨울인걸 까먹을까 걱정이 되었는지
이번주는 내내 추울 예정인 것 같습니다
저도 제 본분을 잊지말고 다시금 되새겨 공부에 더 집중하고
프로젝트 아이디어를 내는데에 생각을 몰두하여
잡념이 들지 않도록 노력해야겠습니다
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 (화)
프로그래밍 도전기 108일차 (2) | 2024.01.28 |
---|---|
프로그래밍 도전기 107일차 (2) | 2024.01.25 |
프로그래머 도전기 105일차 (2) | 2024.01.22 |
프로그래머 도전기 104일차 (0) | 2024.01.18 |
프로그래머 도전기 103일차 (0) | 2024.01.15 |