From 72c52e4b5838e9c24f1d4f332acc9f5f7eb14b69 Mon Sep 17 00:00:00 2001 From: bum12ark Date: Tue, 15 Feb 2022 16:19:10 +0900 Subject: [PATCH] =?UTF-8?q?feat(user-service):=20Spring=20Security=20?= =?UTF-8?q?=EB=B0=8F=20JWT=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D,=20=EC=9D=B8=EA=B0=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Spring Security 구현 - 로그인 인증 구현 - 인증 성공 시 JWT 발급 구현 --- .../userservice/global/config/AppConfig.java | 15 +++ .../userservice/global/dto/LoginRequest.java | 10 ++ .../security/HeaderAuthorizationFilter.java | 28 ++++++ .../security/LoginAuthenticationFilter.java | 91 +++++++++++++++++++ .../global/security/SecurityConfig.java | 43 +++++++++ 5 files changed, 187 insertions(+) create mode 100644 user-service/src/main/java/com/justpickup/userservice/global/config/AppConfig.java create mode 100644 user-service/src/main/java/com/justpickup/userservice/global/dto/LoginRequest.java create mode 100644 user-service/src/main/java/com/justpickup/userservice/global/security/HeaderAuthorizationFilter.java create mode 100644 user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java create mode 100644 user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java diff --git a/user-service/src/main/java/com/justpickup/userservice/global/config/AppConfig.java b/user-service/src/main/java/com/justpickup/userservice/global/config/AppConfig.java new file mode 100644 index 0000000..1df114d --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/global/config/AppConfig.java @@ -0,0 +1,15 @@ +package com.justpickup.userservice.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class AppConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/user-service/src/main/java/com/justpickup/userservice/global/dto/LoginRequest.java b/user-service/src/main/java/com/justpickup/userservice/global/dto/LoginRequest.java new file mode 100644 index 0000000..4f6eda1 --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/global/dto/LoginRequest.java @@ -0,0 +1,10 @@ +package com.justpickup.userservice.global.dto; + +import lombok.Data; + +@Data +public class LoginRequest { + private String name; + private String email; + private String password; +} diff --git a/user-service/src/main/java/com/justpickup/userservice/global/security/HeaderAuthorizationFilter.java b/user-service/src/main/java/com/justpickup/userservice/global/security/HeaderAuthorizationFilter.java new file mode 100644 index 0000000..b26d6f2 --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/global/security/HeaderAuthorizationFilter.java @@ -0,0 +1,28 @@ +package com.justpickup.userservice.global.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +public class HeaderAuthorizationFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + if (request.getServletPath().equals("/login")) { + filterChain.doFilter(request, response); + return; + } + + String email = request.getHeader("jwt-sub"); + log.info("email jwt-sub = {}", email); + + filterChain.doFilter(request, response); + } +} diff --git a/user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java b/user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java new file mode 100644 index 0000000..66d5343 --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/global/security/LoginAuthenticationFilter.java @@ -0,0 +1,91 @@ +package com.justpickup.userservice.global.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.justpickup.userservice.global.dto.LoginRequest; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@RequiredArgsConstructor +@Slf4j +public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + private final AuthenticationManager authenticationManager; + + // login 리퀘스트 패스로 오는 요청을 판단 + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + Authentication authentication; + + try { + LoginRequest credential = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class); + + authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(credential.getEmail(), credential.getPassword()) + ); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + + return authentication; + } + + // 로그인 성공 이후 토큰 생성 + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException { + org.springframework.security.core.userdetails.User user = (User) authResult.getPrincipal(); + + String accessToken = Jwts.builder() + .setSubject(user.getUsername()) + .setExpiration( + new Date(System.currentTimeMillis() + 10 * 60 * 1000) + ) + .signWith(SignatureAlgorithm.HS512, "your-256-bit-secret") + .setIssuer(request.getRequestURI()) + .addClaims(Map.of("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))) + .compact(); + + String refreshToken = Jwts.builder() + .setSubject(user.getUsername()) + .setExpiration( + new Date(System.currentTimeMillis() + 30 * 60 * 1000) + ) + .signWith(SignatureAlgorithm.HS512, "your-256-bit-secret") + .setIssuer(request.getRequestURI()) + .compact(); + + Map tokens = Map.of( + "access_token", accessToken, + "refresh_token", refreshToken + ); + response.setContentType(APPLICATION_JSON_VALUE); + new ObjectMapper().writeValue(response.getOutputStream(), tokens); + } + + @Override + protected void unsuccessfulAuthentication + (HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) + throws IOException, ServletException { + log.warn("로그인 실패!!"); + } +} diff --git a/user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java b/user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java new file mode 100644 index 0000000..cc1bd1e --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java @@ -0,0 +1,43 @@ +package com.justpickup.userservice.global.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final UserDetailsService userDetailsService; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + LoginAuthenticationFilter loginAuthenticationFilter = new LoginAuthenticationFilter(authenticationManagerBean()); + loginAuthenticationFilter.setFilterProcessesUrl("/login"); + + http.csrf().disable(); + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + http.authorizeRequests().antMatchers("/login").permitAll(); + http.addFilter(loginAuthenticationFilter); + http.addFilterBefore(new HeaderAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); + } + + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } +}