📢 오늘의 목표 📢
✔️ 알고리즘, SQL 문제 풀이
✔️ 알고리즘 코드카타
✔️ SQL 코드카타
✔️ 스프링 개인 과제
✔️ 10, 11단계
⏱️ 오늘의 일정 ⏱️
9:00 ~ 10:00 - 알고리즘 코드 카타
10:00 ~ 10:30 - 팀 회의
10:30 ~ 11:00 - 스프링 개인 과제
11:00 ~ 12:30 - 스프링 시큐리티 특강
12:30 ~ 14:00 - 점심시간
14:00 ~ 15:30 - 챌린지반 과제
15:30 ~ 18:00 - 스프링 개인 과제
18:00 ~ 19:00 - 저녁 시간
19:00 ~ 20:00 - 스프링 개인 과제
20:00 ~ 21:00 - TIL 작성
📜 Chapter 1. 스프링 시큐리티 동작원리
1. Spring Security?
Spring Security란 인증과 접근 제어를 위해 세부적인 맞춤 구성이 가능한 강력한 보안을 위한 프레임워크로 필요한 부분만 골라서 구현하면 되는 프레임워크이다.
복잡도를 높일수록 비용도 증가한다.(유지보수가 어렵다.)
구현하는 애플리케이션에 따라 스프링 시큐리티의 구성도 다르게 해야한다.
2. Spring Security 자세히 알아보기
2.1. Spring Security의 인증 프로세스
대부분 인터페이스로 이루어져있다.
내부에서 구현이 되어있기 때문에 필요한 부분만 재정의 해 구현하면 된다.(맞춤 구성, JPA Repository와 같은 원리)
2.2. 사용자 관리 (UserDetailsService)
사용자를 찾는 로직을 loadUserByUserName()메서드 재정의를 통해 구현 한다.
- UserDetailsService: 인증을 위해 사용자를 찾습니다.
- UserDetails: 사용자를 기술합니다.
- GrantedAuthority: 사용자가 실행할 수 있는 작업(권한)을 정의합니다. (읽기, 쓰기, 삭제 권한 등)
- UserDetailsManager: UserDetailsService에서 확장된 형태. 사용자의 생성, 수정, 삭제 및 암호 수정 등의 작업을 지원합니다.
UserDetailsService 으로 제공되는 것보다 세부 작업(사용자의 생성, 수정, 삭제 및 암호 수정 등의 작업)을 원한다면 UserDetailsManager를 이용합니다.
2.3. 암호 처리 (PasswordEncoder)
PasswordEncoder의 종류(구현한 클래스)
- PasswordEncoder: 암호가 유효한지 확인합니다.
- NoOpPasswordEncoder: 인코딩하지 않습니다. 실제 시나리오에는 절대 쓰지 말아야 합니다. (NoOp는 No Operation을 의미합니다)
- StandardPasswordEncoder: SHA-256을 이용해 암호를 해시한다. 강도가 약한 해싱 알고리즘을 사용하기 때문에 사용하지 않는 것이 좋습니다.
- Pbkdf2PasswordEncoder: PBKDF2를 이용합니다.
- BCryptPasswordEncoder: bcrypt 강력 해싱 함수로 암호를 인코딩합니다.
- SCryptPasswordEncoder: scrypt 해싱 함수로 암호를 인코딩합니다.
주로 BCryptPasswordEncoder와 SCryptPasswordEncoder를 많이 사용하며, 함께 사용하기도 합니다.
키 생성기
- BytesKeyGenerator: 특정 길이의 바이트 만큼의 키를 생성합니다.
- StringKeyGenerator: 인코딩된 문자열을 키로 생성합니다.
2.4. 인증 구현 (AuthenticationProvider, SecurityContext)
(여기서 부터 잘 모르겠다...)
AuthenticationProvider 인터페이스로 인증이 처리되면 인증 정보가 담긴 Authentication 객체를 SecurityContext에 저장해 Controller에서 인증 정보를 이용 가능하다.
AuthenticationProvider: 인증 로직을 처리한다.
사용자 관리 책임은 UserDetailsService에 위임하고, PasswordEncoder에 암호 검증 책임을 위임.
- authenticate(): 인증 로직을 정의하고 처리하는 메소드.
- 인증이 실패하면 AuthenticationException을 던지도록 합니다.
- 현재 AuthenticationProvider 구현에서 지원되지 않는 인증 객체를 받으면 null을 리턴하도록 합니다. (여러 Authentication 형식을 사용한 경우)
- 완전히 인증된 객체를 나타내는 Authentication 인스턴스를 리턴하도록 합니다.
- supports(): 현재 AuthenticationProvider가 해당 Authentication을 지원하는지 확인합니다.
- 현재 AuthenticationProvider가 Authentication 객체로 제공된 형식을 지원하면 true를 반환하도록 합니다.
SecurityContext: 인증된 엔티티 정보가 담긴 Authentication 객체를 저장하고 유지.
- AuthenticationManager는 인증 프로세스를 성공적으로 완료한 후 Authentication 인스턴스를 저장하는데 이것을 SecurityContext가 저장합니다.
- SecurityContextHolder: SecurityContext를 관리합니다.
결론: 여러번 써 봐야 할 것 같다.
📜 Chapter 2. 챌린지반 과제
public class RamenProgram {
public static void main(String[] args) {
try{
RamenCook ramenCook = new RamenCook(Integer.parseInt(args[0])); // ramenCook 인스턴스 생성 args[0] 값 주입
new Thread(ramenCook, "A").start(); // 새 ramenCook 스레드 시작. 이름 A 이하 냄비라고 표현.
new Thread(ramenCook, "B").start(); // 냄비 B
new Thread(ramenCook, "C").start(); // 냄비 C
new Thread(ramenCook, "D").start(); // 냄비 D
} catch (Exception e) {
e.printStackTrace(); // 예외 발생시 출력
}
}
}
class RamenCook implements Runnable {
private int ramenCount; // 라멘 갯수 필드
private String[] burners = {"_", "_", "_", "_"}; // 버너는 4개
public RamenCook(int ramenCount) { // 라멘 갯수 주입 받는 생성자
this.ramenCount = ramenCount;
}
@Override
public void run() {
while(ramenCount > 0) { // 끓일 라멘 남아있는 동안
synchronized(this) { // 동기화 구간 시작.
ramenCount--; // 라멘 하나 끓일 거니까 갯수 줄임.
System.out.println( // 라멘 몇 개 남음?
Thread.currentThread().getName() + ": " + ramenCount + "개 남음"
);
} // 동기화 구간 끝.
for(int i = 0; i < burners.length; i++) { // 빈 버너 있는지 순회~
if(!burners[i].equals("_")) { // 빈 버너가 아니면 넘어가
continue;
}
synchronized(this) { // 동기화 구간 시작.
burners[i] = Thread.currentThread().getName(); // 이 빈 버너는 현재 스레드(냄비)가 사용할 것
System.out.println(
" " // 예쁘게 띄어쓰기
+ Thread.currentThread().getName() // 이 냄비는
+ ": [" + (i + 1) + "]번 버너 ON" // i+1번 버너를 사용 시작했다.
);
showBurners(); // 버너 현황 보러가는 메서드
} // 동기화 구간 끝.
try {
Thread.sleep(2000); // 2초 휴식. 라멘이 2초만에 끓어요
} catch (Exception e) { // sleep을 썼으니 try-catch 해줘야 됨
e.printStackTrace();
}
synchronized(this) { // 동기화 구간 시작.
burners[i] = "_"; // 냄비 치워
System.out.println(
" " // 예쁘게 띄어쓰기
+ Thread.currentThread().getName() // 이 냄비는
+ ": [" + (i + 1) + "]번 버너 OFF" // i+1번 버너 사용 끝났다.
);
showBurners(); // 버너 현황 보러가는 메서드
} // 동기화 구간 끝.
break; // 라멘 끓이기 성공했으니 for문 빠져나온다.
}
try {
Thread.sleep(Math.round(1000 * Math.random())); // 다음 라면 끓이기 시도 전까지 랜덤초 휴식
} catch (Exception e) { // sleep을 썼으니 try-catch 해줘야 됨
e.printStackTrace();
}
}
}
private void showBurners() {
String stringToPrint = " "; // 예쁘게 띄어쓰기
for(int i = 0; i < burners.length; i++) { // 버너들 순회
stringToPrint += (" " + burners[i]); // 현재 버너 상태들 String에 집어 넣는다.
}
System.out.println(stringToPrint); // 프린트
}
}
주석을 일부러 좀 이해 잘 되고 재미나게 써봤다.
📜 Chapter 3. 파일? Blob?
파일 다루는 것이 아직 미숙해서 시간이 좀 걸렸다...
@Builder
@Getter
@Entity
@Table(name = "file")
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor
@AllArgsConstructor
public class File {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String type;
@Column(nullable = false)
private int size;
@CreatedDate
@Column(updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt;
@Lob()
@Column(nullable = false, length = 5000000)
private byte[] contents;
public File update(String name, String type, int size, byte[] contents) {
this.name = name;
this.type = type;
this.size = size;
this.contents = contents;
return this;
}
}
우선 파일을 DB에 저장할 것이기 때문에 파일 Entity를 만들었다.
@Lob 애너테이션이 BLOB 타입이라는 것을 알려주는데 저장 할 max 크기에 따라 Column 애너테이션에서 length를 지정 해줘야 한다...
계속 data truncation: data too long for column ... Error가 떠서 ㅠㅠ 해결하기 참 힘들었다.
어디서 길이를 제한할 수 있는지 몰라서!
@PostMapping("/schedule")
@Operation(summary = "일정 작성")
@Parameters({
@Parameter(name = "title", description = "제목(1~200자)", example = "제목입니다."),
@Parameter(name = "contents", description = "내용(1~500자)", example = "내용입니다."),
@Parameter(name = "charge", description = "담당자(email)", example = "damdang@email.com"),
})
public ResponseEntity<ScheduleResponseDto> createSchedule(@Valid @RequestPart ScheduleRequestDto requestDto,
@RequestPart(value = "file", required = false) MultipartFile file,
@AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(scheduleService.createSchedule(requestDto, file, userDetails.getUser()));
}
또, 파일과 json을 같이 받으려니 문제가 생겼다.
@RequestBody, @RequestPart를 같이 보낼 수는 없었다.
그러나 @RequestBody를 @RequestPart로 변경하고 이 두 요소를 폼 데이터로 보내면 동일하게 작동한다.
포스트맨에서 이렇게 보내자!
key를 @RequestPart 파라미터 이름과 맞춰주고, requestDto 부분은 json 데이터를 통채로 보내며 Content-Type을 application/json으로 지정해 준다.
포스트맨에서 Content-Type이 보이지 않는다면
점 세 개 버튼을 눌러 선택한다.
📜 Chapter 4. 로깅?
https://mountain-noroo.tistory.com/149
[내배캠][TIL] 18일 차 - 목요일, 발표 준비?
📢 오늘의 목표 📢 ✔️ 알고리즘, SQL 문제 풀이✔️ 알고리즘 코드카타✔️ SQL 코드카타✔️ 프로그래머스 Level 2✔️ Java 팀 프로젝트✔️ 리팩토링✔️ 발표 준비하기✔️ 스프링 공부: 4장
mountain-noroo.tistory.com
전에 스프링 공부하면서 로깅에 대해 기록한 것이 있다~
과거의 나 덕에 어렵지 않게 해결했다.
🌙 오늘을 마치며 🌙
이제 정말 내일은 과제를 다 마칠 수 있겠다~
'공부 기록 > 내배캠Java_5기' 카테고리의 다른 글
[내배캠][TIL] 32일 차 - 목요일, 과제 개선 (0) | 2024.05.30 |
---|---|
[내배캠][TIL] 31일 차 - 수요일, 과제 다 했고 개인 공부 (0) | 2024.05.29 |
[내배캠][TIL] 28일 차 - 금요일, 스프링 시큐리티를 다시 알아보자. (0) | 2024.05.24 |
[내배캠][TIL] 27일 차 - 목요일, 강의듣기싫다 (0) | 2024.05.23 |
[내배캠][TIL] 25일 차 - 화요일, 숙련 주차 시작! (0) | 2024.05.21 |