Skip to content

Commit c5c4bc2

Browse files
authored
Merge pull request #14 from team-MoPlus/feature/#11
[feat/#11] jwt 인증 필터 구현
2 parents 3b3ef30 + d67a3af commit c5c4bc2

File tree

11 files changed

+352
-21
lines changed

11 files changed

+352
-21
lines changed

src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ public void adminLogin(
3636
) {
3737
// 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다.
3838
}
39+
3940
}

src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@ public class MemberService {
1616
public Member getMemberByEmail(String email) {
1717
return memberRepository.findByEmailOrThrow(email);
1818
}
19+
20+
@Transactional(readOnly = true)
21+
public Member getMemberById(Long id) {
22+
return memberRepository.findById(id).orElseThrow();
23+
}
1924
}

src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22

33
import com.moplus.moplus_server.domain.member.service.MemberService;
44
import com.moplus.moplus_server.global.security.filter.EmailPasswordAuthenticationFilter;
5+
import com.moplus.moplus_server.global.security.filter.JwtAuthenticationFilter;
56
import com.moplus.moplus_server.global.security.handler.EmailPasswordSuccessHandler;
67
import com.moplus.moplus_server.global.security.provider.EmailPasswordAuthenticationProvider;
8+
import com.moplus.moplus_server.global.security.provider.JwtTokenProvider;
9+
import com.moplus.moplus_server.global.security.utils.JwtUtil;
710
import java.util.List;
811
import lombok.RequiredArgsConstructor;
912
import org.springframework.beans.factory.annotation.Value;
1013
import org.springframework.context.annotation.Bean;
1114
import org.springframework.context.annotation.Configuration;
1215
import org.springframework.http.HttpStatus;
1316
import org.springframework.security.authentication.AuthenticationManager;
14-
import org.springframework.security.authentication.ProviderManager;
15-
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
17+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
1618
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1719
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1820
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
@@ -32,7 +34,7 @@ public class SecurityConfig {
3234

3335
private final MemberService memberService;
3436
private final EmailPasswordSuccessHandler emailPasswordSuccessHandler;
35-
private final AuthenticationConfiguration authenticationConfiguration;
37+
private final JwtUtil jwtUtil;
3638

3739
private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**"};
3840

@@ -45,6 +47,17 @@ public WebSecurityCustomizer configure() {
4547
return (web) -> web.ignoring().requestMatchers(allowUrls);
4648
}
4749

50+
@Bean
51+
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
52+
AuthenticationManagerBuilder authenticationManagerBuilder =
53+
http.getSharedObject(AuthenticationManagerBuilder.class);
54+
authenticationManagerBuilder
55+
.authenticationProvider(emailPasswordAuthenticationProvider())
56+
.authenticationProvider(jwtTokenProvider());
57+
authenticationManagerBuilder.parentAuthenticationManager(null);
58+
return authenticationManagerBuilder.build();
59+
}
60+
4861

4962
@Bean
5063
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -62,34 +75,43 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
6275
exception.authenticationEntryPoint((request, response, authException) ->
6376
response.setStatus(HttpStatus.UNAUTHORIZED.value()))); // 인증,인가가 되지 않은 요청 시 발생시
6477

78+
http.authenticationManager(authenticationManager(http));
79+
6580
http
66-
.addFilterAt(emailPasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
67-
// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
68-
// .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class);
81+
.addFilterAt(emailPasswordAuthenticationFilter(authenticationManager(http)),
82+
UsernamePasswordAuthenticationFilter.class)
83+
.addFilterBefore(jwtAuthenticationFilter(authenticationManager(http)),
84+
UsernamePasswordAuthenticationFilter.class);
6985

7086
return http.build();
7187
}
7288

7389
@Bean
74-
public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter() throws Exception {
75-
EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter = new EmailPasswordAuthenticationFilter(
76-
authenticationManager(authenticationConfiguration));
77-
emailPasswordAuthenticationFilter.setFilterProcessesUrl("/api/v1/auth/admin/login");
78-
emailPasswordAuthenticationFilter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler);
79-
emailPasswordAuthenticationFilter.afterPropertiesSet();
80-
return emailPasswordAuthenticationFilter;
90+
public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter(
91+
AuthenticationManager authenticationManager) throws Exception {
92+
EmailPasswordAuthenticationFilter filter = new EmailPasswordAuthenticationFilter(authenticationManager);
93+
filter.setFilterProcessesUrl("/api/v1/auth/admin/login");
94+
filter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler);
95+
filter.afterPropertiesSet();
96+
return filter;
8197
}
8298

8399
@Bean
84-
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
85-
ProviderManager providerManager = (ProviderManager) authenticationConfiguration.getAuthenticationManager();
86-
providerManager.getProviders().add(emailPasswordAuthenticationProvider());
87-
return configuration.getAuthenticationManager();
100+
public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() {
101+
return new EmailPasswordAuthenticationProvider(memberService);
88102
}
89103

