
어느새 학습 커리큘럼의 막바지에 다다르게 되었다.
많아지면 많아졌지, 학습량이 줄어들 기미는 보이지 않는다.
지난 포스팅까지는 CRUD 구현과 Spring Security를 대략적으로 다뤘다.
이번에는 그 위에 올라가는 인증(Authentication)과 인가(Authorization) 부분을 학습하게 되었다.
그리고 마침내 프론트엔드와 백엔드를 연결하는 작업까지 배우게 되었다.
오랜만에 Vue.js를 보니 반갑기도 하지만, 곧 커리큘럼이 끝난다는 생각에 아쉽기도 하다.
어쨌든 이번 주 과정 또한 일련의 흐름들이 모든걸 결정하는 것처럼, 나도 이번 포스팅을 수업과정 흐름에 맡겨보려고 한다.
Spring Security 이어서

지난 포스팅에서는 Spring Security는 사용자 정보를 UserDetails 인터페이스로 추상화해서 이해할수 있는 형태로 전달해야 한다고 말했다.
이 인터페이스는 Spring이 인증 과정에서 필요한 정보를 꺼내는 규약이기 때문에 지켜야만 한다.
UserDetails와 CustomUser
// Spring이 제공하는 User 클래스를 상속해서 확장
public class CustomUser extends User {
private MemberVO member;
public CustomUser(MemberVO vo) {
super(vo.getUsername(), vo.getPassword(), vo.getAuthList());
this.member = vo;
}
}
User 클래스는 UserDetails의 기본 구현체다. 우리는 이걸 상속해서 확장하여 사용하게 된다.
그래서 우리는 직접 처음부터 구현하지 않고 User를 상속해서 필요한 필드(MemberVO)만 추가한다.
이렇게 CustomUser 처럼 클래스 상속으로 커스터마이징 하면 Spring Security가 요구하는 메서드는 부모 클래스(User)가 처리하고, 우리가 필요한 추가 정보는 CustomUser의 member 필드에서 꺼낼 수 있어서 좋다.
UserDetailsService로 DB와 Spring Security 연결하기
@Component
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserDetailsMapper mapper;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
MemberVO vo = mapper.get(username);
if (vo == null) {
throw new UsernameNotFoundException(username);
}
return new CustomUser(vo);
}
}
마찬가지로 UserDetailsService 인터페이스 또한 우리가 사용하기 쉽게 확장을 해 만들어주면 된다.
loadUserByUsername()은 로그인 요청이 들어올 때 Spring Security가 자동으로 호출한다. 따라서 해당 부분을 우리에 맞게 오버라이딩을 해줘야 한다.
DB에서 사용자를 찾으면 CustomUser로 감싸서 반환해주고, 없으면 예외를 던져주면 된다.
이 메서드의 반환값이 이후 비밀번호 검증과 SecurityContext 저장에 사용되니 기억하는게 좋겠다.
컨트롤러에서 로그인 사용자 정보 꺼내기?
// @AuthenticationPrincipal — SecurityContext에서 CustomUser 직접 주입
@GetMapping("/mypage")
public ResponseEntity<?> mypage(@AuthenticationPrincipal CustomUser user) {
return ResponseEntity.ok(user.getMember());
}
@AuthenticationPrincipal을 사용하면 SecurityContext에서 인증된 사용자 객체(CustomUser)를 파라미터로 바로 받을 수 있다고 한다.
getPrincipal()을 직접 호출하는 방식보다 간결하고 타입도 명확하니 이걸 사용하는게 좋겠다.
JWT, 세션 대신 토큰을 쓰는 이유?
이제 사용자 인증에 대한 방식으로 JWT 방식을 사용하는 방법에 대해서 배우게 된다. 하지만 세션 방식도 괜찮은데, 왜 JWT를 사용하는지 의문이 들 수 있다.
우선 세션 기반 인증은 서버가 사용자 상태를 메모리에 보관한다. 문제는 사용자가 많아지면 서버가 기억해야 하는 정보가 많아지게 될거고, 그러면 메모리 관리가 굉장히 까다롭다는 이야기다.
또한 서버가 여러 대로 확장되면 세션 동기화 문제가 생기고, 모바일 클라이언트나 외부 API 서버와 연동하기가 어렵다.
그래서 이 문제를 해결해주는 JWT(JSON Web Token) 를 사용한다는 이야기다.
서버는 아무것도 저장하지 않는다(Stateless). 대신 토큰 자체에 사용자 정보를 담아서 클라이언트가 보관한다. 그래서 서버는 요청이 올 때마다 토큰의 서명을 검증하는 것만으로 인증을 처리한다. 이게 대략적인 JWT의 사용흐름이라고 보면 된다.
JWT의 구조

