설명도 귀찮다. 바로 시작
책을 구매해서 내용들 따라하려 했지만 책에는 SpringSecurity의 버전이
Java 11을 기준으로 작성된 것인데다가 SpringSecurity3 이후로는 작성법이 변경된 관계로 방법을 찾느라 시간이 좀 걸렸다.
물론 해당 프로젝트는 java 17을 기준으로 하고 있다.
스프링 부트 프로젝트 설정은 인터넷에 널려있으니 찾아서 보면 된다.
벡엔드 구현
프로젝트 구조
1 | |
build.gradle
1 | |
보통 데이터베이스 테이블마다 그에 상응하는 엔티티 클래스가 존재한다.
해당 프로젝트는 Todo List를 구현하는 것을 목표로 한다.
그리고 모델과 엔티티를 한 클래스에 구현한다. 복잡한 어플리케이션이 아니라서
모델(Model) : 주로 데이터베이스 조작과 관련된 로직을 처리하는 부분
엔티티(Entity) : 데이터베이스의 테이블과 매핑되는 클래스
이러한 용어들은 다양한 프레임워크나 라이브러리에서 다르게 사용될 수 있으므로, 문맥에 주의하여 사용해야 합니다.
TodoEntity.java
1 | |
서비스가 요청을 처리하고 클라이언트로 반환할 때, 모델 자체를 리턴하는 경우는 별로 없다. 일반적은로는 DTO를 사용하는데, 우선은 비즈니스 로직을 캡슐화하기 위함이다.
모델은 테이터베이스 테이블구조와 매우 유사하다. 모델이 가진 필드와 테이블의 스키마와 비슷할 확률이 높다. 대부분의 회사들은 외부인 자사의 데이터베이스의 스키마를 아는 것을 원치 않을 것이다. 그리고 클라이언트가 필요한 정보를 모두 포함하지 않는 경우가 있기 때문이다. 예를 들어 서비스 실행도중 에러가 나면 에러 메시지는 어디에 포함해야 할 까?
모델은 서비스 로직과는 관련이 없기 때문에 모델에 담기는 애매해서 DTO를 사용한다.
1 | |
이제 http 응답으로 사용할 dto가 필요하다.
1 | |
다음은 ResponseEntity를 리턴해주는 Controller다.
Todo Application: TodoController 설명
이 포스트에서는 Spring Boot로 작성된 Todo Application의 TodoController 클래스에 대해 설명합니다. 이 컨트롤러는 기본적인 CRUD (Create, Read, Update, Delete) 작업을 수행하며, TodoService를 이용해 데이터베이스와 상호작용합니다.
패키지 및 임포트
1 | |
TodoController는 RestController로, HTTP 요청을 처리하는 여러 메서드를 포함하고 있습니다. 각 메서드는 Todo 리스트의 항목을 생성, 읽기, 업데이트, 삭제하는 기능을 담당합니다.
클래스 정의 및 서비스 자동 주입
1 | |
/test 엔드포인트는 서비스의 테스트 메서드를 호출하고 결과를 반환합니다. 이 메서드는 서비스가 제대로 작동하는지 확인하는 데 사용됩니다.
Todo 리스트 조회
1 | |
Todo 생성
1 | |
Todo 삭제
1 | |
TodoService 설명
1 | |
TodoRepository 설명
영속성을 관리하기 위한 패키지를 만들어 넣었다.
1 | |
프론트엔드 구현
해당 내용은 node.js, vscode 환경에서 실행되었다.
Todo 리스트, Todo 삭제, Todo 수정
Todo.js
1 | |
Todo 추가
AddTodo.js
1 | |
화면 구성
App.js
1 | |
서비스 통합
! CORS(Cross-Origin Resource Sharing)
WenMvcConfig.java
1 | |
ApiService.js
백엔드로 요청을 보낼 때 사용하기 위한 유틸리티 함수
1 | |
App.js
1 | |
REST API 인증 기법
Basic 인증
사용자ID와 비밀번호를 base64로 인코딩한 뒤, 헤더에 담아 서버에게 보내는 인증 방식

