์ด์ ๊ธ์์ ์ฐ๋ฆฌ๋ ์ฌ์ฉ์์ ์ธ์ฆ(Authentication) ํ๋ฆ์ ์์ฑํ์ต๋๋ค.
์ด์ ์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ ๋ฌด์์ ํ ์ ์๋๊ฐ, ์ฆ ์ธ๊ฐ(Authorization)๋ฅผ ๋ค๋ฃฐ ์ฐจ๋ก์ ๋๋ค.
Spring Security๋ ์ธ๊ฐ๋ฅผ ์ธ ๊ฐ์ง ์์ค์ผ๋ก ์ ๊ณตํฉ๋๋ค:
hasAuthority() / hasRole()@PreAuthorize, @PostAuthorize์ด๋ฒ ๊ธ์์๋ ์ด ์ธ ์์ค์ ํ๋์ ์ถ์ผ๋ก ์ฐ๊ฒฐํด,
๊ถํ ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด โ ์ญํ ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด โ ์ปค์คํ ํํฐ โ ๋ฉ์๋ ๋ณด์์ ์ ์ฒด ํ๋ฆ์ ์์ฑํฉ๋๋ค.
| ๊ตฌ๋ถ | ์ธ์ฆ (Authentication) | ์ธ๊ฐ (Authorization) |
|---|---|---|
| ๋ชฉ์ | โ๋๊ตฌ์ธ๊ฐ?โ | โ๋ฌด์์ ํ ์ ์๋๊ฐ?โ |
| ๊ฒ์ฆ ๋์ | ์ ์ (ID, ๋น๋ฐ๋ฒํธ, ํ ํฐ ๋ฑ) | ๊ถํ(Role, Authority) |
| ์คํจ ์ HTTP ์ฝ๋ | 401 Unauthorized | 403 Forbidden |
| ์์ | ํญ์ ๋จผ์ ์ํ | ์ธ์ฆ ํ ์ํ |
| ์์ | ์ฌ๊ถ ๊ฒ์ฌ | ํ์น ๊ฒ์ดํธ ์ฒดํฌ |
โ๏ธ ๋น์
- ์ฌ๊ถ ๊ฒ์ฌ: ์ ๋ถ์ ํ์ธํ๋ ์ธ์ฆ ๋จ๊ณ
- ํ์น ๊ฒ์ดํธ: ๋ชฉ์ ์ง ํฐ์ผ์ ํ์ธํ๋ ์ธ๊ฐ ๋จ๊ณ
Spring Security๋ ์ธ๊ฐ๋ฅผ ๊ถํ(GrantedAuthority) ๋จ์๋ก ๊ด๋ฆฌํฉ๋๋ค.
GrantedAuthority ์ธํฐํ์ด์คpublic interface GrantedAuthority {
String getAuthority();
}
๋ชจ๋ ๊ถํ/์ญํ ์ ๋ณด๋ ๋ฌธ์์ด ํํ๋ก ์ ์ฅ๋ฉ๋๋ค.
SimpleGrantedAuthority ๊ตฌํ์ฒดGrantedAuthority auth = new SimpleGrantedAuthority("VIEWACCOUNT");
role์ด๋ผ๋ ํ๋๋ฅผ ๊ฐ์ง๋ง, ๊ถํ(Authority)๊ณผ ์ญํ (Role) ๋ชจ๋ ์ ์ฅ ๊ฐ๋ฅUserDetails.getAuthorities()๋ฅผ ํตํด ๋ถ๋ฌ์ต๋๋ค.๊ธฐ์กด customer ํ
์ด๋ธ์ ํ๋์ role ์ปฌ๋ผ๋ง์ ๊ฐ์ก์ต๋๋ค.
์ด๋ฅผ ํ์ฅํ์ฌ 1:N ๊ด๊ณ๋ก ์ฌ๋ฌ ๊ถํ์ ์ ์ฅํ ์ ์์ต๋๋ค.
CREATE TABLE authorities (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT,
name VARCHAR(50),
FOREIGN KEY (customer_id) REFERENCES customer(id)
);
| id | customer_id | name |
|---|---|---|
| 1 | 1 | VIEWACCOUNT |
| 2 | 1 | VIEWCARDS |
| 3 | 1 | VIEWBALANCE |
| 4 | 1 | VIEWLOANS |
ํ ๊ณ ๊ฐ์ด ์ฌ๋ฌ ๊ถํ์ ๊ฐ์ง ์ ์๋๋ก ์ค๊ณ