JWT는 세 부분으로 구성된다.
Header.Payload.Signature
Header — 해싱 알고리즘과 토큰 타입을 담는다.
{ "alg": "HS256", "typ": "JWT" }
Payload — 실제 담을 데이터(Claims)를 담는다. 여기에 사용자를 식별할 수 있는 정보들를 넣는다.
{ "iss": "toptal.com", "exp": 1426420800, "username": "user1" }
Signature — Header와 Payload를 비밀키로 서명한 값이다. 토큰이 변조되었는지 확인하는 매커니즘이라고 생각하면 된다.
서버만 알고 있는 비밀키로 서명하기 때문에 클라이언트가 내용을 위조하면 검증에서 실패한다.
위 세 구조들을 가지고 JWT 토큰을 만들어 사용하게 된다.
JWT는 반드시 Spring Security가 있어야만 사용이 가능한 기술은 아니긴 하다. 물론 같이 활용하면 좋은 경우가 많을 뿐이다. Security에서 JWT를 쓰는게 편리한 점이 많아서 잘 쓰이는 것인거다.
CORS 설정을 하는 이유?
이제 우리들은 Vue와 Spring을 함께 다루게 될 건데, 이를 위해서 CORS 설정을 해둬야 한다.
Vue 개발 서버(예: localhost:5173)와 Spring 서버(localhost:8080)는 도메인이 다르다.
문제는 브라우저는 기본적으로 다른 출처로의 요청을 차단한다(CORS 정책).
그러니까 우리들은 백엔드에 CorsFilter를 등록해서 허용 출처를 명시해야 한다.
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
API 서버를 위한 Security 기본 설정
JWT 기반 API 서버는 세션을 사용하지 않는다.
그러니까 기존에 사용하던 학습 코드에서 Form 기반 로그인, CSRF, HTTP Basic 인증도 모두 비활성화해주게 된다.
http.httpBasic().disable() // 기본 HTTP 인증 비활성화
.csrf().disable() // CSRF 비활성화 (세션 없으므로 불필요)
.formLogin().disable() // Form 로그인 필터 해제
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 세션 미사용
핵심은 STATELESS라고 보면 된다.
서버가 세션을 만들지 않고, 요청마다 토큰으로만 사용자를 식별하게 만들어주면 된다.
jjwt 라이브러리로 토큰 생성과 검증하기

