전 게시물에서는 Front에서의 요청에 관한 글을 작성했다.
이번 글은 백엔드 서버 연결에 대한 글을 작성할 예정이다.
코드 설명 전 위 그림은 웹 브라우저에서의 요청부터 데이터베이스까지의 흐름을 보여주고 있다.

발표자료의 참고를 받자면
요청 → 필터 → 서블릿 → Controller → Service → Repository → Database → 응답
순서이다.
여기서의 요청은 프론트의 역할이고 앞 게시물에서 설명을 했다.
그 요청이 오는 필터를 보겠다.
JwtAuthenticationFilter 는 Spring Security 와 JWT를 사용하여 인증을 처리하는데 필요한 필터를 구현한 것이다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {}
OncePerRequestFilter 클래스를 상속받아 구현한다. OncePerRequestFilter 클래스는 모든 서블릿 요청당 한 번씩만 실행되도록 보장한다.
doFilterInternal 메소드와 HttpServletRequest 와 HttpServletResponse 인자를 제공한다.
=> 요약하면 OncePerRequestFilter 는 모든 서블릿에 일관된 요청을 처리하기 위해 만들어진 filter다.

토큰을 발급해주는 TokenProvider 객체를 생성해주고
String token = parseBearerToken(request); //아래에 메서드 구현해놓음
log.info("** JwtAuthenticationFilter.java, doFilterInternal(), token 확인=> "+token);
🔍 여기서 parseBearerToken 메서드는 아래와 같다.
private String parseBearerToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
① parseBearerToken(HttpServletRequest request)
parseBearerToken 메서드는 HTTP 요청 객체를 매개변수로 받는다.
② request.getHeader("Authorization")
HTTP 요청의 Authorization 헤더 값을 가져온다.
Bearer 토큰을 포함하고 있음.
③ (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer "))
if문의 조건문. 가져온 헤더값이 null이 아니고 비어있지 않은지 확인하고. / 헤더값이 Bearer로 시작하는지 확인한다.
Bearer 토큰은 Bearer로 시작한다.
④ return bearerToken.substring(7);
Bearer 토큰에서 처음 시작하는 Bearer을 제외한 나머지가 실제 토큰이다. 따라서 이 부분을 제외한 실제 토큰 부분을 추출한다. 그리고 return.
⑤ return null;
만약 위에서의 조건문을 만족하지 않는다면 null 을 반환한다.
Bearer Token
: HTTP 통신에서 사용하는 인증 방식에 Bearer Authentication 을 사용하는 것.
Bearer Authentication 이란?
: HTTP 프로토콜을 통해 클라이언트가 서버에 인증을 요청할 때 사용되는 인증 방식.
토큰 기반의 인증방식 으로, 클라이언트가 서버에 접근할 때 인증 토큰과 함께 제공됨.
서버는 이 토큰을 확인하여 요청에 대한 답을 결정한다.
📍장점: 토큰이 노출되더라도 HTTPS 를 통해 안전하게 통신할 수 있음. 토큰의 만료 기간을 설정하여 보안을 강화할 수 있다.
📍단점: 토큰이 탈취되면 해당 토큰으로 인증된 모든 자원에 대한 접근이 가능해짐. 보안 취약점. 따라서 안전하게 관리하고 보안 강화가 중요해짐.
🖋🖊🖌🖍🖋🖊🖌🖍🖋🖊🖌🖍🖋🖊🖌🖍🖋🖊🖌🖍🖋🖊🖌🖍
Bearer : 지참자, 보유자
-> 토큰을 소지하고 있는 엔터티를 가리킴.
"이 토큰을 나르는(bearer) 사람에게 권한을 부여하시오" 라는 의미.
StringUtils 이란?
: 거의 대부분의 문자열 처리를 수행할 수 있다.
파라미터 값으로 null을 주도라도 NullPointException을 발생시키지 않음.
import org.springframework.util.StringUtils;🖋주요 메서드🖋
hasText() : null, 길이 0, 공백("" or " ") 중 하나라도 있으면 false를 반환함.
hasLength() : null 체크 후, 길이가 0인지 판별한다. ( 공백만 있는 문자열, " " 도 true가 반환되는 점을 주의 )
isEmpty() : null, 길이 0, 공백("" or " ") 중 하나라도 있으면 false를 반환함. (위의 hasText() 권장함)
deleteWhitespace() , trim() : 문자열에 공백 문자가 있으면 모두 제거
equals() : 파라미터 값의 동일성
위에서 확인하여 실제 토큰값을 String token 에 담아줬다.
if (token != null && !token.equalsIgnoreCase("null")) {
Map<String, Object> claims = tokenProvider.validateToken(token);
String userId = (String) claims.get("userId");
List<String> roleList = (List<String>)claims.get("roleList");
이 토큰이 null 이 아니고 "null" 문자열이 아닌지 확인한다.
Map형태로 claims 를 저장한다.
claims 이란?
claims는 JWT 에서 사용하는 용어이고, 토큰에서 포함된 클레임 정보를 나타낸다.
JWT는 헤더, 페이로드, 서명 으로 구성되는데, 그 중 페이로드는 클레임들을 포함한다. 클레임은 해당 토큰이 가지는 정보를 나타내며, 주로 사용자 식별 정보나 권한 등의 데이터를 포함한다.
claims 변수에는 해당 JWT 토큰에서 추출된 클레임 정보가 Map 형태로 저장되어 있다.
그리고 String userId 에 tokenProvider 객체의 validateAndGetUserId 메서드를 사용하여 토큰을 검증하고 클레임 정보를 가져온다.
validateAndGetUserId 메서드란?
: 주어진 JWT 를 검증하고, 유효한 경우 해당 토큰에서 사용자 ID를 추출하여 반환하는 메서드.
클레임 정보를 claims 에 담아준 후, 변수에 사용자 Id 인 'userId', 사용자의 역할 목록인 'roleList' 에 담아준다.
이 정보를 활용하여 사용자의 인증 및 권한 부여 작업을 수행한다.
AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userId,
null,
roleList.stream()
.map(str -> new SimpleGrantedAuthority("ROLE_"+str))
.collect(Collectors.toList()) );
사용자의 인증 정보를 담고 있는 AbstractAuthenticationToken 객체를 생성한다.
UsernamePasswordAuthenticationToken 클래스를 사용한다.
AbstractAuthenticationToken 이란?
스프링 시큐리티에서 인증을 수행하는 데 사용되는 추상 클래스.
UsernamePasswordAuthenticationToken 이란?
스프링 시큐리티에서 사용자의 이름과 비밀번호를 기반으로 한 인증을 나타내는 클래스. AbstractAuthenticationToken 을 확장하여 사용자의 인증 정보를 나타냄.
UsernamePasswordAuthenticationToken 은 AbstractAuthenticationToken 을 확장한 클래스로, 사용자의 이름과 비밀번호를 기반으로 한 인증 정보를 나타낸다.
매개변수는 세 개.
① userId
사용자를 식별하는 주요 정보. 주로 사용자의 ID 가 전달됨.
② null
Password를 의미하며 보통은 null로 처리
③ roleList.stream()
.map(str -> new SimpleGrantedAuthority("ROLE_"+str))
.collect(Collectors.toList())
세 번째 매개변수는 사용자의 권한 목록을 나타낸다.
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
authentication.setDetails() 메서드 호출은 인증 요청에 대한 세부 정보를 추가하는 역할을 한다.
WebAuthenticationDetailsSource 는 스프링 시큐리티에서 제공하는 클래스로, 사용자의 인증 요청에 대한 세부 정보를 생성하는 역할. 이 클래스는 사용자의 IP 주소, 세션 ID 등의 세부 정보를 생성할 수 있다.
buildDetails(request) 는 주어진 요청을 기반으로 사용자의 인증 요청에 대한 세부 정보를 생성한다. 보통은 사용자의 IP 주소와 요청을 보낸 브라우저의 정보 등이 포함된다.
SecurityContextHolder에 인증된 user을 등록해야만 user로 인증한다.
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
처음으로 비어있는 SecurityContext 객체를 만들어준다.
securityContext.setAuthentication(authentication);
생성된 securityContext에 방금 만든 사용자의 인증 정보를 설정한다. 이 경우 authentication 객체가 사용되어 현재 사용자의 인증 상태를 나타낸다.
SecurityContextHolder.setContext(securityContext)
스프링 시큐리티는 현재 스레드의 보안 상태를 설정하고 이 인증 정보를 통해 사용자가 인증되었다는 것을 알 수 있게 된다.
JwtAuthenticationFilter ->->
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private TokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String token = parseBearerToken(request);
log.info("** JwtAuthenticationFilter.java, doFilterInternal(), token 확인=> "+token);
if (token != null && !token.equalsIgnoreCase("null")) {
Map<String, Object> claims = tokenProvider.validateToken(token);
log.info("** Authenticated 결과 JWT claims: " + claims);
String userId = (String) claims.get("userId");
List<String> roleList = (List<String>)claims.get("roleList");
AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userId,
null,
roleList.stream()
.map(str -> new SimpleGrantedAuthority("ROLE_"+str))
.collect(Collectors.toList()) );
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContext);
} //if_token 존재
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
} //doFilterInternal
private String parseBearerToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
} //class
여기까지가 Back 연결 중 토큰을 가져와서 유저를 인식하고 인증하는 Filter 단계다.
글이 길어져서 다음 게시물로~