저번 시간에 했던 것.

 

1. 카테고리 테이블, 서브카테고리 테이블, 게시글 테이블을 생성했다.

 

ㅎㅎ 소마 준비하면서 프로그래머스 sql 고득점 kit을 여러번 돌리면서 sql이 공부가 돼서.. join으로 글 정보랑 카테고리 정보들 가져오는 거 자랑(?)하기

 

2. 게시글 생성일, 수정일 자동으로 db에 저장되게 함.


우선 한 가지 확인하고 싶은게 생겼다. 이전에 필터를 만들어둔 상태고, 로그인/회원가입을 제외한 곳들은 전부다 막아둔 상태다. 

 

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.csrf().disable() // CSRF 공격에 대한 방어를 해제
                .cors().and() // cors 허용
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 서버를 stateless하게 유지 즉 세션 X
                .and()
                .formLogin().disable() // 시큐리티가 기본 제공하는 로그인 화면 없앰. JWT는 로그인과정을 수동으로 클래스로 만들어야 하니까
                .httpBasic().disable() // 토큰 방식을 이용할 것이므로 보안에 취약한 HttpBasic은 꺼두기
                .authorizeRequests() // HttpServletRequest를 사용하는 요청들에 대한 접근제한을 설정하겠다
                .requestMatchers("/account/signup", "/account/login", "/").permitAll() // 요 세 놈에 대한 요청은 인증없이 접근 허용
                .anyRequest().authenticated() // 나머지에 대해선 인증을 받아야 한다.
                .and()
                // 여러 필터들 중 UsernamePassword필터 앞에 내가 만든 필터를 둔다. 이렇게 하면 커스텀 필터로 인가인증을 다룰 수 있음
                .addFilterBefore(jwtAuthenticationFilter,
                        UsernamePasswordAuthenticationFilter.class)
                .build();
    }

 

게시글에 관한 것은 BoardController에서 일괄적으로 처리하게 할 계획이다. 이 때 /board로 들어오는 놈들은 인증을 받아야 한다!

 

@RestController
@RequestMapping(value = "/board")
@RequiredArgsConstructor
public class BoardController {
    private final BoardService boardService;

    @GetMapping("/test")
    public String boardTest() {
        return "test";
    }
}

 

한마디로 /board/test로 생으로 그냥 들어가면 test라는 응답을 받을 수 없을 거고, 토큰을 함께 보내면 test라는 응답을 받을 수 있다는 소리가 되는데.. 정말 잘 동작하는가?를 시험해보고 싶었다.

 

우선 생으로 /board/test로 보내봤다.

 

 

결과는 실패..

이번엔 토큰을 헤더에 담고 보내봤다. 결과는..?

 

 

성공한 모습!! 😄😄

 

 

참고로 위는 유효기간이 지난 토큰인데, 이렇게 유효하지 않은 토큰을 담아 보내면 응답이 오지 않는, 즉 필터가 일을 잘 하고 있는 모습을 볼 수 있었다.

 

 

이제 글 작성 api를 만들 차례. 컨트롤러에 다음과 같은 createBoard메서드를 추가해줬다.

 

@PostMapping("/write")
    public String createBoard(@RequestBody BoardDTO boardDTO) {
        Board newBoard = new Board();
        newBoard.setWriter(boardDTO.getEmail());
        newBoard.setTitle(boardDTO.getTitle());
        newBoard.setContent(boardDTO.getContent());
        newBoard.setCategoryId(boardDTO.getCategoryId());
        newBoard.setLike(0L);
        newBoard.setHits(0L);
        
        boardService.registerNewBoard(newBoard);
        return "success";
    }

 

BoardDTO는 다음과 같다.

 

@Getter
@Setter
public class BoardDTO {
    private String email;
    private String title;
    private String content;
    @JsonProperty("category_id")
    private int categoryId;
}

 

RequestBody는 예전에 작성했듯 클라이언트가 보낸 json데이터를 자바객체로 매핑해주는 역할을 하는데, 이 때 json데이터의 키 값과 매핑될 클래스의 멤버변수들 이름이 같아야 정상적으로 착착 매핑된다. 그러나 json에서 category_id라는 snake형식으로 보낸 값이 클래스에서 camel형식으로 작성된 categoryId에 매핑시키고 싶다면, 저렇게 해당 필드에 @JsonProperty라는 어노테이션을 멕이면 된다.

 

 

