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 @@ -3,11 +3,13 @@
import com.back.together02be.asset.dto.response.UserStockRes;
import com.back.together02be.asset.service.AssetService;
import com.back.together02be.global.apiRes.ApiRes;
import com.back.together02be.global.security.SecurityUser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -21,9 +23,10 @@ public class AssetController {

private final AssetService assetService;

@GetMapping("/stocks/{userId}")
public ResponseEntity<ApiRes<List<UserStockRes>>> getUserStocks(@PathVariable Long userId) {
List<UserStockRes> userStocks = assetService.getUserStocks(userId);
@GetMapping("/stocks")
@Operation(summary = "보유 종목 조회")
public ResponseEntity<ApiRes<List<UserStockRes>>> getUserStocks(@AuthenticationPrincipal SecurityUser user) {
List<UserStockRes> userStocks = assetService.getUserStocks(user.getId());
return ResponseEntity.ok(new ApiRes<>("보유 종목 조회 성공", userStocks));
}
}
18 changes: 0 additions & 18 deletions src/main/java/com/back/together02be/global/config/CorsConfig.java

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

@Configuration
Expand All @@ -26,6 +27,7 @@ public class BaseInitData {
private final UsersRepository usersRepository;
private final StockRepository stockRepository;
private final UserAccountRepository userAccountRepository;
private final PasswordEncoder passwordEncoder;

@Autowired
@Lazy
Expand All @@ -45,19 +47,19 @@ public ApplicationRunner initData() {
@Transactional
public void work1() {
// 테스트용 시드 데이터 (H2 dev 환경 전용)
Users user = usersRepository.save(new Users("testuser", "password", "테스트유저"));
Users user = usersRepository.save(new Users("testuser", passwordEncoder.encode("password1234"), "테스트유저"));
userAccountRepository.save(new UserAccount(user, 0L, 50_000_000L));
}

@Transactional
public void work2() {
if (usersService.count() > 0) {
return;
}
if (usersService.count() > 0) {
return;
}

usersService.signup(new SignupReq("user1", "1234", "1234", "유저1"));
usersService.signup(new SignupReq("user2", "1234", "1234", "유저2"));
usersService.signup(new SignupReq("user3", "1234", "1234", "유저3"));
usersService.signup(new SignupReq("user1", "password01", "password01", "유저1"));
usersService.signup(new SignupReq("user2", "password02", "password02", "유저2"));
usersService.signup(new SignupReq("user3", "password03", "password03", "유저3"));
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.back.together02be.global.security;

import com.back.together02be.global.util.JwtUtil;
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.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class CustomAuthenticationFilter extends OncePerRequestFilter {

private final CustomUserDetailsService userDetailsService;

@Value("${jwt.secret}")
private String jwtSecret;

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

// 헤더에서 토큰 꺼냄
String authHeader = request.getHeader("Authorization");

// 토큰 없으면 그냥 통과
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

// "Bearer eyJhbGciOiJIUzI1NiJ9..." -> 7부터 토큰
String token = authHeader.substring(7);

Map<String, Object> payload = JwtUtil.payloadOrNull(token, jwtSecret);

// JWT 검증 후 유효하면 Security Context에 저장
if (payload != null) {
String username = (String) payload.get("username");
UserDetails userDetails = userDetailsService.loadUserByUsername(username);

Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);

SecurityContextHolder.getContext().setAuthentication(authentication);
}

filterChain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.back.together02be.global.security;

import com.back.together02be.users.entity.Users;
import com.back.together02be.users.repository.UsersRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

private final UsersRepository usersRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = usersRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 아이디입니다: " + username));

return new SecurityUser(
user.getId(),
user.getUsername(),
user.getPassword(),
user.getNickname(),
List.of()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.back.together02be.global.security;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final CustomAuthenticationFilter jwtAuthFilter;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
.requestMatchers("/favicon.ico").permitAll()
.requestMatchers("/h2-console/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers(HttpMethod.POST,
"/api/users/signup",
"/api/users/login",
"/api/users/token"
)
.permitAll()
.requestMatchers(HttpMethod.POST, "/api/users/logout").authenticated()
.anyRequest().authenticated()
)

.headers(headers -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)
)
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint((request, response, authenticationException) -> {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(401);
response.getWriter().write(
"""
{"message": "로그인 후 이용해주세요.", "data": null}
"""
);
})
.accessDeniedHandler((request, response, authenticationException) -> {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(403);
response.getWriter().write(
"""
{"message": "권한이 없습니다.", "data": null}
"""
);
})
);
return http.build();
}

// 기존의 CORS 설정을 옮김
@Bean
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.back.together02be.global.security;

import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

@Getter
public class SecurityUser extends User {

private final Long id;
private final String nickname;

public SecurityUser(
Long id,
String username,
String password,
String nickname,
Collection<? extends GrantedAuthority> authorities
Comment thread
sat7312 marked this conversation as resolved.
) {
super(username, password, authorities);
this.id = id;
this.nickname = nickname;
}
}
Loading