본문 바로가기
프로그래밍/Spring Boot

[ Spring boot + JPA + MySQL ] 원모 싸이버 스쿨 블로그 API 서버

by yonmoyonmo 2021. 7. 16.

서비스 구성도

원모싸이버스쿨 마이크로 서비씨쓰(Micro Services)의 첫 번째 API 서비스입니다.

프론트엔드를 무엇으로 짜던 상관 안하는 무적의 블로깅 API 서비스입니다.(야매 RESTful API)

저는 프론트엔드를 Next.js로 짰습니다.

유저 인증과 이미지 저장 등은 다른 앱에 맡기고 오로지 블로그 비지니스 로직에만 초점을 맞춘 서버앱입니다!

마이크로 서비스가 대세라 함 이렇게 해 봤는데 이게 마이크로 서비스 맞나요? 더 공부해 보기로 하고!

대충 일맥상통 하긴 할듯!!

https://github.com/yonmoyonmo/wcs-blog-api-app

 

yonmoyonmo/wcs-blog-api-app

wonmo cyber school blog api server application. Contribute to yonmoyonmo/wcs-blog-api-app development by creating an account on GitHub.

github.com


원모 싸이버 스쿨 블로그 API 서버

간단 설명

기능 : 그냥 적당히 블로그 비스무리한 것

  • 인증서버와 설정을 공유하는 JWT를 파싱하여 유저 정보를 얻어서 저장
  • 유저 별, 카테고리 별로 구분된 블로그 포스팅 CRUD
  • 포스트마다 댓글 CRUD
  • 유저만의 프로필 관리
  • 카테고리, 공지사항을 관리하는 관리자 기능

대충 이정도 입니다.

MySQL을 썼습니다.

JPA를 썼습니다.

대충 중요해 보이는 코드만 살짝 살펴 볼까유?


maven 디펜던시 입니다.

pom.xml

...생략...
	<dependencies>
    	# JPA
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
        
        # web
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
        # MySQL
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
        
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
        # JWT
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>
        
        # 기타 등등
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-json</artifactId>
			<version>2.5.0</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
    </dependencies>
...생략...

기타 등등에 있는 것들은 무지성으로 추가해서 어디에 어떻게 쓰기 위해 추가했는지 잊었습니다. 흠


application.properties

sever.address=localhost
server.port=8000

spring.datasource.url=jdbc:mysql://데이터베이스호스티:포트/데이터베이스이름?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=데이터베이스유저이름
spring.datasource.password=비번

spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=false # true 하면 SQL 다 보임. 개발할 때 켜놓으면 짱임
# JPA ddl에 관한 것은 설명하지 않겠읍니다.
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
# sql 보이는거 이쁘게 만들기
spring.jpa.properties.hibernate.format_sql=true

jwt.secretKey=원모만의비밀키
jwt.expiration=토큰유효기간
jwt.adminWonmo=어드민원모를위한키

요렇게 데이터 소스 속성이랑 JPA 세팅 정도가 다 입니다.

JWT 비밀 키 같은 경우는 인증서버와 공유하는 부분이기 때문에 나중에 Configure server를 만들어서 이것저것 설정 관련 된 것들의 관리포인트를 하나로 두면 좋을 것 같습니다.

application.properties에 입력한 속성 값을 자바 코드로 쓸 수 있도록 해야합니다.