90104
@Bean
91-
public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() {
92-
return new EmailPasswordAuthenticationProvider(memberService);
105+
public JwtAuthenticationFilter jwtAuthenticationFilter(AuthenticationManager authenticationManager)
106+
throws Exception {
107+
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager);
108+
filter.afterPropertiesSet();
109+
return filter;
110+
}
111+
112+
@Bean
113+
public JwtTokenProvider jwtTokenProvider() {
114+
return new JwtTokenProvider(jwtUtil, memberService);
93115
}
94116

95117
@Bean

src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ public enum ErrorCode {
1111
INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "잘못된 입력 값입니다"),
1212
BAD_CREDENTIALS(HttpStatus.UNAUTHORIZED, "잘못된 인증 정보입니다"),
1313

14+
//Auth
15+
AUTH_NOT_FOUND(HttpStatus.UNAUTHORIZED, "시큐리티 인증 정보를 찾을 수 없습니다."),
16+
UNKNOWN_ERROR(HttpStatus.UNAUTHORIZED, "알 수 없는 에러"),
17+
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 Token입니다"),
18+
UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰 길이 및 형식이 다른 Token입니다"),
19+
WRONG_TYPE_TOKEN(HttpStatus.UNAUTHORIZED, "서명이 잘못된 토큰입니다."),
20+
ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "토큰이 없습니다"),
21+
TOKEN_SUBJECT_FORMAT_ERROR(HttpStatus.UNAUTHORIZED, "Subject 값에 Long 타입이 아닌 다른 타입이 들어있습니다."),
22+
AT_EXPIRED_AND_RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AT는 만료되었고 RT는 비어있습니다."),
23+
RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "RT가 비어있습니다"),
24+
1425
//모의고사
1526
PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"),
1627

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.moplus.moplus_server.global.security.exception;
2+
3+
import org.springframework.security.core.AuthenticationException;
4+
5+
public class JwtInvalidException extends AuthenticationException {
6+
7+
public JwtInvalidException(String msg) {
8+
super(msg);
9+
}
10+
11+
public JwtInvalidException(String msg, Throwable cause) {
12+
super(msg, cause);
13+
}
14+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.moplus.moplus_server.global.security.filter;
2+
3+
import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken;
4+
import jakarta.servlet.FilterChain;
5+
import jakarta.servlet.ServletException;
6+
import jakarta.servlet.http.HttpServletRequest;
7+
import jakarta.servlet.http.HttpServletResponse;
8+
import java.io.IOException;
9+
import java.util.Optional;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.security.authentication.AuthenticationManager;
12+
import org.springframework.security.core.Authentication;
13+
import org.springframework.security.core.context.SecurityContextHolder;
14+
import org.springframework.util.StringUtils;
15+
import org.springframework.web.filter.OncePerRequestFilter;
16+
17+
@RequiredArgsConstructor
18+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
19+
20+
public static final String TOKEN_PREFIX = "Bearer ";
21+
22+
private final AuthenticationManager authenticationManager;
23+
24+
@Override
25+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
26+
throws ServletException, IOException {
27+
28+
String accessToken = extractAccessTokenFromHeader(request);
29+
30+
if (StringUtils.hasText(accessToken)) {
31+
Authentication jwtAuthenticationToken = new JwtAuthenticationToken(accessToken);
32+
Authentication authentication = authenticationManager.authenticate(jwtAuthenticationToken);
33+
SecurityContextHolder.getContext().setAuthentication(authentication);
34+
}
35+
36+
filterChain.doFilter(request, response);
37+
}
38+
39+
private String extractAccessTokenFromHeader(HttpServletRequest request) {
40+
return Optional.ofNullable(request.getHeader("Authorization"))
41+
.filter(header -> header.startsWith(TOKEN_PREFIX))
42+
.map(header -> header.replace(TOKEN_PREFIX, ""))
43+
.orElse(null);
44+
}
45+
}

