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
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import com.woozuda.backend.account.dto.JoinDTO;
import com.woozuda.backend.account.service.JoinService;
import com.woozuda.backend.exception.InvalidEmailException;
import com.woozuda.backend.exception.UsernameAlreadyExistsException;
import com.woozuda.backend.exception.account.InvalidEmailException;
import com.woozuda.backend.exception.account.UsernameAlreadyExistsException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -21,15 +21,7 @@ public JoinController(JoinService joinService){

@PostMapping("/join")
public ResponseEntity<Void> joinProcess(@RequestBody JoinDTO joinDTO){

try {
joinService.joinProcess(joinDTO);
}catch(InvalidEmailException e){
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}catch(UsernameAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}

joinService.joinProcess(joinDTO);
return ResponseEntity.status(HttpStatus.OK).build();
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/woozuda/backend/account/dto/JoinDTO.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.woozuda.backend.account.dto;


import com.woozuda.backend.account.entity.AiType;
import com.woozuda.backend.account.entity.UserEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class JoinDTO {
String username;
String password;

public static JoinDTO transDTO(UserEntity userEntity){
return new JoinDTO(userEntity.getUsername(), userEntity.getPassword());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ public class UserEntity extends BaseTimeEntity {
private String provider;

public static UserEntity transEntity(JoinDTO joinDTO){
return new UserEntity(null, joinDTO.getUsername(), joinDTO.getPassword(), "ROLE_ADMIN", AiType.PICTURE_NOVEL, true, joinDTO.getUsername(), "woozuda");
return new UserEntity(null, joinDTO.getUsername(), joinDTO.getPassword(), "ROLE_USER", AiType.PICTURE_NOVEL, true, joinDTO.getUsername(), "woozuda");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic

if (existData == null) {

UserEntity userEntity = new UserEntity(null, username, null, "ROLE_ADMIN", AiType.PICTURE_NOVEL, true, oAuth2Response.getEmail(), oAuth2Response.getProvider());
UserEntity userEntity = new UserEntity(null, username, null, "ROLE_USER", AiType.PICTURE_NOVEL, true, oAuth2Response.getEmail(), oAuth2Response.getProvider());
userRepository.save(userEntity);
shortLinkUtil.saveShortLink(userEntity);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx
log.info("로그인 디버깅 로그 - ");
log.info(username);
UserEntity userData = userRepository.findByUsername(username);

if(userData != null){
return new CustomUser(userData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
import com.woozuda.backend.account.dto.JoinDTO;
import com.woozuda.backend.account.entity.UserEntity;
import com.woozuda.backend.account.repository.UserRepository;
import com.woozuda.backend.exception.InvalidEmailException;
import com.woozuda.backend.exception.UsernameAlreadyExistsException;
import com.woozuda.backend.exception.account.InvalidEmailException;
import com.woozuda.backend.exception.account.UsernameAlreadyExistsException;
import com.woozuda.backend.shortlink.util.ShortLinkUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -28,7 +27,7 @@ public class JoinService {
private final ShortLinkUtil shortLinkUtil;

@Transactional
public void joinProcess(JoinDTO joinDTO){
public JoinDTO joinProcess(JoinDTO joinDTO){

if(!isValidEmail(joinDTO.getUsername())){
throw new InvalidEmailException("잘못된 이메일 형식을 입력 했습니다");
Expand All @@ -46,15 +45,15 @@ public void joinProcess(JoinDTO joinDTO){
UserEntity data = UserEntity.transEntity(joinDTO);

//레포지터리에 entity를 저장합니다
userRepository.save(data);
UserEntity newUser = userRepository.save(data);

// 유저에 대한 숏링크 제작
shortLinkUtil.saveShortLink(data);


return JoinDTO.transDTO(newUser);
}

public static boolean isValidEmail(String username){
public boolean isValidEmail(String username){

// 이메일 주소 형식이 아닌 경우 false
String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public void alarmTest(String username){
}
}

@Scheduled(fixedDelay = 60000) // 60초마다 하트비트
@Scheduled(fixedDelay = 21600000) // 360분(6시간)마다 하트비트
public void sendHeartbeat() {

//log.info("하트비트 확인용 ");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.woozuda.backend.exception.account;


import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class AccountExceptionHandler {

@ExceptionHandler(InvalidEmailException.class)
public ResponseEntity<Void> handleInvalidEmailException(InvalidEmailException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}

@ExceptionHandler(UsernameAlreadyExistsException.class)
public ResponseEntity<Void> handleUsernameAlreadyExistsException(UsernameAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.woozuda.backend.exception;
package com.woozuda.backend.exception.account;

public class InvalidEmailException extends RuntimeException{
public InvalidEmailException(String message) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.woozuda.backend.exception;
package com.woozuda.backend.exception.account;

public class UsernameAlreadyExistsException extends RuntimeException {
public UsernameAlreadyExistsException(String message) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package com.woozuda.backend.security.config;

import com.woozuda.backend.account.service.CustomOAuth2UserService;
import com.woozuda.backend.security.jwt.IPCheckFilter;
import com.woozuda.backend.security.jwt.JWTFilter;
import com.woozuda.backend.security.jwt.JWTUtil;
import com.woozuda.backend.security.jwt.LoginFilter;
import com.woozuda.backend.security.oauth2.CustomAuthenticationEntryPoint;
import com.woozuda.backend.security.oauth2.CustomSuccessHandler;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
Expand All @@ -28,6 +31,7 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@RequiredArgsConstructor
@Configuration
Expand All @@ -46,6 +50,9 @@ public class SecurityConfig {

private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

@Value("${allow-ips}")
private List<String> adminIps;

//AuthenticationManager Bean 등록
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
Expand All @@ -59,9 +66,8 @@ public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

// 필터 공통 설정
private void configureCommon(HttpSecurity http) throws Exception {
//csrf, formlogin, httpbasic 필터를 비활성화
http
.csrf((auth) -> auth.disable())
Expand All @@ -75,39 +81,65 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.userService(customOAuth2UserService))
.successHandler(customSuccessHandler)
);
http
.exceptionHandling(handling -> handling
.authenticationEntryPoint(customAuthenticationEntryPoint));

//stateless 세션 설정
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

http
.cors(cors -> cors
.configurationSource(corsConfigurationSource()));

}
@Bean
@Order(1)
public SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception {

configureCommon(http);

// /account/sample/admin 에 해당되는 api만 해당 filter chain 이 돌도록 함
http
.securityMatcher("/account/sample/admin");

//경로별 인가 작업
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join", "/error", "/account/sample/alluser", "/favicon.ico", "/api/shortlink/note/**", "/api/shortlink/ai/**", "/actuator/**").permitAll()
.requestMatchers("/account/sample/admin").hasRole("ADMIN")
.anyRequest().authenticated());

http
.exceptionHandling(handling -> handling
.authenticationEntryPoint(customAuthenticationEntryPoint));

//UserNamePasswordAuthenticationFilter 자리에 커스텀 하게 만든 LoginFilter를 실행한다.
//jwt 방식으로 구현하다 보니 , form login 을 비활성화했고, UserNamePasswordAuthenticationFilter 도 비활성화 되었음 (그래서 커스텀 구현이 필요)
http
//.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class)
//.addFilterAfter(new JWTFilter(jwtUtil), OAuth2LoginAuthenticationFilter.class)
//.addFilterAfter(new JWTFilter(jwtUtil), BasicAuthenticationFilter.class)
//.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), BasicAuthenticationFilter.class);
.addFilterAfter(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class);
//.addFilterAfter(new JWTFilter(jwtUtil), OAuth2LoginAuthenticationFilter.class)
//.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), OAuth2LoginAuthenticationFilter.class);
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new IPCheckFilter(adminIps), JWTFilter.class);


//stateless 세션 설정
return http.build();

}

@Bean
@Order(2)
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {

configureCommon(http);

//경로별 인가 작업
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join", "/error", "/account/sample/alluser", "/favicon.ico", "/api/shortlink/note/**", "/api/shortlink/ai/**", "/actuator/**").permitAll()
.anyRequest().authenticated());

//UserNamePasswordAuthenticationFilter 자리에 커스텀 하게 만든 LoginFilter를 실행한다.
//jwt 방식으로 구현하다 보니 , form login 을 비활성화했고, UserNamePasswordAuthenticationFilter 도 비활성화 되었음 (그래서 커스텀 구현이 필요)
http
.cors(cors -> cors
.configurationSource(corsConfigurationSource()));
.addFilterAfter(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class);


return http.build();
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/com/woozuda/backend/security/jwt/IPCheckFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.woozuda.backend.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.beans.factory.annotation.Value;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;

@RequiredArgsConstructor
public class IPCheckFilter extends OncePerRequestFilter {

private final List<String> adminIps;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

String remoteAddr = getClientIpAddr(request);

if (!adminIps.contains(remoteAddr)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "IP not allowed");
return;
}

filterChain.doFilter(request, response);
}

public static String getClientIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
Comment on lines +33 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

if문 조건이 전부다 동일한 것 같은데 이 부분 로직이 정확히 어떻게 되는 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

프록시 같은게 있을 때 client ip 가 바로 나오지 않는다고 하네요
아직 배포는 안해봐서 겪어보지는 못한 이슈입니다
Nginx가 프록시니까 넣어두었어요

참고 : https://stackoverflow.com/questions/4678797/how-do-i-get-the-remote-address-of-a-client-in-servlet

}
}
10 changes: 1 addition & 9 deletions src/main/java/com/woozuda/backend/security/jwt/JWTFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

String path = request.getRequestURI();

// 필터를 적용하지 않을 경로를 지정
if (path.equals("/join") || path.equals("/login") || path.equals("/account/sample/alluser") || path.startsWith("/api/shortlink/note") || path.startsWith("/api/shortlink/ai") || path.startsWith("/actuator")) {
filterChain.doFilter(request, response);
return;
}

//Authorization 을 키 값으로 가지는 것을 찾음

// 헤더 버전
//String authorization= request.getHeader("Authorization");
// String authorization = request.getHeader("Authorization");

String authorization = null;

Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,8 @@ management:
prometheus:
enabled: true #default

allow-ips: "${allow_ips}"

aes:
password: "${aes-password}"

4 changes: 3 additions & 1 deletion src/main/resources/application-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,7 @@ management:
logging:
config: "classpath:./logback-release.xml"

allow-ips: "${allow_ips}"

aes:
password: "${aes_password}"
password: "${aes_password}"
Loading
Loading