우리가 자바로 JWT 토큰을 처리하기 위해서 jjwt를 사용하게 된다.
생성과 추출, 검증 과정을 쉽게 해주는 메소드들이 있으니까 이걸 가져다가 사용하면 된다는 이야기.
// 토큰 생성
public String generateToken(String subject) {
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(new Date().getTime() + TOKEN_VALID_MILISECOND))
.signWith(key)
.compact();
}
// 토큰에서 사용자명 추출
public String getUsername(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// 토큰 유효성 검사
public boolean validateToken(String token) {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true; // 예외가 발생하지 않으면 유효한 토큰
}
추가 정보(비공개 클레임)를 담으려면 claims.put()을 사용하고, 꺼낼 때는 claims.get()을 사용한다.
JWT 운영, Access Token과 Refresh Token

JWT를 사용하다보면 역시 보안에 조금 취약할 수 있다는 문제가 있다.
그래서 요즘은 Access Token이랑 Refresh Token으로 보안성을 조금 더 챙기려고 한다고.
Access Token은 API 요청할 때 사용자를 인증하기 위한 짧은 수명의 토큰이다. 수명이 짧으니까 다른사람이 탈취해도 피해 시간을 줄일 수 있다는 이야기다.
Refresh Token은 말 그대로 Access Token이 만료되었을 때 새로운 Access Token을 받기 위해서 사용하는 조금 긴 수명의 토큰이다.
그런데 강사님이 말하시길 가끔씩 Refresh Token을 안 쓰고, AccessToken의 수명만 길게 만들어서 사용하는 경우가 있다고 한다. 이 경우에는 해당 토큰만 탈취해버리면 꽤나 위험해질 수 있다고 한다. 그러니까 어지간하면 Refresh Token까지 만들어서 보안을 좀 더 챙기는게 좋다고 하신다.
만약에 그냥 내가 모놀리식 서비스를 만든다면, 차라리 JWT로 만들기보다 세션 방식을 사용하는게 더 안전한 선택일 수도 있으니 필요한 기술을 적절하게 선택하는게 좋다고 하신다.
정상 로그인 흐름은 이렇게 진행된다.
1. 클라이언트: ID/PW로 로그인 요청
2. 서버: 검증 후 Access Token + Refresh Token 발급
3. 클라이언트: 이후 API 요청 시 헤더에 Access Token 담아서 요청
Authorization: Bearer <access_token>
Access Token 만료 후 재발급 흐름은 이렇게 진행된다
1. 서버: Access Token 만료 응답 (401)
2. 클라이언트: Refresh Token과 함께 재발급 요청
3. 서버: Refresh Token 검증 후 새 Access Token + Refresh Token 발급
API 인증 필터 흐름

이제 우리는 백엔드를 API 서버처럼 다루게 된다. 이 API 서버에 요청을 할 때 거쳐야하는 인증 절차를 배우게 된다.
JWT 인증에서는 크게 두 가지 흐름을 나눠서 봐야 한다.
첫 번째는 로그인 인증 흐름이다.
토큰 기반 로그인에 필요한 모듈
JWT 기반 인증에서 Form 로그인을 대체하는 핵심 컴포넌트들이 있다.
JwtUsernamePasswordAuthenticationFilter : 로그인 요청에서 username/password를 추출하고 인증을 처리하는 필터다.
// 필터 초기화 시 설정
setFilterProcessesUrl("/api/auth/login");
setAuthenticationSuccessHandler(successHandler);
setAuthenticationFailureHandler(failureHandler);
이 필터는 꺼낸 로그인 정보를 AuthenticationManger에게 넘겨준다. 그러면 내부적으로 UserDetailService를 통해서 DB에서 사용자를 조회하고, 비밀번호가 맞는지 검증하게 된다.
이후 인증에 성공한다면 LoginSuccessHanlder가 실행되게 된다. 이 때 서버는 세션을 만드는 대신에 JWT를 생성해서 클라이언트에게 JSON 형태로 응답을 보내준다.
// LoginSuccessHandler
public void onAuthenticationSuccess(...) {
String token = jwtUtil.generateToken(authentication.getName());
JsonResponse.send(response, token); // JSON으로 토큰 응답
}
만약 인증에 실패한다면 LoginFailureHandler 가 실행되고, 401 상태코드로 응답하게 된다.
API 요청 인증 흐름
로그인 이후의 모든 API 요청은 헤더에 발급받았던 토큰을 담아서 보내게 된다.
Authorization: Bearer <jwt_token>
서버의 JwtAuthenticationFilter는 매 요청마다 이 헤더를 확인한다.
토큰이 없으면 인증되지 않은 요청으로 보고 다음 필터로 넘기거나, 보호된 API라면 401 응답이 발생한다.
토큰이 있으면 서버는 토큰의 서명, 만료 시간, 형식이 올바른지 검증한다.
검증에 성공하면 토큰에서 username을 꺼내고, 그 사용자 정보를 바탕으로 Authentication 객체를 만든다.
SecurityContextHolder.getContext().setAuthentication(authentication);
이 코드가 실행되면 Spring Security는 현재 요청을 “인증된 사용자”의 요청으로 인식한다.
그 이후 컨트롤러에서는 @AuthenticationPrincipal이나 SecurityContextHolder를 통해 로그인한 사용자 정보를 사용할 수 있다.
그러니까 대략 정리해보자면 로그인 필터는 ID/PW를 검증해서 JWT를 발급하는 역할이고, JWT 인증 필터는 매 요청의 JWT를 검증해서 SecurityContext에 인증 정보를 저장하는 역할이다.
JWT 방식은 세션을 사용하지 않기 때문에, 서버는 요청이 올 때마다 토큰을 다시 검증해야 한다. 그래서 로그인할 때 한 번 인증하고 끝나는 것이 아니라, API 요청마다 토큰 검증 과정이 반복된다.
JWT 예외 처리
토큰이 만료되거나 변조된 경우를 구분해서 처리해야 한다.
try {
super.doFilter(request, response, filterChain);
} catch (ExpiredJwtException e) {
// 토큰 만료 → 재발급 안내
} catch (UnsupportedJwtException | MalformedJwtException | SignatureException e) {
// 토큰 이상 → 401 응답
}
이걸 통해서 약간이나마 변조의 위협을 막을 수 있다.
API 인증/인가 예외 처리

인증과 인가는 약간 다른 개념이다.
인증(Authentication)은 이 요청이 누구인지 확인하는 것이다. 실패하면 401 Unauthorized 상태코드를 보내준다.
인가(Authorization)은 이 사용자가 이 리소스에 접근할 권한이 있는지 확인하는 것이다. 실패하면 403 Forbidden 상태코드를 보내준다.
Spring Security는 이 두 가지 예외를 별도 핸들러로 처리한다.
AuthenticationEntryPoint - 401 처리
인증되지 않은 요청이 보호된 리소스에 접근할 때 호출된다.
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
JsonResponse.sendError(response, HttpStatus.UNAUTHORIZED, authException.getMessage());
}
}
AccessDeniedHandler - 403 처리
인증은 됐지만 권한이 부족할 때 호출된다.
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
JsonResponse.sendError(response, HttpStatus.FORBIDDEN, "권한이 부족합니다.");
}
}
SecurityConfig에 등록
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint) // 401
.accessDeniedHandler(accessDeniedHandler); // 403
이 설정 한 줄로 401과 403 응답이 모두 JSON 형태로 일관되게 처리된다.
기존 Form 기반에서는 로그인 페이지로 리다이렉트했지만, REST API에서는 JSON 에러 응답이 맞다.
이제 백엔드 Spring에 대한 내용들을 대부분 배우게 되었다.
이후 드디어 풀스택 커리큘럼에 맞게 프론트와 백엔드를 연결하기 시작했다.
프론트는 우리가 초반에 배웠던 Vue.js 를 사용한다.
Fullstack 기본 설정 - Spring과 Vue

