Spring Security ๋‚ด๋ถ€ ํ๋ฆ„ ์ดํ•ดํ•˜๊ธฐ

2024. 2. 13. 14:00ยท๐Ÿ’ป ๊ฐœ๋ฐœ/๐Ÿ€ Spring

 

 ๋งค๋ฒˆ ์ธ์ฆ, ์ธ๊ฐ€์— ๋Œ€ํ•œ ๋ถ€๋ถ„์— ๋Œ€ํ•ด ์ œ๋Œ€๋กœ ์ˆ™์ง€ํ•˜์ง€ ์•Š๊ณ , ์ฝ”๋“œ๋งŒ ๊ฐ€์ ธ๋‹ค๊ฐ€ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค. ํšŒ์‚ฌ์—์„œ๋„ ์ด์— ๋Œ€ํ•ด ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๊ฐ€ ์—†๋‹ค ๋ณด๋‹ˆ ์ตœ๊ทผ ํ† ์ด ํ”„๋กœ์ ํŠธ๋กœ ํŒจ์…˜ ์ด์ปค๋จธ์Šค ํ”Œ๋žซํผ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์— ๋Œ€ํ•ด ๊ณต๋ถ€๋„ ํ•ด๋ณด๊ณ , ์–ด๋А ์ •๋„ ๊ธฐ๋ณธ์ ์ธ ์ดํ•ด๋ฅผ ํ•˜๋ฉฐ ์ ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
์ฃผ๋กœ ์œ ๋ฐ๋ฏธ(Udemy)์˜ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๊ฐ•์˜๋ฅผ ๋“ฃ๊ณ  ์ดํ•ดํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜์˜€๊ณ , ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ๊ธฐ๋ณธ ๋™์ž‘ ํ๋ฆ„์— ๋Œ€ํ•ด ์ž‘์„ฑํ•˜์˜€๋‹ค.

 

 

Spring Security๋ž€?

