Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jwt choi #12

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
//swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'org.mariadb.jdbc:mariadb-java-client:3.1.4'
implementation 'javax.xml.bind:jaxb-api:2.3.0' //javax.xml.bind.JAXBException 에러 방지용
//JWT
implementation 'io.jsonwebtoken:jjwt:0.9.1' //jjwt 의존성 추가
implementation 'com.h2database:h2:2.1.214' // TODO : testimplementation으로 변경

implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨트룰러에서는 최대한 서비스 로직을 제거해주세요

단일 책임 원칙에 위반됩니다..!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PostMapping("/create")
@Operation(summary = "게시글 생성")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public Article create(@RequestBody CreateDto article) {

    Article article1 = Article.builder().subject(article.getSubject()).content(article.getContent()).build();

    //현재 인증된 사용자 가져오기
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String username = authentication.getName(); // 현재 인증된 사용자의 이름(아이디)를 가져온다.

    User user = userService.getUserByName(username); // DB에서 사용자를 조회합니다.
    return articleService.create(article1, user);
}

Entity를 그대로 반환하는 것은 좋지 않아요!
아래에 User Class를 보니 @JsonManagedReference가 있던데 이를 사용하는것 보다 DTO를 생성하는게 좋아요

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.gdsc.blog.article.controller;

import com.gdsc.blog.article.dto.CreateDto;
import com.gdsc.blog.article.dto.UpdateDto;
import com.gdsc.blog.article.entity.Article;
import com.gdsc.blog.article.service.ArticleService;
import com.gdsc.blog.user.entity.User;
import com.gdsc.blog.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.crossstore.ChangeSetPersister;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import java.nio.file.AccessDeniedException;
import java.util.List;

