๐ŸŽ› ์ธ๊ฐ€(Authorization)์™€ ์ ‘๊ทผ ์ œ์–ด - ๊ถŒํ•œ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด์™€ ์ปค์Šคํ…€ ํ•„ํ„ฐ

okorionยท2025๋…„ 10์›” 6์ผ

๐Ÿ” Spring Security 6

๋ชฉ๋ก ๋ณด๊ธฐ
4/10

๐Ÿ“˜ ๋“ค์–ด๊ฐ€๋ฉฐ

์ด์ „ ๊ธ€์—์„œ ์šฐ๋ฆฌ๋Š” ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ(Authentication) ํ๋ฆ„์„ ์™„์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€, ์ฆ‰ ์ธ๊ฐ€(Authorization)๋ฅผ ๋‹ค๋ฃฐ ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค.

Spring Security๋Š” ์ธ๊ฐ€๋ฅผ ์„ธ ๊ฐ€์ง€ ์ˆ˜์ค€์œผ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  1. ์—”๋“œํฌ์ธํŠธ(API) ์ˆ˜์ค€ โ€“ hasAuthority() / hasRole()
  2. ๋ฉ”์†Œ๋“œ ์ˆ˜์ค€ โ€“ @PreAuthorize, @PostAuthorize
  3. ํ•„ํ„ฐ ์ˆ˜์ค€ โ€“ ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ํ†ตํ•œ ์š”์ฒญ ์ „/ํ›„ ์ฒ˜๋ฆฌ

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ด ์„ธ ์ˆ˜์ค€์„ ํ•˜๋‚˜์˜ ์ถ•์œผ๋กœ ์—ฐ๊ฒฐํ•ด,

๊ถŒํ•œ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด โ†’ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด โ†’ ์ปค์Šคํ…€ ํ•„ํ„ฐ โ†’ ๋ฉ”์†Œ๋“œ ๋ณด์•ˆ์˜ ์ „์ฒด ํ๋ฆ„์„ ์™„์„ฑํ•ฉ๋‹ˆ๋‹ค.


#1. ์ธ์ฆ vs ์ธ๊ฐ€

๊ตฌ๋ถ„์ธ์ฆ (Authentication)์ธ๊ฐ€ (Authorization)
๋ชฉ์ โ€œ๋ˆ„๊ตฌ์ธ๊ฐ€?โ€โ€œ๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?โ€
๊ฒ€์ฆ ๋Œ€์ƒ์‹ ์› (ID, ๋น„๋ฐ€๋ฒˆํ˜ธ, ํ† ํฐ ๋“ฑ)๊ถŒํ•œ(Role, Authority)
์‹คํŒจ ์‹œ HTTP ์ฝ”๋“œ401 Unauthorized403 Forbidden
์‹œ์ ํ•ญ์ƒ ๋จผ์ € ์ˆ˜ํ–‰์ธ์ฆ ํ›„ ์ˆ˜ํ–‰
์˜ˆ์‹œ์—ฌ๊ถŒ ๊ฒ€์‚ฌํƒ‘์Šน ๊ฒŒ์ดํŠธ ์ฒดํฌ

โœˆ๏ธ ๋น„์œ 

  • ์—ฌ๊ถŒ ๊ฒ€์‚ฌ: ์‹ ๋ถ„์„ ํ™•์ธํ•˜๋Š” ์ธ์ฆ ๋‹จ๊ณ„
  • ํƒ‘์Šน ๊ฒŒ์ดํŠธ: ๋ชฉ์ ์ง€ ํ‹ฐ์ผ“์„ ํ™•์ธํ•˜๋Š” ์ธ๊ฐ€ ๋‹จ๊ณ„

#2. Spring Security ์ธ๊ฐ€์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ

Spring Security๋Š” ์ธ๊ฐ€๋ฅผ ๊ถŒํ•œ(GrantedAuthority) ๋‹จ์œ„๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

โœ… GrantedAuthority ์ธํ„ฐํŽ˜์ด์Šค