게시글이 잘 등록되는 걸 볼 수 있다!

 

현재 이 게시판 등록 기능은, 클라이언트 측에서 json으로 작성자의 이메일을 함께 넘겨주는 방식이다. 그러나 구글에 게시글 등록과 관련된 포스트들을 보면, 이메일이 아니라 멤버자체를 게시글 엔티티의 필드로 설정하는 방식도 있었다. 차이점이 뭔지는 잘 모르겠으나..일단 한 번 따라해봤다.

 

우선 Board클래스를 다음과 같이 수정해줬다.

 

@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_id")
    private Long boardId;

    @JoinColumn(name = "writer", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private Member writer;

    @Column(length = 80)
    private String title;
    
    // ... 생략

 

기존엔 write필드를 String으로 해서 이메일 자체를 세팅해줬었지만, 보시다시피 멤버로 받는 모습.

 

@ManyToOne(fetch = FetchType.LAZY) : 게시글 입장에선 멤버와 다대일(N : 1) 관계임. 이 다중성을 나타내는 어노테이션. FetchType은 즉시로딩과 지연로딩에 관한 거라는데..음...뭔 소린지 몰라서 패쓰

 

@JoinColumn(name = "writer") : 외래키를 매핑할 때 사용하는 어노테이션.  name은 외래키의 이름, 즉 조인의 대상으로 사용할 컬럼의 이름을 지정해줌. Board테이블의 writer컬럼에 Member의 pk인 email이 들어갈 것임을 명시하는 것!! Member클래스에서 email필드에 @Id 어노테이션에 멕여져 있어서 가능.

 

그리고 BoardDTO에서 다음과 같이 email필드를 빼줬다.

 

@Getter
@Setter
public class BoardDTO {
    private String title;
    private String content;
    @JsonProperty("category_id")
    private int categoryId;
}

 

그리고 BoardController에서 다음과 같이 인증정보를 가져와서 멤버를 꽂아넣도록(?) 바꿔줬다.

 

@PostMapping("/write")
    public String createBoard(@AuthenticationPrincipal Member member, @RequestBody BoardDTO boardDTO) {
        Board newBoard = new Board();
        newBoard.setWriter(member);
        newBoard.setTitle(boardDTO.getTitle());
        newBoard.setContent(boardDTO.getContent());
        newBoard.setCategoryId(boardDTO.getCategoryId());
        newBoard.setLike(0L);
        newBoard.setHits(0L);

        boardService.registerNewBoard(newBoard);
        return "success";
    }

 

@AuthenticationPrincipal : UserDetails를 구현한 인증객체를 주입할 때 사용하는 어노테이션.  요청을 보낸 사용자가 인증된 경우SecurityContextHolder라는 곳에 저장된 인증객체(현재사용자의 정보!)를 꺼내와 넣어주는 역할! 나같은 경우는 Member자체가 UserDetails를 구현하도록 만들었기 때문에 Member로 받도록 했다.

 

결과는..

 

 

 

서..성공!

 

첫 질문으로 들어와서, 그럼 게시글 등록을 할 때 Board엔티티의 writer필드를 String으로 하는게 맞는걸까 아님 Member로 하는게 맞는걸까? 이는 연관관계 매핑이라는 것과 관련이 있다고 한다. 이것에 대해서 알아봐야겠다.

@Value를 사용해 application.properties에 적은 값을 가져오는 과정, 구체적으론 jwt secret key를 가져오는 과정에서 null값을 가져오는 문제가 생겨버렸다.

 

이상하게도 AccountController에서 jwt관련 기능들을 쓸 때는 null이 아니라 제대로 된 값을 가져오는데, 필터 쪽에서 jwt 토큰 인증을 할 때에만 null로 가져오는 게 문제였다..

 

원인은 찾아보니 알 수 있었다. 바로 AccountController에서는 jwt key를 담는 멤버변수가 있는 객체가 빈으로 등록된 애였지만, 같은 jwt key를 담는 멤버변수를 가지는 필터는 빈으로 등록된 애가 아니었던 것

 

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.csrf().disable() // CSRF 공격에 대한 방어를 해제
                .cors().and() // cors 허용
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 서버를 stateless하게 유지 즉 세션 X
                .and()
                .formLogin().disable() // 시큐리티가 기본 제공하는 로그인 화면 없앰. JWT는 로그인과정을 수동으로 클래스로 만들어야 하니까
                .httpBasic().disable() // 토큰 방식을 이용할 것이므로 보안에 취약한 HttpBasic은 꺼두기
                .authorizeRequests() // HttpServletRequest를 사용하는 요청들에 대한 접근제한을 설정하겠다
                .requestMatchers("/account/signup", "/account/login", "/").permitAll() // 요 세 놈에 대한 요청은 인증없이 접근 허용
                .anyRequest().authenticated() // 나머지에 대해선 인증을 받아야 한다.
                .and()
                // 여러 필터들 중 UsernamePassword필터 앞에 내가 만든 필터를 둔다. 이렇게 하면 커스텀 필터로 인가인증을 다룰 수 있음
                .addFilterBefore(new JwtAuthenticationFilter(tokenProvider),
                        UsernamePasswordAuthenticationFilter.class)
                .build();
    }

 

