Skip to content

Commit fe4954e

Browse files
authored
Merge pull request #200 from Young-Flow/refactor/#199
Refactor/#199
2 parents c5085b3 + a3fb064 commit fe4954e

File tree

6 files changed

+131
-60
lines changed

6 files changed

+131
-60
lines changed

src/main/java/com/pitchain/common/collector/RoleRequestCollector.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.pitchain.common.annotation.RequiredRole;
44
import com.pitchain.common.constant.MemberRole;
55
import org.springframework.context.ApplicationContext;
6-
import org.springframework.core.annotation.AnnotatedElementUtils;
76
import org.springframework.http.HttpMethod;
87
import org.springframework.stereotype.Component;
98
import org.springframework.web.bind.annotation.RequestMethod;
@@ -37,8 +36,7 @@ public RoleRequestCollector(ApplicationContext applicationContext) {
3736

3837
private void collectRoleUris(Map.Entry<RequestMappingInfo, HandlerMethod> entry, Map<MemberRole, Map<HttpMethod, Set<String>>> roleUriMap) {
3938
HandlerMethod handlerMethod = entry.getValue();
40-
RequiredRole requiredRole = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getMethod(), RequiredRole.class);
41-
39+
RequiredRole requiredRole = handlerMethod.getMethod().getAnnotation(RequiredRole.class);
4240
if (requiredRole != null) {
4341
MemberRole memberRole = requiredRole.value();
4442
Map<HttpMethod, Set<String>> uriMap = roleUriMap.get(memberRole);

src/main/java/com/pitchain/common/config/SecurityConfig.java

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.pitchain.common.config;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.pitchain.common.apiPayload.CustomResponse;
5+
import com.pitchain.common.apiPayload.ErrorStatus;
36
import com.pitchain.common.collector.RoleRequestCollector;
47
import com.pitchain.common.filter.JwtAuthenticationFilter;
8+
import jakarta.servlet.http.HttpServletResponse;
59
import lombok.RequiredArgsConstructor;
610
import org.springframework.context.annotation.Bean;
711
import org.springframework.context.annotation.Configuration;
@@ -11,12 +15,15 @@
1115
import org.springframework.security.config.http.SessionCreationPolicy;
1216
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1317
import org.springframework.security.crypto.password.PasswordEncoder;
18+
import org.springframework.security.web.AuthenticationEntryPoint;
1419
import org.springframework.security.web.SecurityFilterChain;
20+
import org.springframework.security.web.access.AccessDeniedHandler;
1521
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
1622
import org.springframework.web.cors.CorsConfiguration;
1723
import org.springframework.web.cors.CorsConfigurationSource;
1824
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
1925

26+
import java.io.IOException;
2027
import java.util.Arrays;
2128
import java.util.List;
2229

@@ -29,17 +36,6 @@ public class SecurityConfig {
2936
private final JwtAuthenticationFilter jwtAuthenticationFilter;
3037
private final RoleRequestCollector roleRequestCollector;
3138

32-
public static final String[] whitelist = {
33-
"/oauth**",
34-
"/resources/**", "/favicon.ico", // resource
35-
"/swagger-ui/**", "/api-docs/**", "/v3/api-docs**", "/v3/api-docs/**", // swagger
36-
"/dev/**", // 개발용,
37-
"/health-check", // health check
38-
39-
"/members/tokens", // 공통 유저
40-
"/companies", "/companies/login", "/companies/emails"// 회사
41-
};
42-
4339
@Bean
4440
public PasswordEncoder passwordEncoder() {
4541
return new BCryptPasswordEncoder();
@@ -55,17 +51,43 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5551
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
5652

5753
http.authorizeHttpRequests(auth -> {
58-
auth.requestMatchers(whitelist).permitAll();
5954
roleRequestCollector.getRoleUriMap().forEach((role, methodUriMap) -> {
6055
methodUriMap.forEach((httpMethod, uriSet) -> {
6156
auth.requestMatchers(httpMethod, uriSet.toArray(new String[0])).hasAnyAuthority(role.getRoles());
6257
});
6358
});
59+
auth.requestMatchers(SWAGGER_PATTERNS).permitAll();
60+
auth.requestMatchers(STATIC_RESOURCES_PATTERNS).permitAll();
61+
auth.requestMatchers(PUBLIC_ENDPOINTS).permitAll();
6462
auth.anyRequest().authenticated();
6563
});
64+
http.exceptionHandling(exception -> {
65+
exception.authenticationEntryPoint(customAuthenticationEntryPoint());
66+
exception.accessDeniedHandler(customAccessDeniedHandler());
67+
});
6668
return http.build();
6769
}
6870

71+
private static final String[] SWAGGER_PATTERNS = {
72+
"/swagger-ui/**",
73+
"/v3/api-docs/**",
74+
};
75+
76+
private static final String[] STATIC_RESOURCES_PATTERNS = {
77+
"/img/**",
78+
"/css/**",
79+
"/js/**",
80+
"/favicon.ico",
81+
};
82+
83+
private static final String[] PUBLIC_ENDPOINTS = {
84+
"/health-check", // health check
85+
"/oauth**",
86+
"/members/tokens", "/members/emails", // 공통 유저
87+
"/companies", "/companies/login", // 회사
88+
"/dev/**", // 개발용
89+
};
90+
6991
@Bean
7092
public CorsConfigurationSource corsConfigurationSource() {
7193
CorsConfiguration config = new CorsConfiguration();
@@ -79,4 +101,30 @@ public CorsConfigurationSource corsConfigurationSource() {
79101
source.registerCorsConfiguration("/**", config);
80102
return source;
81103
}
104+
105+
@Bean
106+
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
107+
return (request, response, authException) -> {
108+
final String message = "유효한 인증 정보가 없거나, 존재하지 않는 API를 요청하셨습니다.";
109+
writeErrorResponse(response, message);
110+
};
111+
}
112+
113+
@Bean
114+
public AccessDeniedHandler customAccessDeniedHandler() {
115+
return (request, response, accessDeniedException) -> {
116+
final String message = "요청하신 API에 대한 접근 권한이 없습니다.";
117+
writeErrorResponse(response, message);
118+
};
119+
}
120+
121+
private static void writeErrorResponse(HttpServletResponse response, String message) throws IOException {
122+
ErrorStatus errorStatus = ErrorStatus._FORBIDDEN;
123+
CustomResponse customResponse = CustomResponse.onFailure(errorStatus.getCode(), message);
124+
125+
response.setStatus(errorStatus.getHttpStatus().value());
126+
response.setContentType("application/json");
127+
response.setCharacterEncoding("UTF-8");
128+
response.getWriter().write(new ObjectMapper().writeValueAsString(customResponse));
129+
}
82130
}

src/main/java/com/pitchain/common/constant/MemberRole.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
import org.springframework.security.core.authority.SimpleGrantedAuthority;
99

1010
import java.util.Collection;
11-
import java.util.List;
11+
import java.util.EnumSet;
1212

1313
@Getter
1414
@RequiredArgsConstructor
1515
public enum MemberRole {
16-
MEMBER(new String[]{"ROLE_INDIVIDUAL", "ROLE_COMPANY"}),
17-
INDIVIDUAL(new String[]{"ROLE_INDIVIDUAL"}),
18-
COMPANY(new String[]{"ROLE_COMPANY"}),
16+
MEMBER(EnumSet.of(RoleType.INDIVIDUAL, RoleType.COMPANY)),
17+
INDIVIDUAL(EnumSet.of(RoleType.INDIVIDUAL)),
18+
COMPANY(EnumSet.of(RoleType.COMPANY)),
1919
;
2020

21-
private final String[] roles;
21+
private final EnumSet<RoleType> roleTypes;
2222

2323
public static MemberRole toEnum(String role) {
2424
try {
@@ -29,8 +29,23 @@ public static MemberRole toEnum(String role) {
2929
}
3030

3131
public Collection<? extends GrantedAuthority> getAuthorities() {
32-
return List.of(roles).stream()
33-
.map(SimpleGrantedAuthority::new)
32+
return roleTypes.stream()
33+
.map(roleType -> new SimpleGrantedAuthority(roleType.getRoleName()))
3434
.toList();
3535
}
36+
37+
public String[] getRoles() {
38+
return roleTypes.stream().map(RoleType::getRoleName).toArray(String[]::new);
39+
}
40+
41+
@Getter
42+
@RequiredArgsConstructor
43+
public enum RoleType {
44+
INDIVIDUAL("ROLE_INDIVIDUAL"),
45+
COMPANY("ROLE_COMPANY"),
46+
;
47+
48+
private final String roleName;
49+
}
50+
3651
}
Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package com.pitchain.common.filter;
22

3-
import com.auth0.jwt.interfaces.DecodedJWT;
4-
import com.pitchain.common.apiPayload.ErrorStatus;
3+
import com.auth0.jwt.exceptions.JWTVerificationException;
54
import com.pitchain.common.constant.MemberRole;
65
import com.pitchain.common.constant.TokenType;
7-
import com.pitchain.common.exception.GeneralException;
8-
import com.pitchain.common.security.MemberDetails;
96
import com.pitchain.common.redis.RedisTokenUtil;
7+
import com.pitchain.common.security.MemberClaims;
8+
import com.pitchain.common.security.MemberDetails;
109
import jakarta.servlet.FilterChain;
1110
import jakarta.servlet.ServletException;
1211
import jakarta.servlet.http.HttpServletRequest;
@@ -17,7 +16,6 @@
1716
import org.springframework.security.core.Authentication;
1817
import org.springframework.security.core.context.SecurityContextHolder;
1918
import org.springframework.stereotype.Component;
20-
import org.springframework.util.PatternMatchUtils;
2119
import org.springframework.web.filter.OncePerRequestFilter;
2220

2321
import java.io.IOException;
@@ -28,37 +26,42 @@
2826
public class JwtAuthenticationFilter extends OncePerRequestFilter {
2927
private final RedisTokenUtil redisTokenUtil;
3028

31-
public static final String[] whitelist = {
32-
"/oauth2/**",
33-
"/resources/**", "/favicon.ico", // resource
34-
"/swagger-ui/**", "/api-docs/**", "/v3/api-docs**", "/v3/api-docs/**", // swagger
35-
"/health-check", // health check
36-
"/dev/**", // 개발용,
37-
"/members/tokens", // 공통 유저
38-
"/companies", "/companies/login", "/companies/emails"// 회사
39-
};
40-
4129
@Override
42-
protected boolean shouldNotFilter(HttpServletRequest request) {
43-
return PatternMatchUtils.simpleMatch(whitelist, request.getRequestURI());
30+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
31+
try {
32+
verifyAccessToken(request);
33+
} catch (JWTVerificationException e1) {
34+
try {
35+
verifyRefreshToken(request, response);
36+
} catch (JWTVerificationException e2) {
37+
filterChain.doFilter(request, response);
38+
return;
39+
}
40+
}
41+
42+
filterChain.doFilter(request, response);
4443
}
4544

46-
@Override
47-
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
48-
String token = redisTokenUtil.extractToken(request, TokenType.ACCESS_TOKEN);
45+
private void verifyAccessToken(HttpServletRequest request) {
46+
String accessToken = redisTokenUtil.extractToken(request, TokenType.ACCESS_TOKEN);
47+
MemberClaims claim = redisTokenUtil.getClaim(accessToken);
48+
setAuthentication(claim);
49+
}
4950

50-
if (token == null)
51-
throw new GeneralException(ErrorStatus.TOKEN_MISSING);
51+
private void verifyRefreshToken(HttpServletRequest request, HttpServletResponse response) {
52+
String refreshToken = redisTokenUtil.extractToken(request, TokenType.REFRESH_TOKEN);
53+
MemberClaims claims = redisTokenUtil.getClaim(refreshToken);
54+
redisTokenUtil.reissueToken(response, claims);
55+
setAuthentication(claims);
56+
}
5257

53-
DecodedJWT decodedJWT = redisTokenUtil.decodedJWT(token);
54-
Long id = decodedJWT.getClaim("id").asLong();
55-
String role = decodedJWT.getClaim("role").asString();
58+
private void setAuthentication(MemberClaims claims) {
59+
Long id = claims.getId();
60+
MemberRole memberRole = claims.getMemberRole();
5661

57-
MemberRole memberRole = MemberRole.toEnum(role);
5862
MemberDetails memberDetails = new MemberDetails(id, memberRole);
5963
Authentication authentication = new UsernamePasswordAuthenticationToken(memberDetails, null, memberRole.getAuthorities());
6064

6165
SecurityContextHolder.getContext().setAuthentication(authentication);
62-
doFilter(request, response, filterChain);
6366
}
6467
}

src/main/java/com/pitchain/common/redis/RedisTokenUtil.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.pitchain.common.exception.GeneralException;
1212
import com.pitchain.common.security.MemberClaims;
1313
import jakarta.servlet.http.HttpServletRequest;
14+
import jakarta.servlet.http.HttpServletResponse;
1415
import lombok.Getter;
1516
import lombok.RequiredArgsConstructor;
1617
import lombok.extern.slf4j.Slf4j;
@@ -49,7 +50,6 @@ public String issueAccessToken(Long memberId, MemberRole memberRole) {
4950
.sign(Algorithm.HMAC512(secretKey));
5051
}
5152

52-
// todo 프로토타입 시연을 위한 임시 메소드
5353
public String issueAccessTokenWithoutExpiration(Long memberId, MemberRole memberRole) {
5454
return JWT.create()
5555
.withSubject(ACCESS_TOKEN_SUBJECT)
@@ -119,14 +119,21 @@ public DecodedJWT decodedJWT(String accessToken) {
119119
}
120120
}
121121

122-
public MemberClaims getClaim(String refreshToken) {
123-
try {
124-
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512(secretKey)).build().verify(refreshToken);
125-
Long id = decodedJWT.getClaim("id").asLong();
126-
MemberRole memberRole = MemberRole.valueOf(decodedJWT.getClaim("role").asString());
127-
return new MemberClaims(id, memberRole);
128-
} catch (JWTVerificationException e) {
129-
throw new GeneralException(ErrorStatus._UNAUTHORIZED);
130-
}
122+
public MemberClaims getClaim(String token) {
123+
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token);
124+
Long id = decodedJWT.getClaim("id").asLong();
125+
MemberRole memberRole = MemberRole.toEnum(decodedJWT.getClaim("role").asString());
126+
return new MemberClaims(id, memberRole);
127+
}
128+
129+
public void reissueToken(HttpServletResponse response, MemberClaims claims) {
130+
Long userId = claims.getId();
131+
MemberRole memberRole = claims.getMemberRole();
132+
133+
String accessToken = issueAccessToken(userId, memberRole);
134+
String refreshToken = issueRefreshToken(userId, memberRole);
135+
136+
response.setHeader(accessHeader, BEARER + accessToken);
137+
response.setHeader(refreshHeader, BEARER + refreshToken);
131138
}
132139
}

src/main/java/com/pitchain/investment/presentation/InvestmentController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void addInvestment(@PathVariable("bmId") Long bmId,
2727
investmentService.addInvestment(bmId, memberDetails, amount);
2828
}
2929

30-
@GetMapping("/{bmId}/investment")
30+
@GetMapping("/{bmId}/investments")
3131
public InvestmentStatusRes getInvestmentStatus(@PathVariable("bmId") Long bmId) {
3232
InvestmentStatusRes investmentStatusRes = investmentService.getInvestmentStatus(bmId);
3333
return investmentStatusRes;

0 commit comments

Comments
 (0)