-
Notifications
You must be signed in to change notification settings - Fork 1
#28 추가 + swagger3.0 version #32
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
base: dev
Are you sure you want to change the base?
Changes from 24 commits
601cd6f
30cee38
edc9379
28be02e
a222825
2834361
45c15c3
a6e56ce
b89688b
60a7aab
25b9f36
2f80ed5
c2c12de
c5659f7
f44dc72
46aa786
66ab013
72a07fe
ef89cbc
cb6c9da
ada4d13
c39b706
4383f35
326509e
663e3bb
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 |
|---|---|---|
|
|
@@ -35,14 +35,11 @@ dependencies { | |
| implementation("org.springframework.boot:spring-boot-starter-validation") | ||
|
|
||
| // PostgreSQL JDBC 드라이버 의존성 | ||
| // runtimeOnly 'org.postgresql:postgresql' | ||
| runtimeOnly 'org.postgresql:postgresql' | ||
| // h2 | ||
| runtimeOnly 'com.h2database:h2' | ||
| // runtimeOnly 'com.h2database:h2' | ||
|
|
||
|
|
||
| // Jakarta Validation API 의존성 | ||
| // implementation("jakarta.validation:jakarta.validation-api") // 최신 버전 사용 권장 | ||
|
|
||
| // Spring Security 사용 시 필요한 의존성 | ||
| implementation("org.springframework.boot:spring-boot-starter-security") | ||
|
|
||
|
|
@@ -58,6 +55,11 @@ dependencies { | |
| // enable production | ||
| implementation ("org.springframework.boot:spring-boot-starter-actuator") | ||
|
|
||
| // modelmapper | ||
| implementation 'org.modelmapper:modelmapper:2.4.4' | ||
|
|
||
| // swagger3 | ||
| implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' | ||
|
Comment on lines
+58
to
+62
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. |
||
| } | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.chukapoka.server.common.authority; | ||
|
|
||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.modelmapper.ModelMapper; | ||
| import org.modelmapper.convention.MatchingStrategies; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| @RequiredArgsConstructor | ||
| public class AppConfig { | ||
| @Bean | ||
| public ModelMapper modelMapper() { | ||
| ModelMapper modelMapper = new ModelMapper(); | ||
| /** 연결 전략 : 같은 타입의 필드명이 같은 경우만 동작 */ | ||
| modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE).setSkipNullEnabled(true).setFieldMatchingEnabled(true) | ||
| .setAmbiguityIgnored(true) // id속성을 매핑에서 제외 | ||
| .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE); | ||
| return modelMapper; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.chukapoka.server.common.authority; | ||
|
|
||
| import io.swagger.v3.oas.models.Components; | ||
| import io.swagger.v3.oas.models.OpenAPI; | ||
| import io.swagger.v3.oas.models.info.Info; | ||
| import io.swagger.v3.oas.models.security.SecurityRequirement; | ||
| import io.swagger.v3.oas.models.security.SecurityScheme; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| /** swagger 의존성만 설정해도 자동 적용되지만, jwt 토큰값을 확인하기 위한 설정 */ | ||
| @Configuration | ||
| public class SwaggerConfig { | ||
|
|
||
|
|
||
| /** SwaggerConfig의 openAPI 함수에 security schemes를 추가 | ||
| * addList 부분과 addSecuritySchemes의 이름 부분은 변경이 가능하지만 둘 다 같은 이름이어야 함 */ | ||
| @Bean | ||
| public OpenAPI openAPI(){ | ||
| return new OpenAPI().addSecurityItem(new SecurityRequirement().addList("JWT")) | ||
| .components(new Components().addSecuritySchemes("JWT", createAPIKeyScheme())) | ||
| .info(new Info().title("Chukapoka API") | ||
| .description("This is Chukapoka API") | ||
| .version("v2.2.0")); | ||
| } | ||
| /** JWT를 적용하려면 confugure에 JWT SecurityScheme이 필요 */ | ||
| private SecurityScheme createAPIKeyScheme() { | ||
| return new SecurityScheme().type(SecurityScheme.Type.HTTP) | ||
| .bearerFormat("JWT") | ||
| .scheme("bearer"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package com.chukapoka.server.common.authority; | ||
| package com.chukapoka.server.common.authority.jwt; | ||
|
|
||
| import com.chukapoka.server.common.entity.Token; | ||
| import com.chukapoka.server.common.repository.TokenRepository; | ||
|
|
@@ -25,7 +25,6 @@ public class JwtAuthenticationFilter extends GenericFilterBean { | |
|
|
||
| public static final String AUTHORIZATION_HEADER = "Authorization"; | ||
| public static final String BEARER_PREFIX = "Bearer"; | ||
|
|
||
| private final JwtTokenProvider jwtTokenProvider; | ||
| private final TokenRepository tokenRepository; | ||
|
Comment on lines
25
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. The import statement and package declaration seem to be incorrect. Please ensure that the package structure matches the directory structure. - package com.chukapoka.server.common.authority;
+ package com.chukapoka.server.common.authority.jwt;
- import com.chukapoka.server.common.entity.Token;
+ import com.chukapoka.server.common.authority.jwt.JwtTokenProvider; |
||
|
|
||
|
|
@@ -36,8 +35,6 @@ public class JwtAuthenticationFilter extends GenericFilterBean { | |
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | ||
| // 1. Request Header 에서 토큰을 꺼냄 | ||
| String accessToken = resolveToken((HttpServletRequest) request); | ||
| // String data = tokenRepository.getAccessToken(token); | ||
| // System.out.println("data = " + data); | ||
| // 2. validateToken 으로 토큰 유효성 검사 | ||
| // 정상 토큰이면 해당 토큰으로 Authentication 을 가져와서 SecurityContext 에 저장 | ||
| if (StringUtils.hasText(accessToken)) { | ||
|
Comment on lines
35
to
40
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. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,10 @@ | ||
| package com.chukapoka.server.common.authority; | ||
| package com.chukapoka.server.common.authority.jwt; | ||
|
|
||
|
|
||
| import com.chukapoka.server.common.dto.CustomUser; | ||
| import com.chukapoka.server.common.dto.CustomUserDetails; | ||
| import com.chukapoka.server.common.dto.TokenDto; | ||
|
|
||
| import com.chukapoka.server.user.entity.User; | ||
| import com.chukapoka.server.user.repository.UserRepository; | ||
| import io.jsonwebtoken.*; | ||
| import io.jsonwebtoken.io.Decoders; | ||
| import io.jsonwebtoken.security.Keys; | ||
|
|
@@ -19,8 +20,10 @@ | |
|
|
||
|
|
||
| import java.security.Key; | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
Comment on lines
+23
to
+26
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. |
||
| import java.util.stream.Collectors; | ||
|
|
||
|
|
||
|
|
@@ -35,12 +38,14 @@ public class JwtTokenProvider { | |
| private static final long ACCESS_EXPIRATION_MILLISECONDS = 1000 * 60 * 30; | ||
| // Refresh Token 만료 시간 상수 (7일) | ||
| private static final long REFRESH_EXPIRATION_MILLISECONDS = 1000L * 60 * 60 * 24 * 7; | ||
| private UserRepository userRepository; | ||
| private final Key key; | ||
| // 비밀 키를 Base64 디코딩한 값으로 초기화 | ||
| @Autowired | ||
| public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) { | ||
| public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, UserRepository userRepository) { | ||
| byte[] keyBytes = Decoders.BASE64.decode(secretKey); | ||
| this.key = Keys.hmacShaKeyFor(keyBytes); | ||
| this.userRepository = userRepository; | ||
|
Comment on lines
+41
to
+48
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. Changes have been made in the constructor of JwtTokenProvider class, including the addition of UserRepository dependency injection. Review if these changes align with the overall design and requirements. - public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
+ public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, UserRepository userRepository) {71: - .claim(USER_KEY, ((CustomUserDetails) authentication.getPrincipal()).getUserId())
+ .claim(USER_KEY, ((CustomUser) authentication.getPrincipal()).getUserId())82: - .claim(USER_KEY, ((CustomUserDetails) authentication.getPrincipal()).getUserId()) // user id
+ .claim(USER_KEY, ((CustomUser) authentication.getPrincipal()).getUserId()) // user id |
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -63,7 +68,7 @@ public TokenDto createToken(Authentication authentication) { | |
| String accessToken = Jwts.builder() | ||
| .setSubject(authentication.getName()) | ||
| .claim(AUTHORITIES_KEY, authorities) // 권한 | ||
| .claim(USER_KEY, ((CustomUser) authentication.getPrincipal()).getUserId()) | ||
| .claim(USER_KEY, ((CustomUserDetails) authentication.getPrincipal()).getUserId()) | ||
| .setIssuedAt(now) | ||
| .setExpiration(accessTokenExpiresIn) // 토큰이 만료될시간 | ||
| .signWith(key, SignatureAlgorithm.HS256) // 비밀키, 암호화 알고리즘이름 | ||
|
|
@@ -74,7 +79,7 @@ public TokenDto createToken(Authentication authentication) { | |
| String refreshToken = Jwts.builder() | ||
| .setSubject(authentication.getName()) | ||
| .claim(AUTHORITIES_KEY, authorities) // 권한 | ||
| .claim(USER_KEY, ((CustomUser) authentication.getPrincipal()).getUserId()) // user id | ||
| .claim(USER_KEY, ((CustomUserDetails) authentication.getPrincipal()).getUserId()) // user id | ||
| .setIssuedAt(now) | ||
| .setExpiration(refreshExpiration) | ||
| .signWith(key, SignatureAlgorithm.HS256) | ||
|
|
@@ -83,8 +88,9 @@ public TokenDto createToken(Authentication authentication) { | |
| return TokenDto.builder() | ||
| .grantType(BEARER_TYPE) | ||
| .accessToken(accessToken) | ||
| .accessTokenExpiresIn(accessTokenExpiresIn.getTime()) | ||
| .refreshToken(refreshToken) | ||
| .atExpiration(formatDate(accessTokenExpiresIn)) | ||
| .rtExpiration(formatDate(refreshExpiration)) | ||
|
Comment on lines
91
to
+93
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. The fields - .accessTokenExpiresIn(accessTokenExpiresIn.getTime())
+ .atExpiration(formatDate(accessTokenExpiresIn))
+ .rtExpiration(formatDate(refreshExpiration)) |
||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -94,7 +100,6 @@ public TokenDto createToken(Authentication authentication) { | |
| /** | ||
| * JWT 토큰에서 사용자 정보를 추출하여 인증 객체를 반환하는 메서드 | ||
| */ | ||
|
|
||
| public Authentication getAuthentication(String token) { | ||
| Claims claims = parseClaims(token); | ||
|
|
||
|
|
@@ -112,8 +117,14 @@ public Authentication getAuthentication(String token) { | |
| .map(SimpleGrantedAuthority::new) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| // 데이터베이스에서 사용자 정보 조회 | ||
| Optional<User> userOptional = userRepository.findById(userId); | ||
| if (userOptional.isEmpty()) { | ||
| throw new RuntimeException("User not found for id: " + userId); | ||
| } | ||
| User user = userOptional.get(); | ||
| // UserDetails 객체 생성 | ||
| UserDetails principal = new CustomUser(userId, claims.getSubject(), authorities); | ||
| UserDetails principal = new CustomUserDetails(user); | ||
|
|
||
| // UsernamePasswordAuthenticationToken을 사용하여 Authentication 객체 반환 | ||
| return new UsernamePasswordAuthenticationToken(principal, "", authorities); | ||
|
Comment on lines
117
to
130
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.
There are significant changes in the method - UserDetails principal = new CustomUser(userId, claims.getSubject(), authorities);
+ UserDetails principal = new CustomUserDetails(user); |
||
|
|
@@ -156,6 +167,11 @@ public boolean isTokenExpired(String token) { | |
| Date expirationDate = claims.getExpiration(); | ||
| return expirationDate != null && expirationDate.before(new Date()); | ||
| } | ||
| /** 토큰 만료기한 날짜 포맷메서드 */ | ||
| private String formatDate(Date date) { | ||
| SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); | ||
| return dateFormat.format(date); | ||
| } | ||
|
Comment on lines
+170
to
+174
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. A new private method + /** 토큰 만료기한 날짜 포맷메서드 */
+ private String formatDate(Date date) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+ return dateFormat.format(date);
+ } |
||
|
|
||
|
|
||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package com.chukapoka.server.common.dto; | ||
|
|
||
| import com.chukapoka.server.user.entity.User; | ||
| import lombok.Getter; | ||
| import org.springframework.security.core.GrantedAuthority; | ||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
| import org.springframework.security.core.userdetails.UserDetails; | ||
|
|
||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
|
|
||
| /** | ||
| * CustomUserDetails 클래스는 Spring Security에서 제공하는 User 클래스를 확장하여 추가적인 사용자 정보를 저장하기 위한 클래스 | ||
| * 주로 사용자의 고유한 식별자(ID)를 추가로 저장하고자 할 때 사용 | ||
| */ | ||
| @Getter | ||
| public class CustomUserDetails implements UserDetails { | ||
|
|
||
| private final User user; | ||
|
|
||
| /** 일반 로그인 */ | ||
| public CustomUserDetails(User user) { | ||
| this.user = user; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<? extends GrantedAuthority> getAuthorities() { | ||
| if (user == null) { | ||
| return Collections.emptyList(); // user가 null인 경우 빈 권한 목록 반환 | ||
| } | ||
| return Collections.singleton(new SimpleGrantedAuthority(user.getAuthorities())); | ||
|
Comment on lines
+19
to
+31
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.
- return Collections.singleton(new SimpleGrantedAuthority(user.getAuthorities()));
+ return Collections.singleton(new SimpleGrantedAuthority(user.getAuthorities().toString())); |
||
| } | ||
|
|
||
| public String getEmail() { | ||
| return user.getEmail(); | ||
| } | ||
|
|
||
| public Long getUserId() { | ||
| return user.getId(); | ||
| } | ||
|
Comment on lines
+34
to
+40
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. |
||
|
|
||
| @Override | ||
| public String getPassword() { | ||
| return user.getPassword(); | ||
| } | ||
|
|
||
| @Override | ||
| public String getUsername() { | ||
| if (user != null) { | ||
| return user.getId().toString(); | ||
| } | ||
| return null; // 사용자 객체가 null인 경우 null 반환 | ||
|
Comment on lines
+47
to
+52
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. |
||
| } | ||
|
|
||
|
|
||
| /** 계정의 만료 여부 반환 (기한이 없으므로 항상 true 반환) */ | ||
| @Override | ||
| public boolean isAccountNonExpired() { | ||
| return true; | ||
| }; | ||
| /** 계정의 잠금 여부 반환 (잠금되지 않았으므로 항상 true 반환)*/ | ||
| @Override | ||
| public boolean isAccountNonLocked() { | ||
| return true; | ||
| } | ||
| /** 자격 증명의 만료 여부 반환 (기한이 없으므로 항상 true 반환)*/ | ||
| @Override | ||
| public boolean isCredentialsNonExpired() { | ||
| return true; | ||
| } | ||
| /** 계정의 활성화 여부 반환 (활성화된 계정이므로 항상 true 반환)*/ | ||
| @Override | ||
| public boolean isEnabled() { | ||
| return true; | ||
| } | ||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ public class TokenDto { | |
| private String grantType; // JWT에 대한 인증 타입. 여기서는 Bearer를 사용. 이후 HTTP 헤더에 prefix로 붙여주는 타입 | ||
| private String accessToken; | ||
| private String refreshToken; | ||
| private Long accessTokenExpiresIn; | ||
| private String atExpiration; | ||
| private String rtExpiration; | ||
|
Comment on lines
+16
to
+17
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. The field names - private Long accessTokenExpiresIn;
+ private String atExpiration;
+ private String rtExpiration; |
||
|
|
||
| } | ||
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.
org.postgresql:postgresql의존성이 주석 처리되고com.h2database:h2의존성이 추가되었습니다. 변경 사항을 확인해야 합니다.