에러를 만나면서 spring security에 대한 이해도가 조금 올라간거 같당
Spring Security의 HttpSecurity구성
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.httpBasic().disable() //Authentication header에 id,pw 주는 방식 (Bearer Token 사용할거니까)
.apply(new MyCustomDsl()) // 커스텀 필터 등록
.and()
.authorizeRequests(authroize -> authroize
.antMatchers("/api/v1/users/join","/api/v1/users/login").permitAll()
.antMatchers("/api/v1/users/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll())
.build();
적용해두고
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http
.addFilter(corsConfig.corsFilter()) //@CrossOrigin(인증X), 시큐리티 필터에 등록 -> 인증(O)
.addFilter(new JwtAuthenticationFilter(authenticationManager))
.addFilter(new JwtAuthorizationFilter(authenticationManager, userRepository));
}
}
만든 필터들을 적용하려고 했다.
UsernamePasswordAuthenticationFilter를 상속 받아서
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter
attemptAuthentication(HttpServletRequest request, HttpServletResponse response)를 오버라이드 해줬다.
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
log.info("JwtAuthenticationFilter : 진입");
// request에 있는 username과 password를 파싱해서 자바 Object로 받기
ObjectMapper om = new ObjectMapper();
LoginRequestDto loginRequestDto = null;
try {
loginRequestDto = om.readValue(request.getInputStream(), LoginRequestDto.class);
} catch (Exception e) {
e.printStackTrace();
}
log.info("JwtAuthenticationFilter{}:",loginRequestDto);
// 유저네임패스워드 토큰 생성
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(
loginRequestDto.getEmail(),
loginRequestDto.getPassword());
log.info("JwtAuthenticationFilter : 토큰생성완료");
// authenticate() 함수가 호출 되면 인증 프로바이더가 유저 디테일 서비스의 (PrincipalDetailsService)
// loadUserByUsername(토큰의 첫번째 파라메터) 를 호출하고
// UserDetails를 리턴받아서 토큰의 두번째 파라메터(credential)과 (return new PrincipalDetails(user);)
// UserDetails(DB값)의 getPassword()함수로 비교해서 동일하면
// Authentication 객체를 만들어서 필터체인으로 리턴해준다.
// Tip: 인증 프로바이더의 디폴트 서비스는 UserDetailsService 타입
// Tip: 인증 프로바이더의 디폴트 암호화 방식은 BCryptPasswordEncoder
// 결론은 인증 프로바이더에게 알려줄 필요가 없음.
Authentication authentication =
authenticationManager.authenticate(authenticationToken);
//PrincipalDetailsService의 loadUserByUsername() 함수가 실행된 후 정상이면 authentication이 리턴된다.
//DB에 있는 email과 password가 일치한다.
PrincipalDetails principalDetailis = (PrincipalDetails) authentication.getPrincipal(); //object 반환
log.info("Authentication:{}",principalDetailis.getUser().getEmail());
return authentication;
}
PrincipalDetails를 세션에 담는 이유는 권한 관리를 위해 즉, hasRole()동작시키기 위해서라고 한다.
authentication 객체가 session영역에 저장을 해야하고 그 방법이 return 해주면 된다.
- 리턴의 이유는 권한관리를 security가 대신 해주기 때문에 편하려고 한다.
- 굳이 JWT 토큰을 사용하면서 세션을 만들 이유가 없지만 권한 처리때문에 session에 넣어 준다.
PrincipalDetails principalDetailis = (PrincipalDetails) authentication.getPrincipal(); //object 반환
log.info("Authentication:{}",principalDetailis.getUser().getEmail());
를 찍어본이유는 JwtAuthorizationFilter로 넘어갈 때 확인해보려고 찍어봤다.
public class JwtAuthorizationFilter extends BasicAuthenticationFilter
BasicAuthenticationFilter 메서드 doFilterInternal를 오버라이드 할때
//인증이나 권한이 필요한 주소요청이 있을때 거침
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(JwtProperties.HEADER_STRING);
//헤더가 있는지 확인
if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
log.info("header:{}",header);
String token = request.getHeader(JwtProperties.HEADER_STRING)
.replace(JwtProperties.TOKEN_PREFIX, "");
// 토큰 검증 (이게 인증이기 때문에 AuthenticationManager도 필요 없음)
// 내가 SecurityContext에 집적접근해서 세션을 만들때 자동으로 UserDetailsService에 있는
// loadByUsername이 호출됨.
// String email = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET)).build().verify(token)//verify(token) 서명
// .getClaim("username").asString(); //서명되면 username꺼내서 String으로
String email = TokenUtils.getUserEmailFromToken(token);
if (email != null) {
User user = userRepository.findByEmail(email).get();
// 인증은 토큰 검증시 끝. 인증을 하기 위해서가 아닌 스프링 시큐리티가 수행해주는 권한 처리를 위해
// 아래와 같이 토큰을 만들어서 Authentication 객체를 강제로 만들고 그걸 세션에 저장!
PrincipalDetails principalDetails = new PrincipalDetails(user);
Authentication authentication = new UsernamePasswordAuthenticationToken(
principalDetails, // 나중에 컨트롤러에서 DI해서 쓸 때 사용하기 편함.
null, // 패스워드는 모르니까 null 처리, 어차피 지금 인증하는게 아니니까!!
principalDetails.getAuthorities());
// 강제로 시큐리티의 세션에 접근하여 값 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
PrincipalDetails를 이용해서 Authentication객체를 강제로 만들어서 SecurityContextHolder에 저장했다.
ㅋㅋㅋㅋ.... 지금은 부끄럽지만 나처럼 처음 접해보는 나같은 실수를 하지 않을까라는 생각으로 글을 쓴다. 나의 문제는
JwtAuthenticationFilter 에서 오버라이드 했던
attemptAuthentication(HttpServletRequest request, HttpServletResponse response)가 실행 후 인증이 정상적으로 되었으면 successfulAuthentication()이 호출될텐데
/api/v1/users/login에 mapping 되어있던 컨트롤러가 실행될때
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
PrincipalDetails principalDetailis = (PrincipalDetails) authResult.getPrincipal();
String token = TokenUtils.generateJwtAccessToken(principalDetailis.getUser());
response.setHeader(JwtProperties.HEADER_STRING,JwtProperties.TOKEN_PREFIX + token);
}
header에 값이 들어올줄 알았는데 ,,, 우잉 아무리 디버그를 찍어봐도 Filter로 넘어오지가 않는거다 뭐지 하면서 혼자 로그도 찍고 찾아보다가
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.httpBasic().disable() //Authentication header에 id,pw 주는 방식 (Bearer Token 사용할거니까)
.apply(new MyCustomDsl()) // 커스텀 필터 등록
.and()
.authorizeRequests(authroize -> authroize
.antMatchers("/api/v1/users/join","/api/v1/users/login").permitAll()
.antMatchers("/api/v1/users/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll())
.build();
Spring Security의 HttpSecurity구성에서 formLogin().disable()을 호출하여 기본 로그인 폼 기능을 비활성화했기 때문에,
UsernamePasswordAuthenticationFilter가 적용되지 않은 것으로 보입니다. 이 필터는 Spring Security에서 제공하는 기본 로그인 처리 필터이며, 로그인 요청을 처리하고 인증을 수행합니다. -gpt...-
.antMatchers("/api/v1/users/join","/api/v1/users/login").permitAll()로 했기때문에
permitAll()을 사용하면 모든 사용자가 해당 엔드포인트에 액세스할 수 있으므로 권한 검사를 수행하지 않습니다. 따라서 hasRole()을 사용하여 엔드포인트에 액세스하는 데 필요한 권한을 지정할 필요가 있습니다. -gpt-
하핳.. successfulAuthentication에서 token값을 알아서 처리해줄줄 알았는데 이런문제로 인해서
@PostMapping("/login") //토큰X
public ResponseEntity<User> login(@RequestBody final LoginRequestDto loginDto,
HttpServletResponse response){
User user = userService.login(loginDto);
String token = TokenUtils.generateJwtAccessToken(user);
log.info("controller token{} :",token);
response.setHeader(JwtProperties.HEADER_STRING,JwtProperties.TOKEN_PREFIX + token);
return ResponseEntity.ok().body(user);
}
controller에서 토큰을 넣어줘야된당,, 근데 문제가 하나더 발생했다...

에러보니까 컨트롤러에서 TokenUtils를 실행할 때 뭔가 문제가 있던거 같은데 뭐가 문제지하고 디버그를 찍어보니까
public static String generateJwtAccessToken(User user) {
JwtBuilder builder = Jwts.builder()
.setSubject(user.getEmail())
.setHeader(createHeader())
.setClaims(createClaims(user))
.setExpiration(new Date(System.currentTimeMillis() + JwtProperties.ACCESS_TOKEN_EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, createSigningKey());
return builder.compact();
}
에서 createSigningKey에서 문제가 있는거 같았다.
private static Key createSigningKey() {
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(JwtProperties.SECRET);
return new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName());
}
흠 뭐가 문제지 하면서 Base64로 secret key를 인코딩할 때 문제가 발생한건가 싶어서 키를 확인했는데
public interface JwtProperties {
String SECRET = "유현우"; // 우리 서버만 알고 있는 비밀값
int ACCESS_TOKEN_EXPIRATION_TIME = 3600000;
String TOKEN_PREFIX = "Bearer ";
String HEADER_STRING = "Authorization";
}
한글로 되어있어서 문제가 발생했나부다,, 영어로 바꾸니까? 해결되었다.
생각해보니까 예전에 MIME공부할 때


한글이 없는데 ㅋㅋㅋ 하암~_~

해결완료~~
진짜 처음 JWT공부할 때 막막했는데 블로그도 찾아보고 영상도 보고 했지만 gpt한테도 많이 물어본거 같다.
스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security (정수원님) 인프런 강의를 보면서 도움이 정말 많이 되었다.
코드에 대한 내용은 스프링부트 시큐리티 & JWT - 최주호님 인프런 강의를 보면서 이렇게 되는거 구나 했던거 같다. ㅜㅜ 책으로 하나하나 찾아보고 했으면 진짜 힘들었을텐데 편하게 공부할 수 있게 해주셔서 감사합니다. (_ _)
'개발 > 첫 번째 프로젝트' 카테고리의 다른 글
[2주] 사이드 프로젝트 회고 (0) | 2023.03.27 |
---|---|
[1주] 사이드 프로젝트 회고 (0) | 2023.03.20 |
모두의졸전 (0) | 2023.03.15 |
에러를 만나면서 spring security에 대한 이해도가 조금 올라간거 같당
Spring Security의 HttpSecurity구성
@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .httpBasic().disable() //Authentication header에 id,pw 주는 방식 (Bearer Token 사용할거니까) .apply(new MyCustomDsl()) // 커스텀 필터 등록 .and() .authorizeRequests(authroize -> authroize .antMatchers("/api/v1/users/join","/api/v1/users/login").permitAll() .antMatchers("/api/v1/users/**") .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/manager/**") .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ROLE_ADMIN')") .anyRequest().permitAll()) .build();
적용해두고
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> { @Override public void configure(HttpSecurity http) throws Exception { AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); http .addFilter(corsConfig.corsFilter()) //@CrossOrigin(인증X), 시큐리티 필터에 등록 -> 인증(O) .addFilter(new JwtAuthenticationFilter(authenticationManager)) .addFilter(new JwtAuthorizationFilter(authenticationManager, userRepository)); } }
만든 필터들을 적용하려고 했다.
UsernamePasswordAuthenticationFilter를 상속 받아서
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter
attemptAuthentication(HttpServletRequest request, HttpServletResponse response)를 오버라이드 해줬다.
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { log.info("JwtAuthenticationFilter : 진입"); // request에 있는 username과 password를 파싱해서 자바 Object로 받기 ObjectMapper om = new ObjectMapper(); LoginRequestDto loginRequestDto = null; try { loginRequestDto = om.readValue(request.getInputStream(), LoginRequestDto.class); } catch (Exception e) { e.printStackTrace(); } log.info("JwtAuthenticationFilter{}:",loginRequestDto); // 유저네임패스워드 토큰 생성 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( loginRequestDto.getEmail(), loginRequestDto.getPassword()); log.info("JwtAuthenticationFilter : 토큰생성완료"); // authenticate() 함수가 호출 되면 인증 프로바이더가 유저 디테일 서비스의 (PrincipalDetailsService) // loadUserByUsername(토큰의 첫번째 파라메터) 를 호출하고 // UserDetails를 리턴받아서 토큰의 두번째 파라메터(credential)과 (return new PrincipalDetails(user);) // UserDetails(DB값)의 getPassword()함수로 비교해서 동일하면 // Authentication 객체를 만들어서 필터체인으로 리턴해준다. // Tip: 인증 프로바이더의 디폴트 서비스는 UserDetailsService 타입 // Tip: 인증 프로바이더의 디폴트 암호화 방식은 BCryptPasswordEncoder // 결론은 인증 프로바이더에게 알려줄 필요가 없음. Authentication authentication = authenticationManager.authenticate(authenticationToken); //PrincipalDetailsService의 loadUserByUsername() 함수가 실행된 후 정상이면 authentication이 리턴된다. //DB에 있는 email과 password가 일치한다. PrincipalDetails principalDetailis = (PrincipalDetails) authentication.getPrincipal(); //object 반환 log.info("Authentication:{}",principalDetailis.getUser().getEmail()); return authentication; }
PrincipalDetails를 세션에 담는 이유는 권한 관리를 위해 즉, hasRole()동작시키기 위해서라고 한다.
authentication 객체가 session영역에 저장을 해야하고 그 방법이 return 해주면 된다.
- 리턴의 이유는 권한관리를 security가 대신 해주기 때문에 편하려고 한다.
- 굳이 JWT 토큰을 사용하면서 세션을 만들 이유가 없지만 권한 처리때문에 session에 넣어 준다.
PrincipalDetails principalDetailis = (PrincipalDetails) authentication.getPrincipal(); //object 반환 log.info("Authentication:{}",principalDetailis.getUser().getEmail());
를 찍어본이유는 JwtAuthorizationFilter로 넘어갈 때 확인해보려고 찍어봤다.
public class JwtAuthorizationFilter extends BasicAuthenticationFilter
BasicAuthenticationFilter 메서드 doFilterInternal를 오버라이드 할때
//인증이나 권한이 필요한 주소요청이 있을때 거침 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String header = request.getHeader(JwtProperties.HEADER_STRING); //헤더가 있는지 확인 if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) { chain.doFilter(request, response); return; } log.info("header:{}",header); String token = request.getHeader(JwtProperties.HEADER_STRING) .replace(JwtProperties.TOKEN_PREFIX, ""); // 토큰 검증 (이게 인증이기 때문에 AuthenticationManager도 필요 없음) // 내가 SecurityContext에 집적접근해서 세션을 만들때 자동으로 UserDetailsService에 있는 // loadByUsername이 호출됨. // String email = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET)).build().verify(token)//verify(token) 서명 // .getClaim("username").asString(); //서명되면 username꺼내서 String으로 String email = TokenUtils.getUserEmailFromToken(token); if (email != null) { User user = userRepository.findByEmail(email).get(); // 인증은 토큰 검증시 끝. 인증을 하기 위해서가 아닌 스프링 시큐리티가 수행해주는 권한 처리를 위해 // 아래와 같이 토큰을 만들어서 Authentication 객체를 강제로 만들고 그걸 세션에 저장! PrincipalDetails principalDetails = new PrincipalDetails(user); Authentication authentication = new UsernamePasswordAuthenticationToken( principalDetails, // 나중에 컨트롤러에서 DI해서 쓸 때 사용하기 편함. null, // 패스워드는 모르니까 null 처리, 어차피 지금 인증하는게 아니니까!! principalDetails.getAuthorities()); // 강제로 시큐리티의 세션에 접근하여 값 저장 SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); }
PrincipalDetails를 이용해서 Authentication객체를 강제로 만들어서 SecurityContextHolder에 저장했다.
ㅋㅋㅋㅋ.... 지금은 부끄럽지만 나처럼 처음 접해보는 나같은 실수를 하지 않을까라는 생각으로 글을 쓴다. 나의 문제는
JwtAuthenticationFilter 에서 오버라이드 했던
attemptAuthentication(HttpServletRequest request, HttpServletResponse response)가 실행 후 인증이 정상적으로 되었으면 successfulAuthentication()이 호출될텐데
/api/v1/users/login에 mapping 되어있던 컨트롤러가 실행될때
@Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { PrincipalDetails principalDetailis = (PrincipalDetails) authResult.getPrincipal(); String token = TokenUtils.generateJwtAccessToken(principalDetailis.getUser()); response.setHeader(JwtProperties.HEADER_STRING,JwtProperties.TOKEN_PREFIX + token); }
header에 값이 들어올줄 알았는데 ,,, 우잉 아무리 디버그를 찍어봐도 Filter로 넘어오지가 않는거다 뭐지 하면서 혼자 로그도 찍고 찾아보다가
@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .httpBasic().disable() //Authentication header에 id,pw 주는 방식 (Bearer Token 사용할거니까) .apply(new MyCustomDsl()) // 커스텀 필터 등록 .and() .authorizeRequests(authroize -> authroize .antMatchers("/api/v1/users/join","/api/v1/users/login").permitAll() .antMatchers("/api/v1/users/**") .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/manager/**") .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ROLE_ADMIN')") .anyRequest().permitAll()) .build();
Spring Security의 HttpSecurity구성에서 formLogin().disable()을 호출하여 기본 로그인 폼 기능을 비활성화했기 때문에,
UsernamePasswordAuthenticationFilter가 적용되지 않은 것으로 보입니다. 이 필터는 Spring Security에서 제공하는 기본 로그인 처리 필터이며, 로그인 요청을 처리하고 인증을 수행합니다. -gpt...-
.antMatchers("/api/v1/users/join","/api/v1/users/login").permitAll()로 했기때문에
permitAll()을 사용하면 모든 사용자가 해당 엔드포인트에 액세스할 수 있으므로 권한 검사를 수행하지 않습니다. 따라서 hasRole()을 사용하여 엔드포인트에 액세스하는 데 필요한 권한을 지정할 필요가 있습니다. -gpt-
하핳.. successfulAuthentication에서 token값을 알아서 처리해줄줄 알았는데 이런문제로 인해서
@PostMapping("/login") //토큰X public ResponseEntity<User> login(@RequestBody final LoginRequestDto loginDto, HttpServletResponse response){ User user = userService.login(loginDto); String token = TokenUtils.generateJwtAccessToken(user); log.info("controller token{} :",token); response.setHeader(JwtProperties.HEADER_STRING,JwtProperties.TOKEN_PREFIX + token); return ResponseEntity.ok().body(user); }
controller에서 토큰을 넣어줘야된당,, 근데 문제가 하나더 발생했다...

에러보니까 컨트롤러에서 TokenUtils를 실행할 때 뭔가 문제가 있던거 같은데 뭐가 문제지하고 디버그를 찍어보니까
public static String generateJwtAccessToken(User user) { JwtBuilder builder = Jwts.builder() .setSubject(user.getEmail()) .setHeader(createHeader()) .setClaims(createClaims(user)) .setExpiration(new Date(System.currentTimeMillis() + JwtProperties.ACCESS_TOKEN_EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, createSigningKey()); return builder.compact(); }
에서 createSigningKey에서 문제가 있는거 같았다.
private static Key createSigningKey() { byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(JwtProperties.SECRET); return new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName()); }
흠 뭐가 문제지 하면서 Base64로 secret key를 인코딩할 때 문제가 발생한건가 싶어서 키를 확인했는데
public interface JwtProperties { String SECRET = "유현우"; // 우리 서버만 알고 있는 비밀값 int ACCESS_TOKEN_EXPIRATION_TIME = 3600000; String TOKEN_PREFIX = "Bearer "; String HEADER_STRING = "Authorization"; }
한글로 되어있어서 문제가 발생했나부다,, 영어로 바꾸니까? 해결되었다.
생각해보니까 예전에 MIME공부할 때


한글이 없는데 ㅋㅋㅋ 하암~_~

해결완료~~
진짜 처음 JWT공부할 때 막막했는데 블로그도 찾아보고 영상도 보고 했지만 gpt한테도 많이 물어본거 같다.
스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security (정수원님) 인프런 강의를 보면서 도움이 정말 많이 되었다.
코드에 대한 내용은 스프링부트 시큐리티 & JWT - 최주호님 인프런 강의를 보면서 이렇게 되는거 구나 했던거 같다. ㅜㅜ 책으로 하나하나 찾아보고 했으면 진짜 힘들었을텐데 편하게 공부할 수 있게 해주셔서 감사합니다. (_ _)
'개발 > 첫 번째 프로젝트' 카테고리의 다른 글
[2주] 사이드 프로젝트 회고 (0) | 2023.03.27 |
---|---|
[1주] 사이드 프로젝트 회고 (0) | 2023.03.20 |
모두의졸전 (0) | 2023.03.15 |