Skip to content

Commit bb300c7

Browse files
authored
Merge pull request #162 from SookmyungTackit/dev
[Release] v5 배포 (dev → main 병합)
2 parents 19c22ed + f62482e commit bb300c7

File tree

16 files changed

+214
-59
lines changed

16 files changed

+214
-59
lines changed

src/main/java/org/example/tackit/config/Redis/RedisUtil.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public void save(String key, String value) {
1616
redisTemplate.opsForValue().set(key, value);
1717
}
1818

19+
// 저장 2 : 비밀번호 재설정 토큰의 만료 시간 지정을 위해
20+
public void save(String key, String value, long timeoutSeconds) { redisTemplate.opsForValue().set(key, value, timeoutSeconds, TimeUnit.SECONDS); }
21+
1922
// 조회 (refreshToken 비교용)
2023
public String getData(String key) {
2124
return redisTemplate.opsForValue().get(key);

src/main/java/org/example/tackit/config/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
7070

7171
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
7272
.requestMatchers("/api/admin/**").hasAuthority("ADMIN") // 관리자 페이지 role 추가
73-
.requestMatchers("/auth/**") // 로그인, 회원가입은 열어주기
73+
.requestMatchers("/auth/**") // 로그인, 회원가입, 이메일 및 비밀번호 찾기는 열어주기
7474
.permitAll()
7575
.requestMatchers("/api/s3/**").permitAll()
7676
.requestMatchers("/api/actuator/**").permitAll() // actuator 경로 열기

src/main/java/org/example/tackit/config/jwt/JwtFilter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
3838
filterChain.doFilter(request, response);
3939
}
4040