Spring ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€(https://spring.io/projects/spring-security)์—์„œ๋Š” Spring Security๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์†Œ๊ฐœํ•˜๊ณ  ์žˆ๋‹ค. ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€๊ฐ€ ์ œ์ผ ์ •ํ™•ํ•˜๊ณ  ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…์ด ๋˜์–ด์žˆ์œผ๋‹ˆ ๋งŒ์•ฝ ๋” ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด ์œ„ ๋งํฌ๋กœ ์ ‘๊ทผํ•˜์—ฌ ์ฐธ๊ณ ํ•ด ๋ณด๊ธธ ์ถ”์ฒœํ•œ๋‹ค.

Spring Security๋Š” ๊ฐ•๋ ฅํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ •์˜๊ฐ€ ๊ฐ€๋Šฅํ•œ ์ธ์ฆ ๋ฐ ์•ก์„ธ์Šค ์ œ์–ด ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ์ด๋Š” Spring ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณด์•ˆ์„ ์œ„ํ•œ ์‚ฌ์‹ค์ƒ์˜ ํ‘œ์ค€์ž…๋‹ˆ๋‹ค. (๊ตฌ๊ธ€ ๋ฒˆ์—ญ)

 

 

 

 

Spring Security ์‚ฌ์šฉ ์ด์œ ?

 

 ๋งŒ์•ฝ ์šฐ๋ฆฌ๊ฐ€ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ง์ ‘ ์ธ์ฆ, ์ธ๊ฐ€์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์„ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด ์–ด๋–จ๊นŒ? ๋ฌผ๋ก  ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ด๊ฑด ๋ถ„๋ช… ์–ด๋ ค์šด ์ผ์ด ๋  ๊ฑฐ๋ผ ์ƒ๊ฐํ•œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋งค์ผ ์ˆ˜ ๋ฐฑ๊ฐœ์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ ์ด ๋ฐํ˜€์ง€๊ณ , ๋ณด์•ˆ ์œ„๋ฐ˜ ์‚ฌํ•ญ์ด ์ผ์–ด๋‚˜๊ธฐ์— ํ•ญ์ƒ ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์ธ๋‹ค๋Š” ๊ฒƒ์ด ์‰ฝ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

 ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ๊ธฐ๋ณธ์ ์ธ ๋ณด์•ˆ ์ทจ์•ฝ์ (CSRF, CORS ๋“ฑ)์— ๋Œ€ํ•œ ๊ฒƒ์„ ๋‹ค๋ฃฐ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋งค์ผ ์ƒˆ๋กœ์šด ์ทจ์•ฝ์ ์— ๋Œ€ํ•ด์„œ๋„ ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€๋“ค์ด ์—…๋ฐ์ดํŠธ๋ฅผ ํ•ด์ค€๋‹ค. ์šฐ๋ฆฌ๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์ด์— ๋Œ€ํ•ด ์‹ ๊ฒฝ์„ ์“ฐ์ง€ ์•Š๊ณ , ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ง‘์ค‘ํ•ด์„œ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

 

 

 

Spring Security ํ๋ฆ„

 

 

 

 

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ๋‚ด๋ถ€ ํ๋ฆ„์— ๋Œ€ํ•ด ๊ทธ๋ ค๋ณด์•˜๋‹ค. ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋‹ˆ ๊ทธ๋ฆผ๊ณผ ์„ค๋ช…์„ ๊ฐ™์ด ๋ณด๋ฉด์„œ ํ๋ฆ„์— ๋Œ€ํ•ด ์ดํ•ดํ•ด ๋ณด์ž.

 

 

์šฐ์„  ์ฒซ ๋ฒˆ์งธ๋กœ ์œ ์ €๋กœ๋ถ€ํ„ฐ ๋กœ๊ทธ์ธ ์ž๊ฒฉ์„ ์–ป๊ธฐ ์œ„ํ•œ ์š”์ฒญ์„ ๋ฐ›๋Š”๋‹ค. Security Filter๋Š” ์ด ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„์„œ ์œ ์ €๊ฐ€ ๋ณด๋‚ธ Username๊ณผ Password๋ฅผ ์ถ”์ถœํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ๋‹จ๊ณ„์—์„œ ์ธ์ฆ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค. 

 ์ด์ œ Security Filter๋Š” ์ด ์š”์ฒญ์„ ์„ธ ๋ฒˆ์งธ ํ๋ฆ„์ธ AuthenticationManager์—๊ฒŒ ๋„˜๊ธฐ๊ฒŒ ๋œ๋‹ค. ์ด AuthenticationManager๋Š” ์‹ค์งˆ์ ์ธ ์ธ์ฆ ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋ฉฐ, ๋„ค ๋ฒˆ์งธ ํ๋ฆ„์— ๋”ฐ๋ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์— ์–ด๋–ค AuthenticationProvider๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. AuthenticationProvider๋Š” ์•ฑ ๋‚ด๋ถ€์— ์—ฌ๋Ÿฌ ๊ฐœ ์กด์žฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž์˜ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋”ฐ๋ผ ๋งˆ์Œ๊ป ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ฐ AuthenticationProvider๋Š” ์ธ์ฆ์— ๋Œ€ํ•œ ์„ฑ๊ณต, ์‹คํŒจ๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ , ์‹คํŒจ์˜ ๊ฒฝ์šฐ ๋‚จ์€ AuthenticationProvider์—๊ฒŒ ์ธ์ฆ ์‹œ๋„๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค. ๋ชจ๋“  ์‹œ๋„์—์„œ ์ธ์ฆ ์‹คํŒจํ•œ ์‹œ์ ์—์„œ์•ผ ์œ ์ €์—๊ฒŒ ์ธ์ฆ์ด ์‹คํŒจํ–ˆ๋‹ค๊ณ  ์‘๋‹ตํ•˜๊ฒŒ ๋œ๋‹ค.

AuthenticationProvider์—์„œ ์ธ์ฆ ๋กœ์ง์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋„ ๋˜๋‚˜, ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค, ํด๋ž˜์Šค์ธ UserDetailsManager, UserDetailsService(๋‹ค์„ฏ ๋ฒˆ์งธ)๋ฅผ ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

์ธ์ฆ์„œ ๋‚ด๋ถ€์—๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅํ•˜๊ฒŒ ๋˜๋ฉด ๋ณด์•ˆ ์ธก๋ฉด์—์„œ ์ทจ์•ฝํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” PasswordEncoder(์—ฌ์„ฏ ๋ฒˆ์งธ)๋กœ ์•”ํ˜ธํ™” ๋˜๋Š” ํ•ด์‹ฑ์„ ํ•˜๊ฒŒ ๋œ๋‹ค.

AuthenticationProvider์˜ ํ”„๋กœ์„ธ์‹ฑ์ด ๋๋‚˜๋ฉด ์ด์ œ ๋‹ค์‹œ ์ผ๊ณฑ ๋ฒˆ์งธ ํ๋ฆ„์— ๋”ฐ๋ผ AuthenticationManager์—๊ฒŒ ๋„˜๊ธด๋‹ค. ์ด ์ •๋ณด๋“ค์€ ๋‹ค์‹œ Security Filter๋กœ ์ „๋‹ฌ(์—ฌ๋Ÿ ๋ฒˆ์งธ)๋˜๋ฉฐ, ์œ ์ €์—๊ฒŒ Response๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์ „์— ๋‘ ๋ฒˆ์งธ ๊ณผ์ •์—์„œ ๋งŒ๋“  ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ์•„ํ™‰ ๋ฒˆ์งธ ํ๋ฆ„์— ๋”ฐ๋ผ Security Context์— ์ €์žฅํ•˜๊ฒŒ ๋œ๋‹ค. Security Context์— ์ €์žฅ๋˜๋Š” ์ธ์ฆ ๊ฐ์ฒด์—๋Š” ์ธ์ฆ์ด ์„ฑ๊ณต์ ์ด์—ˆ๋Š”์ง€, ์„ธ์…˜ ID๋Š” ๋ฌด์—‡์ธ์ง€์— ๋Œ€ํ•ด ์ €์žฅ๋œ๋‹ค. Security Context์— ์ด๋Ÿฌํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ ์ €์˜ ๋‘ ๋ฒˆ์งธ ์š”์ฒญ๋ถ€ํ„ฐ๋Š” ํ•ด๋‹น ์œ ์ €์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์š”๊ตฌํ•˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

์„ฑ๊ณต์ ์œผ๋กœ ์ธ์ฆ ์ •๋ณด๊ฐ€ Security Context์— ์ €์žฅ๋˜์—ˆ๋‹ค๋ฉด, ์œ ์ €๋Š” ์›น ์ ‘๊ทผ์ด๋‚˜ REST API๋กœ ์ ‘๊ทผํ•˜๋ ค ํ–ˆ์„ ๋•Œ, ์—ด ๋ฒˆ์งธ ํ๋ฆ„์— ๋”ฐ๋ผ ์œ ์ €์—๊ฒŒ ์ •๋ณด๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ Spring Security์˜ ๋‚ด๋ถ€ ํ๋ฆ„์ด๋‹ค.

 

 

 

 

Spring Security ํ๋ฆ„ (์ฝ”๋“œ)

 

์–ด๋А ์ •๋„ ๊ธฐ๋ณธ์ ์ธ ํ๋ฆ„์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ๊ฐ”๋‹ค๋ฉด ์ด์ œ ์ฝ”๋“œ๋กœ ๋‚ด๋ถ€ ํ๋ฆ„์„ ์‚ดํŽด๋ณด์ž.

์šฐ์„  Spring Security Filter๋ถ€ํ„ฐ ์‚ดํŽด๋ณผ ๊ฑด๋ฐ, ํ•„ํ„ฐ๋Š” ์—ฌ๋Ÿฌ ์ข…๋ฅ˜๊ฐ€ ์žˆ์ง€๋งŒ ๊ทธ์ค‘์—์„œ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๋Š” ํ•„ํ„ฐ 3๊ฐœ๋งŒ ์ฝ”๋“œ๋กœ ํ™•์ธํ•ด ๋ณด๊ฒ ๋‹ค.

 

 

1-1. Spring Security Filter ( AuthorizationFilter )

 

์ฒซ ๋ฒˆ์งธ๋Š” AuthorizationFilter์ด๋‹ค.

package org.springframework.security.web.access.intercept;

public class AuthorizationFilter extends GenericFilterBean {
    private final AuthorizationManager<HttpServletRequest> authorizationManager;
    private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish;
    private boolean observeOncePerRequest = true;
    private boolean filterErrorDispatch = false;
    private boolean filterAsyncDispatch = false;

    public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
        Assert.notNull(authorizationManager, "authorizationManager cannot be null");
        this.authorizationManager = authorizationManager;
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        if (this.observeOncePerRequest && this.isApplied(request)) {
            chain.doFilter(request, response);
        } else if (this.skipDispatch(request)) {
            chain.doFilter(request, response);
        } else {
            String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
                this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
                if (decision != null && !decision.isGranted()) {
                    throw new AccessDeniedException("Access Denied");
                }

                chain.doFilter(request, response);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }

        }
    }
    
    /* ์ƒ๋žต */

}

 

