| 클래스 | 설명 |
|---|---|
card, card-body, card-text | card 컴포넌트를 적용하는 클래스들이다. |
badge | badge 컴포넌트를 적용하는 클래스이다. |
form-control | 텍스트 창에 form 컴포넌트를 적용하는 클래스이다. |
border-bottom | 아래 방향 테두리 선을 만드는 클래스이다. |
my-3 | 상하 마진값으로 3을 지정하는 클래스이다. |
py-2 | 상하 패딩값으로 2를 지정하는 클래스이다. |
p-2 | 상하좌우 패딩값으로 2를 지정하는 클래스이다. |
d-flex justify-content-end | HTML 요소를 오른쪽으로 정렬하는 클래스이다. |
bg-light | 연회색으로 배경을 지정하는 클래스이다. |
text-dark | 글자색을 검은색으로 지정하는 클래스이다. |
text-start | 글자를 왼쪽으로 정렬하는 클래스이다. |
btn btn-primary | 버튼 컴포넌트를 적용하는 클래스이다. |
| 항목 | 설명 |
|---|---|
@Size | 문자 길이를 제한한다. |
@NotNull | Null을 허용하지 않는다. |
@NotEmpty | Null 또는 빈 문자열("")을 허용하지 않는다. |
@Past | 과거 날짜만 입력할 수 있다. |
@Future | 미래 날짜만 입력할 수 있다. |
@FutureOrPresent | 미래 또는 오늘 날짜만 입력할 수 있다. |
@Max | 최댓값 이하의 값만 입력할 수 있도록 제한한다. |
@Min | 최솟값 이상의 값만 입력할 수 있도록 제한한다. |
@Pattern | 입력값을 정규식 패턴으로 검증한다. |
둘 다 검증을 진행하게 해주는 것
Page<Question> findAll(Pageable pageable);
public Page<Question> getList(int page) {
Pageable pageable = PageRequest.of(page, 10);
return this.questionRepository.findAll(pageable);
}
@GetMapping("/list")
public String list(Model model,
@RequestParam(value = "page", defaultValue = "0") int page) {
Page<Question> paging = this.questionService.getList(page);
model.addAttribute("paging", paging);
return "question_list";
}
Spring Boot의 페이징 기능을 구현할 때 첫 페이지 번호는 1이 아닌 0
기본값으로 0을 설정해야함
GET 방식에서는 값을 전달하기 위해 ? 와 & 기호를 사용
첫 번째 파라미터는 ? , 이후 추가되는 값은 &
그거랑 같네
localhost:8080/wiseSaying/write?content=어쩌구저쩌구&author=어쩌구저쩌구
게시물 번호 =
전체 게시물 개수 - (현재 페이지 * 페이지당 게시물 개수 ) - 나열 인덱스
| 항목 | 설명 |
|---|---|
| 게시물 번호 | 최종 표시될 게시물의 번호 |
| 전체 게시물 개수 | 데이터베이스에 저장된 게시물 전체 개수 |
| 현재 페이지 | 페이징에서 현재 선택한 페이지 |
| 페이지당 게시물 개수 | 한 페이지당 보여 줄 게시물의 개수 |
| 나열 인덱스 | for 문 안의 게시물 순서 (현재 페이지에서 표시할 수 있는 게시물의 인덱스. 예: 10개일 경우 0~9, 2개일 경우 0~1) |
질문 게시물이 12개인 상황으로 예시를 들면
현재 페이지가 0,
게시물의 번호는 전체 게시물 개수 12에서 나열 인덱스 0 ~ 9를 뺀 12 ~ 3이 된다.
현재 페이지가 1이면 페이지당 노출되는 게시물 개수는 10
12에서 10을 뺀 값인 2에 나열 인덱스를 0 ~ 1 다시 빼므로 게시물 번호는 2 ~ 1
무슨 소린지 감이 안옴
| 항목 | 설명 |
|---|---|
paging.getTotalElements | 전체 게시물 개수를 의미한다. |
paging.number | 현재 페이지 번호를 의미한다. |
paging.size | 페이지당 게시물 개수를 의미한다. |
loop.index | 나열 인덱스를 의미한다 (0부터 시작). |
<td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
스프링에 의해 생성 또는 관리되는 객체를 의미
Controller , Service , Repository 등도 모두 빈에 해당
@Bean 이노테이션을 통해 자바 코드 내에서 별도로 빈을 정의하고 등록할 수 있다.
인증되지 않은 사용자가 SBB와 같은 웹 서비스를 사용할 수 없게 만듬
스프링 시큐리티의 기본 기능을 SBB에 고대로 가져다 적용하면 곤란하다.
설정을 통해서 바로 잡아야함
SBB는 로그인하지 않아도 게시물을 조회할 수 있어야 한다
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.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
;
return http.build();
}
}
@Configration 이 파일이 스프링의 환경 설정 파일임을 의미하는 어노테이션
스프링 시큐리티를 설정하기 위해 사용
@EnableWebSecurity 모든 요청 URL이 스프링 시큐리티의 제어를 받도록 만드는 어노테이션
이걸 사용하면 스프링 시큐리티를 활성화 하는 역할을 함
SecurityFilterChain내부적으로 SecurityFilterChain 클래스가 동작하여 모든 요청 URL이
이 클래스가 필터로 적용되어 URL 별로 특별한 설정을 할 수 있게 된다.
스프링 시큐리티의 세부 설정은 @Bean 어노테이션을 통해
SecurityFilterChain 빈을 생성하여 설정할 수 있다.
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
;
인증되지 않은 모든 페이지의 요청을 허락한다는 의미
로그인하지 않더라도 모든 페이지에 접근할 수 있도록 해줌
<input type="hidden" name="_csrf" value="ELCsIKgKv7yGzeFZsXxrCtAcVBiiS-lvBrP8b-1scsRpPlrWHJoKfFsw4ioyr-thtgFFfbLF6eSCUctFCYICYW2l4gC57o4W1"/>
스프링 시큐리티에 의해 CSRF 토큰이 자동으로 생성됨
스프링 시큐리티는 이런 식으로 페이지에 CSRF 토큰을 발행하여
이 값이 다시 서버로 정확하게 들어오는지를 확인하는 과정을 거침
CSRF 토큰이 없거나 해커가 임의로 CSRF 토큰을 강제로 만들어 전송한다면
스프링 시큐리티에 의해 차단됨
H2 콘솔은 스프링 프레임워크가 아니다 그래서 CSRF 토큰을 발행하는 기능이 없어서
403 오류가 발생하는 것
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
h2-console로 시작하는 모든 URL은 CSRF 검증을 하지 않겠다는 설정
이거 하고나면 h2-console 들어가짐
h2 콘솔 화면은 프레임 구조로 작성되어 있다
즉 h2 콘솔 UI 레이아웃이 작업 영업이 나눠져 있음을 의미
스프링 시큐리티는 웹 사이트의 콘텐츠가 다른 사이트에 포함되지 않도록 하기 위해
X-Frame-Option 헤더의 기본값을 DENY 로 사용함
프레임 구조의 웹 사이트는 이 헤더의 값이 DENY 인 경우 오류가 발생
스프링 부트에서
X-Frame-Option헤더는 클릭재킹 공격을 막기 위해 사용
클릭재킹은 사용자의 의도와 다른 작업이 수행되도록 속이는 보안 공격 기술
.headers((headers)-> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
요거 해주면 X-Frame-Option 헤더를 DENY 대신 SAMEORIGIN 으로 설정함
이렇게 설정해주면 프레임에 포함된 웹 페이지가 동일한 사이트에서
제공할 때만 사용이 허락됨
스프링 시큐리티를 왜 사용하는가?
웹 프로그램 또는 애플리케이션의 보안을 강화
사용자 인증 및 권한 부여를 효과적으로 관리
외부 공격으로부터 시스템을 보호
@Getter
@Setter
@Entity
public class SiteUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
@Column(unique = true)
private String email;
}
이렇게 만들면 h2-console에서 봤을 때 siteuser 인덱스에 UK로 붙어서 나옴
unique = true로 지정한 속성은 DB에 유니크 인덱스로 생성
UK = Unique Key
User 서비스에는 User 리포지터리를 사용하여 회원 데이터를 생성하는
create 메서드를 추가
User의 비밀번호는 보안을 위해 반드시 암호화하여 저장해야 함
그러므로 스프링 시큐리티의 BCryptPasswordEncoder 클래스를 사용해
암호화하여 비밀번호를 저장
BCryptPasswordEncoder클래스
비크립트(BCrypt) 해시 함수를 사용
비크립트 = 보안 정보를 안전하게 저장하고 검증할 때 사용하는 암호화 기술
BCryptPasswordEncoder 객체를 직접 new 로 생성하는 방식보다
PasswordEncoder 객체를 빈으로 등록해서 사용하는 것이 ^^bb
왜냐??
암호화 방식을 변경하면 BCryptPasswordEncoder 를 사용한 모든 프로그램을
일일이 찾아다니며 수정해야함 개별로
PasswordEncoder = BCryptPasswordEncoder 의 인터페이스
빈을 만드는 가장 쉬운 방법은 @Configration 이 적용된
SecurityConfig 파일에 @Bean 메서드를 새로 추가하는 것
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean 을 등록하면 UserService 에도 수정
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
user.setPassword(passwordEncoder.encode(password));
이랬던 코드를
private final PasswordEncoder passwordEncoder;
user.setPassword(passwordEncoder.encode(password));
아주 가볍게 만들어줬다 굿굿 ^^bbb
BCryptPasswordEncoder 객체를 직접 생성하여 생성하지 않고
빈으로 등록한 PasswordEncoder 객체를 주입받아 사용할 수 있도록 수정