- 클라이언트가 요청을 보낸다.
- 서버가 401(Unauthorized) 응답과 함께 WWW-Authenticate 헤더에 클라이언트가 어떻게 인증을 해야할 지를 담아 보낸다.
- 클라이언트는 WWW-Authenticate에 담긴 인증 방식대로 Authorization헤더에 인증 정보를 담아 다시 요청을 보낸다.
- 서버는 클라이언트의 인증 정보를 보고 200(OK) or 403(Forbidden) 응답을 한다.
단점
📌 추가적인 보안 필요 HTTP Request를 보면 누구나 유저의 ID & 패스워드를 알 수 있다. 그렇기에 HTTP보다 보안이 강화된 HTTPS와 함께 쓰이는 것이 일반적이다. (HTTPS는 패킷이 탈취되는 것을 방지한다. MITM)
📌 서버 성능 저하 또한, Basic인증의 경우 서버가 요청을 받을 때마다 클라이언트가 보낸 유저ID&비밀번호와 일치하는 유저를 DB에서 매번 찾아야한다. 그렇기에 DB에 저장된 유저가 많을 수록, 트래픽이 많을 수록 서버 성능이 저하된다.
📌 정교한 권한 설정이 어려움 Basic인증 방법으로 정교하게 사용자 권한을 제어하려면 추가적인 작업이 필요하다고 한다. 솔직히 안해봐서 잘 모르겠다.
Bearer 인증
토큰은 그냥 문자열이다. 예를 들자면 abcde 이것도 토큰이 될 수 있다. 하지만 실제로 사용되는 토큰은 외울 수 없도록 만들어져 있다. 명시적으로는 Authorization: Bearer <TOKEN>으로 사용한다.

토큰은 최초 로그인 시 서버가 UUID로 토큰을 작성해 넘긴하다고 하자. 그러면 서버는 이 토큰을 위의 그림 처럼 토큰을 생성해 인증 서버를 통해 저장해야 한다. 그리고 요청을 받을 떄마다 헤더의 토큰을 서버의 토큰과 비교해 클라이언트를 입증할 수 있다.
우선 Basic 토큰과는 달리 비밀번호를 매번 네트워크를 통해 전송해야 할 필요가 없다 -> 보안 측면에서 좀 더 안전. 또 토큰은 서버가 마음대로 생성할 수 있으므로 사용자의 인가 정보또는 유효기간을 정해 관리할 수 있다. 또 디바이스 마다 다르개 생성해 줄 수도 있다.
하지만 아직도 스케일 문제가 남아있다. (앞서 말한 Basic의 문제와 동일)
JWT(JOSN Web Token)
서버에 의해 전자 서명된 토큰을 이용하면 인증으로 인한 스케일 문제 해결 가능
JWT 토큰의 구성 : {header}.{payload}.{signature}

HEADER
일반적으로 토큰의 타입과 토큰이 어떤 암호 방식을 사용했는지를 담고있다.
PAYLOAD
유저 네임등 인증에 필요한 정보들을 담고있다.
SIGNATURE
서버의 secret key를 사용해 서명한 내용을 담고있다. 서버가 서명한 정보이므로, 토큰의 값이 중간에 변경되는 것을 검증할 수 있다. Encoded 된 HEADER, PAYLOAD와 secret key를 암호화한 정보를 담고있다. 암호화 알고리즘은 HEADER에 적힌 방법대로 수행한다.

-
클라이언트가 로그인 요청을 보낸다. 유저정보를 바탕으로
{header}.{payload}작성 생성된{header}.{payload}를 secret키로 전자 서명 -> 결과 :X,{header}.{payload}.X를 Base64로 인코딩 후 반환 - 서버는 인증 과정을 거친 뒤, 토큰을 생성한다.
- 서버는 토큰을 응답에 담아 보낸다.
- 클라이언트는 토큰을 브라우저에 저장한다.(localStorage나 쿠키 등)
- 클라이언트는 이후 요청을 보낼 때 마다 요청 헤더에 토큰을 담아 보낸다.
유정게서 받은
<Token>을 Base64로 디코딩 -> 결과 :{header}.{payload}.X앞부분{header}.{payload}를 떼서 secret키로 전자 서명 -> 결과 :Y디코딩된 토큰의 마지막 부분X와 방금 전자 서명한 결과Y를 비교X = Y인 경우 서명이 일치하므로 검증 완료 - 서버는 받은 토큰을 해석해 유저를 검증한다.
User 레이어 구현
사용자를 관리하기 위해서는 유저에 관련된 모델, 서비스, 리포지터리, 컨트롤러가 필요하다.
UserEntity.java
1 | |
UserRepository.java
1 | |
UserService.java
1 | |
UserDTO.java
UserController 구현하기 전에 현재 유저를 가져오는 기능 구현에 필요한 부분
1 | |
UserController.java
1 | |
해당 사진은 PostMan에서 실행한 결과이다.


