Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6409352

Browse files
committed
feat: Redis로 accessToken 관리, RefreshToken 필요
1 parent f0776fc commit 6409352

File tree

16 files changed

+227
-108
lines changed

16 files changed

+227
-108
lines changed

‎member_request.http‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Content-Type: application/json
99
}
1010

1111
### 로그인
12-
POST http://localhost:8081/login
12+
POST http://localhost:8081/auth/login
1313
Content-Type: application/json
1414

1515
{
@@ -19,11 +19,13 @@ Content-Type: application/json
1919

2020
### 사용자 수정
2121
PUT http://localhost:8081/members/1
22+
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoic2Vva3JhZSIsImVtYWlsIjoic2Vva0BnbWFpbC5jb20iLCJpc3MiOiJqd3QtcmVkaXMtc2VydmljZSIsInN1YiI6Imp3dC1zZXJ2aWNlIiwiYXVkIjoic2VvayIsImlhdCI6MTYyODI0MzIzMiwiZXhwIjoxNjI4MjQzMjkyfQ.a4i3VIm7IIGmbmHgnQLFuiftSC4dwzA7lfxQwjxpd3
2223
Content-Type: application/json
2324

2425
{
26+
"email": "seok@gmail.com",
2527
"password": "1234",
26-
"name": "seok"
28+
"name": "seokrae"
2729
}
2830

2931

‎src/main/java/com/example/springwebjwtredis/aop/GlobalExceptionHandler.java‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎src/main/java/com/example/springwebjwtredis/aop/auth/AuthenticationInterceptor.java‎

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import com.example.springwebjwtredis.aop.jwt.JwtUtil;
44
import com.example.springwebjwtredis.domain.MemberDto;
55
import com.example.springwebjwtredis.dto.RequestLoginMember;
6+
import com.example.springwebjwtredis.redis.domain.RedisMemberToken;
7+
import com.example.springwebjwtredis.redis.repository.CustomRedisRepository;
68
import com.example.springwebjwtredis.service.MemberService;
79
import com.fasterxml.jackson.databind.ObjectMapper;
810
import lombok.extern.slf4j.Slf4j;
9-
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.http.HttpHeaders;
1012
import org.springframework.http.HttpMethod;
1113
import org.springframework.util.StreamUtils;
1214
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -18,23 +20,22 @@
1820
import javax.servlet.http.HttpServletResponse;
1921
import java.io.IOException;
2022
import java.nio.charset.StandardCharsets;
23+
import java.util.Map;
24+
25+
import static com.example.springwebjwtredis.aop.jwt.JwtUtil.TOKEN_ACCESS_EXPIRED;
2126

2227
@Slf4j
2328
public class AuthenticationInterceptor implements HandlerInterceptor {
2429

25-
private final JwtUtil jwtUtil;
2630
private final ObjectMapper mapper = new ObjectMapper();
27-
@Autowired
28-
private MemberService memberService;
31+
private final CustomRedisRepository redisRepository;
32+
private final MemberService memberService;
33+
private final JwtUtil jwtUtil;
2934

30-
public AuthenticationInterceptor(JwtUtil jwtUtil) {
35+
public AuthenticationInterceptor(JwtUtil jwtUtil, MemberServicememberService, CustomRedisRepositoryredisRepository) {
3136
this.jwtUtil = jwtUtil;
32-
}
33-
34-
private RequestLoginMember getBodyStream(HttpServletRequest request) throws IOException {
35-
ServletInputStream inputStream = request.getInputStream();
36-
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
37-
return mapper.readValue(messageBody, RequestLoginMember.class);
37+
this.memberService = memberService;
38+
this.redisRepository = redisRepository;
3839
}
3940

4041
@Override
@@ -52,15 +53,34 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
5253
return false;
5354
}
5455

55-
String tokens = jwtUtil.createTokens(memberDto);
56-
57-
response.addHeader("Authorization", "Bearer " + tokens);
58-
56+
request.setAttribute("memberDto", memberDto);
5957
return true;
6058
}
6159

60+
private RequestLoginMember getBodyStream(HttpServletRequest request) throws IOException {
61+
ServletInputStream inputStream = request.getInputStream();
62+
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
63+
return mapper.readValue(messageBody, RequestLoginMember.class);
64+
}
65+
6266
@Override
6367
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
6468
log.info("AuthenticationInterceptor : postHandle()");
69+
70+
// 사용자 조회
71+
MemberDto memberDto = (MemberDto) request.getAttribute("memberDto");
72+
73+
String tokens = jwtUtil.generateToken(memberDto, JwtUtil.TOKEN_ACCESS_EXPIRED);
74+
String signature = tokens.split("\\.")[2];
75+
76+
redisRepository.saveRedis(
77+
signature,
78+
RedisMemberToken.builder()
79+
.email(memberDto.getEmail())
80+
.name(memberDto.getName())
81+
.build()
82+
);
83+
84+
response.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + tokens);
6585
}
6686
}

