묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결스프링 시큐리티
20:00 부분에 공격자가 갑자기 어떻게 JSESSIONID를 가지고있는건가요?
PPT 설명이 다 끝나고 실습에 들어가는데 갑자기 JSESSIONID를 공격자가 로그인을 하지않았는데도 가지고 있을수가 있는건가요??혹시 로그인 과정을 했다고 가정하시고 생략하신건가요?? 그리고 Session ID를 이미 가지고 있는데 Login 페이지를 접근한다는것 자체가 조금 말이 안되는 상황인것 같은데 이 부분에 대해서는 다른 강의에서 다루고 있나요? 혹시 생략하신거라면, 어떠한 방식(자막, 공지, etc..)으로든 알려주시면 감사할것 같습니다. 듣는 수강생 입장에서는 "뜬금없이 갑자기 로그인이 되어있네"라는 생각을 할 수밖에 없는것 같습니다. 그렇게되면 그 전에 로그인을 했는데 내가 놓친건가?라고 다시보게되고 저같은 경우에는 왠만해선 질문을 안하려는 성격이여서 계속 제가 놓쳤다고 생각하고 시간을 쓰기때문에 이렇게 뭔가 결국 생략했다라는걸 알게되면 매우 허탈합니다. 그래서 이게 Interrupt가 걸리고 강의에 집중하는데 힘이듭니다.이런 부분이 한두군데가 아니여서 이렇게 말씀드립니다. 부탁드리겠습니다.
-
미해결스프링부트 시큐리티 & JWT 강의
JWT + Oauth2 붙이기
안녕하세요. 강사님! 수업 정말 잘 들었습니다! 비전공자로써 3개월째인데 JWT 강의 중에 정말 이해가 잘 되었습니다!!! 진심으로 감사합니다~~다름이 아니라 더 나아가서, JWT와 Oauth2를 붙이려고 하는데 JWT 강의 초반에 기존의 Oauth2 방식을 사용하지 않고 다른 방식을 써야 된다고 하셔서 질문합니다.어떤 부분이 달라져야할지 전혀 감이 안 오는데, 제가 조금 참고할만한 자료가 있을까요? 혹시 JWT+Oauth를 붙이려고 할 때 코드가 많이 변경될까요?(혹시 강의 제작하실 생각 없으신가요...ㅎㅎ)
-
미해결스프링부트 시큐리티 & JWT 강의
시큐리티 1강 환경설정 10분 요렇
configureViewResolvers 을 오버라이딩 했을때내부구성을 요렇게 바꿔 주세요를 하셨을때요렇게를 어떻게 요렇게 바꾸나요?
-
미해결스프링부트 시큐리티 & JWT 강의
로그인시 JWT 반환하는거를 구현하려는데 에러
코드 링크 : https://github.com/YuYoHan/JWT나오는 에러 :2023-06-10 13:57:50.618 ERROR 17488 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root causejava.lang.NullPointerException: nullat com.example.jwt.service.UserService.login(UserService.java:61) ~[classes/:na]at com.example.jwt.service.UserService$$FastClassBySpringCGLIB$$41c8b5f9.invoke(<generated>) ~[classes/:na]at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.27.jar:5.3.27]at org.springframework.aop.framework.CglibAopProxy.invokeMethod(CglibAopProxy.java:386) ~[spring-aop-5.3.27.jar:5.3.27]at org.springframework.aop.framework.CglibAopProxy.access$000(CglibAopProxy.java:85) ~[spring-aop-5.3.27.jar:5.3.27]at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:704) ~[spring-aop-5.3.27.jar:5.3.27]at com.example.jwt.service.UserService$$EnhancerBySpringCGLIB$$e9411f63.login(<generated>) ~[classes/:na]at com.example.jwt.controller.UserController.authorize(UserController.java:54) ~[classes/:na]at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.27.jar:5.3.27]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.27.jar:5.3.27]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.27.jar:5.3.27]at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.27.jar:5.3.27]at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072) ~[spring-webmvc-5.3.27.jar:5.3.27]at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) ~[spring-webmvc-5.3.27.jar:5.3.27]at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.27.jar:5.3.27]at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.27.jar:5.3.27]at javax.servlet.http.HttpServlet.service(HttpServlet.java:555) ~[tomcat-embed-core-9.0.74.jar:4.0.FR]at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.27.jar:5.3.27]at javax.servlet.http.HttpServlet.service(HttpServlet.java:623) ~[tomcat-embed-core-9.0.74.jar:4.0.FR]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-9.0.74.jar:9.0.74]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at com.example.jwt.config.jwt.JwtAuthenticationFilter.doFilter(JwtAuthenticationFilter.java:64) ~[classes/:na]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.8.jar:5.7.8]at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.27.jar:5.3.27]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.27.jar:5.3.27]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.74.jar:9.0.74]at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na] 제가 알기로는 이 오류는 final을 추가 하지 않아서 생기는 오류로 알고 있는데 문제는 제가 @RequiredArgsConstructor와 final을 모두 추가를 해줬습니다. 그런데 계속 null 오류가 생기네요. 강의와 똑같은 코드는 아니고 변형을 했는데전체 코드는 git 링크를 올렸으니 일단 오류에서 나온 코드만 따로 올립니다.**UserService** import java.util.Optional; @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; private final BCryptPasswordEncoder bCryptPasswordEncoder; private final AuthenticationManagerBuilder authenticationManagerBuilder; private final JwtProvider jwtProvider; @Transactional public UserEntity signUp(User user) throws Exception { UserEntity userEntity = UserEntity.builder() .email(user.getEmail()) .password(bCryptPasswordEncoder.encode(user.getPassword())) .userName(user.getUserName()) .role(Role.USER) .build(); if(userEntity != null) { return userRepository.save(userEntity); } else { throw new Exception("가입이 실패하셨습니다."); } } public Optional<UserEntity> findById(Long userId) { Optional<UserEntity> byId = userRepository.findById(userId); return byId; } public TokenDTO login(String userEmail, String userPw) { UserEntity byEmailAndPassword = userRepository.findByEmailAndPassword(userEmail, userPw); // 1. Login ID/PW를 기반으로 Authentication 객체 생성 // 이 때 authentication는 인증 여부를 확인하는 authenticated 값이 false UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(byEmailAndPassword.getEmail(), byEmailAndPassword.getPassword()); // 2. 실제 검증 (사용자 비밀번호 체크)이 이루어지는 부분 // authenticate 매서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드가 실행 Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); // 해당 객체를 SecurityContextHolder에 저장하고 SecurityContextHolder.getContext().setAuthentication(authentication); // authentication 객체를 createToken 메소드를 통해서 JWT Token을 생성 // 3. 인증 정보를 기반으로 JWT 토큰 생성 TokenDTO tokenDTO = jwtProvider.createToken(authentication); HttpHeaders headers = new HttpHeaders(); // response header에 jwt token을 넣어줌 headers.add(JwtAuthenticationFilter.HEADER_AUTHORIZATION, "Bearer " + tokenDTO); return tokenDTO; } } **UserController** @PostMapping("/login") public ResponseEntity<?> authorize(@RequestBody User user) { String email = user.getEmail(); String password = user.getPassword(); TokenDTO login = userService.login(email, password); return ResponseEntity.ok().body(login); } @RequiredArgsConstructor @Slf4j public class JwtAuthenticationFilter extends GenericFilterBean { public static final String HEADER_AUTHORIZATION ="Authorization" ; private final JwtProvider jwtProvider; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 1. Requset Header에서 JWT 토큰 추출 String jwt = resolveToken(httpServletRequest); String requestURI = httpServletRequest.getRequestURI(); if(StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)) { // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext 에 저장 Authentication authentication = jwtProvider.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); log.info("Security Context에 '{}' 인증 정보를 저장했습니다., uri : {}", authentication.getName(), requestURI); } else { log.debug("유효한 JWT 토큰이 없습니다. uri : {}", requestURI); } chain.doFilter(request, response); } // Request Header 에서 토큰 정보를 꺼내오기 위한 메소드 private String resolveToken(HttpServletRequest httpServletRequest) { String bearerToken = httpServletRequest.getHeader(HEADER_AUTHORIZATION); if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){ return bearerToken.substring(7); } else { return null; } } } 제가 JWT에 대해서는 잘 몰라서 계획한게 맞는 구성인지는 모르겠지만 계획한 것은JwtProvider에서 accessToken과 refreshToken을 만들고 TokenDTO담아주고 로그인시 그것을 리턴해주려고 계획했습니다. @Slf4j @Component public class JwtProvider { private static final String AUTHORITIES_KEY = "auth"; private Key key; public JwtProvider(@Value("${jwt.secret_key}") String secret ) { byte[] keyBytes = Decoders.BASE64.decode(secret); this.key = Keys.hmacShaKeyFor(keyBytes); } // 유저 정보를 가지고 AccessToken, RefreshToken을 생성하는 메서드 public TokenDTO createToken(Authentication authentication) { // 권한 가져오기 String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); // Access Token 생성 Date accessTokenExpire = new Date(now + 360000); String accessToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth", authorities) .setExpiration(accessTokenExpire) .signWith(key, SignatureAlgorithm.ES512) .compact(); // Refresh Token 생성 // Date 생성자에 삽입하는 숫자 86480000은 토큰의 유효기간으로써 1일을 나타낸다. String refreshToken = Jwts.builder() .setExpiration(new Date(now + 86400000)) .signWith(key, SignatureAlgorithm.ES512) .compact(); return TokenDTO.builder() .grantType("Bearer") .accessToken(accessToken) .refreshToken(refreshToken) .build(); } // JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 코드 // 토큰으로 클레임을 만들고 이를 이용해 유저 객체를 만들어서 최종적으로 authentication 객체를 리턴 // 인증 정보 조회 public Authentication getAuthentication(String token) { // 토큰 복호화 메소드 Claims claims = parseClaims(token); if(claims.get("auth") == null) { throw new RuntimeException("권한 정보가 없는 토큰입니다."); } // 클레임 권한 정보 가져오기 Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // UserDetails 객체를 만들어서 Authentication 리턴 User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, token, authorities); } private Claims parseClaims(String token) { try { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } catch (ExpiredJwtException e) { return e.getClaims(); } } // 토큰의 유효성 검증을 수행 public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { log.info("잘못된 JWT 서명입니다."); } catch (ExpiredJwtException e) { log.info("만료된 JWT 토큰입니다."); } catch (UnsupportedJwtException e) { log.info("지원되지 않는 JWT 토큰입니다."); } catch (IllegalArgumentException e) { log.info("JWT 토큰이 잘못되었습니다."); } return false; } }package com.example.jwt.domain.jwt; import lombok.*; @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor public class TokenDTO { // JWT 대한 인증 타입, 여기서는 Bearer를 사용하고 // 이후 HTTP 헤더에 prefix로 붙여주는 타입 private String grantType; private String accessToken; private String refreshToken; } 그리고 추가 질문이 있습니다. PrincipalDetailService에서 @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { UserEntity userEntity = userRepository.findByEmail(email); log.info("user : " + userEntity); return new PrincipalDetails(userEntity); }여기서 오버라이드한게 인자를 username으로 받는데 강의에서는 로그인 자체를 name으로 했지만 저는 로그인을 email형식으로 하려고해서 email로 바꿨는데 맞는건가요..?
-
미해결스프링 프레임워크는 내 손에 [스프2탄]
다음 강의 또 언제 나오나요
ㅠㅠ 6월 5일인데 언제나오나요
-
해결됨스프링부트 시큐리티 & JWT 강의
PrincipalDetails, PrincipalDetailsService에 대한 질문
PrincipalDetails시큐리티가 /login을 낚아채서 로그인을 진행시킨다.PrincipalDetailsService시큐리티 설정에서 loginProcessingUrl("/login") 처리 이게 view를 리턴하는 방식에서는 formLogin을 처리해줘서 이렇게 여기서 작성하면 controller에서 기능을 구현하지 않아도 login을 시큐리티가 알아서 해주는 걸로 알고 있는데요.그러면 REST API 방식일때는 SecurityConfig에 formLogin.disable()로 처리하는 걸로 알고 있는데 그러면 PrincipalDetails, PrincipalDetailsService은 REST 방식에서는 작성하지 않는건가요? 근데 그러면 의문이 저 2개의 클래스를 생성하지 않으면@Data public class PrincipalDetails implements UserDetails, OAuth2User { private User user; private Map<String, Object> attributes; // 일반 로그인 public PrincipalDetails(User user) { this.user = user; } // Oauth 로그인 public PrincipalDetails(User user, Map<String, Object> attributes) { this.user = user; this.attributes = attributes; } // 해당 User의 권한을 리턴하는 곳 @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> collection = new ArrayList<>(); collection.add(new GrantedAuthority() { @Override public String getAuthority() { return user.getRole(); } }); return collection; } // 사용자의 패스워드를 반환 @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getEmail(); } // 계정 만료 여부 반환 @Override public boolean isAccountNonExpired() { // 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 잠금 여부 반환 @Override public boolean isAccountNonLocked() { // true = 잠금되지 않음 return true; } // 패스워드의 만료 여부 반환 @Override public boolean isCredentialsNonExpired() { // 패스워드가 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 사용 가능 여부 반환 @Override public boolean isEnabled() { // 계정이 사용 가능한지 확인하는 로직 // true = 사용 가능 return true; } @Override public Map<String, Object> getAttributes() { return attributes; } @Override public String getName() { return null; } } 여기서 UserDtails, OAuth2User을 상속받아서 Override하는 부분은 어떻게 구현하나요?
-
미해결스프링 시큐리티
강사님께 말씀드립니다.
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 먼저 본 강의는 88000 원에 책정되어 있음을 알려 드립니다. 어디에 13만원으로 되어 있는가요? 현재 강의를 1섹센을 듣고 계시는 중인데 그 중에 현재 5개 강의를 학습한 걸로 나옵니다. 참고로 1섹션과 2섹션은 개념과 이론을 학습하는 부분이라 원래부터 강의 소스 자체를 제공하지 않고 있습니다. 5개의 강의를 들으셨는데 이미 완성된 소스라고 말씀하시는 기준이 무엇인가요? 1섹션에서는 이미 완성된 소스라는 개념 자체가 성립이 되질 않습니다. 강의소스는 깃헙에 섹션 3부터 브랜치 별로 제공되고 있습니다. 혹시 master 브랜치만 보고서 말씀하시는건지 모르겠지만 글을 작성하실 때는 정확히 사실에 기반해서 작성해 주시길 정중히 요청드립니다. 라고 수강평에 글을 올려주셨는데 제가 구매할 당시에는 이 강의는 8만8천원이 할인가였고 정가는 13만원이었습니다. 정확한 사실이고요. 섹션 1,2만 수강했다고 하는데, 지금 섹션3강의에 첫번째 강의를 듣다가 글 올렸습니다. 그러니 강사님께는 조회가 되지 않겠네요? 섹션3에서 강의를 열자마자 이미 소스가 어느정도 완성된 상태에서 강의를 진행하시잖아요? 사실에 기반하여 작성하고 있는 것 맞구요. 브랜치마다 소스도 어느정도 완성되어서 글을 올려주시잖습니까? "먼저 죄송한 말씀 드립니다.강의 소스가 강의 챕터별로 제공되지 못해 불편을 겪고 계시리라 생각합니다.제가 강의 소스를 당시 브랜치별로 만들었는데 master 를 제외하고 실수로 날려버렸습니다.다행이 얼마전 복구가 되었는데 좀 오래전이라 강의에 사용되던거와 완전 일치하지는 않을 것 같습니다.실전 프로젝트편 소스입니다.여기 가시면 브랜치별로 생성이 되어 있습니다.다만 브랜치명이 숫자로 되어 있어서 각 브랜치가 어떤 강의에 해당되는지 찾으셔야 할 것 같습니다.실전프로젝트 편 챕터순으로 되어 있으니 찾기가 그렇게 어렵지는 않을 것 같습니다.불편을 드려서 거듭 죄송합니다." 그리고 위와 같이 글을 작성해주셨는데, 강의를 구매한 사람에게 제대로된 서비스를 해주시고 계신것 맞습니까? 브랜치에 제대로 이름이 적혀있지도 않구요. 챕터순으로 되어있다는데 브랜치에 CH가 섹션입니까? 제가 이런걸 하나하나 게시판에 글을 올려서 확인받고 소스를 확인해야하나요? 애초에 제대로 공지해주시면 이렇게 되새김질 하지 않는데요. 제대로 서비스를 제공해주시고 계시지 않다고 생각 안드시나요?
-
미해결스프링 시큐리티
강사님? 섹션3의 있는 실전 프로젝트 생성강의에서
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 실전 프로젝트 생성강의에서 이미 코드가 admin, user쪽 모두 생성되어있는것같은데 깃허브 링크로 알려주세요. https://github.com/onjsdnjs/corespringsecurity이거 말구요~ 어느 브랜치인지 정확히 말씀주셔요^^
-
미해결스프링 시큐리티
강사님께 얘기합니다.
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 강사님. 소스코드를 날렸더라면, 강의 소스코드를 다시 만들어주시던가 하셔야하지 않을까요?얼마전 복구가 되었다하더라도 완전히 일치하지 않는다는게 '나몰라라'식이라서수강하는 입장에서는 괜히 학습했다는 입장입니다. 브랜치가 챕터순으로 되어있다는데, 저희는 섹션순으로 학습을 합니다만, 무슨 말씀인지 이해가 안되네요.불편을 주셨다면 이에 대해 강사님이 다시 소스를 올려주시던가 해야하지 않나요? 8만 8천원이나 금액 측정하셨다면 그에 대한 학습 퀄리티도 그만큼 준비하시는게 맞는데 뭐하자는 겁니까.아무리 스스로 학습하라고 하지만, 이에 대해 강의에 있는 소스와 전혀 다른데 뭘 어떻게 학습하란 얘기입니까.어떤 브랜치인지 정확히 기재해주던가 하세요. 이런거 기재도 못해주시나요?
-
해결됨호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
안녕하세요. 호돌맨님. 서비스 정책 로직 위치에 대해 궁금한 점이 있습니다.
좋은 강의 정말 감사드립니다. 강의 수강 도중 서비스 로직 관련 궁금한 점이 있어 문의드리게 되었습니다.제가 DDD 를 공부하면서 알게된 부분이 DDD 에서는 비즈니스 로직을 도메인에 몰아서 작성하라고 했는데 본 강의에서는 절대 서비스 정책을 도메인에 둬서는 안된다고 말씀하신 걸로 알고 있습니다. 호돌맨님께서 말씀하신 서비스 정책을 두지 말라는 조언은 DDD를 적용하지 않았기 때문인지 두지 말라고 하신건지 알고 싶습니다.(서비스 정책 == 비즈니스 로직으로 이해했습니다. 혹시 제가 이해한 부분이 잘못 됐을까요?)감사합니다.
-
미해결스프링부트 시큐리티 & JWT 강의
언제 db에 값을 넣었나요?
언제 db에 값을 넣었나요? 강의중에 넣은 영상을 본적이 없었던 것 같아서요
-
해결됨호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
인터셉터 활용?
안녕하세요 호돌맨님!JWT 관련 강의에서 ArgumentResolver를 활용해 토큰 검증을 하고 사용자 정보를 추출해 반환하셨는데, 만약 프로젝트를 진행할 때 로그인을 해야 모든 URL에 접근할 수 있다고 한다면 아래 코드처럼 인터셉터에서 토큰 검증을 하고 ArgumentResolver에선 별다른 검증 없이 subject만 추출해서 반환해도 문제가 없을까요? 혹시 제가 접근 자체를 잘못하고 있다면 알려주시면 감사하겠습니다@Slf4j @RequiredArgsConstructor public class LoginCheckInterceptor implements HandlerInterceptor { private final JwtService jwtService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String requestURI = request.getRequestURI(); log.info("인증 체크 인터셉터 실행 {}", requestURI); String accessToken = request.getHeader("Authorization"); jwtService.validateAccessToken(accessToken); try { jwtService.getSubject(accessToken); } catch (JwtException e) { throw new Unauthorized(); } return true; } } @RequiredArgsConstructor public class JwtArgumentResolver implements HandlerMethodArgumentResolver { private final JwtService jwtService; @Override public boolean supportsParameter(MethodParameter parameter) { boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class); boolean hasLoginType = LoginUser.class.isAssignableFrom(parameter.getParameterType()); return hasLoginAnnotation && hasLoginType; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { String accessToken = webRequest.getHeader("Authorization"); Long userId = jwtService.getSubject(accessToken); log.info("userId = {}", userId); return new LoginUser(userId); } }
-
미해결스프링 프레임워크는 내 손에 [스프1탄]
41강 아이디 중복확인 Ajax 질문이 있습니다
안녕하세요 선생님41강 아이디 중복확인 섹션에서 Interface인 MemberMapper.java 에서public Member registerCheck(String memID);위처럼 선언해주었고,MemberMapper.xml 에서<select id="registerCheck" resultType="srv.ges.entity.Member">SELECT * FROM mem_tbl WHERE memID = #{memID} </select>위처럼 select문에 resultType만 작성하셨는데,'memID'를 Parameter로 넘기니 ParameterType도 작성해야 하는 것 아닌가요?
-
미해결스프링 시큐리티
userDetailService
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.userDetailService를 강의에서 만들지도 않았는데 의존주입하는건 처음보네요; 깃허브 소스에도 없구요. 어떻게 구현하라는 뜻인가요.진짜 환불하고싶습니다. 내용도 너무 어렵게 설명하구요.
-
미해결스프링 시큐리티
UserDetailService
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. UserDetailService를 해당강의에서 만드신적도 없는데 의존주입을 하시네요? 깃허브 소스에도 없고;; 도대체 어떻게 이해하란 말씀이십니까.. 그리고 내용도 너무 어려워서 환불하고 싶어죽겠네요 하아 강의자료를 왜 다운받아서.. 여튼 라이브코딩도 아니고 참.. UserDetailService를 보여주셔야할것같네요;
-
미해결스프링 시큐리티
userDetailService를 작성한 적이 없는데;
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 강사님.. 너무 강의 못하시는거아닙니까.. userDetailService를 만든적도 없고 깃허브관련 소스들어가니까 있지도않은데;; 진짜 환불하고싶습니다.. 따라가기에 너무 어렵구요;;
-
미해결스프링 시큐리티
오류 질문입니다.
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.이런 오류가 뜨는데,, 어떻게 해결해야하나요..?
-
미해결스프링 시큐리티
spring boot 2.7버전에서 customUserDetailsServce 등록에 관해 질문드립니다
안녕하세요 선생님, 강의에서는 customUserDetailsServce를 configure메서드를 오버라이드 해서 적용하고 있는데요, 새로운 버전에서는 어떻게 적용을 명시적으로 하는지 잘 모르겠고 지정을 하지 않아도 잘 동작하더라구요.제가 찾아봤을 때는 @Bean CustomUserDetailsService customUserDetailsService() { return new CustomUserDetailsService(); }이런식으로 Bean으로 등록하면 된다는데, https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/user-details-service.htmlSecurityconfig파일에서 Bean으로 등록하지 않아도 자동으로 customUserDetailsService가 동작하더라구요 그래서 제가 나름대로 결론 내린 것은 userDetailsService를 구현하는, 빈으로 등록된 커스텀 userDetailsService가 있다면 폼 로그인 방식에서 자동으로 커스텀 userDetailsService를 사용한다. userDetailsService를 구현하는 여러 커스텀 userDetailsService가 잇으면 configuration에서 명시적으로 지정해주어야 한다. 라고 이해했는데, 혹시 제가 잘못 생각한 부분이 있다면 알려주시면 감사하겠습니다.
-
미해결호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
Spring boot + Vue 배포
당연히 구글링 해보셨져? 원하는 결과를 못찾으셨나요? 어떤 검색어를 입력했는지 알려주세요.문제가 발생한 코드(프로젝트)를 Github에 올리시고 링크를 알려주세요.안녕하세요! 강의 보면서 저만의 프로젝트를 열심히 만들어보고 있습니다.다름이 아니라 호돌맨님 프로젝트와 비슷하게 프로젝트 폴더 아래 front와 src (Springboot)가 따로 있는 상황이어서 인터넷을 찾아보며 vue build 결과물 (dist 폴더)를 src/main/resources/static/ 에 넣고 배포하는 식으로 진행해보았습니다.위 처럼 했을 때는 5173 => 8080 이 서로 통신하는게 아닌게 되버린 것인지 Vue에서 작성했던 코드들이 정상적으로 작동하지 않아 문제를 겪고있습니다.그 외에도 다른 글들을 참고하면서 build를 해보면 build 과정에서 오류가 발생하는 상황입니다.제대로 동작하게 배포를 하고 싶은데,vue 부분을 따로 배포하는 것 말고는 방법이 없을까요?좀 오랫동안 삽질하게 되어 질문드립니다... 방향이나 참고할만한 내용이 있을까요...?git - https://github.com/ppusda/NyangMunity
-
미해결스프링 시큐리티
EntryPoint와 Handler의 차이가 궁금합니다.
안녕하세요 인증이나 인가 예외가 터졌을 때 인증 예외의 경우에는 AuthenticationEntryPoint가 리다이렉트 등의 로직을 수행하고 AccessDeniedException의 경우에는 AccessDeniedHandler가 동작하는 것으로 이해하였습니다. 근데 둘다 결국 예외가 발생했을 때 처리를 하는 역할인데 굳이 하나는 엔트리포인트라는 객체를, 다른 하나는 핸들러 객체를 사용하는지 모르겠습니다. 저는 둘 다 엔트리포인트나 혹은 핸들러라고 할 수 있는 거 아닌가 생각이 들었는데 둘이 무슨 차이가 있는지 잘 모르겠어서요 ㅠㅠ 알려주시면 감사하겠습니다.