Skip to content

Commit b8cd725

Browse files
author
bu2000
committed
realize login and signup
1 parent d4006d9 commit b8cd725

12 files changed

Lines changed: 500 additions & 6 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package com.webservice.algorithmchef.config;
2+
3+
import java.io.IOException;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.springframework.http.HttpHeaders;
8+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
9+
import org.springframework.security.core.context.SecurityContextHolder;
10+
import org.springframework.security.core.userdetails.UserDetails;
11+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
12+
import org.springframework.stereotype.Component;
13+
import org.springframework.web.filter.OncePerRequestFilter;
14+
15+
// (⭐수정) algorithmchef 프로젝트의 의존성으로 변경
16+
import com.webservice.algorithmchef.service.UserService;
17+
import com.webservice.algorithmchef.util.JwtUtil;
18+
19+
import io.jsonwebtoken.Claims;
20+
import jakarta.servlet.FilterChain;
21+
import jakarta.servlet.ServletException;
22+
import jakarta.servlet.http.HttpServletRequest;
23+
import jakarta.servlet.http.HttpServletResponse;
24+
import lombok.RequiredArgsConstructor;
25+
26+
@Component
27+
@RequiredArgsConstructor
28+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
29+
30+
private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
31+
private final JwtUtil jwtUtil;
32+
private final UserService userService; // (UserService가 UserDetailsService를 구현해야 함)
33+
34+
/**
35+
* 💡 임시 비밀번호(status="TEMPORARY") 사용자가 접근할 수 있는 유일한 경로.
36+
* 이 경로 외의 모든 API 접근은 403 Forbidden을 반환합니다.
37+
*/
38+
private static final String ALLOWED_PATH_FOR_TEMP_USER = "/auth/findPassword";
39+
40+
@Override
41+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
42+
throws ServletException, IOException {
43+
44+
log.info("JwtAuthenticationFilter 실행: URI = {}", request.getRequestURI());
45+
46+
final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
47+
48+
// 1. JWT 헤더가 없거나 'Bearer'가 아닌 경우
49+
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
50+
log.warn("Authorization 헤더가 없거나 Bearer 타입이 아님");
51+
filterChain.doFilter(request, response);
52+
return;
53+
}
54+
55+
final String token = authHeader.substring(7);
56+
Claims claims = null;
57+
58+
// 2. 토큰 유효성 검사 (만료, 변조 등)
59+
try {
60+
claims = jwtUtil.getClaims(token);
61+
} catch (Exception e) {
62+
log.warn("유효하지 않은 JWT 토큰: {}", e.getMessage());
63+
// (참고) 여기서 401 Unauthorized를 바로 반환할 수도 있으나,
64+
// SecurityConfig의 exceptionHandling에서 처리하도록 위임하는 것이 일반적입니다.
65+
}
66+
67+
// 3. 토큰이 유효하고, 아직 SecurityContext에 인증 정보가 없는 경우
68+
if (claims != null && SecurityContextHolder.getContext().getAuthentication() == null) {
69+
70+
// claims.getSubject()는 User 엔티티의 "userLoginId" (로그인 ID)여야 합니다.
71+
String userLoginId = claims.getSubject();
72+
String status = claims.get("status", String.class); // "status" 클레임 추출
73+
log.info("토큰 유효, userLoginId: {}, status: {}", userLoginId, status);
74+
75+
// 4. '임시 비밀번호' 사용자인지 확인
76+
if ("TEMPORARY".equals(status)) {
77+
String requestURI = request.getRequestURI();
78+
log.info("임시 비밀번호 사용자 접근. 요청 URI: {}", requestURI);
79+
80+
// 5. 허용된 URL이 아닌 경우 접근을 차단 (403 Forbidden)
81+
if (!ALLOWED_PATH_FOR_TEMP_USER.equals(requestURI)) {
82+
log.warn("임시 비밀번호 사용자가 허용되지 않은 API에 접근 시도: {}", requestURI);
83+
response.sendError(HttpServletResponse.SC_FORBIDDEN, "비밀번호를 변경해야 합니다.");
84+
return; // 필터 체인 중단
85+
}
86+
}
87+
88+
// 6. (정상 사용자) 또는 (임시 사용자가 허용된 URL에 접근한 경우)
89+
// UserDetails를 DB에서 조회하여 인증 토큰 생성
90+
UserDetails userDetails = userService.loadUserByUsername(userLoginId);
91+
92+
UsernamePasswordAuthenticationToken authenticationToken =
93+
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
94+
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
95+
96+
// 7. SecurityContext에 인증 정보 저장
97+
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
98+
log.info("SecurityContext에 인증 정보 저장 완료: {}", userLoginId);
99+
}
100+
101+
// 8. 다음 필터로 체인 넘김
102+
filterChain.doFilter(request, response);
103+
}
104+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.webservice.algorithmchef.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
7+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
8+
import org.springframework.security.config.http.SessionCreationPolicy;
9+
import org.springframework.security.web.SecurityFilterChain;
10+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
11+
12+
13+
14+
import lombok.RequiredArgsConstructor;
15+
16+
@Configuration
17+
@EnableWebSecurity
18+
@RequiredArgsConstructor
19+
public class SecurityConfig {
20+
21+
private final JwtAuthenticationFilter jwtAuthenticationFilter;
22+
23+
24+
@Bean
25+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
26+
http
27+
.csrf(AbstractHttpConfigurer::disable)
28+
.httpBasic(AbstractHttpConfigurer::disable)
29+
.formLogin(AbstractHttpConfigurer::disable);
30+
31+
http
32+
.sessionManagement(session -> session
33+
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
34+
35+
http
36+
.authorizeHttpRequests(authorize -> authorize
37+
// 실제 사용하는 로그인/회원가입 경로로 수정해주세요.
38+
.requestMatchers("/auth/login", "/auth/signUp").permitAll()
39+
.anyRequest().authenticated());
40+
41+
http
42+
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
43+
44+
return http.build();
45+
}
46+
47+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.webservice.algorithmchef.controller;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.http.ResponseEntity;
5+
import org.springframework.web.bind.annotation.PostMapping;
6+
import org.springframework.web.bind.annotation.RequestBody;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
import com.webservice.algorithmchef.dto.user.UserLoginRequest;
11+
import com.webservice.algorithmchef.dto.user.UserLoginResponse;
12+
import com.webservice.algorithmchef.dto.user.UserSignUpRequest;
13+
import com.webservice.algorithmchef.dto.user.UserSignUpResponse;
14+
import com.webservice.algorithmchef.service.UserService;
15+
16+
import lombok.RequiredArgsConstructor;
17+
18+
@RestController
19+
@RequiredArgsConstructor
20+
@RequestMapping("/auth")
21+
public class UserController {
22+
23+
private final UserService userService;
24+
25+
@PostMapping("/login")
26+
public ResponseEntity<?> login(@RequestBody UserLoginRequest userLoginRequest){
27+
try {
28+
UserLoginResponse userLoginResponse = userService.login(userLoginRequest);
29+
return ResponseEntity.ok(userLoginResponse);
30+
}catch(IllegalArgumentException e) {
31+
return ResponseEntity.badRequest().body(e.getMessage());
32+
}
33+
}
34+
35+
@PostMapping("/signUp")
36+
public ResponseEntity<?> signUp(@RequestBody UserSignUpRequest userSignUpRequest){
37+
try {
38+
UserSignUpResponse userSignUpResponse = userService.signUp(userSignUpRequest);
39+
return ResponseEntity.status(HttpStatus.CREATED).body(userSignUpResponse);
40+
41+
}catch (IllegalArgumentException e) {
42+
return ResponseEntity.badRequest().body(e.getMessage());
43+
}
44+
}
45+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.webservice.algorithmchef.dto.user;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public class UserLoginRequest {
7+
8+
private String userId;
9+
private String password;
10+
11+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.webservice.algorithmchef.dto.user;
2+
3+
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@AllArgsConstructor
9+
public class UserLoginResponse {
10+
11+
private String accessToken;
12+
private LoginStatus status;
13+
14+
public enum LoginStatus {
15+
SUCCESS,
16+
FORCE_PASSWORD_CHANGE
17+
}
18+
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.webservice.algorithmchef.dto.user;
2+
3+
import java.time.LocalDateTime;
4+
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import lombok.Setter;
8+
9+
@Getter
10+
@Setter
11+
@NoArgsConstructor
12+
public class UserSignUpRequest {
13+
14+
private String userId;
15+
private String password;
16+
private LocalDateTime birthDate;
17+
private String email;
18+
private String gender;
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.webservice.algorithmchef.dto.user;
2+
3+
import com.webservice.algorithmchef.model.User;
4+
5+
import lombok.Getter;
6+
7+
@Getter
8+
public class UserSignUpResponse {
9+
10+
private Long id;
11+
private String userId;
12+
private String role;
13+
14+
public UserSignUpResponse(User user) {
15+
this.id = user.getId();
16+
this.userId = user.getUserId();
17+
this.role=user.getRole();
18+
}
19+
20+
}

src/main/java/com/webservice/algorithmchef/model/User.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22

33
import java.time.LocalDate;
44
import java.time.LocalDateTime;
5+
import java.util.Collection;
6+
import java.util.Collections;
57
import java.util.List;
68

79
import org.hibernate.annotations.CreationTimestamp;
10+
import org.springframework.security.core.GrantedAuthority;
11+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
12+
import org.springframework.security.core.userdetails.UserDetails;
813

914
import jakarta.persistence.CascadeType;
1015
import jakarta.persistence.Column;
@@ -29,7 +34,7 @@
2934
@Setter
3035
@Builder
3136
@ToString
32-
public class User {
37+
public class User implements UserDetails {
3338

3439
@Id
3540
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -53,7 +58,13 @@ public class User {
5358
private String gender;
5459

5560
@Column(nullable=false)
56-
private LocalDate birthDate;
61+
private LocalDateTime birthDate;
62+
63+
@Column(nullable = false, columnDefinition = "varchar(255) default 'ROLE_USER'")
64+
private String role;
65+
66+
@Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT false")
67+
private boolean isTemporaryPassword;
5768

5869
@OneToMany(mappedBy = "user",cascade = CascadeType.ALL,orphanRemoval = true)
5970
@ToString.Exclude
@@ -63,11 +74,23 @@ public class User {
6374
@ToString.Exclude
6475
private List<UserAllergy> userAllergyList;
6576

66-
@OneToMany(mappedBy="user",cascade = CascadeType.ALL,orphanRemoval = true)
77+
@OneToOne(mappedBy="user",cascade = CascadeType.ALL,orphanRemoval = true)
6778
@ToString.Exclude
68-
private List<UserPreference> userPreferenceList;
79+
private UserPreference userPreference;
6980

70-
@OneToOne(mappedBy="user",cascade = CascadeType.ALL,orphanRemoval = true)
81+
@OneToMany(mappedBy="user",cascade = CascadeType.ALL,orphanRemoval = true)
7182
@ToString.Exclude
72-
private UserFridge userFridge;
83+
private List<UserFridge> userFridge;
84+
85+
@Override
86+
public Collection<? extends GrantedAuthority> getAuthorities() {
87+
// TODO Auto-generated method stub
88+
return Collections.singletonList(new SimpleGrantedAuthority(this.role));
89+
}
90+
91+
@Override
92+
public String getUsername() {
93+
// TODO Auto-generated method stub
94+
return this.userId;
95+
}
7396
}

src/main/resources/com/webservice/algorithmchef/model/UserPreference.java renamed to src/main/java/com/webservice/algorithmchef/model/UserPreference.java

File renamed without changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.webservice.algorithmchef.repository;
2+
3+
import java.util.Optional;
4+
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
import com.webservice.algorithmchef.model.User;
8+
9+
public interface UserRepository extends JpaRepository<User, Long> {
10+
11+
public Optional<User> findByUserId(String userId);
12+
13+
}

0 commit comments

Comments
 (0)