public interface GrantedAuthority {
    String getAuthority();
}

๋ชจ๋“  ๊ถŒํ•œ/์—ญํ•  ์ •๋ณด๋Š” ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

โœ… SimpleGrantedAuthority ๊ตฌํ˜„์ฒด

GrantedAuthority auth = new SimpleGrantedAuthority("VIEWACCOUNT");
  • role์ด๋ผ๋Š” ํ•„๋“œ๋ฅผ ๊ฐ–์ง€๋งŒ, ๊ถŒํ•œ(Authority)๊ณผ ์—ญํ• (Role) ๋ชจ๋‘ ์ €์žฅ ๊ฐ€๋Šฅ
  • UserDetails.getAuthorities()๋ฅผ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.

#3. ๊ถŒํ•œ ํ…Œ์ด๋ธ” ์„ค๊ณ„

๊ธฐ์กด 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)
);

์˜ˆ์‹œ ๋ฐ์ดํ„ฐ

idcustomer_idname
11VIEWACCOUNT
21VIEWCARDS
31VIEWBALANCE
41VIEWLOANS

ํ•œ ๊ณ ๊ฐ์ด ์—ฌ๋Ÿฌ ๊ถŒํ•œ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„


#4. ์—”ํ‹ฐํ‹ฐ ๋งคํ•‘ ๊ตฌ์กฐ

โœ… 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;

#5. ๊ถŒํ•œ ์ •๋ณด ๋กœ๋”ฉ ๋กœ์ง

โœ… 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 ํ•„๋“œ์— ์ €์žฅ๋จ


#6. Spring Security ์ธ๊ฐ€ ์„ค์ •

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 ๊ธฐ๋ฐ˜์˜ ๋ณต์žกํ•œ ์กฐ๊ฑด ๊ตฌ์„ฑ ๊ฐ€๋Šฅ

#7. ๊ถŒํ•œ vs ์—ญํ• 

๊ตฌ๋ถ„๊ถŒํ•œ(Authority)์—ญํ• (Role)
์˜๋ฏธ์„ธ๋ฐ€ํ•œ ๋™์ž‘ ๋‹จ์œ„ (์˜ˆ: VIEWACCOUNT)์—ฌ๋Ÿฌ ๊ถŒํ•œ์˜ ๋ฌถ์Œ (์˜ˆ: ROLE_USER)
์ ‘๋‘์‚ฌ์—†์ŒROLE_ (์ž๋™ ์ธ์‹๋จ)
Spring Security ์„ค์ •hasAuthority("VIEWACCOUNT")hasRole("USER")
๋ฐ์ดํ„ฐ๋ฒ ์ด์ŠคVIEWACCOUNT, VIEWBALANCEROLE_USER, ROLE_ADMIN

๐Ÿงฉ ์—ญํ• ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณด์•ˆ ๊ตฌ์„ฑ์„ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ,

์„ธ๋ฐ€ํ•œ API ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋• ๊ถŒํ•œ ๊ธฐ๋ฐ˜ ์ œ์–ด๊ฐ€ ๋” ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.


#8. ๊ถŒํ•œ ๋ถ€์—ฌ ์‹คํŒจ ์ฒ˜๋ฆฌ

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 ๊ธฐ๋ก ๋“ฑ ์ปค์Šคํ…€ ํ›„์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ


#9. Security Filter Chain ์ดํ•ด

๋ชจ๋“  ์š”์ฒญ์€ Spring Security ํ•„ํ„ฐ ์ฒด์ธ์„ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค.

FilterChainProxy๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ชจ๋“  ํ•„ํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉฐ

๊ฐ ํ•„ํ„ฐ๋Š” ์ธ์ฆ, ์ธ๊ฐ€, ๋กœ๊น…, CSRF ๋“ฑ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.


#10. ์ปค์Šคํ…€ ํ•„ํ„ฐ ์ฃผ์ž…

Spring Security๋Š” ๋‹ค์Œ ๋ฉ”์†Œ๋“œ๋กœ ํ•„ํ„ฐ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

