Spring Boot - 9

현곤·2024년 12월 20일

Card 컴포넌트 사용

클래스설명
card, card-body, card-textcard 컴포넌트를 적용하는 클래스들이다.
badgebadge 컴포넌트를 적용하는 클래스이다.
form-control텍스트 창에 form 컴포넌트를 적용하는 클래스이다.
border-bottom아래 방향 테두리 선을 만드는 클래스이다.
my-3상하 마진값으로 3을 지정하는 클래스이다.
py-2상하 패딩값으로 2를 지정하는 클래스이다.
p-2상하좌우 패딩값으로 2를 지정하는 클래스이다.
d-flex justify-content-endHTML 요소를 오른쪽으로 정렬하는 클래스이다.
bg-light연회색으로 배경을 지정하는 클래스이다.
text-dark글자색을 검은색으로 지정하는 클래스이다.
text-start글자를 왼쪽으로 정렬하는 클래스이다.
btn btn-primary버튼 컴포넌트를 적용하는 클래스이다.

Spring Boot Validation 라이브러리

항목설명
@Size문자 길이를 제한한다.
@NotNullNull을 허용하지 않는다.
@NotEmptyNull 또는 빈 문자열("")을 허용하지 않는다.
@Past과거 날짜만 입력할 수 있다.
@Future미래 날짜만 입력할 수 있다.
@FutureOrPresent미래 또는 오늘 날짜만 입력할 수 있다.
@Max최댓값 이하의 값만 입력할 수 있도록 제한한다.
@Min최솟값 이상의 값만 입력할 수 있도록 제한한다.
@Pattern입력값을 정규식 패턴으로 검증한다.

@Valid, BindingResult

둘 다 검증을 진행하게 해주는 것

페이징

Repository

Page<Question> findAll(Pageable pageable);

Service

public Page<Question> getList(int page) {
        Pageable pageable = PageRequest.of(page, 10);
        return this.questionRepository.findAll(pageable);
    }

Controller

@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>

@Bean

스프링에 의해 생성 또는 관리되는 객체를 의미

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 객체를 주입받아 사용할 수 있도록 수정

profile
코딩하는 곤쪽이

0개의 댓글