‎src/main/java/com/example/springwebjwtredis/aop/auth/AuthorizationInterceptor.java‎

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package com.example.springwebjwtredis.aop.auth;
22

3+
import com.example.springwebjwtredis.aop.exception.IllegalArgumentJwtException;
4+
import com.example.springwebjwtredis.aop.exception.RedisNotExistException;
5+
import com.example.springwebjwtredis.aop.jwt.JwtUtil;
6+
import com.example.springwebjwtredis.redis.repository.CustomRedisRepository;
7+
import io.jsonwebtoken.ClaimJwtException;
8+
import io.jsonwebtoken.ExpiredJwtException;
39
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.http.HttpHeaders;
411
import org.springframework.web.servlet.HandlerInterceptor;
512
import org.springframework.web.servlet.ModelAndView;
613

@@ -10,10 +17,44 @@
1017
@Slf4j
1118
public class AuthorizationInterceptor implements HandlerInterceptor {
1219

20+
private final JwtUtil jwtUtil;
21+
private final CustomRedisRepository redisRepository;
22+
23+
public AuthorizationInterceptor(JwtUtil jwtUtil, CustomRedisRepository redisRepository) {
24+
this.jwtUtil = jwtUtil;
25+
this.redisRepository = redisRepository;
26+
}
27+
1328
@Override
1429
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
1530
log.info("AuthorizationInterceptor : preHandle()");
16-
return HandlerInterceptor.super.preHandle(request, response, handler);
31+
32+
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
33+
if(header.isEmpty()) {
34+
throw new IllegalArgumentJwtException("인증 토큰이 존재 하지 않습니다.");
35+
}
36+
String accessToken = header.replace("Bearer ", "");
37+
38+
try {
39+
if(jwtUtil.isValidToken(accessToken)) {
40+
log.info(" Valid AccessToken: {}", accessToken);
41+
String signature = accessToken.split("\\.")[2];
42+
43+
if(hasAccessToken(signature)) { // Redis에 Token 존재 여부 확인
44+
log.info(" Success Resource Access ");
45+
return true;
46+
}
47+
throw new RedisNotExistException("레디스에 토큰이 존재 하지 않습니다.");
48+
}
49+
50+
} catch(ExpiredJwtException e) {
51+
log.debug("Expired Jwt Exception 으로 재발급");
52+
}
53+
return false;
54+
}
55+
56+
private boolean hasAccessToken(String hashKey) {
57+
return redisRepository.hasRedisByAccessToken(hashKey);
1758
}
1859

