diff --git a/boot_gradle_security/src/main/java/com/boot/test1/config/CustomAuthenticationProvider.java b/boot_gradle_security/src/main/java/com/boot/test1/config/CustomAuthenticationProvider.java index ee921f2..9bfb147 100644 --- a/boot_gradle_security/src/main/java/com/boot/test1/config/CustomAuthenticationProvider.java +++ b/boot_gradle_security/src/main/java/com/boot/test1/config/CustomAuthenticationProvider.java @@ -8,6 +8,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -19,6 +20,9 @@ public class CustomAuthenticationProvider implements AuthenticationProvider { @Autowired private AccountService accountService; + + @Autowired + private PasswordEncoder passwordEncoder; private Logger log = LoggerFactory.getLogger(this.getClass()); @@ -28,8 +32,15 @@ public class CustomAuthenticationProvider implements AuthenticationProvider { log.info("### authenticate ### "); String username = (String) authentication.getPrincipal(); + String password = (String) authentication.getCredentials(); + String passwordEnc = passwordEncoder.encode(password); Account account = (Account) accountService.loadUserByUsername(username); + + // pw같은지 검증. + if ( !passwordEncoder.matches(password,account.getPassword())) { + throw new BadCredentialsException(username); + } return new UsernamePasswordAuthenticationToken(account, account, account.getAuthorities()); } diff --git a/boot_gradle_security/src/main/java/com/boot/test1/config/SecurityConfig.java b/boot_gradle_security/src/main/java/com/boot/test1/config/SecurityConfig.java index 0a5a015..5cb1544 100644 --- a/boot_gradle_security/src/main/java/com/boot/test1/config/SecurityConfig.java +++ b/boot_gradle_security/src/main/java/com/boot/test1/config/SecurityConfig.java @@ -1,15 +1,27 @@ package com.boot.test1.config; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import com.boot.test1.handler.CustomAuthenticationFailureHandler; +import com.boot.test1.handler.CustomAuthenticationSuccessHandler; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter{ + @Autowired + private AuthenticationSuccessHandler successHandler; + + @Autowired + private AuthenticationFailureHandler failureHandler; + @Override protected void configure(HttpSecurity http) throws Exception{ http @@ -24,7 +36,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter{ .loginPage("/login") // 로그인이 수행될 경로. .loginProcessingUrl("/loginProcess")// 로그인form의 action과 일치시켜주어야 함. .defaultSuccessUrl("/loginSuccess") // 로그인 성공 시 이동할 경로. - .failureUrl("/login?error=true") // 인증에 실패했을 때 보여주는 화면 url, 로그인 form으로 파라미터값 error=true로 보낸 + //.failureUrl("/login?error=true") // 인증에 실패했을 때 보여주는 화면 url, 로그인 form으로 파라미터값 error=true로 보낸다. + .successHandler(successHandler) + .failureHandler(failureHandler) .permitAll() .and() .logout() @@ -47,6 +61,17 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter{ public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } - + + // 로그인 성공 처리를 위한 Handler + @Bean + public AuthenticationSuccessHandler successHandler() { + return new CustomAuthenticationSuccessHandler("loginRedirect", "/login", false); + } + + // 실패 처리를 위한 Handler + @Bean + public AuthenticationFailureHandler failureHandler() { + return new CustomAuthenticationFailureHandler("username", "password" , "loginRedirect" , "securityExceptionMsg" , "/login?fail=true"); + } } \ No newline at end of file diff --git a/boot_gradle_security/src/main/java/com/boot/test1/handler/CustomAuthenticationFailureHandler.java b/boot_gradle_security/src/main/java/com/boot/test1/handler/CustomAuthenticationFailureHandler.java new file mode 100644 index 0000000..513725d --- /dev/null +++ b/boot_gradle_security/src/main/java/com/boot/test1/handler/CustomAuthenticationFailureHandler.java @@ -0,0 +1,76 @@ +package com.boot.test1.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { + + private String loginIdName ; + private String loginPasswordName ; + private String loginRedirectName ; + private String exceptionMsgName ; + private String defaultFailureUrl ; + + public CustomAuthenticationFailureHandler(String loginIdName, String loginPasswordName, String loginRedirectName, + String exceptionMsgName, String defaultFailureUrl) { + this.loginIdName = loginIdName; + this.loginPasswordName = loginPasswordName; + this.loginRedirectName = loginRedirectName; + this.exceptionMsgName = exceptionMsgName; + this.defaultFailureUrl = defaultFailureUrl; + } + + public String getLoginIdName() { + return loginIdName; + } + public void setLoginIdName(String loginIdName) { + this.loginIdName = loginIdName; + } + public String getLoginPasswordName() { + return loginPasswordName; + } + public void setLoginPasswordName(String loginPasswordName) { + this.loginPasswordName = loginPasswordName; + } + public String getLoginRedirectName() { + return loginRedirectName; + } + public void setLoginRedirectName(String loginRedirectName) { + this.loginRedirectName = loginRedirectName; + } + public String getExceptionMsgName() { + return exceptionMsgName; + } + public void setExceptionMsgName(String exceptionMsgName) { + this.exceptionMsgName = exceptionMsgName; + } + public String getDefaultFailureUrl() { + return defaultFailureUrl; + } + public void setDefaultFailureUrl(String defaultFailureUrl) { + this.defaultFailureUrl = defaultFailureUrl; + } + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + + String loginId = request.getParameter(loginIdName); + String loginPw = request.getParameter(loginPasswordName); + String loginRedirect = request.getParameter(loginRedirectName); + + request.setAttribute(loginIdName, loginId); + request.setAttribute(loginPasswordName, loginPw); + request.setAttribute(loginRedirectName, loginRedirect); + + request.setAttribute(exceptionMsgName, exception.getMessage()); + + request.getRequestDispatcher(defaultFailureUrl).forward(request, response); + } +} diff --git a/boot_gradle_security/src/main/java/com/boot/test1/handler/CustomAuthenticationSuccessHandler.java b/boot_gradle_security/src/main/java/com/boot/test1/handler/CustomAuthenticationSuccessHandler.java new file mode 100644 index 0000000..1a11b09 --- /dev/null +++ b/boot_gradle_security/src/main/java/com/boot/test1/handler/CustomAuthenticationSuccessHandler.java @@ -0,0 +1,191 @@ +package com.boot.test1.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.WebAttributes; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; +import org.springframework.util.StringUtils; + +public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + // RedirectStrategy은 화면을 이동하기위한 인터페이스이다. + private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + private RequestCache requestCache = new HttpSessionRequestCache(); + + private String targetUrlParameter ; + private String defaultUrl ; + private boolean useReferer ; + + private Logger log = LoggerFactory.getLogger(this.getClass()); + + // constructor + public CustomAuthenticationSuccessHandler(String targetUrlParameter, String defaultUrl ,boolean useReferer) { + this.targetUrlParameter=targetUrlParameter; + this.defaultUrl=defaultUrl; + this.useReferer = useReferer; + log.info(" targetUrlParameter : " , targetUrlParameter + ", defaultUrl : " + defaultUrl +", useReferer : " + useReferer ); + } + + // getter, settger + public String getTargetUrlParameter() { + return targetUrlParameter; + } + + + public void setTargetUrlParameter(String targetUrlParameter) { + this.targetUrlParameter = targetUrlParameter; + } + + + public String getDefaultUrl() { + return defaultUrl; + } + + + public void setDefaultUrl(String defaultUrl) { + this.defaultUrl = defaultUrl; + } + + + public boolean isUseReferer() { + return useReferer; + } + + + public void setUseReferer(boolean useReferer) { + this.useReferer = useReferer; + } + + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + clearAuthenticationAttributes(request); + + int intRedirectStrategy = decideRedirectStrategy(request,response); + + switch(intRedirectStrategy) { + + case 1: + useTargetUrl(request, response); + break; + case 2: + useSessionUrl(request, response); + break; + case 3: + useRefererUrl(request, response); + break; + default: + useDefaultUrl(request, response); + } + + } + + /** + * + * 인증 성공 후 어떤 URL로 redirect 할지를 결정한다. + * + * 판단 기준은 targetUrlParameter 값을 읽은 URL이 존재할 경우 그것을 1순위 + * + * 1순위 URL이 없을 경우 Spring Security가 세션에 저장한 URL을 2순위 + * 2순위 URL이 없을 경우 Request의 REFERER를 사용하고 그 REFERER URL이 존재할 경우 그 URL을 3순위 + * 3순위 URL이 없을 경우 Default URL을 4순위로 한다. + * + * @param request + * @param response + * @return 1 : targetUrlParameter 값을 읽은 URL + * 2 : Session에 저장되어 있는 URL + * 3 : referer 헤더에 있는 URL + * 0 : default URL + */ + + private int decideRedirectStrategy(HttpServletRequest request, HttpServletResponse response) { + + int result = 0 ; + + SavedRequest savedRequest = requestCache.getRequest(request, response); + + if ( !"".equals(targetUrlParameter) ) { + + String targetUrl = request.getParameter(targetUrlParameter); + + if(StringUtils.hasText(targetUrl)) { + result = 1; + }else { + if (savedRequest != null ) result=2; + else { + String refererUrl = request.getHeader("REFERER"); + if ( useReferer && StringUtils.hasText(refererUrl)) result = 3; + else result = 0 ; + } + } + return result; + } + + if( savedRequest != null ) { + result = 2 ; + return result ; + } + + String refererUrl = request.getHeader("REFERER"); + if(useReferer && StringUtils.hasText(refererUrl)) result =3; + else result = 0 ; + + return result; + } + + /* + * 기본적으로 SpringSecurity는 로그인 실패하면 에러세션을 저장한다. + * 그러나, 예를들어 로그인을 5번시도하고 성공했다고해도 그 에러세션이 저장되어 있을것이다. + * 이 남아있는 에러세션을 주기위한 메서드이다. + */ + private void clearAuthenticationAttributes(HttpServletRequest request) { + HttpSession session = request.getSession(false); + + if ( session == null ) return; + + session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); // SpringSecurity는 에러발생시 해당 key값을 사용한다. + } + + private void useTargetUrl(HttpServletRequest request, HttpServletResponse response) throws IOException { + SavedRequest savedRequest = requestCache.getRequest(request, response); + + if(savedRequest != null ) { + requestCache.removeRequest(request, response); + } + + String targetUrl = request.getParameter(targetUrlParameter); + redirectStrategy.sendRedirect(request, response, targetUrl); + } + + private void useSessionUrl(HttpServletRequest request, HttpServletResponse response) throws IOException { + SavedRequest savedRequest = requestCache.getRequest(request, response); + String targetUrl = savedRequest.getRedirectUrl(); + redirectStrategy.sendRedirect(request, response, targetUrl); + } + + private void useRefererUrl(HttpServletRequest request, HttpServletResponse response) throws IOException { + String targetUrl = request.getHeader("REFERER"); + redirectStrategy.sendRedirect(request, response, targetUrl); + } + + private void useDefaultUrl(HttpServletRequest request, HttpServletResponse response) throws IOException { + redirectStrategy.sendRedirect(request, response, defaultUrl); + } + + +} \ No newline at end of file diff --git a/boot_gradle_security/src/main/webapp/WEB-INF/jsp/loginPage.jsp b/boot_gradle_security/src/main/webapp/WEB-INF/jsp/loginPage.jsp index 6314217..da574ee 100644 --- a/boot_gradle_security/src/main/webapp/WEB-INF/jsp/loginPage.jsp +++ b/boot_gradle_security/src/main/webapp/WEB-INF/jsp/loginPage.jsp @@ -57,15 +57,15 @@ - + + <%-- --%> -

- Your login attempt was not successful due to
- ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message} -

- +

Your login attempt was not successful, try again

+

${securityExceptionMsg}

+ <%-- --%>
+