박민혁 박민혁 08-06
240806 박민혁 로그인 로그아웃 기능 구현
@52eecdc457611031791eefa785e09c2a995a3bc6
 
src/main/java/com/takensoft/ai_lms/common/confing/RedisConfig.java (added)
+++ src/main/java/com/takensoft/ai_lms/common/confing/RedisConfig.java
@@ -0,0 +1,26 @@
+package com.takensoft.ai_lms.common.confing;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public RedisConnectionFactory redisConnectionFactory() {
+        return new LettuceConnectionFactory();
+    }
+
+    @Bean
+    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(connectionFactory);
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(new StringRedisSerializer());
+        return redisTemplate;
+    }
+}
src/main/java/com/takensoft/ai_lms/common/confing/SecurityConfig.java
--- src/main/java/com/takensoft/ai_lms/common/confing/SecurityConfig.java
+++ src/main/java/com/takensoft/ai_lms/common/confing/SecurityConfig.java
@@ -6,6 +6,7 @@
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -29,16 +30,20 @@
     private final JwtUtil jwtUtil;
     private final CommonConfig commonConfig;
 
+    private final RedisTemplate<String, String> redisTemplate;
+
     private static String FRONT_URL; // 프론트 경로
     private static long JWT_ACCESSTIME; // access 토큰 유지 시간
     private static int COOKIE_TIME; // 쿠키 유지 시간
 
+
     public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JwtUtil jwtUtil, CommonConfig commonConfig,
-                          @Value("${frontUrl}") String fUrl, @Value("${spring.jwt.accessTime}") long aTime, @Value("${spring.jwt.refreshTime}") long rTime, @Value("${cookie.time}") int ctime
+                          RedisTemplate<String, String> redisTemplate, @Value("${frontUrl}") String fUrl, @Value("${spring.jwt.accessTime}") long aTime, @Value("${spring.jwt.refreshTime}") long rTime, @Value("${cookie.time}") int ctime
     ) {
         this.authenticationConfiguration = authenticationConfiguration;
         this.jwtUtil = jwtUtil;
         this.commonConfig = commonConfig;
+        this.redisTemplate = redisTemplate;
 
         this.FRONT_URL = fUrl;
         this.JWT_ACCESSTIME = aTime;
@@ -90,7 +95,7 @@
                 .anyRequest().authenticated()); // 나머지 경로는 인증 필요
 
         // jwt 필터 처리 적용
-        http.addFilterBefore(new JwtFilter(jwtUtil, commonConfig), LoginFilter.class); // 토큰 검증 필터
+        http.addFilterBefore(new JwtFilter(jwtUtil, commonConfig, redisTemplate), LoginFilter.class); // 토큰 검증 필터
         http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, commonConfig, JWT_ACCESSTIME), UsernamePasswordAuthenticationFilter.class);
 
         return http.build();
src/main/java/com/takensoft/ai_lms/common/filter/JwtFilter.java
--- src/main/java/com/takensoft/ai_lms/common/filter/JwtFilter.java
+++ src/main/java/com/takensoft/ai_lms/common/filter/JwtFilter.java
@@ -11,6 +11,7 @@
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.security.authentication.InsufficientAuthenticationException;
@@ -30,6 +31,7 @@
 
     private final JwtUtil jwtUtil;
     private final CommonConfig commonConfig;
+    private final RedisTemplate<String, String> redisTemplate;
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
@@ -45,6 +47,12 @@
             if (jwtUtil.isExpired(accessToken)) {
                 throw new JwtException("Token expired");
             }
+
+            // Redis에서 토큰이 존재하는지 확인 (로그아웃된 토큰인지 확인)
+            if (redisTemplate.hasKey(accessToken)) {
+                throw new JwtException("Invalid token");
+            }
+
             // 토큰에서 페이로드 확인[ 발급시 명시 ]
             String category = jwtUtil.getCategory(accessToken);
             if (!category.equals("Authorization")) {
src/main/java/com/takensoft/ai_lms/lms/auth/service/AuthService.java
--- src/main/java/com/takensoft/ai_lms/lms/auth/service/AuthService.java
+++ src/main/java/com/takensoft/ai_lms/lms/auth/service/AuthService.java
@@ -1,5 +1,6 @@
 package com.takensoft.ai_lms.lms.auth.service;
 
+import com.takensoft.ai_lms.lms.auth.dto.LoginDTO;
 import com.takensoft.ai_lms.lms.auth.vo.UserVO;
 
 /**
@@ -10,4 +11,8 @@
  */
 public interface AuthService {
     int insertUser(UserVO userVO) throws Exception;
+
+    String login(LoginDTO loginDTO) throws Exception;
+
+    void logout(String token) throws Exception;
 }
src/main/java/com/takensoft/ai_lms/lms/auth/service/Impl/AuthServiceImpl.java
--- src/main/java/com/takensoft/ai_lms/lms/auth/service/Impl/AuthServiceImpl.java
+++ src/main/java/com/takensoft/ai_lms/lms/auth/service/Impl/AuthServiceImpl.java
@@ -3,10 +3,14 @@
 import com.takensoft.ai_lms.common.idgen.service.IdgenService;
 import com.takensoft.ai_lms.common.util.JwtUtil;
 import com.takensoft.ai_lms.lms.auth.dao.AuthDAO;
+import com.takensoft.ai_lms.lms.auth.dto.LoginDTO;
 import com.takensoft.ai_lms.lms.auth.service.AuthService;
 import com.takensoft.ai_lms.lms.auth.vo.UserVO;
 import lombok.RequiredArgsConstructor;
 import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -16,6 +20,8 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * @author  : 방선주
@@ -31,6 +37,10 @@
     private final AuthDAO authDAO;
     private final BCryptPasswordEncoder bCryptPasswordEncoder;
     private final JwtUtil jwtUtil;
+    @Value("${spring.jwt.accessTime}")
+    private long jwtAccessTime;
+
+    private final RedisTemplate<String, String> redisTemplate;
 
     private final IdgenService userIdgn;
 
@@ -72,6 +82,46 @@
         return result;
     }
 
+    /**
+     * @author  : 박민혁
+     * @since   : 2024.08.02
+     * 로그인 기능
+     */
+    @Override
+    public String login(LoginDTO loginDTO) throws Exception{
+        try {
+            UserDetails userDetails = loadUserByUsername(loginDTO.getLoginId());
+            if (userDetails != null && bCryptPasswordEncoder.matches(loginDTO.getPassword(), userDetails.getPassword())) {
+                UserVO userVO = (UserVO) userDetails;
+                return jwtUtil.createJwt(
+                        "access",
+                        userVO.getUsid(),
+                        userVO.getLoginId(),
+                        userVO.getUserNm(),
+                        userVO.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()),
+                        jwtAccessTime // 액세스 토큰 유효 시간
+                );
+            } else {
+                throw new UsernameNotFoundException("유효하지 않은 사용자 이름 또는 비밀번호");
+            }
+        } catch (UsernameNotFoundException e) {
+            throw new Exception("유효하지 않은 사용자 이름 또는 비밀번호", e);
+        }
+    }
+
+    @Override
+    public void logout(String token) throws Exception {
+        try {
+            // 토큰에서 사용자 ID 추출
+            String userId = jwtUtil.getUsid(token);
+
+            // Redis에 토큰을 저장하여 무효화 시킴
+            redisTemplate.opsForValue().set(token, userId, jwtAccessTime, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            System.err.println("로그아웃 오류: " + e.getMessage());
+            throw new Exception("로그아웃 중 오류가 발생했습니다.", e);
+        }
+    }
 
 }
 
src/main/java/com/takensoft/ai_lms/lms/auth/web/AuthController.java
--- src/main/java/com/takensoft/ai_lms/lms/auth/web/AuthController.java
+++ src/main/java/com/takensoft/ai_lms/lms/auth/web/AuthController.java
@@ -2,15 +2,13 @@
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
+import com.takensoft.ai_lms.lms.auth.dto.LoginDTO;
 import com.takensoft.ai_lms.lms.auth.service.AuthService;
 import com.takensoft.ai_lms.lms.auth.vo.UserVO;
 import io.swagger.v3.oas.annotations.Operation;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * @author  : 방선주
@@ -62,5 +60,46 @@
 
     }
 
+    /**
+     * @author  : 박민혁
+     * @since   : 2024.08.02
+     * 로그인 기능
+     */
+    @PostMapping("/login.json")
+    @Operation(summary = "사용자 로그인")
+    public String login(@RequestBody LoginDTO loginDTO) {
+        Gson gson = new Gson();
+        JsonObject response = new JsonObject();
+
+        try {
+            String token = authService.login(loginDTO);
+            response.addProperty("status", "success");
+            response.addProperty("token", token);
+            return gson.toJson(response);
+        } catch (Exception e) {
+            response.addProperty("status", "error");
+            response.addProperty("message", e.getMessage());
+            return gson.toJson(response);
+        }
+    }
+
+    @PostMapping("/logout.json")
+    @Operation(summary = "사용자 로그아웃")
+    public String logout(@RequestHeader("Authorization") String token) {
+        Gson gson = new Gson();
+        JsonObject response = new JsonObject();
+
+        try {
+            authService.logout(token);
+
+            response.addProperty("status", "success");
+            response.addProperty("message", "성공적으로 로그아웃되었습니다.");
+            return gson.toJson(response);
+        } catch (Exception e) {
+            response.addProperty("status", "error");
+            response.addProperty("message", e.getMessage());
+            return gson.toJson(response);
+        }
+    }
 
 }
src/main/resources/application.yml
--- src/main/resources/application.yml
+++ src/main/resources/application.yml
@@ -47,6 +47,11 @@
     secret: 42b08045ac84dbcdf50e946bf8ad34d35af707979b3230cfb84fb8fe34236d2f
     accessTime: 600000      # 10분 600000
     refreshTime: 86400000   # 24시간 86400000
+  data:
+    redis:
+      host: localhost
+      port: 6379
+      timeout: 60000
 cookie:
   time: 86400
 
Add a comment
List