@ConfigurationProperties(prefix = "jwt") : prefix 는 application.properties에 기입한 항목의 제일 첫 머릿말을 입력

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
    private String secretKey;
    private String expiration;
    private String adminWonmo;

    public String getAdminWonmo() {
        return adminWonmo;
    }

    public void setAdminWonmo(String adminWonmo) {
        this.adminWonmo = adminWonmo;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public String getExpiration() {
        return expiration;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public void setExpiration(String expiration) {
        this.expiration = expiration;
    }
}

요렇게 하고 난 뒤 스프링 부트 어플리케이션 메인 클래스에

@EnableConfigurationProperties(JwtProperties.class)

어노테이션을 달아서 코딩한 것을 사용할 수 있도록 합니다.

@SpringBootApplication
@EnableConfigurationProperties(JwtProperties.class)
public class WcsBlogApiApplication {

	public static void main(String[] args) {
		SpringApplication.run(WcsBlogApiApplication.class, args);
	}

}

기본적으로 스프링 프레임워크 스타일의 계층화 패턴으로 짰습니다.

컨트롤러-서비스-리포지토리
---엔티티(도메인모델)----

요런 너낌입니다. 

절반 정도 RESTful 한 API입니다.(말이 안되는 소리이긴 하지만 아무튼 그렇습니다.)

post, comment(댓글), admin(카테고리, 공지 등), user 네 가지로 구분한 Controller를 4개 사용했습니다.

아래는 그 중 하나 PostController입니다. 나머지 코드는 저의 깃허브 리포지또리(https://github.com/yonmoyonmo/wcs-blog-api-app)에서 확인할 수 있습니다!

PostController

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import wonmocyberschool.wcsblogapi.entity.Post;
import wonmocyberschool.wcsblogapi.payload.Response;
import wonmocyberschool.wcsblogapi.service.PostService;
import wonmocyberschool.wcsblogapi.util.JwtUtil;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api")
public class PostController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final PostService postService;
    private final JwtUtil jwtUtil;
    @Autowired
    public PostController(PostService postService,
                          JwtUtil jwtUtil){
        this.postService = postService;
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/post")
    public ResponseEntity<Response> createPost(HttpServletRequest request,
            @RequestBody Post post){

        logger.info("at : /post : POST");

        Response response = new Response();
        String token = request.getHeader("Authorization");
        String email = jwtUtil.getUserEmailFromToken(token);
        if(post.getCategoryId() == null){
            logger.info("category id 없음");
            response.setMessage("category id 없음");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.BAD_REQUEST);
        }
        if(postService.savePost(post, email)){
            response.setMessage("post added");
            response.setSuccess(true);
            return new ResponseEntity<Response>(response, HttpStatus.OK);
        }else{
            response.setMessage("failed");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @PutMapping("/post")
    public ResponseEntity<Response> updatePost(HttpServletRequest request,
                                               @RequestBody Post post){
        logger.info("at : /post : PUT");

        String token = request.getHeader("Authorization");
        String email = jwtUtil.getUserEmailFromToken(token);

        Response response = new Response();

        if(post.getId() == null){
            response.setMessage("no post id");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.BAD_REQUEST);
        }
        if(!postService.updatePost(email, post)){
            response.setMessage("something went wrong");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }else{
            response.setMessage("post updated");
            response.setSuccess(true);
            return new ResponseEntity<Response>(response, HttpStatus.OK);
        }

    }

    @DeleteMapping("/post")
    public ResponseEntity<Response> deletePost(HttpServletRequest request,
                                               @RequestBody Post post){
        logger.info("at : /post : DELETE");

        String token = request.getHeader("Authorization");
        String email = jwtUtil.getUserEmailFromToken(token);

        Response response = new Response();
        Long postId = post.getId();
        if(postId == null){
            response.setMessage("there's no post id");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.BAD_REQUEST);
        }
        if(!postService.deleteByPostId(email, postId)){
            response.setMessage("delete failed");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }else{
            response.setMessage("delete success");
            response.setSuccess(true);
            return new ResponseEntity<Response>(response, HttpStatus.OK);
        }
    }

    @GetMapping("/public/post/category/{categoryId}")
    public ResponseEntity<Response> getPostsByCategory(@PathVariable("categoryId") Long categoryId){
        logger.info("at : /public/post/category/{categoryId} : GET");

        Response response = new Response();

        List<Post> posts = postService.getPostsByCategory(categoryId);
        if(posts.isEmpty()){
            response.setMessage("this category has no post");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.NOT_FOUND);
        }else if(posts == null){
            response.setMessage("service went wrong");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        } else{
            response.setMessage("OK");
            response.setSuccess(true);
            response.setData(posts);
            return new ResponseEntity<Response>(response, HttpStatus.OK);
        }
    }

    @GetMapping("/public/post/{id}")
    public ResponseEntity<Response> getOnePost(@PathVariable("id") Long postId){
        logger.info("at : /public/post/{id} : GET");

        Response response = new Response();

        Optional<Post> postOptional = postService.getPostById(postId);
        if(postOptional.isPresent()){
            response.setMessage("OK");
            response.setSuccess(true);
            response.setData(postOptional.get());
            return new ResponseEntity<Response>(response, HttpStatus.OK);
        }else if(postOptional == null){
            response.setMessage("service went wrong");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }else{
            response.setMessage("there's no post with id : " + postId);
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.NOT_FOUND);
        }
    }

    @GetMapping("/public/post/tag/{tagId}")
    public ResponseEntity<Response> getPostsByTagId(
            @PathVariable("tagId") Long tagId){

        logger.info("at : /public/post/tag/{tagId} : GET");

        Response response = new Response();
        List<Post> posts = postService.getPostsByTagId(tagId);
        if(posts.isEmpty()){
            response.setMessage("no posts");
            response.setSuccess(true);
            return new ResponseEntity<Response>(response, HttpStatus.OK);
        }else if(posts == null){
            response.setMessage("something went wrong");
            response.setSuccess(false);
            return new ResponseEntity<Response>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }else{
            response.setMessage("OK");
            response.setSuccess(true);
            response.setData(posts);
            return new ResponseEntity<Response>(response, HttpStatus.OK);
        }
    }

}

 

딱히 특별할 것 없는, 흔한 Spring RestController입니다.

JwtUtil class는 어떻게 되어 있는지 함 보까요?

JwtUtil

import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import wonmocyberschool.wcsblogapi.config.JwtProperties;
import wonmocyberschool.wcsblogapi.entity.Admin;
import wonmocyberschool.wcsblogapi.entity.BlogUser;

import java.util.Calendar;
import java.util.Date;


import static java.lang.Integer.parseInt;

@Component
public class JwtUtil {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private JwtProperties jwtProperties;
    public JwtUtil(JwtProperties jwtProperties){
        this.jwtProperties = jwtProperties;
    }

    public String createAdminToken(Admin admin) {

        Date expDate = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(expDate);
        int exp = parseInt(jwtProperties.getExpiration());
        calendar.add(Calendar.SECOND, exp);
        expDate = calendar.getTime();
        logger.info(expDate.toString());

        Claims claim = Jwts.claims();
        claim.put("email", admin.getAdminEmail());
        claim.put("username", admin.getUsername());

        return Jwts.builder()
                .setClaims(claim)
                .setSubject(Long.toString(admin.getId()))
                .setIssuedAt(new Date())
                .setExpiration(expDate)
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecretKey())
                .compact();
    }

    public String createBlogUserToken(BlogUser blogUser) {
        Date expDate = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(expDate);
        int exp = parseInt(jwtProperties.getExpiration());
        calendar.add(Calendar.SECOND, exp);
        expDate = calendar.getTime();
        logger.info(expDate.toString());

        Claims claim = Jwts.claims();
        claim.put("email", blogUser.getEmail());
        claim.put("username", blogUser.getUsername());
        return Jwts.builder()
                .setClaims(claim)
                .setSubject(Long.toString(blogUser.getId()))
                .setIssuedAt(new Date())
                .setExpiration(expDate)
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecretKey())
                .compact();
    }


    public String getUserEmailFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey())
                .parseClaimsJws(token)
                .getBody();
        String email = claims.get("email").toString();
        return email;
    }
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey())
                .parseClaimsJws(token)
                .getBody();
        String username = claims.get("username").toString();
        return username;
    }

    public boolean validateToken(String authToken) {
        try {
            Claims claims = Jwts.parser().setSigningKey(jwtProperties.getSecretKey()).parseClaimsJws(authToken).getBody();
            logger.info(claims.get("email").toString());
            return true;
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty.");
        } catch(Exception e){
            logger.error(e.getMessage());
        }
        return false;
    }
}

요런 유틸성 코드들은 컴포넌트화 하여 쓰면 좋은 것 같습니다.


비지니스 로직을 맡는 Service 계층 역시 별 다를 것 없는 스프링 스타일입니다. 간단히 PostService만 함 가져와 보겠습니다.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import wonmocyberschool.wcsblogapi.entity.*;
import wonmocyberschool.wcsblogapi.repository.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
@Transactional
public class PostService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final PostRepository postRepository;
    private final BlogUserRepository blogUserRepository;
    private final ImageRepository imageRepository;
    private final TagRepository tagRepository;
    private final CategoryRepository categoryRepository;
    private final PostTagRepository postTagRepository;
    @Autowired
    public PostService(PostRepository postRepository,
                       BlogUserRepository blogUserRepository,
                       ImageRepository imageRepository,
                       TagRepository tagRepository,
                       CategoryRepository categoryRepository,
                       PostTagRepository postTagRepository){
        this.postRepository = postRepository;
        this.blogUserRepository = blogUserRepository;
        this.imageRepository = imageRepository;
        this.tagRepository = tagRepository;
        this.categoryRepository = categoryRepository;
        this.postTagRepository = postTagRepository;
    }

    public List<Post> getPostsByCategory(Long categoryId){
        Optional<Category> category = categoryRepository.findById(categoryId);
        if(category.isPresent()) {
            try {
                return postRepository.findAllByCategory(category.get());
            } catch (Exception e) {
                logger.error(e.getMessage() + " : getPostsByCate : "+e);
                return null;
            }
        }else return null;
    }

    public Optional<Post> getPostById(Long postId){
        try{
            return postRepository.findById(postId);
        }catch (Exception e){
            logger.error(e.getMessage() + " : getPostById : "+e);
            return null;
        }
    }

    public List<Post> getPostsByTagId(Long tagId){
        Optional<Tag> tagOptional = tagRepository.findById(tagId);
        List<Post> posts = new ArrayList<>();
        if(tagOptional.isPresent()){
            try {
                Tag tag = tagOptional.get();
                List<PostTagRelation> postTagRelations = postTagRepository.findAllByTag(tag);
                for (PostTagRelation postTagRelation : postTagRelations) {
                    if(!posts.contains(postTagRelation.getPost())){
                        posts.add(postTagRelation.getPost());
                    }
                }
                return posts;
            }catch (Exception e){
                logger.error(e +" : getPostsByTagId");
                return null;
            }
        }else{
            logger.info("there's no tag with id : " + tagId);
            return posts;
        }
    }

    public boolean deleteByPostId(String email, Long postId){
        Optional<Post> targetPostOptional = postRepository.findById(postId);
        if(!targetPostOptional.isPresent()){
            logger.error("there's no post with id : "+ postId);
            return false;
        }else{

            Post targetPost = targetPostOptional.get();
            //BlogUser user = blogUserRepository.findByEmail(email);

            //요청하신 분과 포스팅 주인님이 같은지 확인
            if(!targetPost.getBlogUser().getEmail().equals(email)){
                logger.error("email : "+email+
                        "\nthis post's user email : "
                        +targetPost.getBlogUser().getEmail());
                return false;
            }
            postRepository.delete(targetPost);
            return true;
        }
    }

    public boolean updatePost(String email, Post post){
        Optional<Post> existingPostOptional = postRepository.findById(post.getId());

        //이럴 필요 없을듯
        //BlogUser user = blogUserRepository.findByEmail(email);

        if(!existingPostOptional.isPresent()){
            logger.error("no post with id : "+post.getId());
            return false;
        }else{
            //제목, 글 내용, 이미지, 태그
            Post existingPost = existingPostOptional.get();

            //요청하신 분과 포스팅 주인님이 같은지 확인
            if(!existingPost.getBlogUser().getEmail().equals(email)){
                logger.error("email : "+email+
                        "\nthis post's user email : "
                        +existingPost.getBlogUser().getEmail());
                return false;
            }

            existingPost.setTitle(post.getTitle());
            existingPost.setText(post.getText());
            postRepository.save(existingPost);

            try {
                postTagRepository.deleteRelatedTags(existingPost);
                imageRepository.deleteRelatedImages(existingPost);
            }catch (Exception e){
                logger.error(e.toString());
            }

            List<String> tagList = post.getTagList();
            List<String> imageUrlList = post.getImageUrlList();

            if(saveTags(tagList, existingPost) && saveImages(imageUrlList, existingPost)){
                postRepository.save(existingPost);
                return true;
            }else{
                logger.error("at update post");
                return false;
            }

        }

    }

    public boolean savePost(Post post, String email){
        //인터셉터에서 검증한 토큰이므로 이메일로 찾으면 백퍼 있음
        BlogUser blogUser = blogUserRepository.findByEmail(email);
        if(blogUser == null){
            logger.error("오우시발");
            return false;
        }
        Optional<Category> categoryOptional = categoryRepository.findById(post.getCategoryId());
        Category category = categoryOptional.orElse(null);
        post.setBlogUser(blogUser);
        if(category == null){
            logger.error("no category");
            return false;
        }
        post.setCategory(category);
        post.setCreatedTime(new Date());
        postRepository.save(post);

        //이미지와 태그를 임시 필드에서 빼가지고 영속화해야함...
        List<String> tagList = post.getTagList();
        List<String> imageUrlList = post.getImageUrlList();

        if(saveTags(tagList, post) && saveImages(imageUrlList, post)){
            postRepository.save(post);
            return true;
        }else{
            logger.error("at savePost");
            return false;
        }
    }

    private boolean saveTags(List<String> tagList, Post post){
        try{
            for(String tag : tagList){
                if(tagRepository.existsByTagName(tag)){
                    Tag existingTag = tagRepository.findByTagName(tag);
                    PostTagRelation postTagRelation = new PostTagRelation();
                    postTagRelation.setPost(post);
                    postTagRelation.setTag(existingTag);
                    postTagRepository.save(postTagRelation);
                }else{
                    Tag newTag = new Tag();
                    newTag.setTagName(tag);
                    newTag.setCreatedTime(new Date());
                    tagRepository.save(newTag);

                    PostTagRelation postTagRelation = new PostTagRelation();
                    postTagRelation.setPost(post);
                    postTagRelation.setTag(newTag);
                    postTagRepository.save(postTagRelation);
                }
            }
        }catch (Exception e){
            logger.error(e.getMessage() + " : while saving tags at savePost : " + e);
            return false;
        }
        logger.info("tags are saved");
        return true;
    }

    private boolean saveImages(List<String> imageUrlList, Post post){
        try{
            for(String imageUrl : imageUrlList){
                Image newImage = new Image();
                newImage.setImageURI(imageUrl);
                newImage.setPost(post);
                newImage.setCreatedTime(new Date());
                imageRepository.save(newImage);
            }
        }catch (Exception e){
            logger.error(e.getMessage() + " : while saving images at savePost : "+e);
            return false;
        }
        return true;
    }
}

정말 특별할게 없습니다.


스프링 부트 인터셉터 사용법

설명할게 별로 없어서 스프링 부트 인터셉터 사용법이라도 설명하겠습니다.

어떤 분께서 아주 보기 좋게 잘 만들어 주신 덕에 잘 우려먹고 있는 도식입니다.

이것저것 다 거친 HTTP 요청, 응답을 딱 중간에서 간섭해 주는 역할을 합니다.

저는 인터셉터로 JWT를 검문하는 로직을 만들었습니다.

JwtInterceptor

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import wonmocyberschool.wcsblogapi.entity.BlogUser;
import wonmocyberschool.wcsblogapi.entity.UserProfile;
import wonmocyberschool.wcsblogapi.repository.BlogUserRepository;
import wonmocyberschool.wcsblogapi.repository.UserProfileRepository;
import wonmocyberschool.wcsblogapi.util.JwtUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

public class JwtInterceptor implements HandlerInterceptor {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private final JwtUtil jwtUtil;
    private final BlogUserRepository blogUserRepository;
    private final UserProfileRepository userProfileRepository;

    public JwtInterceptor(JwtUtil jwtUtil,BlogUserRepository blogUserRepository
            ,UserProfileRepository userProfileRepository){
        this.jwtUtil = jwtUtil;
        this.blogUserRepository = blogUserRepository;
        this.userProfileRepository = userProfileRepository;
    }
	//컨트롤러로 가기 전 요청을 처리하려면 preHandele을 오버라이드 하시면 됩니다.
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        if(request.getHeader("Authorization") != null) {
            String token = request.getHeader("Authorization");
            logger.info("token : " + token);
            if(!jwtUtil.validateToken(token)){
                logger.info("token invalid");
                response.setContentType("application/json");
                response.setCharacterEncoding("UTF-8");
                response.getWriter().write("{\"success\":false,\"message\":\"token invalid\"}");
                return false;
            }else{
                String tokenEmail = jwtUtil.getUserEmailFromToken(token);
                if(blogUserRepository.existsByEmail(tokenEmail)){
                    logger.info("logged in : " + tokenEmail);
                }else{
                    String username = jwtUtil.getUsernameFromToken(token);

                    BlogUser newUser = new BlogUser();
                    newUser.setEmail(tokenEmail);
                    newUser.setUsername(username);
                    newUser.setCreatedTime(new Date());

                    UserProfile userProfile = new UserProfile();
                    userProfile.setCreatedTime(new Date());
                    userProfile.setOwner(newUser);

                    blogUserRepository.save(newUser);
                    userProfileRepository.save(userProfile);


                    logger.info("new user has been registered : " + tokenEmail);
                }
                return true;
            }
        }else{
            logger.info(request.getHeader("Authorization"));
            logger.info("no token, 인터셉터가 처리했으니 안심하라구!");
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write("{\"success\":false,\"message\":\"no token\"}");
            return false;
        }
    }
}