์ด ํ•„ํ„ฐ๋Š” ์œ ์ €๊ฐ€ ์ ‘๊ทผํ•˜๊ณ ์ž ํ•˜๋Š” URL์— ์ ‘๊ทผ์„ ์ œํ•œ์‹œํ‚จ๋‹ค. doFilter()๋ฅผ ๋ณด๋ฉด try๋ฌธ ์•ˆ์—์„œ AuthorizitionManager์˜ check()๋ฅผ ํ†ตํ•ด ์š”์ฒญ๋ฐ›์€ URL์ด ๊ณต๊ฐœ URL์ธ์ง€ ๋ณด์•ˆ URL์ธ์ง€ ์ฒดํฌํ•˜๊ณ , ๊ณต๊ฐœ URL์ด๋ผ๋ฉด ์œ ์ €์—๊ฒŒ ์ž๊ฒฉ ์ฆ๋ช…์„ ์š”๊ตฌํ•˜์ง€ ์•Š๊ณ  ์‘๋‹ตํ•˜๊ฒ ์ง€๋งŒ, ๋ณด์•ˆ URL์ด๋ผ๋ฉด ์œ ์ €์˜ ์ ‘๊ทผ์„ ๋ฉˆ์ถ”๊ณ  ํ•ด๋‹น ์š”์ฒญ์„ Spring Security Filter Chain์˜ ๋‹ค์Œ Filter๋กœ Redirect ํ•œ๋‹ค. 

 

 

 