보이다시피 new 연산자를 통해 새로운 필터객체를 만들어서 끼워주는 모습을 볼 수 있다. 이 필터객체가 @Value 어노테이션을 통해 jwt key를 가져와야 하던 놈이다..근데 빈이 아니라 못 가져오던 것! 뭐, 해결법은 간단하다. 필터를 빈으로 만들어 끼우면 된다! 

 

private final JwtAuthenticationFilter jwtAuthenticationFilter;

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.csrf().disable() // CSRF 공격에 대한 방어를 해제
                .cors().and() // cors 허용
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 서버를 stateless하게 유지 즉 세션 X
                .and()
                .formLogin().disable() // 시큐리티가 기본 제공하는 로그인 화면 없앰. JWT는 로그인과정을 수동으로 클래스로 만들어야 하니까
                .httpBasic().disable() // 토큰 방식을 이용할 것이므로 보안에 취약한 HttpBasic은 꺼두기
                .authorizeRequests() // HttpServletRequest를 사용하는 요청들에 대한 접근제한을 설정하겠다
                .requestMatchers("/account/signup", "/account/login", "/").permitAll() // 요 세 놈에 대한 요청은 인증없이 접근 허용
                .anyRequest().authenticated() // 나머지에 대해선 인증을 받아야 한다.
                .and()
                // 여러 필터들 중 UsernamePassword필터 앞에 내가 만든 필터를 둔다. 이렇게 하면 커스텀 필터로 인가인증을 다룰 수 있음
                .addFilterBefore(jwtAuthenticationFilter,
                        UsernamePasswordAuthenticationFilter.class)
                .build();
    }

 

 

결론!

@Value어노테이션을 통해 값이 멕여지는 멤버변수를 가지는 객체는 빈으로 등록돼있어야 값을 잘 가져온다.


근데 왜 Bean으로 등록해야 가져와지는 거지?

결론적으로 Bean 생성과정에서 @Value가 inject되기 때문..

구체적인 과정들은 현재 내 수준에선 뭔 말인지 잘 모르기 때문에 생략(ㅠ)

 


 

참고한 링크

https://wildeveloperetrain.tistory.com/143

 

@Value 어노테이션 null이 나오는 문제 해결 방법

@Value annotation을 사용하여 properties에 있는 메타 정보 값을 가져오는 과정에서, 값이 null으로 들어오는 문제를 해결하며 기록한 내용입니다. @Value Annotation 쉽게 @Value 어노테이션은 데이터베이스

wildeveloperetrain.tistory.com

https://duooo-story.tistory.com/32

 

@Value는 어디서 set이 되는걸까?

프로젝트에서 reousrce로 등록한 데이터를 @value를 통해서 데이터를 받아 사용하고 있습니다. 이 기능 덕분에 각 존별로 다른데이터를 코드의 분기처리없이 사용하고 있습니다. @Configuration public cla

duooo-story.tistory.com

 

+ Recent posts