-
Notifications
You must be signed in to change notification settings - Fork 1
[feat] JWT 기반 로그인, 회원가입 구현 #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3f2a9bb
156c10b
4c257ed
4c067df
62db006
bc19333
c474bc0
f8b4b5a
823e5cc
1896d58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package com.wayble.server.common.config; | ||
|
|
||
| import com.wayble.server.common.config.security.jwt.JwtAuthenticationFilter; | ||
| import com.wayble.server.common.config.security.jwt.JwtTokenProvider; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.http.HttpMethod; | ||
| import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| import org.springframework.security.config.http.SessionCreationPolicy; | ||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.security.web.SecurityFilterChain; | ||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
|
||
| @Configuration | ||
| @RequiredArgsConstructor | ||
| @EnableMethodSecurity | ||
| public class SecurityConfig { | ||
|
|
||
| private final JwtTokenProvider jwtProvider; | ||
|
|
||
| @Bean | ||
| public PasswordEncoder passwordEncoder() { | ||
| return new BCryptPasswordEncoder(); | ||
| } | ||
|
|
||
| @Bean | ||
| public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
| http | ||
| .csrf(csrf -> csrf.disable()) | ||
| .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | ||
| .authorizeHttpRequests(auth -> auth | ||
| .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() | ||
| .requestMatchers( | ||
| "/api/v1/users/signup", | ||
| "/api/v1/users/login", | ||
| "/swagger-ui/**", | ||
| "/v3/api-docs/**" | ||
| ).permitAll() | ||
| .anyRequest().authenticated() | ||
| ) | ||
| .addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class); | ||
|
|
||
| return http.build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,29 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.wayble.server.common.config.security; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @ConfigurationProperties(prefix = "jwt") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class JwtProperties { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String secret; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private long accessExp; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public String getSecret() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return secret; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public long getAccessExp() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return accessExp; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void setSecret(String secret) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.secret = secret; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void setAccessExp(long accessExp) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.accessExp = accessExp; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion JWT 설정 검증 및 보안 강화 필요 현재 구현에서 다음 사항들이 개선되어야 합니다:
다음과 같이 개선하는 것을 권장합니다: @Component
@ConfigurationProperties(prefix = "jwt")
+@Validated
public class JwtProperties {
+ @NotBlank(message = "JWT secret key cannot be blank")
+ @Size(min = 32, message = "JWT secret key must be at least 32 characters")
private String secret;
+
+ @Positive(message = "Access token expiration must be positive")
private long accessExp;
+ @PostConstruct
+ public void validateSecretKey() {
+ if (secret.getBytes().length < 32) {
+ throw new IllegalArgumentException("JWT secret key must be at least 256 bits (32 bytes)");
+ }
+ }
// getters and setters...
+
+ @Override
+ public String toString() {
+ return "JwtProperties{accessExp=" + accessExp + ", secret=[PROTECTED]}";
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.wayble.server.common.config.security.jwt; | ||
|
|
||
| import org.springframework.security.authentication.AbstractAuthenticationToken; | ||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
| import java.util.List; | ||
|
|
||
| public class JwtAuthentication extends AbstractAuthenticationToken { | ||
| private final String email; | ||
|
|
||
| public JwtAuthentication(String email) { | ||
| super(List.of(new SimpleGrantedAuthority("ROLE_USER"))); | ||
| this.email = email; | ||
| setAuthenticated(true); | ||
| } | ||
|
|
||
| @Override public Object getCredentials() { return ""; } | ||
| @Override public Object getPrincipal() { return email; } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.wayble.server.common.config.security.jwt; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.FilterChain; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.ServletException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final JwtTokenProvider jwtProvider; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected void doFilterInternal(HttpServletRequest req, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpServletResponse res, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FilterChain chain) throws ServletException, IOException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String header = req.getHeader("Authorization"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (header != null && header.startsWith("Bearer ")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String token = header.substring(7); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (jwtProvider.validateToken(token)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토큰 인증에 실패하면 사용자의 refreshToken을 바탕으로 accessToken을 재발급하는 것으로 알고 있습니다! 그런데 현재 코드에는 refreshToken 관련 로직이나 refreshToken Repository가 없어서 accessToken 만료 시에 문제가 생길 것 같아요...!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이것도 refreshToken 발급 관련 구현할때 로직 추가하겠습니다~! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String email = jwtProvider.getEmail(token); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var authentication = new JwtAuthentication(email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SecurityContextHolder.getContext().setAuthentication(authentication); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 토큰 무효화 시 SecurityContext 정리가 필요합니다. 토큰이 유효하지 않거나 검증에 실패한 경우, 기존 SecurityContext를 명시적으로 정리하는 것이 보안상 좋습니다. if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtProvider.validateToken(token)) {
String email = jwtProvider.getEmail(token);
var authentication = new JwtAuthentication(email);
SecurityContextHolder.getContext().setAuthentication(authentication);
+ } else {
+ SecurityContextHolder.clearContext();
}
+} else {
+ SecurityContextHolder.clearContext();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chain.doFilter(req, res); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,44 @@ | ||||||||||||||||||||||||
| package com.wayble.server.common.config.security.jwt; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import com.wayble.server.common.config.security.JwtProperties; | ||||||||||||||||||||||||
| import io.jsonwebtoken.*; | ||||||||||||||||||||||||
| import io.jsonwebtoken.security.Keys; | ||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import java.security.Key; | ||||||||||||||||||||||||
| import java.util.Date; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||
| public class JwtTokenProvider { | ||||||||||||||||||||||||
| private final JwtProperties jwtProperties; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private Key getSigningKey() { | ||||||||||||||||||||||||
| return Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 서명 키 생성 성능 개선이 필요합니다. 매번 새로운 키를 생성하는 것은 비효율적입니다. 키를 캐싱하여 성능을 향상시키세요. @Component
@RequiredArgsConstructor
public class JwtTokenProvider {
private final JwtProperties jwtProperties;
+ private Key signingKey;
- private Key getSigningKey() {
- return Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes());
- }
+ private Key getSigningKey() {
+ if (signingKey == null) {
+ signingKey = Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes());
+ }
+ return signingKey;
+ }🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public String generateToken(String email, String role) { | ||||||||||||||||||||||||
| return Jwts.builder() | ||||||||||||||||||||||||
| .setSubject(email) | ||||||||||||||||||||||||
| .claim("role", role) | ||||||||||||||||||||||||
| .setIssuedAt(new Date()) | ||||||||||||||||||||||||
| .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getAccessExp())) | ||||||||||||||||||||||||
| .signWith(getSigningKey(), SignatureAlgorithm.HS256) | ||||||||||||||||||||||||
| .compact(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public boolean validateToken(String token) { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token); | ||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||
| } catch (JwtException | IllegalArgumentException e) { | ||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public String getEmail(String token) { | ||||||||||||||||||||||||
| return Jwts.parserBuilder().setSigningKey(getSigningKey()).build() | ||||||||||||||||||||||||
| .parseClaimsJws(token).getBody().getSubject(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+40
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보안 취약점: 토큰 검증 없이 파싱하고 있습니다.
public String getEmail(String token) {
+ if (!validateToken(token)) {
+ throw new IllegalArgumentException("Invalid token");
+ }
return Jwts.parserBuilder().setSigningKey(getSigningKey()).build()
.parseClaimsJws(token).getBody().getSubject();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,60 @@ | ||
| package com.wayble.server.user.controller; | ||
|
|
||
| import com.wayble.server.common.response.CommonResponse; | ||
| import com.wayble.server.user.dto.UserLoginRequestDto; | ||
| import com.wayble.server.user.dto.UserRegisterRequestDto; | ||
| import com.wayble.server.user.dto.token.TokenResponseDto; | ||
| import com.wayble.server.user.service.UserService; | ||
| import com.wayble.server.user.service.auth.AuthService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.media.Content; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/v1/users") | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/users") | ||
| @Validated | ||
| public class UserController { | ||
|
|
||
| private final UserService userService; | ||
| private final AuthService authService; | ||
|
|
||
| // 참고용 컨트롤러(지우셔도 돼요) | ||
| @GetMapping("/hello") | ||
| public CommonResponse<String> hello() { | ||
| return CommonResponse.success("hello"); | ||
| @PostMapping("/signup") | ||
| @Operation( | ||
| summary = "유저 회원가입", | ||
| description = "신규 유저 회원가입을 수행합니다." | ||
| ) | ||
| @ApiResponses({ | ||
| @ApiResponse(responseCode = "200", description = "회원가입 성공", | ||
| content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))), | ||
| @ApiResponse(responseCode = "400", description = "이미 존재하는 회원", | ||
| content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))) | ||
| }) | ||
| public CommonResponse<String> signup(@RequestBody @Valid UserRegisterRequestDto req) { | ||
| userService.signup(req); | ||
| return CommonResponse.success("회원가입 성공"); | ||
| } | ||
|
|
||
| // 예외 사용 참고용 컨트롤러(지우셔도 돼요) | ||
| @GetMapping("/ex") | ||
| public CommonResponse<String> exception() { | ||
| userService.makeException(); | ||
| return CommonResponse.success("예외 발생!"); | ||
| @PostMapping("/login") | ||
| @Operation( | ||
| summary = "유저 로그인", | ||
| description = "이메일+비밀번호 로그인 및 JWT 발급" | ||
| ) | ||
| @ApiResponses({ | ||
| @ApiResponse(responseCode = "200", description = "로그인 성공", | ||
| content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))), | ||
| @ApiResponse(responseCode = "400", description = "아이디 혹은 비밀번호가 잘못됨", | ||
| content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))) | ||
| }) | ||
| public CommonResponse<TokenResponseDto> login(@RequestBody @Valid UserLoginRequestDto req) { | ||
| TokenResponseDto tokenDto = authService.login(req); | ||
| return CommonResponse.success(tokenDto); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.wayble.server.user.dto; | ||
|
|
||
| import jakarta.validation.constraints.Email; | ||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| public record UserLoginRequestDto( | ||
| @NotBlank(message = "이름 또는 닉네임은 필수입니다") | ||
| String name, | ||
|
|
||
| @NotBlank(message = "이메일은 필수입니다") | ||
| @Email(message = "유효한 이메일 형식이 아닙니다") | ||
| String email, | ||
|
|
||
| @NotBlank(message = "비밀번호는 필수입니다") | ||
| String password | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package com.wayble.server.user.dto.token; | ||
|
|
||
| public record TokenResponseDto(String accessToken) {} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보통 유저가 회원가입 할 때 accessToken과 refreshToken을 모두 발급하는 것으로 알고 있습니다! refreshToken은 아예 관련 로직이 없고 accessToken만 존재하는데 이유가 궁금합니다!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. refreshToken 저장 방법을 redis를 써서 저장해보고 싶었는데 해당 로직을 고민하고 있었습니다! 아직 토큰의 저장 방법 및 만료시 재발급, 갱신에 관한 흐름을 생각하고 있어서 우선 로그인,회원가입 로직을 구현하고 나서 추가적으로 구현할 생각이였습니다.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 저도 refreshToken 저장을 Redis에 저장하는게 제일 좋은 방법으로 알고 있습니다! 다만 저희가 프리티어인데 Elastic Search까지 써야 해서 redis 쓰면 터질 확률이 높아보여요 흑흑 (저도 그래서 추천 기능에 Redis 쓰고 싶은데 못 쓰고 있어요 ㅠㅠ) rdb로도 repository 파서 refreshToken을 저장하긴 해서 그렇게 짜야할 수도 있어요 ㅠㅠ |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
JWT 라이브러리 버전 확인 필요
JJWT 0.11.5 버전이 사용되고 있는데, 최신 버전과 보안 취약점 여부를 확인해야 합니다.
🌐 Web query:
💡 Result:
The latest version of the io.jsonwebtoken (JJWT) library is 0.12.6. (javadoc.io)
Regarding version 0.11.5, there are no direct vulnerabilities reported for this version in Snyk's vulnerability database. (security.snyk.io)
However, it's important to note that version 0.11.5 is outdated, and the latest version, 0.12.6, includes several updates and fixes. (github.com)
To ensure your application benefits from the latest security patches and features, it's recommended to upgrade to version 0.12.6.
Citations:
JWT 라이브러리 버전 업그레이드 권고
현재
io.jsonwebtoken:jjwt0.11.5에는 알려진 취약점은 없으나, 최신 버전인 0.12.6 에 보안 패치와 기능 개선이 포함되어 있습니다. 안정성과 최신 기능 활용을 위해 의존성을 다음과 같이 업데이트하시기 바랍니다.· 대상 파일:
build.gradle(36–39줄)· 변경 전:
· 제안하는 변경:
📝 Committable suggestion
🤖 Prompt for AI Agents