๋งค๋ฒ ์ธ์ฆ, ์ธ๊ฐ์ ๋ํ ๋ถ๋ถ์ ๋ํด ์ ๋๋ก ์์งํ์ง ์๊ณ , ์ฝ๋๋ง ๊ฐ์ ธ๋ค๊ฐ ์ฌ์ฉํ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ค. ํ์ฌ์์๋ ์ด์ ๋ํด ๋ค๋ฃฐ ์ ์๋ ๊ธฐํ๊ฐ ์๋ค ๋ณด๋ ์ต๊ทผ ํ ์ด ํ๋ก์ ํธ๋ก ํจ์ ์ด์ปค๋จธ์ค ํ๋ซํผ์ ๊ตฌํํ๋ฉด์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ํด ๊ณต๋ถ๋ ํด๋ณด๊ณ , ์ด๋ ์ ๋ ๊ธฐ๋ณธ์ ์ธ ์ดํด๋ฅผ ํ๋ฉฐ ์ ์ฉํ๊ฒ ๋์๋ค.
์ฃผ๋ก ์ ๋ฐ๋ฏธ(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๋ฅผ ํ ์ดํ๋ก์ ํธ์ ์ ์ฉํ๋ ๊ฒฝํ์ ๋ํด ์์ฑํด ๋ณด๊ฒ ๋ค.