Vue 앱을 빌드하면 정적 파일(HTML, JS, CSS)이 생성될거다.
이 파일들을 Spring 서버의 정적 리소스 경로에 배포하면 단일 서버로 운영할 수 있다.
그러면 이를 위한 과정들을 살펴보자.
vite.config.js - 빌드 출력 경로 설정
export default defineConfig({
build: {
outDir: 'C:../scoula/backend/src/main/webapp/resources',
},
});
빌드 결과물이 Spring 프로젝트의 정적 파일 경로로 직접 출력된다.
Spring - 루트 경로 처리
Vue는 SPA(Single Page Application)다. 모든 경로는 index.html을 반환해야 한다.
Spring에서 /로 들어오는 요청을 index.html로 포워딩한다.
registry.addViewController("/").setViewName("forward:/resources/index.html");
존재하지 않는 경로로 요청이 오면 NoHandlerFoundException이 발생한다.
이것도 index.html로 보내서 Vue Router가 처리하게 한다.
@ExceptionHandler(NoHandlerFoundException.class)
public String handle404(NoHandlerFoundException ex) {
return "/resources/index.html";
}
Vue 라우팅 - 기능별 모듈화
라우팅 설정을 기능별로 파일을 나눠서 관리한다.
// router/board.js
export default [
{ path: '/board/list', name: 'board/list', component: () => import('…') },
];
// router/index.js
import boardRoutes from './board';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', name: 'home', component: HomePage },
...boardRoutes,
],
});
...boardRoutes로 스프레드하면 기능이 늘어나도 index.js는 건드리지 않아도 된다.
대략 이렇게 프론트와 백엔드의 설정을 맞춘 후에, 회원가입같은 기능들을 구현해보기 시작했다.
회원가입 전송 흐름