Authority ์ํฐํฐ ์์@Entity
@Table(name = "authorities")
@Getter @Setter
public class Authority {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
}
Customer ์ํฐํฐ ๋ด ๊ด๊ณ ์ถ๊ฐ@OneToMany(mappedBy = "customer", fetch = FetchType.EAGER)
private Set<Authority> authorities;
EazyBankUserDetailsService@Override
public UserDetails loadUserByUsername(String username) {
Customer customer = repo.findByEmail(username).orElseThrow();
List<GrantedAuthority> authorities = customer.getAuthorities().stream()
.map(a -> new SimpleGrantedAuthority(a.getName()))
.toList();
return new User(customer.getEmail(), customer.getPassword(), authorities);
}
DB์์ Authority ์ํฐํฐ๋ฅผ ์ฝ์ด์ SimpleGrantedAuthority๋ก ๋ณํ์ธ์ฆ ์ฑ๊ณต ์ Authentication ๊ฐ์ฒด์ authorities ํ๋์ ์ ์ฅ๋จ
ProjectSecurityConfig ๋ด์์ ๊ฐ API๋ณ๋ก ์ ๊ทผ ๊ถํ์ ์ง์ ํฉ๋๋ค.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/myAccount").hasAuthority("VIEWACCOUNT")
.requestMatchers("/myBalance").hasAnyAuthority("VIEWBALANCE", "VIEWACCOUNT")
.requestMatchers("/myLoans").hasAuthority("VIEWLOANS")
.requestMatchers("/myCards").hasAuthority("VIEWCARDS")
.requestMatchers("/user").authenticated()
);
| ๋ฉ์๋ | ์ค๋ช |
|---|---|
hasAuthority("A") | ํน์ ๊ถํ ๋ณด์ ์ ์ ๊ทผ ํ์ฉ |
hasAnyAuthority("A","B") | ์ฌ๋ฌ ๊ถํ ์ค ํ๋๋ผ๋ ์์ผ๋ฉด ํ์ฉ |
access(expression) | SpEL ๊ธฐ๋ฐ์ ๋ณต์กํ ์กฐ๊ฑด ๊ตฌ์ฑ ๊ฐ๋ฅ |
| ๊ตฌ๋ถ | ๊ถํ(Authority) | ์ญํ (Role) |
|---|---|---|
| ์๋ฏธ | ์ธ๋ฐํ ๋์ ๋จ์ (์: VIEWACCOUNT) | ์ฌ๋ฌ ๊ถํ์ ๋ฌถ์ (์: ROLE_USER) |
| ์ ๋์ฌ | ์์ | ROLE_ (์๋ ์ธ์๋จ) |
| Spring Security ์ค์ | hasAuthority("VIEWACCOUNT") | hasRole("USER") |
| ๋ฐ์ดํฐ๋ฒ ์ด์ค | VIEWACCOUNT, VIEWBALANCE | ROLE_USER, ROLE_ADMIN |
๐งฉ ์ญํ ์ ์ฌ์ฉํ๋ฉด ๋ณด์ ๊ตฌ์ฑ์ ๋จ์ํํ ์ ์์ง๋ง,
์ธ๋ฐํ API ์ ์ด๊ฐ ํ์ํ ๋ ๊ถํ ๊ธฐ๋ฐ ์ ์ด๊ฐ ๋ ์ ํฉํฉ๋๋ค.
403 Forbidden์ด ๋ฐํ๋ ๋, ๋ฐฑ์๋์์ ์ถ๊ฐ ์ฒ๋ฆฌ๋ฅผ ํ ์๋ ์์ต๋๋ค.
Spring Security๋ ์ด๋ AuthorizationDeniedEvent๋ฅผ ๋ฐํํฉ๋๋ค.
@Component
@Slf4j
public class AuthorizationEvents {
@EventListener
public void onDenied(AuthorizationDeniedEvent event) {
Authentication auth = event.getAuthentication().get();
log.error("Authorization denied for user: {} -> {}",
auth.getName(), event.getAuthorizationDecision());
}
}
โ ์ด๋ฉ์ผ ์๋ฆผ, ๊ฐ์ฌ ๋ก๊ทธ, DB ๊ธฐ๋ก ๋ฑ ์ปค์คํ ํ์ฒ๋ฆฌ ๊ฐ๋ฅ
๋ชจ๋ ์์ฒญ์ Spring Security ํํฐ ์ฒด์ธ์ ํต๊ณผํฉ๋๋ค.