๋ฉ”์†Œ๋“œ์‹คํ–‰ ์œ„์น˜์„ค๋ช…
addFilterBefore()์ง€์ • ํ•„ํ„ฐ ์ด์ „์ธ์ฆ ์ „ ๋กœ์ง (์ž…๋ ฅ ๊ฒ€์ฆ, ์š”์ฒญ ๋ถ„์„ ๋“ฑ)
addFilterAfter()์ง€์ • ํ•„ํ„ฐ ์ดํ›„์ธ์ฆ ํ›„ ๋กœ์ง (๋กœ๊ทธ, ๊ฐ์‚ฌ ๋“ฑ)
addFilterAt()๋™์ผ ์œ„์น˜์ˆœ์„œ ๋ถˆ๋ช…ํ™• (๊ถŒ์žฅ โŒ)

โœ… ์˜ˆ์‹œ 1) RequestValidationBeforeFilter

@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);


โœ… ์˜ˆ์‹œ 2) AuthoritiesLoggingAfterFilter

@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)


#11. ๋ฉ”์†Œ๋“œ ๋ ˆ๋ฒจ ๋ณด์•ˆ

โœ… ํ™œ์„ฑํ™” ์„ค์ •

@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: ๋ฐ˜ํ™˜ ๋ฆฌ์ŠคํŠธ์—์„œ ์กฐ๊ฑด ๋ถˆ๋งŒ์กฑ ๊ฐ์ฒด ์ œ๊ฑฐ


#12. ์ „์ฒด ๊ตฌ์กฐ ์š”์•ฝ (Mermaid)


#13. ํ•™์Šต ์š”์•ฝ

๋‹จ๊ณ„๊ตฌ์„ฑ๋ชฉ์ ๋Œ€ํ‘œ ์˜ˆ์‹œ
1์ธ์ฆ(Authentication)์‚ฌ์šฉ์ž ์‹ ์› ๊ฒ€์ฆUsernamePasswordAuthenticationFilter
2์ธ๊ฐ€(Authorization)์ ‘๊ทผ ํ—ˆ์šฉ ์—ฌ๋ถ€ ํŒ๋‹จhasAuthority(), @PreAuthorize
3๊ถŒํ•œ ์ €์žฅ๋‹ค์ค‘ ๊ถŒํ•œ ์ €์žฅ ๊ตฌ์กฐCustomer โ†” Authority
4ํ•„ํ„ฐ ์ฒด์ธ์š”์ฒญ ์ „/ํ›„ ์ปค์Šคํ…€ ๋กœ์ง ์‚ฝ์ž…addFilterBefore, addFilterAfter
5๋ฉ”์†Œ๋“œ ๋ณด์•ˆ์„œ๋น„์Šค/๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋‹จ ๊ถŒํ•œ ์ œ์–ด@PostFilter, @PreFilter

๐Ÿงฉ ๊ฒฐ๋ก 

โ€œ์ธ์ฆ์€ โ€˜๋ˆ„๊ตฐ๊ฐ€โ€™๋ฅผ ์ฆ๋ช…ํ•˜๊ณ ,

์ธ๊ฐ€๋Š” โ€˜๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€โ€™๋ฅผ ํ†ต์ œํ•œ๋‹ค.โ€

Spring Security์˜ ์ธ๊ฐ€ ์‹œ์Šคํ…œ์€ ๋‹ค์Œ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค:

  • โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์˜ ๋‹ค์ค‘ ๊ถŒํ•œ ๊ด€๋ฆฌ
  • โœ… API/๋ฉ”์†Œ๋“œ/ํ•„ํ„ฐ ๋‹จ์œ„์˜ ๋‹ค์ธต ๋ณด์•ˆ
  • โœ… ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•œ ์ปค์Šคํ…€ ํ™•์žฅ์„ฑ
profile
Tech Archive 2026

0๊ฐœ์˜ ๋Œ“๊ธ€