Spring Security์์ ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด๋ ๊ฐ์ฒด
Authentication ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค์ด๋ฉฐ "ํ์ฌ ์ฌ์ฉ์๊ฐ ๋๊ตฌ์ธ์ง", "์ด๋ค ๊ถํ์ ๊ฐ์ง๊ณ ์๋์ง" ๋ฅผ ๋ํ๋ธ๋ค.
new UsernamePasswordAuthenticationToken(username, password);
isAuthenticated() โ falsenew UsernamePasswordAuthenticationToken(principal, credentials, authorities);
principal = username, credentials = null, authorities = ROLE_USERisAuthenticated() -> true
Authentication์ธํฐํ์ด์ค์ ๋ฉ์๋ ์ค ํ๋๋ก, ํ์ฌAuthentication๊ฐ์ฒด๊ฐ ์ ์์ ์ผ๋ก ์ธ์ฆ๋์๋์ง(true) ๋๋ ๊ทธ๋ ์ง ์์์ง(false)๋ฅผ ๋ฐํ
| ์ํฉ | ์์ | isAuthenticated() ๊ฐ |
|---|---|---|
| ์ธ์ฆ ์ (๋ก๊ทธ์ธ ์๋ ๋จ๊ณ) | new UsernamePasswordAuthenticationToken(username, password) | false |
| ์ธ์ฆ ํ (JWT ๊ฒ์ฆ ์๋ฃ ๋ฑ) | new UsernamePasswordAuthenticationToken(username, null, ๊ถํ๋ชฉ๋ก) | true |
// ๋ก๊ทธ์ธ ์๋ (์ธ์ฆ ์ )
UsernamePasswordAuthenticationToken unauthenticated =
new UsernamePasswordAuthenticationToken("user", "1234");
System.out.println(unauthenticated.isAuthenticated()); // false
JWT ์ธ์ฆ ํ (์ธ์ฆ ์๋ฃ)
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_USER"));
UsernamePasswordAuthenticationToken authenticated =
new UsernamePasswordAuthenticationToken("user", null, authorities);
System.out.println(authenticated.isAuthenticated()); // true
GrantedAuthority์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค ์ค ํ๋๋ก "์ด ์ฌ์ฉ์๊ฐ ์ด๋ค ๊ถํ(์ญํ )์ ๊ฐ์ง๊ณ ์๋๊ฐ?" ๋ฅผ ๋ํ๋ธ๋ค.
์ฌ์ฉ์์ ๊ถํ(Role)์ ํํํ ๋ ์ฌ์ฉํ๋ ํด๋์ค (์ : ROLE_USER, ROLE_ADMIN )
String role = "USER";
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
username,
null,
Collections.singleton(new SimpleGrantedAuthority("ROLE_" + role))
);
์ฌ๊ธฐ์ "ROLE_" + role โ "ROLE_USER"
์ด ๊ถํ ์ ๋ณด๊ฐ Authentication ๊ฐ์ฒด์ ํฌํจ๋์ด ์ปจํธ๋กค๋ฌ๋ ๋ณด์ ์ค์ ์์ ์ฌ์ฉ๋๋ค.
@PreAuthorize("hasRole('USER')") // ๋ด๋ถ์ ์ผ๋ก ROLE_USER๋ฅผ ๊ธฐ๋
public void viewUserData() {
...
}
// ๋ ๋ค ์ฌ์ฉ ๊ฐ๋ฅ
http.authorizeHttpRequests()
.requestMatchers("/admin/**").hasRole("ADMIN")
์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด๋ ์ฌ์ฉ์ ์ ์ ํด๋์ค
Spring Security์์ UserDetails๋ฅผ ์ปค์คํฐ๋ง์ด์งํ ํด๋์ค๋ก Spring Security์์ ์ธ์ฆ์ ์ฒ๋ฆฌํ ๋ UserDetailsService์ loadUserByUsername() ๋ฉ์๋๋ฅผ ํตํด ๋ฐํ๋๋ ๊ฐ์ฒด๋ UserDetails ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค์ฌ์ผ ํ๋ค.
UserDetails ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค -> CustomUserDetails
public class CustomUserDetails implements UserDetails
public class CustomUserDetails implements UserDetails {
private final User user;
public CustomUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// ๊ณ์ ๋ง๋ฃ, ์ ๊ธ ๋ฑ์ ๊ธฐ๋ณธ๊ฐ ์ฒ๋ฆฌ
@Override public boolean isAccountNonExpired() { return true; }
@Override public boolean isAccountNonLocked() { return true; }
@Override public boolean isCredentialsNonExpired() { return true; }
@Override public boolean isEnabled() { return true; }
public User getUser() {
return user;
}
}
UserDetailSErvice๊ฐ DB์์ ์ฌ์ฉ์ ์ ๋ณด ์กฐํCustomerUserDetails๋ก ๊ฐ์ธ์ ๋ฐํSpring Security์์ ํ์ฌ ์คํ ์ค์ธ ์ค๋ ๋์ ๋ณด์ ์ปจํ ์คํธ๋ฅผ ์ ์ฅ/์กฐํํ๋ ํด๋์ค๋ก ์ธ์ฆ ์ ๋ณด๋ฅผ thread-safeํ๊ฒ ์ ์ฅํ๋ ์ ์ญ ์ ์ฅ์ ์ญํ ์ ํ๋ค.
public class SecurityContextHolder {
// ํ์ฌ ์ค๋ ๋์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ThreadLocal
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
public static SecurityContext getContext();
public static void setContext(SecurityContext context);
public static void clearContext();
}
ThreadLocal์ ์ด์ฉํด, ๊ฐ ์์ฒญ(์ค๋ ๋)๋ง๋ค ๋
๋ฆฝ์ ์ธ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅAuthentication ๊ฐ์ฒด๋ฅผ ์์ฑ (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().setAuthentication(auth) ํธ์ถSecurityContext์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅSecurityContextHolder.getContext().getAuthentication()์ผ๋ก ์ ๋ณด ๊บผ๋getContext() : ํ์ฌ ์์ฒญ์ ์ธ์ฆ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ setContext(SecurityContext) : ์ธ์ฆ ์ ๋ณด ์ ์ฅclearContext() : ์ธ์ฆ ์ ๋ณด ์ ๊ฑฐ (๋ก๊ทธ์์ ๋ฑ)๋๊ฐ ๋ก๊ทธ์ธํ๋์ง(Authentication) ์ด๋ค ๊ถํ์ด ์๋์ง ๋ฑ์ ์ ๋ณด๋ฅผ ๋ณด๊ดํ๋ ๋ณด์ ์ปจํ
์คํธ ๊ฐ์ฒด
// ๋ด๋ถ์๋ ๋จ ํ๋์ ํ๋๋ง ์กด์ฌ
public interface SecurityContext {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
// ๊ตฌํ์ฒด
public class SecurityContextImpl implements SecurityContext, Serializable {
private Authentication authentication;
public SecurityContextImpl() {}
public SecurityContextImpl(Authentication authentication) {
this.authentication = authentication;
}
@Override
public Authentication getAuthentication() {
return this.authentication;
}
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
// ์ธ์ฆ์ด ์๋ฃ๋๋ฉด
Authentication auth = new UsernamePasswordAuthenticationToken("user", null, ๊ถํ);
SecurityContext context = SecurityContextHolder.createEmptyContext(); // ๋น ์ปจํ
์คํธ ์์ฑ
context.setAuthentication(auth); // ์ธ์ฆ ์ ๋ณด ์ฃผ์
SecurityContextHolder.setContext(context); // SecurityContextHolder์ ์ ์ฅ
์ธ์ฆ์ด ์๋ฃ๋๋ฉด ์ธ์ ๋ SecurityContextHolder.getContext().getAuthentication() ๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊บผ๋ผ ์ ์๋ค.
ํ์ฌ ์ฌ์ฉ์๊ฐ ๋๊ตฌ์ธ์ง, ์ธ์ฆ๋์๋์ง, ์ด๋ค ๊ถํ์ด ์๋์ง๋ฅผ ๋ํ๋ด๋ ์ธํฐํ์ด์ค
Spring Security์์๋ ๋ชจ๋ ์ธ์ฆ ์ ๋ณด๋ฅผ Authentication ๊ฐ์ฒด ํ๋์ ๋ด๋๋ค.
๋ก๊ทธ์ธํ๋์ง ์ฌ๋ถ, ์ฌ์ฉ์๋ช
, ๋น๋ฐ๋ฒํธ, ๊ถํ, ์ธ์ฆ ์ํ ๋ฑ
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); // ๊ถํ ๋ชฉ๋ก
Object getCredentials(); // ๋น๋ฐ๋ฒํธ ๋ฑ ์ธ์ฆ ์๋จ
Object getDetails(); // ์์ฒญ ๊ด๋ จ ์ถ๊ฐ ์ ๋ณด (IP, ์ธ์
๋ฑ)
Object getPrincipal(); // ์ฌ์ฉ์ ์ ๋ณด
boolean isAuthenticated(); // ์ธ์ฆ ์ฌ๋ถ
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
| ๋ฉ์๋ | ์ค๋ช | ์์ |
|---|---|---|
getPrincipal() | ์ฌ์ฉ์ ์ฃผ์ฒด ์ ๋ณด | ๋ณดํต username ๋๋ UserDetails ๊ฐ์ฒด |
getCredentials() | ์ธ์ฆ ์๋จ | ๋น๋ฐ๋ฒํธ ๋๋ null (JWT ์ฌ์ฉ ์) |
getAuthorities() | ๊ถํ ๋ชฉ๋ก | ROLE_USER, ROLE_ADMIN ๋ฑ |
isAuthenticated() | ์ธ์ฆ ์ฌ๋ถ | ๋ก๊ทธ์ธ ์ฑ๊ณต ํ true |
getDetails() | ์ธ๋ถ ์ ๋ณด | IP, ์ธ์ ๋ฑ (์ต์ ) |
| ๊ตฌํ์ฒด | ์ฉ๋ |
|---|---|
UsernamePasswordAuthenticationToken | ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ์ธ์ฆ ๊ฐ์ฒด (ํผ ๋ก๊ทธ์ธ, JWT ๋ฑ) |
AnonymousAuthenticationToken | ์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์ (์ต๋ช ์ฌ์ฉ์ ์ฒ๋ฆฌ์ฉ) |
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
"user123", // principal
null, // credentials (JWT๋ผ null)
List.of(new SimpleGrantedAuthority("ROLE_USER")) // ๊ถํ
);
authentication.setAuthenticated(true);
// ํ์ ์ ์ฅ๋๋ Authentication ๊ฐ์ฒด
SecurityContextHolder.getContext().setAuthentication(authentication);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName(); // ๋๋ getPrincipal()
// ์ปจํธ๋กค๋ฌ์์ ๋ฐ๋ก ์ฃผ์
๊ฐ๋ฅ
@GetMapping("/me")
public ResponseEntity<?> getMyInfo(Authentication auth){
return ResponseEntity.ok(auth.getName());
}
| ๊ณ์ธต | ํด๋์ค | ์ฑ ์ |
|---|---|---|
| 1๏ธโฃ ์ฌ์ฉ์ ์ ๋ณด | Authentication | ์ฌ์ฉ์ ID, ์ธ์ฆ ์ฌ๋ถ, ๊ถํ ๋ฑ ์ค์ ์ธ์ฆ ์ ๋ณด |
| 2๏ธโฃ ๋ณด์ ์ปจํ ์คํธ | SecurityContext | ํ์ฌ ์์ฒญ์ ์ธ์ฆ ์ํ๋ฅผ ๋ด๊ณ ์์ (Authentication ๊ฐ์ฒด 1๊ฐ๋ง ๋ณด๊ด) |
| 3๏ธโฃ ์์ฒญ ์ค๋ ๋ ์ ์ฅ์ | SecurityContextHolder | ํ์ฌ ์์ฒญ(์ค๋ ๋)์ SecurityContext๋ฅผ ์ ์ฅ/๊ด๋ฆฌ (์ ์ ThreadLocal ์ฌ์ฉ) |
๋ฉ์๋ ๋จ์์์ @PreAuthorize, @PostAuthorize ๊ฐ์ ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋๋ก ๋ฉ์๋ ๋ณด์ ํ์ฑํ
ํด๋ผ์ด์ธํธ ์์ฒญ (ex. /user/profile)
โ
1. Spring Security Filter Chain ์์
โ
2. JwtFilter ์คํ
- Authorization ํค๋์์ ํ ํฐ ์ถ์ถ
- ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ (JwtUtil)
- ์ฌ์ฉ์ ์ ๋ณด(username, role ๋ฑ) ์ถ์ถ
- Authentication ๊ฐ์ฒด ์์ฑ
- SecurityContextHolder์ ์ ์ฅ
โ
3. ๋ค์ ํํฐ๋ค (Spring ๋ด๋ถ ์ธ์ฆ/์ธ๊ฐ ํํฐ๋ค)
- SecurityContextHolder์ Authentication ์์ผ๋ฉด
์ธ์ฆ๋ ์ฌ์ฉ์๋ก ๊ฐ์ฃผ
โ
4. ์ปจํธ๋กค๋ฌ ๋๋ฌ (@PreAuthorize ๋ฑ๋ ์ ์ฉ ๊ฐ๋ฅ)
โ
5. ์์ฒญ ์ฒ๋ฆฌ ๋ฐ ์๋ต ๋ฐํ
์ฌ์ฉ์๊ฐ /user/profile ์์ฒญ (Authorization: Bearer abc.def.ghi)
โ
[SecurityFilterChain] (filterChain()์์ ๊ตฌ์ฑ๋จ)
โ
[JwtFilter]
- Authorization ํค๋์์ ํ ํฐ ๊บผ๋
- ์ ํจํ ํ ํฐ์ธ์ง ๊ฒ์ฌ (jwtUtil.validateToken)
- ํ ํฐ์์ username, role ์ถ์ถ
- ์ธ์ฆ ๊ฐ์ฒด ์์ฑํด์ SecurityContextHolder์ ์ ์ฅ
โ
[UsernamePasswordAuthenticationFilter] ๋ฑ ๋ค์ ํํฐ๋ค
- SecurityContextHolder์ ์ธ์ฆ ์ ๋ณด๊ฐ ์์ผ๋ฏ๋ก ํต๊ณผ
โ
[Controller (@GetMapping("/user/profile"))]
- ROLE_USER or ROLE_ADMIN ํ์ธ ํ ์์ฒญ ์ฒ๋ฆฌ
โ
[์๋ต ๋ฐํ]