From 8738a0818900ed8d3e8bce9105fdfc67cd8309ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=84=9D=20=EC=B5=9C?= Date: Thu, 23 Nov 2023 18:03:39 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=9E=91=EC=97=85=EC=A4=91.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/login/EgovLoginContent.jsx | 2 +- .../auth/EgovLoginApiController.java | 26 ------- ...nUsernamePasswordAuthenticationFilter.java | 76 +++++++++++++++++++ .../config/security/SecurityConfig.java | 72 +++++++++++++++--- .../src/main/resources/application.properties | 2 +- 5 files changed, 138 insertions(+), 40 deletions(-) create mode 100644 kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/JsonUsernamePasswordAuthenticationFilter.java diff --git a/egovframe-template-simple-react-contribution/src/pages/login/EgovLoginContent.jsx b/egovframe-template-simple-react-contribution/src/pages/login/EgovLoginContent.jsx index 174d84c..483452b 100644 --- a/egovframe-template-simple-react-contribution/src/pages/login/EgovLoginContent.jsx +++ b/egovframe-template-simple-react-contribution/src/pages/login/EgovLoginContent.jsx @@ -62,7 +62,7 @@ function EgovLoginContent(props) { const submitFormHandler = (e) => { console.log("EgovLoginContent submitFormHandler()"); - const loginUrl = "/auth/login" + const loginUrl = "/login" const requestOptions = { method: "POST", headers: { diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/EgovLoginApiController.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/EgovLoginApiController.java index 7f45f97..e083aa2 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/EgovLoginApiController.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/EgovLoginApiController.java @@ -101,30 +101,4 @@ public class EgovLoginApiController extends BaseController { } return resultMap; } - - /** - * 로그아웃한다. - * @return resultVO - * @exception Exception - */ - @Operation( - summary = "로그아웃", - description = "로그아웃 처리(JWT,일반 관계 없이)", - tags = {"EgovLoginApiController"} - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "로그아웃 성공"), - }) - @GetMapping(value = "/logout") - public ResultVO actionLogoutJSON(HttpServletRequest request, HttpServletResponse response) throws Exception { - - ResultVO resultVO = new ResultVO(); - - new SecurityContextLogoutHandler().logout(request, response, null); - - resultVO.setResultCode(ResponseCode.SUCCESS.getCode()); - resultVO.setResultMessage(ResponseCode.SUCCESS.getMessage()); - - return resultVO; - } } \ No newline at end of file diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/JsonUsernamePasswordAuthenticationFilter.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/JsonUsernamePasswordAuthenticationFilter.java new file mode 100644 index 0000000..ccb34a4 --- /dev/null +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/JsonUsernamePasswordAuthenticationFilter.java @@ -0,0 +1,76 @@ +package com.dbnt.kcscbackend.config.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.util.StreamUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Slf4j +public class JsonUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + private static final String DEFAULT_LOGIN_REQUEST_URL = "/login"; // /login/oauth2/ + ????? 로 오는 요청을 처리할 것이다 + private static final String HTTP_METHOD = "POST"; //HTTP 메서드의 방식은 POST 이다. + private static final String CONTENT_TYPE = "application/json";//json 타입의 데이터로만 로그인을 진행한다. + private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER = + new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URL, HTTP_METHOD); //=> /login 의 요청에, POST로 온 요청에 매칭된다. + + private final ObjectMapper objectMapper; + + public JsonUsernamePasswordAuthenticationFilter(ObjectMapper objectMapper, + AuthenticationSuccessHandler authenticationSuccessHandler, // 로그인 성공 시 처리할 핸들러 + AuthenticationFailureHandler authenticationFailureHandler // 로그인 실패 시 처리할 핸들러 + ) { + + super(DEFAULT_LOGIN_PATH_REQUEST_MATCHER); // 위에서 설정한 /oauth2/login/* 의 요청에, GET으로 온 요청을 처리하기 위해 설정한다. + + this.objectMapper = objectMapper; + setAuthenticationSuccessHandler(authenticationSuccessHandler); + setAuthenticationFailureHandler(authenticationFailureHandler); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + + if (request.getContentType() == null || !request.getContentType().equals(CONTENT_TYPE)) { + throw new AuthenticationServiceException("Authentication Content-Type not supported: " + request.getContentType()); + } + + LoginDto loginDto = objectMapper.readValue(StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8), LoginDto.class); + + String username = loginDto.getUsername(); + String password = loginDto.getPassword(); + + if (username == null || password == null) { + throw new AuthenticationServiceException("DATA IS MISS"); + } + + UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); + // Allow subclasses to set the "details" property + setDetails(request, authRequest); + return this.getAuthenticationManager().authenticate(authRequest); + } + + protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { + authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); + } + + @Data + private static class LoginDto { + String username; + String password; + } +} diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/SecurityConfig.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/SecurityConfig.java index b474631..008b853 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/SecurityConfig.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/security/SecurityConfig.java @@ -1,17 +1,27 @@ package com.dbnt.kcscbackend.config.security; import com.dbnt.kcscbackend.auth.entity.UserInfo; +import com.dbnt.kcscbackend.auth.service.EgovLoginService; import com.dbnt.kcscbackend.config.jwt.EgovJwtTokenUtil; import com.dbnt.kcscbackend.config.jwt.JwtAuthenticationEntryPoint; import com.dbnt.kcscbackend.config.jwt.JwtAuthenticationFilter; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @@ -24,6 +34,7 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import javax.annotation.Resource; import javax.servlet.http.HttpSession; import java.util.Arrays; @@ -39,10 +50,14 @@ import java.util.Arrays; */ @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { @Autowired private EgovJwtTokenUtil jwtTokenUtil; + @Resource(name = "loginService") + private UserDetailsService loginService; + private final ObjectMapper objectMapper; //Http Methpd : Get 인증예외 List private String[] AUTH_GET_WHITELIST = { @@ -55,6 +70,7 @@ public class SecurityConfig { private String[] AUTH_WHITELIST = { "/", "/login/**", + "/login", "/auth/login-jwt",//JWT 로그인 "/auth/login",//일반 로그인 "/auth/join",//회원가입 @@ -107,22 +123,26 @@ public class SecurityConfig { .antMatchers(AUTH_WHITELIST).permitAll() .antMatchers(HttpMethod.GET,AUTH_GET_WHITELIST).permitAll() .anyRequest().authenticated() - ).sessionManagement((sessionManagement) -> + ); + http.sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) - .cors().and() - .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class) + ); + + http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class) .exceptionHandling(exceptionHandlingConfigurer -> exceptionHandlingConfigurer .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) - ) - .cors().and() - .formLogin().loginProcessingUrl("/auth/login") - .successHandler(loginSuccessHandler()) - .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")) - .logoutSuccessUrl("/") - .invalidateHttpSession(true) - .deleteCookies("JSESSIONID"); + ); + + http.httpBasic().disable() + .csrf().disable() + .formLogin().disable(); + +// http.authorizeHttpRequests() +// .requestMatchers(new AntPathRequestMatcher("/auth/login")).permitAll() +// .anyRequest().authenticated(); + + http.cors().and().addFilterBefore(jsonUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } @@ -138,5 +158,33 @@ public class SecurityConfig { }; } + @Bean + public AuthenticationFailureHandler loginFailureHandler(){ + return (request, response, exception) -> { + new DefaultRedirectStrategy().sendRedirect(request, response, "/login-error"); + }; + } + + @Bean + public JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter() { + JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter = new JsonUsernamePasswordAuthenticationFilter(objectMapper, loginSuccessHandler(), loginFailureHandler()); + jsonUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager()); + return jsonUsernamePasswordAuthenticationFilter; + } + + @Bean + public AuthenticationManager authenticationManager() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + + provider.setPasswordEncoder(passwordEncoder()); + provider.setUserDetailsService(loginService); + + return new ProviderManager(provider); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new Pbkdf2PasswordEncoder(); + } } \ No newline at end of file diff --git a/kcsc-back-end/src/main/resources/application.properties b/kcsc-back-end/src/main/resources/application.properties index a7048d4..1319570 100644 --- a/kcsc-back-end/src/main/resources/application.properties +++ b/kcsc-back-end/src/main/resources/application.properties @@ -5,4 +5,4 @@ Globals.pageUnit=10 Globals.pageSize=10 #JWT secret key -Globals.jwt.secret = egovframe +Globals.jwt.secret = qWwMroux3QtiIJcPSIZARNTZEBBnWVH0jZ2Lx7tfFChCYi0ViZllo1bekZdiU0B3FRjJI7g90n0ha120dwlz8JZU8rOkmNCe9Uq0