1-2. Spring Security Filter ( DefaultLoginPageGeneratingFilter )

 

๋ณด์•ˆ URL๋กœ ์ ‘๊ทผํ•˜๋ ค ํ•œ๋‹ค๋ฉด ๊ทธ๋‹ค์Œ์œผ๋กœ ๋งŒ๋‚˜๊ฒŒ ๋˜๋Š” ํ•„ํ„ฐ๋Š” DefaultLoginPageGeneratingFilter ๋‹ค.

 

package org.springframework.security.web.authentication.ui;

public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {

  /* ์ƒ๋žต */
  
  private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess) {
        String errorMsg = "Invalid credentials";
        if (loginError) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                AuthenticationException ex = (AuthenticationException)session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
                errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
            }
        }

        String contextPath = request.getContextPath();
        StringBuilder sb = new StringBuilder();
        sb.append("<!DOCTYPE html>\n");
        sb.append("<html lang=\"en\">\n");
        sb.append("  <head>\n");
        sb.append("    <meta charset=\"utf-8\">\n");
        sb.append("    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n");
        sb.append("    <meta name=\"description\" content=\"\">\n");
        sb.append("    <meta name=\"author\" content=\"\">\n");
        sb.append("    <title>Please sign in</title>\n");
        sb.append("    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n");
        sb.append("    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n");
        sb.append("  </head>\n");
        sb.append("  <body>\n");
        sb.append("     <div class=\"container\">\n");
        
        /* ์ƒ๋žต */
    }

}

 

์šฐ๋ฆฌ๊ฐ€ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ํ›„ ๋ณด์•ˆ URL์— ์ ‘์†ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋กœ๊ทธ์ธID, ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ ํŽ˜์ด์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ ๋ฐ”๋กœ ์ด ํ•„ํ„ฐ๊ฐ€ ๊ทธ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. generateLoginPageHtml()๋ฅผ ๋ณด๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ๋Š” html ์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

 

 

1-3. Spring Security Filter ( UsernamePasswordAuthenticationFilter )

 

๋งˆ์ง€๋ง‰์œผ๋กœ ์•Œ์•„๋ณผ ํ•„ํ„ฐ๋Š” UsernamePasswordAuthenticationFilter์ด๋‹ค.

 

package org.springframework.security.web.authentication;

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	/* ์ƒ๋žต */

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		username = (username != null) ? username.trim() : "";
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
				password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}

	/* ์ƒ๋žต */

}

 

attemptAuthentication()๋ฅผ ๋ณด๋ฉด HttpServletRequest๋กœ๋ถ€ํ„ฐ username๊ณผ password๋ฅผ ์ถ”์ถœํ•˜๊ณ , UsernamePasswordAuthenticationToken ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. UsernamePasswordAuthenticationToken ์€ Authentication ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค. ์ด ๊ฐ์ฒด๋Š” authenticate()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ AuthenticationManager์—๊ฒŒ ๋„˜๊ธฐ๊ฒŒ ๋œ๋‹ค.

 

 

 

2. AuthenticationManager ( ProviderManager )

 

AuthenticationManager ๋˜ํ•œ ์ธํ„ฐํŽ˜์ด์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ProviderManager๋ผ๋Š” ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

package org.springframework.security.authentication;

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

	/* ์ƒ๋žต */
    
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
     
     /* ์ƒ๋žต */
     
    }
    
    /* ์ƒ๋žต */

}

 

 

ProviderManager๋Š” ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ ํ”„๋ ˆ์ž„์›Œํฌ ๋‚ด์— ์กด์žฌํ•˜๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“   Authentication Provider ๋“ค์˜ ๋งค๋‹ˆ์ € ์—ญํ• ์„ ํ•œ๋‹ค. authenticate() ๋‚ด๋ถ€๋ฅผ ๋ณด๋ฉด for๋ฌธ์œผ๋กœ ๋ชจ๋“  AuthenticationProvider๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ์ธ์ฆ ์„ฑ๊ณต, ์‹คํŒจ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ๋‹ค. ๋งŒ์•ฝ AuthenticationProvider๊ฐ€ ๋‘ ๊ฐœ ์ด์ƒ ์กด์žฌํ•˜๊ณ , ์ฒซ ๋ฒˆ์งธ AuthenticationProvider์—์„œ ์ธ์ฆ์ด ์„ฑ๊ณตํ•œ๋‹ค๋ฉด ๊ทธ ์ดํ›„์˜ AuthenticationProvider๋Š” ๊ฑด๋„ˆ๋›ฐ๊ฒŒ ๋œ๋‹ค. ๋ฐ˜๋Œ€๋กœ AuthenticationProvider์˜ ์ธ์ฆ์ด ์‹คํŒจํ–ˆ๋‹ค๋ฉด ๋‹ค์Œ AuthenticationProvider์—๊ฒŒ ์ธ์ฆ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ๋‹ค.

 

 

 

 

3. AuthenticationProvider ( DaoAuthenticationProvider )

 

์ด๋ฒˆ์—๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” AuthenticationProvider๋ฅผ ์‚ดํŽด๋ณผ ๊ฒƒ์ด๋‹ค. ๊ธฐ๋ณธ ํ๋ฆ„์—์„œ ProviderManager๋Š” DaoAuthenticationProvider๋ผ๋Š” AuthenticationProvider ๊ตฌํ˜„์ฒด๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

 