1960
@Override
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.example.springwebjwtredis.aop.exception;
2+
3+
public class IllegalArgumentJwtException extends RuntimeException {
4+
public IllegalArgumentJwtException() {
5+
}
6+
7+
public IllegalArgumentJwtException(String message) {
8+
super(message);
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.example.springwebjwtredis.aop.exception;
2+
3+
public class RedisNotExistException extends RuntimeException {
4+
public RedisNotExistException() {
5+
}
6+
7+
public RedisNotExistException(String message) {
8+
super(message);
9+
}
10+
}

‎src/main/java/com/example/springwebjwtredis/aop/jwt/JwtUtil.java‎

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
public class JwtUtil {
1818

1919
private static final int TOKEN_DEFAULT_EXPIRED = 0;
20-
private static final int TOKEN_ACCESS_EXPIRED = 1;
20+
public static final int TOKEN_ACCESS_EXPIRED = 1;
2121

2222
private final String issue;
2323
private final Key key;
@@ -29,17 +29,17 @@ public JwtUtil(String issue, String secret, Long expiredDate) {
2929
this.expiredDate = expiredDate;
3030
}
3131

32-
public String createTokens(MemberDto memberDto) {
33-
// DB 조회 시 오류 발생 처리 필요
34-
String email = memberDto.getEmail();
35-
// 엑세스 토큰 발급 및 헤더 저장
36-
String accessToken = generateToken(memberDto, TOKEN_ACCESS_EXPIRED);
37-
// accessTokenService.add(email, accessToken);
38-
// 리프레시 토큰 발급 및 DB 저장
39-
// String refreshToken = jwtUtils.generateToken(memberDto, JwtConst.REFRESH_EXPIRED);
40-
// refreshTokenService.add(email, refreshToken);
41-
return accessToken;
42-
}
32+
// public String createTokens(MemberDto memberDto, int plus) {
33+
// // DB 조회 시 오류 발생 처리 필요
34+
// String email = memberDto.getEmail();
35+
// // 엑세스 토큰 발급 및 헤더 저장
36+
// String accessToken = generateToken(memberDto, TOKEN_ACCESS_EXPIRED);
37+
//// accessTokenService.add(email, accessToken);
38+
// // 리프레시 토큰 발급 및 DB 저장
39+
//// String refreshToken = jwtUtils.generateToken(memberDto, JwtConst.REFRESH_EXPIRED);
40+
//// refreshTokenService.add(email, refreshToken);
41+
// return accessToken;
42+
// }
4343

4444
/**
4545
* JWT 토큰 생성 메서드
@@ -62,7 +62,6 @@ public String generateToken(MemberDto memberDto, Integer plusMinutes) {
6262
.setAudience("seok") // 토큰 대상자
6363
.setIssuedAt(createDate(TOKEN_DEFAULT_EXPIRED)) // 토큰 발생 시간
6464
.setExpiration(createDate(plusMinutes)) // 만료시간
65-
6665
.compact();
6766
}
6867

@@ -101,7 +100,8 @@ private Map<String, Object> createClaims(MemberDto memberDto) {
101100
// 비공개 클레임으로 사용자의 이름과 이메일을 설정, 세션 처럼 정보를 넣고 빼서 쓸 수 있다.
102101
Map<String, Object> claims = new HashMap<>();
103102

104-
claims.put("id", memberDto.getEmail());
103+
claims.put("email", memberDto.getEmail());
104+
claims.put("name", memberDto.getName());
105105
// claims.put("role", memberDto.getRole());
106106

107107
log.info("Token 2: {}", claims);
@@ -118,24 +118,25 @@ private Claims getClaimsFormToken(String token) {
118118
log.info("token Body: {}", Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody());
119119
return Jwts.parser()
120120
.setSigningKey(key)
121-
.parseClaimsJws(token).getBody();
121+
.parseClaimsJws(token)
122+
.getBody();
122123
}
123124

124125
/**
125126
* @param token JWT의 Payload 값
126127
* @return payload에서 현재 프로젝트에서 설정한 사용자 값을 구분하는 값인 userEmail을 반환
127128
*/
128-
public String getUserNameFromToken(String token) {
129+
public String getUserEmail(String token) {
129130
/* 복호화된 Payload로 id 확인 */
130131
Claims claims = getClaimsFormToken(token);
131-
return (String) claims.get("id");
132+
return (String) claims.get("email");
132133
}
133134

134135
/**
135136
* @param token 사용자 정보를 HS256로 암호화한 JWT
136137
* @return token 복호화하여 Payload 값 반환
137138
*/
138-
public Claims getClaims(String token) {
139+
private Claims getClaims(String token) {
139140
// Jws는 sign이 포함된 JWT를 말함
140141
return Jwts.parser()
141142
.setSigningKey(key)
@@ -162,22 +163,22 @@ public boolean isValidToken(String token) throws ExpiredJwtException {
162163
log.info("=================== Claims ===================");
163164
return true;
164165
} catch (SignatureException exception) {
165-
log.error("SignatureException 오류");
166+
log.debug("SignatureException 오류");
166167
} catch (MalformedJwtException exception) {
167-
log.error("구조적인 문제가 있는 JWT인 경우");
168+
log.debug("구조적인 문제가 있는 JWT인 경우");
168169
} catch (UnsupportedJwtException exception) {
169-
log.error("암호화된 JWT를 사용하는 애프리케이션에 암호화되지 않은 JWT가 전달되는 경우");
170+
log.debug("암호화된 JWT를 사용하는 애프리케이션에 암호화되지 않은 JWT가 전달되는 경우");
170171
/* access Token 예외 발생으로 인해 refreshToken 체크 시점 */
171172
// } catch (ExpiredJwtException exception) {
172-
// log.error("Token ExpiredJwtException");
173+
// log.debug("Token ExpiredJwtException");
173174
} catch (PrematureJwtException exception) {
174-
log.error("접근이 허용되기 전인 JWT가 수신된 경우");
175+
log.debug("접근이 허용되기 전인 JWT가 수신된 경우");
175176
} catch (ClaimJwtException exception) {
176-
log.error("JWT 권한claim 검사가 실패했을 때");
177+
log.debug("ClaimJwtException JWT 권한claim 검사가 실패했을 때");
177178
} catch (JwtException exception) {
178-
log.error("Token Tampered");
179+
log.debug("Token Tampered");
179180
} catch (NullPointerException exception) {
180-
log.error("Token is null");
181+
log.debug("Token is null");
181182
throw new RuntimeException("Token is null");
182183
}
183184
return false;

‎src/main/java/com/example/springwebjwtredis/config/WebMvcConfig.java‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.example.springwebjwtredis.aop.auth.AuthenticationInterceptor;
44
import com.example.springwebjwtredis.aop.auth.AuthorizationInterceptor;
55
import com.example.springwebjwtredis.aop.jwt.JwtUtil;
6+
import com.example.springwebjwtredis.redis.repository.CustomRedisRepository;
7+
import com.example.springwebjwtredis.service.MemberService;
68
import org.springframework.context.annotation.Bean;
79
import org.springframework.context.annotation.Configuration;
810
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@@ -12,36 +14,40 @@
1214
public class WebMvcConfig implements WebMvcConfigurer {
1315

1416
private final JwtUtil jwtUtil;
17+
private final MemberService memberService;
18+
private final CustomRedisRepository redisRepository;
1519

16-
public WebMvcConfig(JwtUtil jwtUtil) {
20+
public WebMvcConfig(JwtUtil jwtUtil, MemberServicememberService, CustomRedisRepositoryredisRepository) {
1721
this.jwtUtil = jwtUtil;
22+
this.memberService = memberService;
23+
this.redisRepository = redisRepository;
1824
}
1925

2026
@Bean
2127
public AuthenticationInterceptor authenticationInterceptor() {
22-
return new AuthenticationInterceptor(jwtUtil);
28+
return new AuthenticationInterceptor(jwtUtil, memberService, redisRepository);
2329
}
2430

2531
@Bean
2632
public AuthorizationInterceptor authorizationInterceptor() {
27-
return new AuthorizationInterceptor();
33+
return new AuthorizationInterceptor(jwtUtil, redisRepository);
2834
}
2935

3036
@Override
3137
public void addInterceptors(InterceptorRegistry registry) {
3238
registry
3339
.addInterceptor(authenticationInterceptor())
3440
.addPathPatterns(
35-
"/login"
41+
"/auth/**"
42+
);
43+
44+
registry
45+
.addInterceptor(authorizationInterceptor())
46+
.addPathPatterns(
47+
"/members/**"
3648
)
3749
.excludePathPatterns(
38-
"/members*"
50+
"/members"
3951
);
40-
41-
// registry
42-
// .addInterceptor(authorizationInterceptor())
43-
// .addPathPatterns(
44-
// "/members/**"
45-
// );
4652
}
4753
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /