스프링부트 공부기록(32) - API 서비스 만들기
my code archive
article thumbnail
반응형
  • JSON을 이용하는 API 서버를 어떻게 만들어야 하는지 살펴본다.
  • API 서버 구성 시 가장 주의해야 하는 부분이 보안과 인증에 대한 문제이므로 스프링 시큐리티를 사용해서 처리하고 인증 처리는 JWT를 사용하도록 한다.

🤍간단한 Note를 작성하고 이를 이용하는 API 서버 프로젝트 구현하기

1. Note 엔티티 작성

 

2. NoteRepository 인터페이스 작성

 

3. DTO 작성

 

3. 서비스 계층 작성 

  • 등록, 수정, 삭제, 조회 처리
public interface NoteService {

    Long register(NoteDTO noteDTO);

    NoteDTO get(Long num);

    void modify(NoteDTO noteDTO);

    void remove(Long num);

    List<NoteDTO> getAllWithWriter(String writerEmail);

    default Note dtoToEntity(NoteDTO noteDTO){
        Note note = Note.builder()
                .num(noteDTO.getNum())
                .title(noteDTO.getTitle())
                .content(noteDTO.getContent())
                .writer(ClubMember.builder().email(noteDTO.getWriterEmail()).build())
                .build();
        return note;
    }

    default NoteDTO entityToDTO(Note note){
        NoteDTO noteDTO = NoteDTO.builder()
                .num(note.getNum())
                .title(note.getTitle())
                .content(note.getContent())
                .writerEmail(note.getWriter().getEmail())
                .regDate(note.getRegDate())
                .modDate(note.getModDate())
                .build();

        return noteDTO;
    }

 

4. 컨트롤러 작성

 

4-1. 등록 테스트

 

  • 포스트맨으로 등록 테스트를 해보면 아래와 같이 글번호(Note num)가 출력된다.

 

4-2. 특정 번호 Note 확인하기

  • 포스트맨으로 'notes/1'과 같이 요청하면 1번 글을 조회할 수 있다.

 

4-3. 특정 회원의 모든 Note 확인하기

  • getList는 파라미터로 전달되는 이메일주소로 해당 회원이 작성한 모든 Note에 대한 정보를 JSON으로 반환함.

 

4-4. Note 삭제 테스트

  • DELETE 방식으로 처리함.

 

4-5. Note 수정 테스트

  • 수정할 때에는 등록과 달리 JSON 데이터에 num 속성을 포함해서 전송함.

🤍API 서버 필터

  • 위에서는 /notes/라는 경로로 아무런 제약없이 접근이 가능했지만 이번에는 인증을 거친 사용자에 한해서만 서비스를 제공하고자함.
  • 외부에서 API를 호출 시에는 주로 인증 정보나 인증 키(key)를 같이 전송해서 처리함.
  • 이러한 키를 토큰(token)이라는 용어로 사용하기도 하는데 이번 프로젝트에서는 JWT(JSON Web Token)을 사용하도록 함.
  • 특정 API를 호출할 때 반드시 인증에 사용할 토큰을 같이 전송하고 서버에서는 검증함 -> 필터(filter)

1. OncePerRequestFilter 사용해보기

 

ApiCheckFilter 클래스를 추가하고 SecurityConfig 클래스에 빈을 등록한다.

@Bean
    public ApiCheckFilter apiCheckFilter(){
        return new ApiCheckFilter();
    }

적용 후 '/sample/all'과 같이 permitAll()을 적용한 URL 혹은 기존에 GET 방식으로 접근할 수 있는 URL을 호출하면 아래와 같은 로그가 기록되는 것을 확인할 수 있다.

 

2. 이번에는 '/notes'로 시작하는 경우에만 동작하도록 수정해 본다.

@Bean
    public ApiCheckFilter apiCheckFilter(){
        return new ApiCheckFilter("/notes/**/*");
    }
public class ApiCheckFilter extends OncePerRequestFilter {

    private AntPathMatcher antPathMatcher;
    private String pattern;

    public ApiCheckFilter(String pattern){
        this.antPathMatcher = new AntPathMatcher();
        this.pattern=pattern;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        log.info("REQUESTURI: "+request.getRequestURI());
        log.info(antPathMatcher.match(pattern, request.getRequestURI()));

        if(antPathMatcher.match(pattern, request.getRequestURI())){
            log.info("ApiCheckFilter............................");
            log.info("ApiCheckFilter............................");
            log.info("ApiCheckFilter............................");
            
            return;
        }
        filterChain.doFilter(request, response);
    }
 }

 

변경 후 프로젝트를 실행하면 '/notes/'로 시작하는 경로에만 로그가 출력되는 것을 확인할 수 있다.

 

🤍Authorization 헤더 처리

  • 특정 API를 호출하는 클라이언트는 다른 서버 및 애플리케이션으로 실행되기 때문에 쿠키나 세션을 활용할 수 없음.
  • 이때 Authorization 헤더를 사용해서 헤더 메시지에 특별한 값을 지정해서 전송할 수 있음.

1. ApiCheckFilter 작성

 

2. 커스텀 헤더를 Authorization, 12345678로 추가 후 요청하면 정상적인 결과가 나온다.

 

그런데 Authorization이 없어도 별다른 에러가 발생하지 않는다. 이때에는 AuthenticationManager를 이용해서 인증 처리를 해주면 된다.

3. ApiLoginFailHandler 작성 후

 

4. 잘못된 아이디, 로그인을 입력하면 아래와 같이 에러 메시지가 전송되는 것을 확인할 수 있다.

 

5. 인증 성공 처리

 

🤍JWT 토큰 생성 / 검증

  • 인증 성공 후에는 적절한 데이터를 만들어 전송해 주어야 함 -> JWT(JSON Web Token) 이용
  • 인증이 성공한 사용자에게 특수한 문자열(JWT)을 전송해 주고 API 호출 시에는 이 문자열을 읽어서 해당 Request가 정상적인 요청인지 확인하는 용도로 사용

1. JWT 라이브러리 추가

/jsonbtoken 라이브러리
    implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'

 

2. JWTUtil 클래스 작성

@Log4j2
public class JWTUtil {

    private String secretKey = "zerock12345678";

    //1month
    private long expire = 60 *24 * 30;

    public String generateToken(String content) throws Exception{
        return Jwts.builder()
                .setIssuedAt(new Date())
                .setExpiration(Date.from(ZonedDateTime.now().plusMinutes(expire).toInstant()))
               // .setExpiration(Date.from(ZonedDateTime.now().plusSeconds(1).toInstant()))
                .claim("sub",content)
                .signWith(SignatureAlgorithm.HS256,secretKey.getBytes("UTF-8"))
                .compact();
    }
    public String validateAndExtract(String tokenStr) throws Exception{
        String contentValue = null;

        try {
            DefaultJws defultJws = (DefaultJws) Jwts.parser()
                    .setSigningKey(secretKey.getBytes("UTF-8"))
                    .parseClaimsJws(tokenStr);

            log.info(defultJws);
            log.info(defultJws.getBody().getClass());

            DefaultClaims claims = (DefaultClaims) defultJws.getBody();

            log.info("-------------------------------");

            contentValue = claims.getSubject();
        }catch (Exception e){
            e.printStackTrace();
            log.error(e.getMessage());
            contentValue = null;
        }
        return contentValue;
    }
}

 

3. 테스트 코드 작성

public class JWTTests {

    private JWTUtil jwtUtil;

    @BeforeEach
    public void testBefore(){
        System.out.println("testBefore.............");
        jwtUtil = new JWTUtil();
    }

    @Test
    public void testEncode() throws Exception{
        String email = "user95@zerock.org";
        String str = jwtUtil.generateToken(email);
        System.out.println(str);
    }

    @Test
    public void testValidate() throws Exception{
        String email = "user95@zerock.org";
        String str = jwtUtil.generateToken(email);
        Thread.sleep(5000);
        String resultEmail = jwtUtil.validateAndExtract(str);
        System.out.println(resultEmail);
    }
}

 

4. 테스트 통과 후 만들어지는 JWT 문자열을 확인할 수 있음.

 

5. jwt.io 사이트에 접속 후 JWTUtil 클래스에 비밀키로 지정해놓은 'zerock12345678'을 입력하면 정상적인 JWT 문자열인지 검증할 수 있다.

6. Filter에 JWT를 적용하기 위해 먼저 ApiLoginFilter 클래스를 JWTUtil을 생성자로 주입받는 구조로 수정한다.

 

7. SecurityConfig 클래스도 수정한다.

8. 프로젝트를 실행하고 브라우저에 아래와 같이 입력하면 JWT가 발행된 것을 확인할 수 있다.

반응형
profile

my code archive

@얼레벌레 개발자👩‍💻

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

반응형