@RestController
@Slf4j
@Tag(name = "Article", description = "Article API")
@RequestMapping("/api/article")
public class ArticleController {
private final ArticleService articleService; //private final로 안넣은 이유??
private final UserService userService;

public ArticleController(ArticleService articleService, UserService userService){
this.articleService = articleService;
this.userService = userService;
}

@PostMapping("/create")
@Operation(summary = "게시글 생성")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")

public Article create(@RequestBody CreateDto article) {

Article article1 = Article.builder().subject(article.getSubject()).content(article.getContent()).build();

//현재 인증된 사용자 가져오기
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName(); // 현재 인증된 사용자의 이름(아이디)를 가져온다.

User user = userService.getUserByName(username); // DB에서 사용자를 조회합니다.
return articleService.create(article1, user);
}
@GetMapping("/myallarticle") //내 게시글 목록
@Operation(summary = "내 게시글 목록")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public List<Article> myallarticle(HttpServletRequest req) {
User user = userService.whoami(req);
return articleService.myAllArticle(user);
}

@GetMapping("article/{id}") // 특정 게시글
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
@Operation(summary = "특정 게시글")
public Article getarticle(HttpServletRequest req, @PathVariable("id") Long id){
return articleService.getArticle(id);
}

@GetMapping("/allarticle") // 모든 게시글 조회
@Operation(summary = "모든 게시글 조회 ")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public List<Article> allarticle(HttpServletRequest req) {
return articleService.getAllAriticle();
}

@PutMapping("/update/{id}")
@Operation(summary = "게시글 업데이트")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public Article updateArticle(@PathVariable("id") Long id, @RequestBody UpdateDto article, HttpServletRequest req) throws ChangeSetPersister.NotFoundException, AccessDeniedException {
// 현재 인증된 사용자 가져오기
User user = userService.whoami(req);
Article updatedArticle = articleService.updateArticle(id, article, user);
return updatedArticle;
}

@DeleteMapping("/delete/{id}")
@Operation(summary = "게시글 삭제")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public ResponseEntity<?> deleteArticle(@PathVariable("id") Long id, HttpServletRequest req) throws ChangeSetPersister.NotFoundException, AccessDeniedException {
// 현재 인증된 사용자 가져오기
User user = userService.whoami(req);
articleService.deleteArticle(id, user);
return ResponseEntity.ok().build();
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/gdsc/blog/article/dto/CreateDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.gdsc.blog.article.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CreateDto {
@Schema(description = "제목", example = "첫 게시글 입니다.")
public String subject;

@Schema(description = "내용", example = "안녕하세요.")
public String content;
}
18 changes: 18 additions & 0 deletions src/main/java/com/gdsc/blog/article/dto/UpdateDto.java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Article CreateDto와 UpdateDto가 코드가 일치하는것 같은데, 차라리 Article Form Dto 로 통일하는건 어떨까요?

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.gdsc.blog.article.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UpdateDto {
@Schema(description = "제목", example = "수정될 제목")
public String subject;

@Schema(description = "내용", example = "수정될 내용")
public String content;
}
33 changes: 33 additions & 0 deletions src/main/java/com/gdsc/blog/article/entity/Article.java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DB(SQL)에 시간을 저장할 때는
java.time.LocalDateTime 대신
java.sql.timestamp 클래스를 사용해주세요

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.gdsc.blog.article.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.gdsc.blog.user.entity.User;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Data
@Builder //빌더 패턴을 사용
@AllArgsConstructor //모든 필드를 매개변수로 받는 생성자
@NoArgsConstructor //매개변수가 없는 기본 생성자
public class Article {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;

private String subject;

private String content;

private LocalDateTime createDate;

@JsonBackReference
@ManyToOne
private User user;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.gdsc.blog.article.repository;

import com.gdsc.blog.article.entity.Article;
import org.springframework.data.jpa.repository.JpaRepository;


public interface ArticleRepository extends JpaRepository<Article, Long> {
}
74 changes: 74 additions & 0 deletions src/main/java/com/gdsc/blog/article/service/ArticleService.java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public Article getArticle(Long id){
    List<Article> Allarticle = articleRepository.findAll();

    for(Article article: Allarticle){
        if(article.getIdx() == id){
            return article;
        }
    }
    throw new RuntimeException("No article");
}

위와 같은 코드는

Repository에 findbyUserIdx 메소드를 생성해주세요
Jpa Pallete를 활용하면 편하게 메소드를 생성할 수 있어요

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.gdsc.blog.article.service;

import com.gdsc.blog.article.dto.UpdateDto;
import com.gdsc.blog.article.entity.Article;
import com.gdsc.blog.article.repository.ArticleRepository;
import com.gdsc.blog.user.entity.User;
import com.gdsc.blog.user.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@RequiredArgsConstructor
@Service
@Slf4j
public class ArticleService {

private final ArticleRepository articleRepository;

public Article create(Article article, User user){
article.setCreateDate(LocalDateTime.now());
article.setUser(user);
return articleRepository.save(article);
}

public Article getArticle(Long id){
List<Article> Allarticle = articleRepository.findAll();

for(Article article: Allarticle){
if(article.getIdx() == id){
return article;
}
}
throw new RuntimeException("No article");
}

public List<Article> myAllArticle(User user){
return user.getArticleList();
}

public List<Article> getAllAriticle(){
return articleRepository.findAll();
}

public Article updateArticle(Long idx, UpdateDto newarticle, User user){
List<Article> Allarticle = articleRepository.findAll();

for(Article article: Allarticle){
if(article.getIdx() == idx){
article.setSubject(newarticle.getSubject());
article.setContent(newarticle.getContent());
return articleRepository.save(article);
}
}
throw new RuntimeException("No article");
}

public void deleteArticle(Long id, User user){
// find article by id
Article article = articleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Article not found"));

// check if the logged-in user is the owner of the article
if (!article.getUser().getIdx().equals(user.getIdx())) {
throw new RuntimeException("Permission denied");
}

// delete the article
articleRepository.delete(article);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/v3/api-docs/**", "/api/user/signin", "/api/user/signup")
.permitAll().anyRequest().authenticated();

http.csrf().disable();
http.csrf(csrf->csrf.disable());

http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider));

return http.build();
}
}
16 changes: 12 additions & 4 deletions src/main/java/com/gdsc/blog/security/jwt/JwtTokenFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.IOException;


import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

Expand All @@ -16,6 +17,7 @@

// We should use OncePerRequestFilter since we are doing a database call, there is no point in doing
// this more than once
@Slf4j
public class JwtTokenFilter extends OncePerRequestFilter {

private JwtTokenProvider jwtTokenProvider;
Expand All @@ -25,23 +27,29 @@ public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
}

@Override

protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
logger.info("doFilterInternal 시작");
String token = jwtTokenProvider.resolveToken(httpServletRequest);
try {
logger.info("토큰 확인: " + token);
if (token != null && jwtTokenProvider.validateToken(token)) {
logger.info("토큰 검증 통과");
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception ex) {
// this is very important, since it guarantees the user is not authenticated at all
logger.error("예외 발생: " + ex.getMessage());
SecurityContextHolder.clearContext();
httpServletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
logger.info("sendError 호출 후");
return;
}

logger.info("filterChain.doFilter 호출 전");
filterChain.doFilter(httpServletRequest, httpServletResponse);
logger.info("filterChain.doFilter 호출 후");
}

}
Loading