요렇게 HandlerInterceptor를 구현한 클래스를 하나 만들고 난 뒤에는 스프링 설정 클래스를 이용해 줍니다.

WebMvcConfigurer를 구현한 클래스입니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import wonmocyberschool.wcsblogapi.entity.BlogUser;
import wonmocyberschool.wcsblogapi.interceptor.AdminInterceptor;
import wonmocyberschool.wcsblogapi.interceptor.JwtInterceptor;
import wonmocyberschool.wcsblogapi.repository.AdminRepository;
import wonmocyberschool.wcsblogapi.repository.BlogUserRepository;
import wonmocyberschool.wcsblogapi.repository.UserProfileRepository;
import wonmocyberschool.wcsblogapi.util.JwtUtil;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private AdminRepository adminRepository;
    @Autowired
    private BlogUserRepository blogUserRepository;
    @Autowired
    private UserProfileRepository userProfileRepository;

	//cors 설정입니다.
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:7000","http://localhost:8000","http://localhost:3000",
                "http://wonmocyberschool.com","https://wonmocyberschool.com")
                .allowedMethods("POST","GET","PUT", "DELETE")
                .allowedHeaders("*")
                .maxAge(3600);
    }

	//구현한 인터셉터를 등록하는 addInterceptors를 오버라이드 합니다.
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	//위에 적어 놓은 인터셉터를 추가합니다. 필요한 파라미터도 같이 넘겨줍니다.
        registry.addInterceptor(new JwtInterceptor(jwtUtil,blogUserRepository,userProfileRepository))
                .addPathPatterns("/api/**") //인터셉터로 간섭할 경로를 설정
                .excludePathPatterns("/api/public/**");//제외할 경로를 설정
                
        registry.addInterceptor(new AdminInterceptor(jwtUtil, adminRepository))
                .addPathPatterns("/admin/**")//이건 다른 인터셉터를 추가하는 부분입니다.
                .excludePathPatterns("/admin/wonmo/**", "/admin/test/oauth/**", "/admin/public/**");
    }

	//Configuration 어노테이션 붙여놓은 김에 대충 여기다 빈 하나 추가한 모습입니다.
    //중요하지 않습니다.
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

이렇게 하시면 인터셉터가 설정한대로 컨트롤러로 넘어가기 전에 리퀘스트를 검문해 줍니다.


요런 식으로 구성된 API 서버 앱입니다.

자세한 코드는 원모싸이버깃허브리포지토리에서!

댓글