Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/main/java/com/example/redunm/config/PasswordConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.redunm.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
63 changes: 37 additions & 26 deletions src/main/java/com/example/redunm/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,58 +1,69 @@
package com.example.redunm.config;

import com.example.redunm.filter.JsonAuthenticationFilter;
import com.example.redunm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

private final UserService userService;

@Autowired
public SecurityConfig(UserService userService) {
this.userService = userService;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // CSRF 비활성화
.cors(Customizer.withDefaults()) // 기본 CORS 설정
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authManager) throws Exception {
http
.csrf(csrf -> csrf.disable()) // CSRF 비활성화
.cors(Customizer.withDefaults())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/auth/signup/**", // 회원가입 관련 요청 허용
"/api/auth/login/**", // 로그인 관련 요청 허용
"/api/cart/**", // 장바구니 API 요청 허용
"/api/data-models/**", // 데이터 모델 API 요청 허용
"/css/**", // 정적 리소스 허용
"/api/auth/signup/**",
"/api/auth/login/**",
"/api/cart/**",
"/api/data-models/**",
"/css/**",
"/js/**",
"/models/**"
).permitAll()
.anyRequest().authenticated() // 그 외의 요청은 인증 필요
.anyRequest().authenticated()
)

.formLogin(form -> form
.loginPage("/login") // 커스텀 로그인 페이지
.loginProcessingUrl("/api/auth/login") // 로그인 요청 URL
.defaultSuccessUrl("/home", true) // 로그인 성공 후 이동 URL
.logout(logout -> logout
.logoutUrl("/api/auth/logout")
.logoutSuccessUrl("/")
.permitAll()
)
.formLogin(form -> form.disable());

.logout(logout -> logout
.logoutUrl("/api/auth/logout") // 로그아웃 URL
.logoutSuccessUrl("/") // 로그아웃 성공 후 이동 URL
.permitAll()
);
JsonAuthenticationFilter jsonAuthenticationFilter = new JsonAuthenticationFilter("/api/auth/login", authManager, userService);
http.addFilterBefore(jsonAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
25 changes: 9 additions & 16 deletions src/main/java/com/example/redunm/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
package com.example.redunm.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class WebConfig {
public class WebConfig implements WebMvcConfigurer {

@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://192.168.0.20:3000", "http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
};
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
18 changes: 7 additions & 11 deletions src/main/java/com/example/redunm/dto/SignUpRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,25 @@

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public class SignUpRequest {

@NotBlank(message = "아이디는 필수입니다.")
@NotBlank(message = "Username is mandatory")
private String username;

@NotBlank(message = "비밀번호는 필수입니다.")
@Size(min = 6, message = "비밀번호는 최소 6자 이상이어야 합니다.")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{6,}$",
message = "비밀번호는 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다.")
@NotBlank(message = "Password is mandatory")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;

@NotBlank(message = "비밀번호 확인은 필수입니다.")
@NotBlank(message = "Confirm Password is mandatory")
private String confirmPassword;

@NotBlank(message = "이메일은 필수입니다.")
@Email(message = "유효한 이메일 형식을 입력해주세요.")
@NotBlank(message = "Email is mandatory")
@Email(message = "Email should be valid")
private String email;

@NotBlank(message = "전화번호는 필수입니다.")
@Pattern(regexp = "^\\d{10,15}$", message = "유효한 전화번호를 입력해주세요.")
@NotBlank(message = "Phone number is mandatory")
private String phone;

// Getters and Setters
Expand Down
36 changes: 33 additions & 3 deletions src/main/java/com/example/redunm/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Document(collection = "users")
public class User {
public class User implements UserDetails {

@Id
private String id;
Expand All @@ -23,19 +27,20 @@ public void setId(String id) {
this.id = id;
}

@Override
public String getUsername() {
return username;
return email;
}

public void setUsername(String username) {
this.username = username;
}

@Override
public String getPassword() {
return password;
}

// 비밀번호는 암호화된 상태로 설정됨
public void setPassword(String password) {
this.password = password;
}
Expand All @@ -55,4 +60,29 @@ public String getPhone() {
public void setPhone(String phone) {
this.phone = phone;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
Comment on lines +64 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Return an empty collection instead of null.
Returning null might lead to NullPointerException in certain Spring Security setups.

- public Collection<? extends GrantedAuthority> getAuthorities() {
-     return null;
- }
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+     return java.util.Collections.emptyList();
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return java.util.Collections.emptyList();
}


@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.HttpRequestMethodNotSupportedException;

@ControllerAdvice
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<?> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex) {
return ResponseEntity
.status(HttpStatus.METHOD_NOT_ALLOWED)
.body("지원되지 않는 HTTP 메서드입니다. " + ex.getMethod() + "이 엔드포인트에서 지원되지 않습니다.");
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();

ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});

return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleAllExceptions(Exception ex) {
Map<String, String> error = new HashMap<>();
error.put("message", ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.example.redunm.filter;

import com.example.redunm.login.LoginRequest;
import com.example.redunm.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;

import java.io.IOException;

public class JsonAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

private final ObjectMapper objectMapper = new ObjectMapper();
private final UserService userService;

public JsonAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager, UserService userService) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authenticationManager);
this.userService = userService;
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {

if (!request.getMethod().equals("POST")) {
throw new ServletException("Authentication method not supported: " + request.getMethod());
}

LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class);

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
loginRequest.getPassword()
);

return getAuthenticationManager().authenticate(authRequest);
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
response.setContentType("application/json");
response.getWriter().write("{\"message\": \"로그인 성공\"}");
}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"이메일 또는 비밀번호가 잘못되었습니다.\"}");
}
}
Loading
Loading