FS_hansol
[Spring] newfeedproject_Trouble Shooting(auth) ๋ณธ๋ฌธ
[Spring] newfeedproject_Trouble Shooting(auth)
FS29 2025. 8. 9. 21:361) ์ธ์ฆ ๊ฐ์ฒด ๋ฑ๋ก์ด ๋ค์์
์๋ ๊ตฌํํ๋ ค๊ณ ํ๋ ์๋
JwtAuthorizationFilter ์ ๋ชฉ์ ์
- ์์ฒญ์ Authorization ํค๋์์ JWT ํ ํฐ์ ๊บผ๋ด์จ๋ค.
- ํ ํฐ์ด ๋ง๋ฃ๋์ง ์๊ณ , ์์กฐ๋์ง ์์์ง ํ์ธํ๋ค.
- ํ ํฐ ์์ ๋ค์ด์๋ ์ฌ์ฉ์ ์ ๋ณด(์ด๋ฉ์ผ)๋ฅผ ๊บผ๋ด์ DB์์ ์ง์ง ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
- ๊ทธ ์ ์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก Spring Security ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค.
- ๋ง๋ ์ธ์ฆ ๊ฐ์ฒด๋ฅผ SecurityContext์ ๋ฃ์ด์, ์ดํ ์์ฒญ ์ฒ๋ฆฌ ๊ณผ์ ์์ "์~!~! ์ด ์์ฒญ์ ์ธ์ฆ๋ ์ฌ์ฉ์ ์์ฒญ์ด๊ตฌ๋" ํ๊ณ ์ ์ ์๊ฒ ํ๋ค.
์ฆ, JWT ํ ํฐ์ผ๋ก ์ธ์ฆ๋ ์ฌ์ฉ์์์ Spring Security์ ์๋ ค์ฃผ๋ ๋ค๋ฆฌ ์ญํ
๋ฌธ์ ๊ฐ ๋ ์ฝ๋๋ ์๋์ ๊ฐ๋ค
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// Authorization ํค๋์์ Bearer ํ ํฐ์ ์ถ์ถ
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7); // "Bearer " ์ดํ์ ํ ํฐ ๋ถ๋ถ๋ง ์ถ์ถ
// ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฌ
if (JwtUtil.validateToken(token) && !"refresh".equals(JwtUtil.getUserEmailFromToken(token))) {
// JWT์์ ์ด๋ฉ์ผ์ ์ถ์ถ
String email = JwtUtil.getUserEmailFromToken(token);
// DB์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ก๋
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(email);
// UserDetails๋ฅผ UserDetailsImpl๋ก ์บ์คํ
UserDetailsImpl userDetailsImpl = (UserDetailsImpl) userDetails;
// ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
Authentication auth = new UsernamePasswordAuthenticationToken(
// ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
userDetailsImpl, null, userDetailsImpl.getAuthorities());
// ์ธ์ฆ ์ ๋ณด๋ฅผ SecurityContext์ ์ค์
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
// ํํฐ ์ฒด์ธ์์ ๋ค์ ํํฐ๋ก ๋์ด๊ฐ
filterChain.doFilter(request, response);
}
์์ธ ๋ถ์
- ์ค๋ณต ๋ก์ง ๋ฐ์
ํ ํฐ → ์ด๋ฉ์ผ ์ถ์ถ → DB ์กฐํ → ์ธ์ฆ ๊ฐ์ฒด ์์ฑ ๊ณผ์ ์ด ํํฐ ๋ด๋ถ์์ ์ง์ ๊ตฌํ๋จ
→ ๋ค๋ฅธ ํํฐ/์๋น์ค์์ ๋์ผ ๊ณผ์ ์ด ํ์ํ๋ฉด ๋ ์์ฑํด์ผ ํจ - ์ ์ง๋ณด์ ์ด๋ ค์
์ธ์ฆ ๋ก์ง์ ์์ ํ ๋ ๋ชจ๋ ํํฐ ์ฝ๋๋ฅผ ์ฐพ์์ ์์ ํด์ผ ํจ
→ ๋ฒ๊ทธ ๊ฐ๋ฅ์ฑ ์ฆ๊ฐ - ์ฝ๋ ๊ฐ๋
์ฑ ์ ํ
ํํฐ์ ํต์ฌ ์ญํ ์ด ๋ฌปํ (ํ ํฐ ๊ฒ์ฆ + ์ธ์ฆ ๊ฐ์ฒด ๋ฑ๋ก์ด ๋ค์์)
Spring Security Authentication ๊ฐ์ฒด ์์ฑ(๊ถํ์ ๋น ๋ฆฌ์คํธ)
public static Authentication getAuthentication(String token) {
String email = getUserEmailFromToken(token);
return new UsernamePasswordAuthenticationToken(email, null, List.of());
}
ํด๊ฒฐ๋ ์
- JwtUtil์ getAuthentication() ๋ฉ์๋๋ฅผ ์ถ๊ฐ
→ ํ ํฐ์์ ์ด๋ฉ์ผ ์ถ์ถ → DB ์กฐํ → ์ธ์ฆ ๊ฐ์ฒด ์์ฑ๊น์ง ํ ๋ฒ์ ์ฒ๋ฆฌ
๊ฐ์ ๋ ์ฝ๋
- UserDetailsImpl.java
@Override
public String getUsername() {
// ์ด๋ฉ์ผ์ ๋ก๊ทธ์ธ ์๋ณ์๋ก ์ด๋ค๋ฉด
return user.getUserName();
}
// getEmail์ ๋ฐ๋ก ์์ฑํจ
public String getEmail() {
return user.getEmail();
}
- JwtAuthorizationFilter.java
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (JwtUtil.validateToken(token) && !"refresh".equals(JwtUtil.getUserEmailFromToken(token))) {
// ํต์ฌ ๋ณ๊ฒฝ: ์ฌ๊ธฐ์ ์ง์ ์ธ์ฆ ๊ฐ์ฒด ์ ๋ง๋ค๊ณ , JwtUtil ๋ฉ์๋๋ก ๋์ฒด
Authentication auth = JwtUtil.getAuthentication(token, userDetailsServiceImpl);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(request, response);
}
์ฌ๊ธฐ์ ์ ์ฝ๋์
if (JwtUtil.validateToken(token) && !"refresh".equals(JwtUtil.getUserEmailFromToken(token))) {
๋ฅผ ํ ์ด์ ๋ ์๋์ ๊ฐ๋ค
public static String createAccessToken(Long userId, String email) {
return JWT.create()
.withSubject(email) // sub = ์ฌ์ฉ์ email
.withClaim("userId", userId)
.withExpiresAt(new Date(System.currentTimeMillis() + ACCESS_EXP))
.sign(algorithm);
}
Access ํ ํฐ์ subject์ ์ฌ์ฉ์์ ์ด๋ฉ์ผ์ ๋ฃ๋
public static String createRefreshToken(Long userId) {
return JWT.create()
.withSubject("refresh") // sub = "refresh" (๊ณ ์ ๋ฌธ์์ด)
.withClaim("userId", userId)
.withExpiresAt(new Date(System.currentTimeMillis() + REFRESH_EXP))
.sign(algorithm);
}
Refresh ํ ํฐ์ subject์ "refresh"๋ผ๋ ๊ณ ์ ๋ฌธ์์ด์ ๋ฃ๋๋ค
์ฆ, ๋ฆฌํ๋ ์ ํ ํฐ์ ์ฌ๋ฐ๊ธ ์ฉ๋์ง ๋ก๊ทธ์ธ ์ธ์ฆ์ธ๊ฐ์ ์ฐ๋ฉด ์๋๊ธฐ ๋๋ฌธ์, subject๊ฐ์ด "refresh"์ธ ํ ํฐ(์ฆ, ๋ฆฌํ๋ ์ ํ ํฐ)์ ํํฐ์์ ๊ฑธ๋ฌ๋ฒ๋ฆฌ๋ ๊ฑฐ์
- JwtUtil.java
public static Authentication getAuthentication(String token, UserDetailsServiceImpl userDetailsServiceImpl) {
// 1. ํ ํฐ์์ ์ด๋ฉ์ผ ๊บผ๋ด๊ธฐ
String email = getUserEmailFromToken(token);
// 2. ์ด๋ฉ์ผ๋ก DB์์ ์ ์ ์ ๋ณด ๋ก๋
UserDetailsImpl userDetails = userDetailsServiceImpl.loadUserByUsername(email);
// 3. ์ ์ ์ ๋ณด ๊ธฐ๋ฐ์ผ๋ก ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
// - principal: userDetails (์ธ์ฆ๋ ์ ์ ์ ๋ณด)
// - credentials: null (๋น๋ฐ๋ฒํธ ํ์ ์์)
// - authorities: List.of() (ํ์ฌ ๊ถํ ์์)
return new UsernamePasswordAuthenticationToken(userDetails, null, List.of());
}
- ์ ์ฝ๋
- ํํฐ ๋ด๋ถ์์ ๋งค๋ฒ ํ ํฐ ํ์ฑ → ์ด๋ฉ์ผ ์ถ์ถ → DB ์กฐํ → ์ธ์ฆ ๊ฐ์ฒด ์์ฑ → SecurityContextHolder ์ธํ => ์ค๋ณต + ์ฅํฉ
- ํ ์ฝ๋
- ํํฐ์์๋ JwtUtil.getAuthentication() ํ ์ค๋ก ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
→ ํํฐ๊ฐ ๋ ์ฝ๊ธฐ ์ฝ๊ณ , ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
→ ์ ์ง๋ณด์ ํธํจ
- ํํฐ์์๋ JwtUtil.getAuthentication() ํ ์ค๋ก ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
ํ๋ง๋๋ก ํํฐ์์ ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ๋ณต์กํ ๊ณผ์ ์ ์์ ๊ณ
JwtUtil์ด ์์์ ๋ค ํด์ฃผ๋๋ก ์์ ํจ
2) ํจ์ค์๋ ๋ณ๊ฒฝ Spring Security 403 Forbidden
์๋ ๊ตฌํํ๋ ค๊ณ ํ๋ ์๋
๋ชฉํ๋ก๋ ์ธ์ฆ๋ ์ฌ์ฉ์๋ง ํน์ API(/api/myinfo/modify/password)์ ๊ทผ ๊ฐ๋ฅํ๊ฒ ์ค์
SecurityConfig์์ URL ๊ถํ ์ค์ ์ ํ์
JWT ์ธ์ฆํํฐ๋ฅผ ํตํด SecurityContext์ ์ฌ์ฉ์ ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ์ธํ
ํ๋ ๊ตฌ์กฐ
๋ฌธ์ ๋ฐ์
์ธ์ฆํ ํฐ์ ๋ฃ๊ณ ์์ฒญํ์์๋ HTTP 403 Forbidden์๋ต
ํฌ์คํธ๋งจ์์๋ ์๋ต ์ฝ๋๋ง ๋ณด์ด๊ณ ๊ตฌ์ฒด์ ์ธ ์์ธ์ ์ ์ ์์์
์ฝ์ ์ฐ์ด๋ด๋ JWT ํ ํฐ๊ฐ์ด ์ฐํ๋๋ฐ, ๋ฌธ์ ํ์
์ด ์๋์์
์์ธ๋ถ์
application.properties์
logging.level.org.springframework.security=DEBUG
Spring Security ๋ด๋ถ ์ฒ๋ฆฌ ํ๋ฆ์ด ์ฝ์์ ์์ธํ ์ถ๋ ฅ๋จ
ํํฐ ์ฒด์ธ ์คํ ์์, ์ธ์ฆ/์ธ๊ฐ ํ๋จ๊ณผ์ SecurityContext ์ํ ๋ฑ ํ์ธ ๊ฐ๋ฅ
DEBUG o.s.s.w.FilterChainProxy : Secured POST /api/myinfo/modify/password
DEBUG o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous
DEBUG o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
์์ฒญ์ด Security FilterChain์ ์ํด ๋ณด์ ๊ฒ์ฌ๋ฅผ ๊ฑฐ์นจ
JwtAuthorizationFilter์์ ์ธ์ฆ์ด ์ธํ
๋์ง ์์ AnonymousAuthenticationToken์ผ๋ก ์ฒ๋ฆฌ ๋จ
์ธ๊ฐ ๋จ๊ณ์์ hasRole("USER")์กฐ๊ฑด ๋ถ์ถฉ์กฑ-> 403๋ฐ์
ํด๊ฒฐ๋ฐฉ๋ฒ
์ธ์ฆ ํํฐ๊ฐ ํด๋น ์์ฒญ์ ์คํตํ๊ณ ์์์
SecurityConfig ๋๋ AuthorizationFilter์์ /api/myinfo/modify/password๊ฒฝ๋ก๋ฅผ ์ ์ธ ๋ชฉ๋ก์ ์๋ชป ํฌํจํ๊ธฐ ๋๋ฌธ
- ํํฐ ์คํต์กฐ๊ฑด์์ ํด๋น๊ฒฝ๋ก ์ ๊ฑฐ
if ("POST".equalsIgnoreCase(request.getMethod())
&& "/api/auth/signin".equals(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
- SecurityConfig์ ๊ถํ ์ค์ ๋ณ๊ฒฝ
.requestMatchers("/api/myinfo/modify/password").hasRole("USER")
Spring Security๋ฌธ์ ๋ ๋ก๊ทธ ์์ด ์์ธ ํ์
์ด ์ด๋ ต๋ค
logging.level.org.springframework.security=DEBUG๋ ํ์ ๋๋ฒ๊น
๋๊ตฌ
ํํฐ ์คํต ์กฐ๊ฑด์ ์ต์ํ์ผ๋ก ์ ์งํด์ผ ํ๋ฉฐ ์ธ์ฆ์ด ํ์ํ ๊ฒฝ๋ก๋ ๋ฐ๋์ SecurityContext์ ์ธ์ฆ ๊ฐ์ฒด๊ฐ ๋ค์ด๊ฐ์ผ ํจ
3) ๋ก๊ทธ์์ 403
์๋ ๊ตฌํํ๋ ค๊ณ ํ๋ ์๋
์๋ ์๋๋ /api/auth/signout ์๋ํฌ์ธํธ์์ ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์๋ง ๋ก๊ทธ์์ ๊ฐ๋ฅํ๊ฒ ํ๊ณ ์์๋ค.
AccessToken์ Authorization ํค๋์ ๋ด์ ์์ฒญ์ ๋ณด๋ JWT ํํฐ์์ ์ธ์ฆ ์ธํ
hasRole("USER")์กฐ๊ฑด ํต๊ณผํ ๋ก๊ทธ์์ ์ฒ๋ฆฌ
๋ฌธ์ ๋ฐ์
๋ก๊ทธ์์ ์์ฒญ ์ ์๋ฒ ์๋ต
{
"timestamp": "2025-08-12T01:12:27.515+00:00",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/api/auth/signout"
}
๋๋ฒ๊ทธ ๋ก๊ทธ
[JWT] Authorization header = Bearer ...
AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
ํ ํฐ์ด ์๋๋ฐ๋ SecurityContext๊ฐ anonymous๋ก ์ธํ ๋๊ณ hasRole("USER")๊ฒ์ฌ์์ 403๋ฐ์
์์ธ ๋ถ์
JWT์ธ์ฆ ํํฐ์์ /api/auth/signout์์ฒญ์ ์คํตํ๋๋ก ์กฐ๊ฑด์ด ์ค์ ๋์ด ์์์
if ("POST".equalsIgnoreCase(request.getMethod())
&& ("/api/auth/signin".equals(request.getRequestURI())
|| "/api/auth/signout".equals(request.getRequestURI()))) {
filterChain.doFilter(request, response);
return;
}
์ด ์กฐ๊ฑด ๋๋ฌธ์ /api/auth/signout ์์ฒญ์ JWT ์ธ์ฆ์ ์ฐจ๋ฅผ ๊ฑฐ์น์ง ์์
SecurityContext๋น์ด์์
SecurityContext๊ฐ ๋น์ด ์์ผ๋ฉด Spring Security๋ AnonymousAuthenticationFilter๋ก anonymous๋ฅผ ์ธํ
ํจ
์ดํ hasRole("USER")๊ฒ์ฌ์์ ์คํจ -> 403 Forbidden ๋ฐ์
ํด๊ฒฐ ๋ฐฉ๋ฒ
๋ก๊ทธ์์์ ์ธ์ฆ ์๊ตฌ
ํํฐ ์คํต ์กฐ๊ฑด์์ /api/auth/signout์ ๊ฑฐํ๊ณ JWT ํํฐ๊ฐ ์ธ์ฆ ์ธํ
์ ํ๋๋ก ๋ณ๊ฒฝ
if ("POST".equalsIgnoreCase(request.getMethod())
&& "/api/auth/signin".equals(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
์ด๋ ๊ฒ ํ๋ฉด ํ ํฐ์ด ์ ํจํ ๊ฒฝ์ฐ SecurityContext์ ์ธ์ฆ ์ ๋ณด๊ฐ ์ธํ ๋๊ณ hasRole("USER") ํต๊ณผ
'Spring_๋ถ์บ > Trouble Tang Tang ๐ซ๐ซ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] Trouble Gradle (0) | 2025.08.20 |
---|---|
[Java] Level.3_Trouble Shooting (0) | 2025.07.17 |
[Java] Level.2_Trouble Shooting (0) | 2025.07.14 |
[Java] Level.1_Trouble Shooting (3) | 2025.07.10 |
[Spring] 2์ฃผ์ฐจ-clone trouble (1) | 2025.07.09 |