package org.springframework.security.authentication.dao;

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    /* ์ƒ๋žต */
    
    @Override
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    /* ์ƒ๋žต */

}

 

 

๋จผ์ € DaoAuthenticationProvider ํด๋ž˜์Šค๊ฐ€ ์ƒ์†๋ฐ›๊ณ  ์žˆ๋Š” AbstractUserDetailsAuthenticationProvider ํด๋ž˜์Šค๋ฅผ ํ™•์ธํ•ด ๋ณด์ž.

 

package org.springframework.security.authentication.dao;

public abstract class AbstractUserDetailsAuthenticationProvider
		implements AuthenticationProvider, InitializingBean, MessageSourceAware {
        
    /* ์ƒ๋žต */    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));
        String username = determineUsername(authentication);
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;
            try {
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException ex) {
                this.logger.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw ex;
                }
                throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }
        try {
            this.preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException ex) {
            if (!cacheWasUsed) {
                throw ex;
            }
            // There was a problem, so try again after checking
            // we're using latest data (i.e. not from the cache)
            cacheWasUsed = false;
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            this.preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
        }
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }
        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
    /* ์ƒ๋žต */        
}

 

AbstractUserDetailsAuthenticationProvider ํด๋ž˜์Šค์˜ authenticate()๋กœ ์‹ค์ œ ์ธ์ฆ ๋กœ์ง์ด ๊ตฌํ˜„๋˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๋จผ์ € username์„ ๋ถˆ๋Ÿฌ์˜จ ํ›„ ๊ตฌํ˜„ ํด๋ž˜์Šค(DaoAuthenticationProvider)์— ์ •์˜๋˜์–ด ์žˆ๋Š” retriveUser()๋ฅผ ํ˜ธ์ถœํ•˜๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ retriveUser()๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๋ฉด, ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” UserDetailService์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. UserDetailService๋„ ์ธํ„ฐํŽ˜์ด์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ ๋กœ์ง์—์„œ๋Š” ์ด์— ๋Œ€ํ•œ ๊ตฌํ˜„์ฒด๋กœ InMemoryUserDetailsManager๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

package org.springframework.security.provisioning;

public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {

	/* ์ƒ๋žต */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		UserDetails user = this.users.get(username.toLowerCase());
		if (user == null) {
			throw new UsernameNotFoundException(username);
		}
		return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
				user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
	}
    
	/* ์ƒ๋žต */

}

 

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด InMemoryUserDetailsManager ํด๋ž˜์Šค๋Š” UserDetailsManager ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›๊ณ  ์žˆ๊ณ , loadUserByUsername()๋ฅผ ํ†ตํ•ด ์ธ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

DaoAuthenticationProvider๋ฅผ ํ†ตํ•ด ์ธ์ฆ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜์—ˆ๋‹ค๋ฉด ์ด ์‘๋‹ต์€ ProviderManager์— ์ „๋‹ฌ๋˜๊ณ , ์œ ์ €๋Š” ์ด์ œ ๋ณด์•ˆ URL์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ ์ฝ”๋“œ๋กœ ์‚ดํŽด๋ณธ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ๊ธฐ๋ณธ ํ๋ฆ„์ด๋‹ค.

 

 

 

๋งˆ์น˜๋ฉฐ

Spring Secutiry์˜ ๋‚ด๋ถ€ ํ๋ฆ„๊ณผ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” Filter, AuthenticationManager,  AuthenticationProvider ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์–ด๋–ค ์‹์œผ๋กœ ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ›์•„ ์ธ์ฆ ๊ฐ์ฒด๋กœ ์ €์žฅ๋˜๋Š”์ง€ ์•Œ์•„๋ณด์•˜๋‹ค. ๋‹ค์Œ ๊ธ€์—์„œ๋Š” Spring Security์™€ JWT, OAuth2๋ฅผ ํ† ์ดํ”„๋กœ์ ํŠธ์— ์ ์šฉํ–ˆ๋˜ ๊ฒฝํ—˜์— ๋Œ€ํ•ด ์ž‘์„ฑํ•ด ๋ณด๊ฒ ๋‹ค.

 

 