41+
// 필터를 적용하지 않을 경로 지정
42+
// auth/reset-password 요청은 토큰 인증 건너뛰도록
43+
@Override
44+
protected boolean shouldNotFilter(HttpServletRequest request) {
45+
return request.getRequestURI().equals("/auth/reset-password") && request.getMethod().equals("PATCH");
46+
}
47+
4148
private String resolveToken(HttpServletRequest request) {
4249
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
4350
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {

src/main/java/org/example/tackit/config/jwt/TokenProvider.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@
1111
import org.example.tackit.domain.entity.Member;
1212
import org.example.tackit.domain.entity.Status;
1313
import org.springframework.security.authentication.BadCredentialsException;
14-
import org.springframework.security.core.userdetails.User;
1514
import org.example.tackit.domain.auth.login.dto.TokenDto;
1615
import org.springframework.beans.factory.annotation.Value;
1716
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1817
import org.springframework.security.core.Authentication;
1918
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2019
import org.springframework.security.core.GrantedAuthority;
21-
import org.springframework.security.core.userdetails.UserDetails;
2220
import org.springframework.stereotype.Component;
2321

2422
import java.security.Key;
@@ -35,6 +33,9 @@ public class TokenProvider {
3533
private static final String BEARER_TYPE = "Bearer";
3634
private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24; // 1일
3735
private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7; // 7일
36+
37+
// 비밀번호 재설정 토큰 (5분)
38+
private static final long RESET_TOKEN_EXPIRE_TIME = 1000 * 60 * 5;
3839
private final Key key;
3940
private final RedisUtil redisUtil;
4041
private final MemberRepository memberRepository;
@@ -68,6 +69,24 @@ public TokenDto generateTokenDto(Authentication authentication) {
6869
.build();
6970
}
7071

72+
// 비밀번호 재설정 전용 일회성 토큰
73+
// 보안 원칙 : 최소 권한 부여
74+
// authorities 클레임을 포함시키지 않게 하여, 다른 API 호출하지 않도록
75+
public String generateResetToken(String email) {
76+
long now = (new Date()).getTime();
77+
78+
return Jwts.builder()
79+
.setSubject(email)
80+
.setExpiration(new Date(now + RESET_TOKEN_EXPIRE_TIME))
81+
.claim("isResetToken", true)
82+
.signWith(key, SignatureAlgorithm.HS512)
83+
.compact();
84+
}
85+
86+
// 비밀번호 재설정 토큰의 만료 시간 반환
87+
public long getResetTokenExpiresIn() {
88+
return RESET_TOKEN_EXPIRE_TIME;
89+
}
7190

7291

7392
public TokenDto reissueAccessToken(String refreshToken) {
@@ -195,6 +214,17 @@ public boolean validateToken(String token) {
195214
return false;
196215
}
197216

217+
// 비밀번호 재설정 토큰인지 확인
218+
public boolean isResetToken(String token) {
219+
try {
220+
Claims claims = parseClaims(token);
221+
Object resetClaim = claims.get("isResetToken");
222+
return resetClaim instanceof Boolean && (Boolean) resetClaim;
223+
} catch (Exception e) {
224+
return false;
225+
}
226+
}
227+
198228

199229
private Claims parseClaims(String accessToken) {
200230
try {

src/main/java/org/example/tackit/domain/admin/repository/MemberRepository.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.example.tackit.domain.admin.repository;
22

33
import org.example.tackit.domain.entity.Member;
4+
import org.example.tackit.domain.entity.Role;
45
import org.example.tackit.domain.entity.Status;
56
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Modifying;
68
import org.springframework.data.jpa.repository.Query;
79
import org.springframework.data.repository.query.Param;
810

@@ -27,6 +29,17 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
2729

2830
// 탈퇴 회원 통계
2931
List<Member> findByStatus(Status status);
30-
Long countByStatus(Status status);
32+
33+
// 1년마다 뉴비 -> 시니어 자동 갱신
34+
@Modifying(clearAutomatically = true, flushAutomatically = true)
35+
@Query("UPDATE Member m " +
36+
"SET m.role = :newRole " +
37+
"WHERE m.role = :oldRole " +
38+
"AND m.joinedYear <= :thresholdYear")
39+
int bulkUpdateRole(@Param("oldRole") Role oldRole,
40+
@Param("newRole") Role newRole,
41+
@Param("thresholdYear") int thresholdYear);
42+
// 닉네임 중복확인
43+
boolean existsByNickname(String nickname);
3144

3245
}

src/main/java/org/example/tackit/domain/auth/login/controller/AuthController.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,30 @@ public ResponseEntity<FindEmailRespDto> findEmail(
5454

5555
return ResponseEntity.ok(resp);
5656
}
57+
58+
// 비밀번호 찾기
59+
@PostMapping("/find-password")
60+
public ResponseEntity<ResetTokenDto> findPassword(
61+
@RequestBody FindPwReqDto findPwReqDto
62+
) {
63+
ResetTokenDto tokenDto = authService.findPwByIdentity(
64+
findPwReqDto.getName(),
65+
findPwReqDto.getOrganization(),
66+
findPwReqDto.getEmail()
67+
);
68+
69+
return ResponseEntity.ok(tokenDto);
70+
}
71+
72+
// 비밀번호 재설정
73+
@PatchMapping("/reset-password")
74+
public ResponseEntity<?> resetPassword(
75+
@RequestHeader("Authorization") String authorizationHeader,
76+
@RequestBody ResetPwReqDto resetPwReqDto
77+
) {
78+
authService.resetPassword(authorizationHeader, resetPwReqDto.getNewPassword());
79+
80+
return ResponseEntity.ok().body(Map.of("status", "OK", "message", "비밀번호가 성공적으로 변경되었습니다."));
81+
}
5782
}
5883

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.example.tackit.domain.auth.login.dto;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class FindPwReqDto {
7+
private String organization;
8+
private String name;
9+
private String email;
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.example.tackit.domain.auth.login.dto;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class ResetPwReqDto {
7+
private String newPassword;
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.example.tackit.domain.auth.login.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
6+
@Builder
7+
@Data
8+
public class ResetTokenDto {
9+
private String grantType;
10+
private String resetToken;
11+
private Long expiresIn;
12+
}

src/main/java/org/example/tackit/domain/auth/login/repository/UserRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ public interface UserRepository extends JpaRepository<Member, Long> {
1818
Optional<Member> findByOrganizationAndName(String organization, String name);
1919

2020
Optional<Member> findByEmailAndOrganization(String email, String organization);
21+
22+
Optional<Member> findByNameAndOrganizationAndEmail(String name, String organization, String email);
2123
}
2224

0 commit comments

Comments
 (0)