+++ src/main/java/com/takensoft/ai_lms/common/confing/RedisConfig.java
... | ... | @@ -0,0 +1,26 @@ |
1 | +package com.takensoft.ai_lms.common.confing; | |
2 | + | |
3 | +import org.springframework.context.annotation.Bean; | |
4 | +import org.springframework.context.annotation.Configuration; | |
5 | +import org.springframework.data.redis.connection.RedisConnectionFactory; | |
6 | +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; | |
7 | +import org.springframework.data.redis.core.RedisTemplate; | |
8 | +import org.springframework.data.redis.serializer.StringRedisSerializer; | |
9 | + | |
10 | +@Configuration | |
11 | +public class RedisConfig { | |
12 | + | |
13 | + @Bean | |
14 | + public RedisConnectionFactory redisConnectionFactory() { | |
15 | + return new LettuceConnectionFactory(); | |
16 | + } | |
17 | + | |
18 | + @Bean | |
19 | + public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) { | |
20 | + RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); | |
21 | + redisTemplate.setConnectionFactory(connectionFactory); | |
22 | + redisTemplate.setKeySerializer(new StringRedisSerializer()); | |
23 | + redisTemplate.setValueSerializer(new StringRedisSerializer()); | |
24 | + return redisTemplate; | |
25 | + } | |
26 | +} |
--- 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 @@ |
6 | 6 |
import org.springframework.beans.factory.annotation.Value; |
7 | 7 |
import org.springframework.context.annotation.Bean; |
8 | 8 |
import org.springframework.context.annotation.Configuration; |
9 |
+import org.springframework.data.redis.core.RedisTemplate; |
|
9 | 10 |
import org.springframework.security.authentication.AuthenticationManager; |
10 | 11 |
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; |
11 | 12 |
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
... | ... | @@ -29,16 +30,20 @@ |
29 | 30 |
private final JwtUtil jwtUtil; |
30 | 31 |
private final CommonConfig commonConfig; |
31 | 32 |
|
33 |
+ private final RedisTemplate<String, String> redisTemplate; |
|
34 |
+ |
|
32 | 35 |
private static String FRONT_URL; // 프론트 경로 |
33 | 36 |
private static long JWT_ACCESSTIME; // access 토큰 유지 시간 |
34 | 37 |
private static int COOKIE_TIME; // 쿠키 유지 시간 |
35 | 38 |
|
39 |
+ |
|
36 | 40 |
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JwtUtil jwtUtil, CommonConfig commonConfig, |
37 |
- @Value("${frontUrl}") String fUrl, @Value("${spring.jwt.accessTime}") long aTime, @Value("${spring.jwt.refreshTime}") long rTime, @Value("${cookie.time}") int ctime |
|
41 |
+ 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 |
|
38 | 42 |
) { |
39 | 43 |
this.authenticationConfiguration = authenticationConfiguration; |
40 | 44 |
this.jwtUtil = jwtUtil; |
41 | 45 |
this.commonConfig = commonConfig; |
46 |
+ this.redisTemplate = redisTemplate; |
|
42 | 47 |
|
43 | 48 |
this.FRONT_URL = fUrl; |
44 | 49 |
this.JWT_ACCESSTIME = aTime; |
... | ... | @@ -90,7 +95,7 @@ |
90 | 95 |
.anyRequest().authenticated()); // 나머지 경로는 인증 필요 |
91 | 96 |
|
92 | 97 |
// jwt 필터 처리 적용 |
93 |
- http.addFilterBefore(new JwtFilter(jwtUtil, commonConfig), LoginFilter.class); // 토큰 검증 필터 |
|
98 |
+ http.addFilterBefore(new JwtFilter(jwtUtil, commonConfig, redisTemplate), LoginFilter.class); // 토큰 검증 필터 |
|
94 | 99 |
http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, commonConfig, JWT_ACCESSTIME), UsernamePasswordAuthenticationFilter.class); |
95 | 100 |
|
96 | 101 |
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
... | ... | @@ -11,6 +11,7 @@ |
11 | 11 |
import jakarta.servlet.http.HttpServletRequest; |
12 | 12 |
import jakarta.servlet.http.HttpServletResponse; |
13 | 13 |
import lombok.RequiredArgsConstructor; |
14 |
+import org.springframework.data.redis.core.RedisTemplate; |
|
14 | 15 |
import org.springframework.http.HttpStatus; |
15 | 16 |
import org.springframework.http.MediaType; |
16 | 17 |
import org.springframework.security.authentication.InsufficientAuthenticationException; |
... | ... | @@ -30,6 +31,7 @@ |
30 | 31 |
|
31 | 32 |
private final JwtUtil jwtUtil; |
32 | 33 |
private final CommonConfig commonConfig; |
34 |
+ private final RedisTemplate<String, String> redisTemplate; |
|
33 | 35 |
|
34 | 36 |
@Override |
35 | 37 |
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws jakarta.servlet.ServletException, IOException { |
... | ... | @@ -45,6 +47,12 @@ |
45 | 47 |
if (jwtUtil.isExpired(accessToken)) { |
46 | 48 |
throw new JwtException("Token expired"); |
47 | 49 |
} |
50 |
+ |
|
51 |
+ // Redis에서 토큰이 존재하는지 확인 (로그아웃된 토큰인지 확인) |
|
52 |
+ if (redisTemplate.hasKey(accessToken)) { |
|
53 |
+ throw new JwtException("Invalid token"); |
|
54 |
+ } |
|
55 |
+ |
|
48 | 56 |
// 토큰에서 페이로드 확인[ 발급시 명시 ] |
49 | 57 |
String category = jwtUtil.getCategory(accessToken); |
50 | 58 |
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
... | ... | @@ -1,5 +1,6 @@ |
1 | 1 |
package com.takensoft.ai_lms.lms.auth.service; |
2 | 2 |
|
3 |
+import com.takensoft.ai_lms.lms.auth.dto.LoginDTO; |
|
3 | 4 |
import com.takensoft.ai_lms.lms.auth.vo.UserVO; |
4 | 5 |
|
5 | 6 |
/** |
... | ... | @@ -10,4 +11,8 @@ |
10 | 11 |
*/ |
11 | 12 |
public interface AuthService { |
12 | 13 |
int insertUser(UserVO userVO) throws Exception; |
14 |
+ |
|
15 |
+ String login(LoginDTO loginDTO) throws Exception; |
|
16 |
+ |
|
17 |
+ void logout(String token) throws Exception; |
|
13 | 18 |
} |
--- 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 @@ |
3 | 3 |
import com.takensoft.ai_lms.common.idgen.service.IdgenService; |
4 | 4 |
import com.takensoft.ai_lms.common.util.JwtUtil; |
5 | 5 |
import com.takensoft.ai_lms.lms.auth.dao.AuthDAO; |
6 |
+import com.takensoft.ai_lms.lms.auth.dto.LoginDTO; |
|
6 | 7 |
import com.takensoft.ai_lms.lms.auth.service.AuthService; |
7 | 8 |
import com.takensoft.ai_lms.lms.auth.vo.UserVO; |
8 | 9 |
import lombok.RequiredArgsConstructor; |
9 | 10 |
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; |
11 |
+import org.springframework.beans.factory.annotation.Value; |
|
12 |
+import org.springframework.data.redis.core.RedisTemplate; |
|
13 |
+import org.springframework.security.core.GrantedAuthority; |
|
10 | 14 |
import org.springframework.security.core.userdetails.UserDetails; |
11 | 15 |
import org.springframework.security.core.userdetails.UserDetailsService; |
12 | 16 |
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
... | ... | @@ -16,6 +20,8 @@ |
16 | 20 |
|
17 | 21 |
import java.util.HashMap; |
18 | 22 |
import java.util.Map; |
23 |
+import java.util.concurrent.TimeUnit; |
|
24 |
+import java.util.stream.Collectors; |
|
19 | 25 |
|
20 | 26 |
/** |
21 | 27 |
* @author : 방선주 |
... | ... | @@ -31,6 +37,10 @@ |
31 | 37 |
private final AuthDAO authDAO; |
32 | 38 |
private final BCryptPasswordEncoder bCryptPasswordEncoder; |
33 | 39 |
private final JwtUtil jwtUtil; |
40 |
+ @Value("${spring.jwt.accessTime}") |
|
41 |
+ private long jwtAccessTime; |
|
42 |
+ |
|
43 |
+ private final RedisTemplate<String, String> redisTemplate; |
|
34 | 44 |
|
35 | 45 |
private final IdgenService userIdgn; |
36 | 46 |
|
... | ... | @@ -72,6 +82,46 @@ |
72 | 82 |
return result; |
73 | 83 |
} |
74 | 84 |
|
85 |
+ /** |
|
86 |
+ * @author : 박민혁 |
|
87 |
+ * @since : 2024.08.02 |
|
88 |
+ * 로그인 기능 |
|
89 |
+ */ |
|
90 |
+ @Override |
|
91 |
+ public String login(LoginDTO loginDTO) throws Exception{ |
|
92 |
+ try { |
|
93 |
+ UserDetails userDetails = loadUserByUsername(loginDTO.getLoginId()); |
|
94 |
+ if (userDetails != null && bCryptPasswordEncoder.matches(loginDTO.getPassword(), userDetails.getPassword())) { |
|
95 |
+ UserVO userVO = (UserVO) userDetails; |
|
96 |
+ return jwtUtil.createJwt( |
|
97 |
+ "access", |
|
98 |
+ userVO.getUsid(), |
|
99 |
+ userVO.getLoginId(), |
|
100 |
+ userVO.getUserNm(), |
|
101 |
+ userVO.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()), |
|
102 |
+ jwtAccessTime // 액세스 토큰 유효 시간 |
|
103 |
+ ); |
|
104 |
+ } else { |
|
105 |
+ throw new UsernameNotFoundException("유효하지 않은 사용자 이름 또는 비밀번호"); |
|
106 |
+ } |
|
107 |
+ } catch (UsernameNotFoundException e) { |
|
108 |
+ throw new Exception("유효하지 않은 사용자 이름 또는 비밀번호", e); |
|
109 |
+ } |
|
110 |
+ } |
|
111 |
+ |
|
112 |
+ @Override |
|
113 |
+ public void logout(String token) throws Exception { |
|
114 |
+ try { |
|
115 |
+ // 토큰에서 사용자 ID 추출 |
|
116 |
+ String userId = jwtUtil.getUsid(token); |
|
117 |
+ |
|
118 |
+ // Redis에 토큰을 저장하여 무효화 시킴 |
|
119 |
+ redisTemplate.opsForValue().set(token, userId, jwtAccessTime, TimeUnit.MILLISECONDS); |
|
120 |
+ } catch (Exception e) { |
|
121 |
+ System.err.println("로그아웃 오류: " + e.getMessage()); |
|
122 |
+ throw new Exception("로그아웃 중 오류가 발생했습니다.", e); |
|
123 |
+ } |
|
124 |
+ } |
|
75 | 125 |
|
76 | 126 |
} |
77 | 127 |
|
--- 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 @@ |
2 | 2 |
|
3 | 3 |
import com.google.gson.Gson; |
4 | 4 |
import com.google.gson.JsonObject; |
5 |
+import com.takensoft.ai_lms.lms.auth.dto.LoginDTO; |
|
5 | 6 |
import com.takensoft.ai_lms.lms.auth.service.AuthService; |
6 | 7 |
import com.takensoft.ai_lms.lms.auth.vo.UserVO; |
7 | 8 |
import io.swagger.v3.oas.annotations.Operation; |
8 | 9 |
import lombok.RequiredArgsConstructor; |
9 | 10 |
import lombok.extern.slf4j.Slf4j; |
10 |
-import org.springframework.web.bind.annotation.PostMapping; |
|
11 |
-import org.springframework.web.bind.annotation.RequestBody; |
|
12 |
-import org.springframework.web.bind.annotation.RequestMapping; |
|
13 |
-import org.springframework.web.bind.annotation.RestController; |
|
11 |
+import org.springframework.web.bind.annotation.*; |
|
14 | 12 |
|
15 | 13 |
/** |
16 | 14 |
* @author : 방선주 |
... | ... | @@ -62,5 +60,46 @@ |
62 | 60 |
|
63 | 61 |
} |
64 | 62 |
|
63 |
+ /** |
|
64 |
+ * @author : 박민혁 |
|
65 |
+ * @since : 2024.08.02 |
|
66 |
+ * 로그인 기능 |
|
67 |
+ */ |
|
68 |
+ @PostMapping("/login.json") |
|
69 |
+ @Operation(summary = "사용자 로그인") |
|
70 |
+ public String login(@RequestBody LoginDTO loginDTO) { |
|
71 |
+ Gson gson = new Gson(); |
|
72 |
+ JsonObject response = new JsonObject(); |
|
73 |
+ |
|
74 |
+ try { |
|
75 |
+ String token = authService.login(loginDTO); |
|
76 |
+ response.addProperty("status", "success"); |
|
77 |
+ response.addProperty("token", token); |
|
78 |
+ return gson.toJson(response); |
|
79 |
+ } catch (Exception e) { |
|
80 |
+ response.addProperty("status", "error"); |
|
81 |
+ response.addProperty("message", e.getMessage()); |
|
82 |
+ return gson.toJson(response); |
|
83 |
+ } |
|
84 |
+ } |
|
85 |
+ |
|
86 |
+ @PostMapping("/logout.json") |
|
87 |
+ @Operation(summary = "사용자 로그아웃") |
|
88 |
+ public String logout(@RequestHeader("Authorization") String token) { |
|
89 |
+ Gson gson = new Gson(); |
|
90 |
+ JsonObject response = new JsonObject(); |
|
91 |
+ |
|
92 |
+ try { |
|
93 |
+ authService.logout(token); |
|
94 |
+ |
|
95 |
+ response.addProperty("status", "success"); |
|
96 |
+ response.addProperty("message", "성공적으로 로그아웃되었습니다."); |
|
97 |
+ return gson.toJson(response); |
|
98 |
+ } catch (Exception e) { |
|
99 |
+ response.addProperty("status", "error"); |
|
100 |
+ response.addProperty("message", e.getMessage()); |
|
101 |
+ return gson.toJson(response); |
|
102 |
+ } |
|
103 |
+ } |
|
65 | 104 |
|
66 | 105 |
} |
--- src/main/resources/application.yml
+++ src/main/resources/application.yml
... | ... | @@ -47,6 +47,11 @@ |
47 | 47 |
secret: 42b08045ac84dbcdf50e946bf8ad34d35af707979b3230cfb84fb8fe34236d2f |
48 | 48 |
accessTime: 600000 # 10분 600000 |
49 | 49 |
refreshTime: 86400000 # 24시간 86400000 |
50 |
+ data: |
|
51 |
+ redis: |
|
52 |
+ host: localhost |
|
53 |
+ port: 6379 |
|
54 |
+ timeout: 60000 |
|
50 | 55 |
cookie: |
51 | 56 |
time: 86400 |
52 | 57 |
|
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?