์ €์ž‘์žํ‘œ์‹œ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'๐Ÿ’ป ๊ฐœ๋ฐœ > ๐Ÿ€ Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

์„œ๋น„์Šค ํ™•์žฅ์„ ์œ„ํ•œ ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ์™€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์„ค๊ณ„  (6) 2024.11.15
Redis ๊ธฐ๋ฐ˜์˜ ์บ์‹ฑ ๋ฐ ๋Œ€๊ธฐ์—ด ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•œ ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค ์„ฑ๋Šฅ ๊ฐœ์„   (4) 2024.11.07
๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ธฐ (synchronized, reentrantLock)  (0) 2024.09.29
์Šคํ”„๋ง ๋ถ€ํŠธ์— OpenAI Whisper API ์ ์šฉํ•˜๊ธฐ  (1) 2023.08.24
'๐Ÿ’ป ๊ฐœ๋ฐœ/๐Ÿ€ Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ์„œ๋น„์Šค ํ™•์žฅ์„ ์œ„ํ•œ ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ์™€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์„ค๊ณ„
  • Redis ๊ธฐ๋ฐ˜์˜ ์บ์‹ฑ ๋ฐ ๋Œ€๊ธฐ์—ด ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•œ ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค ์„ฑ๋Šฅ ๊ฐœ์„ 
  • ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ธฐ (synchronized, reentrantLock)
  • ์Šคํ”„๋ง ๋ถ€ํŠธ์— OpenAI Whisper API ์ ์šฉํ•˜๊ธฐ
EastShine_
EastShine_
๋” ๋‚˜์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜๊ธฐ ์œ„ํ•œ ๋‚˜์˜ ๊ธฐ๋ก ๐Ÿ“
  • EastShine_
    ๊ฐœ๋ฐœ.LOG ๐Ÿ’ป
    EastShine_
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
  • 06-23 15:43
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (27)
      • ๐Ÿ’ป ๊ฐœ๋ฐœ (21)
        • ๐Ÿ–ฅ๏ธ ์šด์˜์ฒด์ œ (3)
        • ๐ŸŒ ๋„คํŠธ์›Œํฌ (0)
        • ๐Ÿ’พ Database (3)
        • ๐ŸŽ› Java (0)
        • ๐Ÿ–ฒ Javascript (0)
        • ๐Ÿ€ Spring (5)
        • ๐ŸŽธ ETC (4)
        • ๐Ÿ“ˆ ์•Œ๊ณ ๋ฆฌ์ฆ˜ (3)
        • ๐Ÿ“– TIL (Today I Learned) (3)
      • ๐Ÿ  ์ผ์ƒ (6)
        • ๐Ÿ““ ์ผ์ƒ ์ผ๊ธฐ (6)
  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    Whisper API
    ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค
    transactionaleventlistener
    ๋™์‹œ์„ฑ์ฒ˜๋ฆฌ
    e-book pdf ๋ณ€ํ™˜
    ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค
    ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ
    ํšŒ๊ณ 
    ๋Œ€๊ธฐ์—ด
    redis
    ์ฝ”๋”ฉํ…Œ์ŠคํŠธ
    ์ฝ˜์„œํŠธ์˜ˆ์•ฝ์„œ๋น„์Šค
    spring
    e-book pdf ์ถ”์ถœ
    ๋ฐฑ์—”๋“œ
    ๋‚™๊ด€์ ๋ฝ
    Python
    ์•Œ๊ณ ๋ฆฌ์ฆ˜
    6๊ธฐ
    ๋น„๊ด€์ ๋ฝ
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.1
EastShine_
Spring Security ๋‚ด๋ถ€ ํ๋ฆ„ ์ดํ•ดํ•˜๊ธฐ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”