FilterChainProxy๊ฐ ๋ด๋ถ์ ์ผ๋ก ๋ชจ๋ ํํฐ๋ฅผ ๊ด๋ฆฌํ๋ฉฐ
๊ฐ ํํฐ๋ ์ธ์ฆ, ์ธ๊ฐ, ๋ก๊น , CSRF ๋ฑ์ ๋ด๋นํฉ๋๋ค.
Spring Security๋ ๋ค์ ๋ฉ์๋๋ก ํํฐ๋ฅผ ์ฃผ์ ํ ์ ์์ต๋๋ค:
| ๋ฉ์๋ | ์คํ ์์น | ์ค๋ช |
|---|---|---|
addFilterBefore() | ์ง์ ํํฐ ์ด์ | ์ธ์ฆ ์ ๋ก์ง (์ ๋ ฅ ๊ฒ์ฆ, ์์ฒญ ๋ถ์ ๋ฑ) |
addFilterAfter() | ์ง์ ํํฐ ์ดํ | ์ธ์ฆ ํ ๋ก์ง (๋ก๊ทธ, ๊ฐ์ฌ ๋ฑ) |
addFilterAt() | ๋์ผ ์์น | ์์ ๋ถ๋ช ํ (๊ถ์ฅ โ) |
@Component
public class RequestValidationBeforeFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String header = req.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.toLowerCase().contains("test")) {
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
chain.doFilter(request, response);
}
}
์ด๋ฉ์ผ์ test๊ฐ ํฌํจ๋๋ฉด ์ธ์ฆ ์ ์ฐจ๋จ
addFilterBefore(RequestValidationBeforeFilter.class, BasicAuthenticationFilter.class);
@Slf4j
@Component
public class AuthoritiesLoggingAfterFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null)
log.info("User: {}, Roles: {}", auth.getName(), auth.getAuthorities());
chain.doFilter(req, res);
}
}
์ธ์ฆ ์ฑ๊ณต ํ ์ฌ์ฉ์์ ๊ถํ์ ๋ก๊น
addFilterAfter(AuthoritiesLoggingAfterFilter.class, BasicAuthenticationFilter.class)
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig { }
| ์ด๋ ธํ ์ด์ | ์ค๋ช | ์์ |
|---|---|---|
@PreAuthorize | ํธ์ถ ์ ๊ถํ ๊ฒ์ฆ | Before method |
@PostAuthorize | ํธ์ถ ํ ๋ฐํ ๊ฒ์ฆ | After method |
@PreFilter | ์ ๋ ฅ ํํฐ๋ง | Before method |
@PostFilter | ์ถ๋ ฅ ํํฐ๋ง | After method |
@PreAuthorize("hasRole('USER')")
public List<Loan> findByCustomerId(Long id);
ROLE_USER๊ฐ ์์ผ๋ฉด ๋ฉ์๋ ํธ์ถ ์์ฒด๊ฐ ์ฐจ๋จ๋ฉ๋๋ค.
@PreFilter("filterObject.contactName != 'Test'")
public List<Contact> saveContacts(List<Contact> contacts) { ... }
@PostFilter("filterObject.contactName != 'Test'")
public List<Contact> getContacts() { ... }
@PreFilter: ์ ๋ ฅ ๋ฐ์ดํฐ ์ค ์กฐ๊ฑด์ ๋ง๋ ๊ฐ์ฒด๋ง ํ์ฉ
@PostFilter: ๋ฐํ ๋ฆฌ์คํธ์์ ์กฐ๊ฑด ๋ถ๋ง์กฑ ๊ฐ์ฒด ์ ๊ฑฐ

| ๋จ๊ณ | ๊ตฌ์ฑ | ๋ชฉ์ | ๋ํ ์์ |
|---|---|---|---|
| 1 | ์ธ์ฆ(Authentication) | ์ฌ์ฉ์ ์ ์ ๊ฒ์ฆ | UsernamePasswordAuthenticationFilter |
| 2 | ์ธ๊ฐ(Authorization) | ์ ๊ทผ ํ์ฉ ์ฌ๋ถ ํ๋จ | hasAuthority(), @PreAuthorize |
| 3 | ๊ถํ ์ ์ฅ | ๋ค์ค ๊ถํ ์ ์ฅ ๊ตฌ์กฐ | Customer โ Authority |
| 4 | ํํฐ ์ฒด์ธ | ์์ฒญ ์ /ํ ์ปค์คํ ๋ก์ง ์ฝ์ | addFilterBefore, addFilterAfter |
| 5 | ๋ฉ์๋ ๋ณด์ | ์๋น์ค/๋ฆฌํฌ์งํ ๋ฆฌ ๋จ ๊ถํ ์ ์ด | @PostFilter, @PreFilter |
โ์ธ์ฆ์ โ๋๊ตฐ๊ฐโ๋ฅผ ์ฆ๋ช ํ๊ณ ,
์ธ๊ฐ๋ โ๋ฌด์์ ํ ์ ์๋๊ฐโ๋ฅผ ํต์ ํ๋ค.โ
Spring Security์ ์ธ๊ฐ ์์คํ ์ ๋ค์์ ๋ณด์ฅํฉ๋๋ค: