React 에서 axios 를 통해서 백엔드에서 키를 호출하는 방법에 대해 기록한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
import React from "react";
import {useState, useEffect, useRef} from "react";
import {Link, useNavigate} from "react-router-dom";
import axios from "axios";
import { JSEncrypt } from "jsencrypt"; // npm install jsencrypt
const Signup = () => {
//const baseUrl = process.env.REACT_APP_API_BASE_URL || "http://localhost:8080";
const baseUrl = "https://spb.abc.com:8080";
const navigate = useNavigate();
const initData = {
userID: '',
password: ''
}
const [publicKey, setPublicKey] = useState("");
const [sessionId, setSessionId] = useState(""); // 세션 ID 저장
const isFetched = useRef(false); // 실행 여부 체크
useEffect(() => {
if (isFetched.current) return;
isFetched.current = true;
console.log("🔹 공개키 요청 실행됨");
// 백엔드에서 공개키 가져오기
axios.get(baseUrl + "/api/public-key")
.then(response => {
console.log("🔹 공개키 응답:", response.data);
setPublicKey(response.data.publicKey);
setSessionId(response.data.sessionId); // 세션 ID 저장
})
.catch(error => console.error("공개키 가져오기 실패", error));
}, []);
const encrypt = new JSEncrypt(); // Start our encryptor.
encrypt.setPublicKey(publicKey); // Assign our encryptor to utilize the public key.
const [formData, setFormData] = useState(initData);
const [users, setUsers] = useState([]);
// 첫번째 원소 : 현재 상태, 두번재 원소 : 상태를 바꾸어 주는 함수
const [error, setError] = useState(null);
const onChangeInput = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
// e.target.name 은 해당 input의 name을 가리킨다.
//console.log(formData);
}
const submitForm = (e) => {
e.preventDefault();
if (!sessionId) {
console.error("세션 ID가 없습니다. 공개키를 다시 요청하세요.");
setError(true);
return;
}
const params = {
username: formData.userID,
password: encrypt.encrypt(formData.password), // 비밀번호 암호화
//password: formData.password,
sessionId: sessionId // 세션 ID 포함
}
console.log(params);
axios.post(baseUrl + '/login', params)
.then((res) => {
console.log(res);
if (res.status === 200) {
console.log(res.data);
localStorage.setItem("accessToken", res.data.token);
axios.defaults.headers.common["Authorization"] = "Bearer " + res.data.token;
navigate('/');
} else {
console.log(res.data.message);
setError(true);
}
})
.catch(error => {
console.log(error.response)
});
}
return (
<div className="container h-100 mt-5">
<div className="row d-flex justify-content-center align-items-center h-100">
<div className="col-12 col-md-9 col-lg-7 col-xl-6">
<div className="card">
<div className="card-body p-5">
<h2 className="text-uppercase text-center mb-5">로그인</h2>
<form onSubmit={submitForm}>
<div className="form-outline mb-4">
<label className="form-label" htmlFor="userID">userID</label>
<input type="text" name="userID" onChange={onChangeInput} id="userID"
className="form-control form-control-lg" value={formData.userID}
required/>
</div>
<div className="form-outline mb-4">
<label className="form-label" htmlFor="password">Password</label>
<input type="password" name="password" onChange={onChangeInput} id="password"
className="form-control form-control-lg" value={formData.password}
required/>
</div>
{ error &&
<div className="alert alert-danger" role="alert">
로그인 정보를 다시 한번 확인하세요!
</div>
}
<div className="d-flex justify-content-center">
<button type="submit"
className="btn btn-primary btn-block btn-lg gradient-custom-3 text-body">로그인
</button>
</div>
<p className="text-center text-muted mt-5 mb-0">
<Link to="/register" className="fw-bold text-body"><u>회원가입</u></Link>
</p>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
export default Signup
|
백엔드 : Spring Boot 설정 파일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
package com.example.springjwt.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins(
"https://spb.abc.com",
"http://localhost:3000"
) // 정확한 출처 지정
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 메서드 명확히 설정
.allowCredentials(true) // 인증 정보 포함 허용
.allowedHeaders("Authorization", "Content-Type", "Cache-Control");
}
}
/**
* 이 설정을 유지하려면 SecurityConfig.java에서 .cors() 설정을 제거해야 한다.
* (권장) WebMvcConfigurer를 삭제하고, SecurityConfig.java에서 CORS를 관리하는 것이 좋다.
*/
package com.example.springjwt.config;
import com.example.springjwt.jwt.CustomLogoutFilter;
import com.example.springjwt.jwt.JWTFilter;
import com.example.springjwt.jwt.JWTUtil;
import com.example.springjwt.jwt.LoginFilter;
import com.example.springjwt.repository.RefreshRepository;
import com.example.springjwt.service.RSAService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import java.util.Arrays;
import java.util.Collections;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// AuthenticationManager 가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
private final JWTUtil jwtUtil;
private final RefreshRepository refreshRepository;
private final RSAService rsaService;
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, RefreshRepository refreshRepository, RSAService rsaService) {
this.authenticationConfiguration = authenticationConfiguration;
this.jwtUtil = jwtUtil;
this.refreshRepository = refreshRepository;
this.rsaService = rsaService;
}
//AuthenticationManager Bean 등록
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors((corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList(
"https://spb.abc.com",
"http://localhost:3000"
)); // allowedOrigins("*")을 사용하지 않고, 구체적인 출처를 명시
configuration.setAllowCredentials(true);
// allowedMethods("*") 대신 GET, POST, PUT, DELETE, OPTIONS를 명확하게 설정
// configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
// allowedHeaders("*") 대신 Authorization, Content-Type, Cache-Control을 지정
// configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Cache-Control"));
configuration.setExposedHeaders(Collections.singletonList("Authorization"));
configuration.setMaxAge(3600L);
return configuration;
}
})));
//csrf disable
http
.csrf((auth) -> auth.disable());
//From 로그인 방식 disable
http
.formLogin((auth) -> auth.disable());
//http basic 인증 방식 disable
http
.httpBasic((auth) -> auth.disable());
//경로별 인가 작업
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/api/join", "/login", "/", "/api/public-key", "/api/pub-key").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/reissue").permitAll()
.anyRequest().authenticated());
http
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class);
http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshRepository, rsaService), UsernamePasswordAuthenticationFilter.class);
http
.addFilterBefore(new CustomLogoutFilter(jwtUtil, refreshRepository), LogoutFilter.class);
//세션 설정
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http
.csrf((auth) -> auth.disable());
return http.build();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
package com.example.springjwt.controller;
import com.example.springjwt.dto.JoinDTO;
import com.example.springjwt.service.JoinService;
import com.example.springjwt.service.RSAService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api")
public class AuthController {
private final RSAService rsaService;
private final JoinService joinService;
@Autowired
public AuthController(RSAService rsaService, JoinService joinService) {
this.rsaService = rsaService;
this.joinService = joinService;
}
/**
* 회원 가입용 공개키
*/
// @GetMapping("/pub-key")
// public Map<String, String> getPubKey() {
// return Map.of("publicKey", rsaService.getPubKey());
// }
@GetMapping("/pub-key")
public Map<String, String> getPubKey(HttpServletRequest request) {
String clientIp = getClientIp(request);
int clientPort = request.getRemotePort(); // ✅ 클라이언트 포트 가져오기
System.out.println("📌 클라이언트 IP: " + clientIp + " | 포트: " + clientPort);
return Map.of("publicKey", rsaService.getPubKey());
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 로그인 용 공개키
*/
@GetMapping("/public-key")
public Map<String, String> getPublicKey() {
String sessionId = UUID.randomUUID().toString(); // 클라이언트마다 새로운 세션 ID 생성
String publicKey = rsaService.generateNewPublicKey(sessionId);
return Map.of("sessionId", sessionId, "publicKey", publicKey);
}
@PostMapping("/join")
public Map<String, String> joinProcess(@RequestBody JoinDTO joinDTO) {
joinService.joinProcess(joinDTO, rsaService.getPriKey());
return Map.of("message", "회원가입 성공");
}
}
|
'Spring Boot > Security' 카테고리의 다른 글
Spring Boot SSL 인증서 적용 (0) | 2025.03.09 |
---|---|
Spring Boot Security 적용 예시 화면 (0) | 2025.03.08 |
Spring Boot RSA 암호화/복호화 함수 (1) | 2025.03.04 |