회원가입을 할 때 고려할 사항은, username이 중복되었는지, 암호화가 되었는지, 회원 권한도 같이 저장되는 트랜젝션이 구현되는지, 이미지가 정상적으로 업로드 되었는지 등을 고려하게 된다.
이를 위해서 각각 MemberJoinDTO라던가 MemberDTO와 같이 전송 객체들을 구현해준다. 그런 구현들은 이전에 했던 것들을 따라하거나 복붙해서 사용하면 된다.
우리가 사용할 회원가입은 일반 JSON 요청과 다르다.
우리는 프로필 이미지 파일을 함께 전송해야 하기 때문에 multipart/form-data 인코딩을 사용해야만 한다. 인코딩 방식이 다르다는걸 반드시 기억하자.
Vue - FormData로 요청 구성
const formData = new FormData();
formData.append('username', member.username);
formData.append('password', member.password);
if (member.avatar) {
formData.append('avatar', member.avatar); // 파일이 있을 때만 추가
}
const headers = { 'Content-Type': 'multipart/form-data' };
const { data } = await api.post(BASE_URL, formData, { headers });
FormData는 파일과 텍스트 데이터를 함께 담을 수 있는 객체라고 한다.
우리는 이미지 파일을 회원가입할 때 같이 업로드해야 하니까 이 객체를 활용하게 된다.
헤더에 Content-Type: multipart/form-data를 명시해주면 서버 파싱 문제를 방지할 수 있다.
Spring Controller - @RequestBody 없이 받기
public ResponseEntity<MemberDTO> join(MemberJoinDTO member) {
// @RequestBody를 붙이지 않는다
// multipart 데이터는 자동으로 MemberJoinDTO에 바인딩됨
}
@RequestBody는 JSON 본문을 파싱하는 역할이어서, 파일 업로드가 포함된 multipart/form-data 에서는 DTO를 그냐 파라미터로 받아버리거나, @ModelAttribute 를 붙여서 받게 된다.
MultipartFile 타입으로 DTO에 선언하면 Spring이 자동으로 바인딩한다고 한다.
비밀번호 암호화
비밀번호는 절대 평문으로 저장해서는 안된다. 그래서 회원가입시 입력받은 비밀번호는 그대로 DB에 저장하지 않고 해시값으로 변환한 뒤에 저장해야 한다
passwordEncoder.encode(member.getPassword())
BCryptPasswordEncoder는 같은 입력이라도 매번 다른 해시값을 생성해준다. Salt 값을 같이 사용하기 때문이라고 한다.
따라서 로그인 검증 시에는 사용자가 입력한 비밀번호를 갖고 다시 인코딩해서 값 비교하는건 안 된다.
검증할 때는 passwordEncoder.matches(rawPassword, encodedPassword)로 입력값과 저장된 해시값이 같은 비번에서 나온건지 검증해야 한다.
대략 이정도로 회원가입의 흐름을 볼 수 있다.
로그인/로그아웃 - axios 인터셉터로 토큰 자동 관리

회원가입이 가능해졌다면 이제 로그인도 가능해야 하겠다.
우리가 로그인을 하면 이제 토큰을 발급받을 탠데, 결국 이걸 통해 인증을 해야 하니 사용자가 어딘가에 저장해줘야 한다.
Pinia Store는 우리의 앱이 실행되는 순간에는 상태를 저장할 수 있지만, 새로고침을 해버리거나 브라우저를 다시 연다면 메모리에서 날아가버려서 초기화가 될거다.
그래서 토큰을 로컬스토리지에 함께 저장해두고, 앱이 다시 시작될 때도 저장된 값을 읽어 Pinia 상태를 복원해보는게 목표다.
localStorage - 토큰 영속성
localStorage는 브라우저를 닫아도 데이터가 유지되고, 앱이 다시 시작해도 복원할 수 있다.
// 저장
localStorage.setItem('auth', JSON.stringify(authData));
// 복원 (앱 시작 시)
const load = () => {
const auth = localStorage.getItem('auth');
if (auth != null) {
state.value = JSON.parse(auth);
}
};
load();
문제는 로컬스토리지는 문자열만 저장할 수 있다. 그래서 JSON 객체 형태의 인증 정보를 저장할 때는 JSON.stringify()로 직렬화하고, 문자열로 바꿔서 저장한다.
꺼낼 때는 JSON.parse()로 역직렬화해서 복원하면 된다.
axios 인터셉터 - 모든 요청에 토큰 자동 주입