결과는 잘 나오지만 문제가 있다. 딱 로그인만 되고 로그인 상태가 유지되지 않는다. 그리고 로그인 여부 자체를 확인하지 않는다. 마지막으로 비밀번호를 암호화 하지 않는다. (JWT는 반드시 https와 함께 사용해야한다. 나중에 설명)
Spring Security 통합
JWT 인증 로직 구현
TokenProvider.java
1 | |
UserController의 /signin에서 토큰 생성 및 반환
1 |
|

- Base64로 디코딩한 토큰
1
2
3
4
5
6
7
8{"alg":"HS512"} { "sub":"40285be790f462040190f46235620000", "iss":"demo app", "iat":1722086999, "exp":1722173399 } y{ F8^C]\'D_5D&Ӛ3@-)Ccf7>_O:> // 의미없는 값
스프링 시큐리티와 서블릿 필터

위의 그림처럼 API가 실행될 때마다 사용자를 인증해주는 부분을 구현해야 한다. 이 부분은 스프링 시큐리티의 도움을 받아 구현한다.
스프링 시큐리티는 간단히 말하면 서블릿 필터의 집합이다. 서블릿 필터는 서블릿 실행 전에 실행되는 클래스들이다. 스프링이 구현하는 서블릿은 바로 디스패처 서블릿이다. 서블릿 필터는 디스패처 서블릿이 실행되기전에 항상 실행된다.
서블릿 필터는 구현된 로직에 따라 원치 않는 http 요청을 걸러 낼 수 있다. 필터를 거쳐 살아남은 요청만 디스패쳐 서블릿으로 넘어와 우리 컨트롤러에서 실행된다.
인증 완료시 다음 서블릿 필터를 실행, 아니라면 HttpServletResponse의 status를 403 Forbidden으로 바꾼다. 예외의 경우 디스패쳐 서블릿을 실행하지 않고 리턴될 것이다.
스프링 부트를 사용하지 않는 다면 web.xml과 같은 설정 파일에 이 필터를 어느 경로(예, /todo)에 적용해야 하는지 알려줘야 한다.
-> 개발자인 우리는 서블릿 필터를 구현하고 서블릿 컨테이너가 실행하도록 설정만 해주면 끝!
JWT를 이용한 인증 구현
JwtAuthenticationFilter.java
1 | |
스프링 시큐리티 설정
WebSecurityConfig.java
1 | |
-
스프링 시큐리티 필터 로그
2024-07-27T22:29:44.406+09:00 INFO 23460 --- [demo] [main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [ org.springframework.security.web.session.DisableEncodeUrlFilter@60317de8, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@363e2009, org.springframework.security.web.context.SecurityContextHolderFilter@31885b4b, org.springframework.security.web.header.HeaderWriterFilter@7a57c5d9, org.springframework.web.filter.CorsFilter@7b55fc83, com.example.demo.security.JwtAuthenticationFilter@609319c3, org.springframework.security.web.authentication.logout.LogoutFilter@106ac5f4, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@43c64d6f, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6b247ef6, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3615f8d9, org.springframework.security.web.session.SessionManagementFilter@4f93a8f6, org.springframework.security.web.access.ExceptionTranslationFilter@3bdc8975, org.springframework.security.web.access.intercept.AuthorizationFilter@15af06f]

패스워드 암호화 로직 구현
UserService.java 수정
1 | |
UserController.java 수정
1 |
|