src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.moplus.moplus_server.domain.member.domain.Member;
44
import com.moplus.moplus_server.global.security.AuthConstants;
5-
import com.moplus.moplus_server.global.security.JwtUtil;
5+
import com.moplus.moplus_server.global.security.utils.JwtUtil;
66
import jakarta.servlet.http.HttpServletRequest;
77
import jakarta.servlet.http.HttpServletResponse;
88
import lombok.RequiredArgsConstructor;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.moplus.moplus_server.global.security.provider;
2+
3+
import com.moplus.moplus_server.domain.member.domain.Member;
4+
import com.moplus.moplus_server.domain.member.service.MemberService;
5+
import com.moplus.moplus_server.global.error.exception.ErrorCode;
6+
import com.moplus.moplus_server.global.security.exception.JwtInvalidException;
7+
import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken;
8+
import com.moplus.moplus_server.global.security.utils.JwtUtil;
9+
import io.jsonwebtoken.Claims;
10+
import io.jsonwebtoken.ExpiredJwtException;
11+
import io.jsonwebtoken.MalformedJwtException;
12+
import io.jsonwebtoken.security.SignatureException;
13+
import java.util.List;
14+
import lombok.RequiredArgsConstructor;
15+
import org.springframework.security.authentication.AuthenticationProvider;
16+
import org.springframework.security.authentication.BadCredentialsException;
17+
import org.springframework.security.core.Authentication;
18+
import org.springframework.security.core.AuthenticationException;
19+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
20+
import org.springframework.stereotype.Service;
21+
22+
@Service
23+
@RequiredArgsConstructor
24+
public class JwtTokenProvider implements AuthenticationProvider {
25+
26+
private final JwtUtil jwtUtil;
27+
private final MemberService memberService;
28+
29+
@Override
30+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
31+
Claims claims = getClaims(authentication);
32+
final Member member = getMemberById(claims.getSubject());
33+
34+
return new JwtAuthenticationToken(
35+
member,
36+
"",
37+
List.of(new SimpleGrantedAuthority(member.getRole().getValue())
38+
));
39+
}
40+
41+
private Claims getClaims(Authentication authentication) {
42+
Claims claims;
43+
try {
44+
claims = jwtUtil.getAccessTokenClaims(authentication);
45+
} catch (ExpiredJwtException expiredJwtException) {
46+
throw new JwtInvalidException(ErrorCode.EXPIRED_TOKEN.getMessage());
47+
} catch (SignatureException signatureException) {
48+
throw new JwtInvalidException(ErrorCode.WRONG_TYPE_TOKEN.getMessage());
49+
} catch (MalformedJwtException malformedJwtException) {
50+
throw new JwtInvalidException(ErrorCode.UNSUPPORTED_TOKEN.getMessage());
51+
} catch (IllegalArgumentException illegalArgumentException) {
52+
throw new JwtInvalidException(ErrorCode.UNKNOWN_ERROR.getMessage());
53+
}
54+
return claims;
55+
}
56+
57+
private Member getMemberById(String id) {
58+
try {
59+
return memberService.getMemberById(Long.parseLong(id));
60+
} catch (Exception e) {
61+
throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage());
62+
}
63+
}
64+
65+
@Override
66+
public boolean supports(Class<?> authentication) {
67+
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
68+
}
69+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.moplus.moplus_server.global.security.token;
2+
3+
import java.util.Collection;
4+
import org.springframework.security.authentication.AbstractAuthenticationToken;
5+
import org.springframework.security.core.GrantedAuthority;
6+
7+
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
8+
9+
private String jsonWebToken;
10+
private Object principal;
11+
private Object credentials;
12+
13+
public JwtAuthenticationToken(String jsonWebToken) {
14+
super(null);
15+
this.jsonWebToken = jsonWebToken;
16+
this.setAuthenticated(false);
17+
}
18+
19+
public JwtAuthenticationToken(Object principal, Object credentials,
20+
Collection<? extends GrantedAuthority> authorities) {
21+
super(authorities);
22+
this.principal = principal;
23+
this.credentials = credentials;
24+
super.setAuthenticated(true);
25+
}
26+
27+
public Object getCredentials() {
28+
return credentials;
29+
}
30+
31+
public Object getPrincipal() {
32+
return this.principal;
33+
}
34+
35+
public String getJsonWebToken() {
36+
return this.jsonWebToken;
37+
}
38+
}

src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java renamed to src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
package com.moplus.moplus_server.global.security;
1+
package com.moplus.moplus_server.global.security.utils;
22

33
import com.moplus.moplus_server.domain.member.domain.Member;
44
import com.moplus.moplus_server.global.properties.jwt.JwtProperties;
5+
import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken;
6+
import io.jsonwebtoken.Claims;
57
import io.jsonwebtoken.Jwts;
68
import io.jsonwebtoken.security.Keys;
79
import java.security.Key;
810
import java.util.Date;
911
import lombok.RequiredArgsConstructor;
1012
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.security.core.Authentication;
1114
import org.springframework.stereotype.Component;
1215

1316
@Slf4j
@@ -45,6 +48,16 @@ public String generateRefreshToken(Member member) {
4548
.compact();
4649
}
4750

51+
public Claims getAccessTokenClaims(Authentication authentication) {
52+
53+
return Jwts.parserBuilder()
54+
.requireIssuer(jwtProperties.issuer())
55+
.setSigningKey(getAccessTokenKey())
56+
.build()
57+
.parseClaimsJws(((JwtAuthenticationToken) authentication).getJsonWebToken())
58+
.getBody();
59+
}
60+
4861
private Key getAccessTokenKey() {
4962
return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes());
5063
}

0 commit comments

Comments
 (0)