로그인 이후의 API 요청에는 보통 Authorization 헤더가 필요하다.
그렇지만 모든 API 호출 코드마다 토큰을 꺼내고 헤더를 붙이고 하는 짓은 너무 귀찮고 비효율적이다.
axios 인터셉터를 사용하면 요청이 나가기 전에 자동으로 토큰을 주입할 수 있다.
// 요청 인터셉터 — 토큰 자동 주입
instance.interceptors.request.use(
(config) => {
const { getToken } = useAuthStore();
const token = getToken();
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
이 코드는 API 요청이 발생할 때마다 Pinia Store이 `getToken()` 을 호출해서 현재 토큰을 가져온다. 그리고 토큰이 있으면 토큰값을 헤더에 추가해서 보내준다.
응답 인터셉터 - 401 자동 처리
응답 인터셉터는 서버 응답이 컴포넌트로 전달되기 전에 공통 처리를 수행하는 지점이다.
특히 JWT 인증에서는 서버가 `401 Unauthorized`를 반환하는 경우가 중요하다.
401은 보통 토큰이 없거나, 만료되었거나, 유효하지 않다는 뜻이라고 생각하면 되겠다.
// 응답 인터셉터 — 401 감지 후 자동 로그아웃
instance.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
const { logout } = useAuthStore();
logout();
router.push('/auth/login?error=login_required');
return Promise.reject({ error: '로그인이 필요한 서비스입니다.' });
}
return Promise.reject(error);
}
);
서버에서 401이 오면 클라이언트는 이 인증상태를 더이상 신뢰할 수 없다. 그래서 Store의 인증 정보를 비우고 로컬스토리지에 저장된 토큰도 제거한 다음에 로그인 페이지로 이동시켜버린다.
이 로직을 인터셉터에 넣으면 각 API 호출마다 401 처리를 따로 작성하지 않아도 된다. 대신 401과 403은 구분하는게 좋다고 한다. 401은 인증이 안된거고, 403은 권한이 없는 것이니까 적절한 처리를 해주면 된다.
Vue에서 DOM 요소 참조 - ref
파일 입력 요소처럼 직접 DOM에 접근해야 할 때 Vue의 ref를 사용한다.
const avatar = ref(null);
// <input type="file" ref="avatar" />
일반적인 값 입력은 v-model로 처리하지만, 파일 input 처리들은 브라우저가 값을 직접 제어하는게 필요하기 때문에 이렇게 사용하게 된다.
마무리

이번주는 JWT를 이용한 인증 흐름을 풀스택의 관점에서 살펴보는 과정이었다.
진짜 이번에도 너무나도 많은 내용들을 배우게 되었다. 이걸 전부 외운다는건 아무래도 무리가 있지 않을까... 그런데 강사님은 전부 기억속에서 가져와서 코드를 작성하시는데, 진짜 말도 안되는 것 같다.
그래도 전반적인 흐름만큼은 이해하는게 중요하다고 강사님이 항상 말씀하신다. 결국 데이터 흐름과 프로세스의 흐름을 따라가다보면 자연히 구조가 익혀질 것이라고 하신다.
이 흐름을 머릿속에 그릴 수 있을 때까지 이해하고, 나머지 절반은 직접 손으로 구현해보는게 나에게 남은 몫이다. 이제 곧 파이널 프로젝트를 들어가게 될탠데, 걱정이 이만저만이 아니다. 팀원들에게 누가 되지 않도록 열심히 공부하는게 좋겠다.

'개발 > KB IT's Your Life 7기' 카테고리의 다른 글
| KB IT's Your Life 7기 - 서버 사이드 백엔드 개발 후기 (0) | 2026.07.12 |
|---|---|
| KB IT's Your Life 7기 - CRUD부터 Spring Security까지 (0) | 2026.06.28 |
| KB IT's Your Life 7기 - Servlet과 Spring 핥아보기 (0) | 2026.06.21 |
| KB IT's Your Life 7기 - JDBC와 MyBatis로 백엔드랑 친해지기 (0) | 2026.06.13 |
| KB IT's Your Life 7기 - 2026 중간 회고록 (0) | 2026.06.07 |