Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public void adminLogin(
) {
// 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다.
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ public class MemberService {
public Member getMemberByEmail(String email) {
return memberRepository.findByEmailOrThrow(email);
}

@Transactional(readOnly = true)
public Member getMemberById(Long id) {
return memberRepository.findById(id).orElseThrow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

import com.moplus.moplus_server.domain.member.service.MemberService;
import com.moplus.moplus_server.global.security.filter.EmailPasswordAuthenticationFilter;
import com.moplus.moplus_server.global.security.filter.JwtAuthenticationFilter;
import com.moplus.moplus_server.global.security.handler.EmailPasswordSuccessHandler;
import com.moplus.moplus_server.global.security.provider.EmailPasswordAuthenticationProvider;
import com.moplus.moplus_server.global.security.provider.JwtTokenProvider;
import com.moplus.moplus_server.global.security.utils.JwtUtil;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
Expand All @@ -32,7 +34,7 @@ public class SecurityConfig {

private final MemberService memberService;
private final EmailPasswordSuccessHandler emailPasswordSuccessHandler;
private final AuthenticationConfiguration authenticationConfiguration;
private final JwtUtil jwtUtil;

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

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

@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder
.authenticationProvider(emailPasswordAuthenticationProvider())
.authenticationProvider(jwtTokenProvider());
authenticationManagerBuilder.parentAuthenticationManager(null);
return authenticationManagerBuilder.build();
}


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

http.authenticationManager(authenticationManager(http));

http
.addFilterAt(emailPasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
// .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class);
.addFilterAt(emailPasswordAuthenticationFilter(authenticationManager(http)),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationFilter(authenticationManager(http)),
UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter() throws Exception {
EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter = new EmailPasswordAuthenticationFilter(
authenticationManager(authenticationConfiguration));
emailPasswordAuthenticationFilter.setFilterProcessesUrl("/api/v1/auth/admin/login");
emailPasswordAuthenticationFilter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler);
emailPasswordAuthenticationFilter.afterPropertiesSet();
return emailPasswordAuthenticationFilter;
public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter(
AuthenticationManager authenticationManager) throws Exception {
EmailPasswordAuthenticationFilter filter = new EmailPasswordAuthenticationFilter(authenticationManager);
filter.setFilterProcessesUrl("/api/v1/auth/admin/login");
filter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler);
filter.afterPropertiesSet();
return filter;
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
ProviderManager providerManager = (ProviderManager) authenticationConfiguration.getAuthenticationManager();
providerManager.getProviders().add(emailPasswordAuthenticationProvider());
return configuration.getAuthenticationManager();
public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() {
return new EmailPasswordAuthenticationProvider(memberService);
}

@Bean
public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() {
return new EmailPasswordAuthenticationProvider(memberService);
public JwtAuthenticationFilter jwtAuthenticationFilter(AuthenticationManager authenticationManager)
throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager);
filter.afterPropertiesSet();
return filter;
}

@Bean
public JwtTokenProvider jwtTokenProvider() {
return new JwtTokenProvider(jwtUtil, memberService);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ public enum ErrorCode {
INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "잘못된 입력 값입니다"),
BAD_CREDENTIALS(HttpStatus.UNAUTHORIZED, "잘못된 인증 정보입니다"),

//Auth
AUTH_NOT_FOUND(HttpStatus.UNAUTHORIZED, "시큐리티 인증 정보를 찾을 수 없습니다."),
UNKNOWN_ERROR(HttpStatus.UNAUTHORIZED, "알 수 없는 에러"),
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 Token입니다"),
UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰 길이 및 형식이 다른 Token입니다"),
WRONG_TYPE_TOKEN(HttpStatus.UNAUTHORIZED, "서명이 잘못된 토큰입니다."),
ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "토큰이 없습니다"),
TOKEN_SUBJECT_FORMAT_ERROR(HttpStatus.UNAUTHORIZED, "Subject 값에 Long 타입이 아닌 다른 타입이 들어있습니다."),
AT_EXPIRED_AND_RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AT는 만료되었고 RT는 비어있습니다."),
RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "RT가 비어있습니다"),

//모의고사
PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.moplus.moplus_server.global.security.exception;

import org.springframework.security.core.AuthenticationException;

public class JwtInvalidException extends AuthenticationException {

public JwtInvalidException(String msg) {
super(msg);
}

public JwtInvalidException(String msg, Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.moplus.moplus_server.global.security.filter;

import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

public static final String TOKEN_PREFIX = "Bearer ";

private final AuthenticationManager authenticationManager;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String accessToken = extractAccessTokenFromHeader(request);

if (StringUtils.hasText(accessToken)) {
Authentication jwtAuthenticationToken = new JwtAuthenticationToken(accessToken);
Authentication authentication = authenticationManager.authenticate(jwtAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}

filterChain.doFilter(request, response);
}

private String extractAccessTokenFromHeader(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader("Authorization"))
.filter(header -> header.startsWith(TOKEN_PREFIX))
.map(header -> header.replace(TOKEN_PREFIX, ""))
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.moplus.moplus_server.domain.member.domain.Member;
import com.moplus.moplus_server.global.security.AuthConstants;
import com.moplus.moplus_server.global.security.JwtUtil;
import com.moplus.moplus_server.global.security.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.moplus.moplus_server.global.security.provider;

import com.moplus.moplus_server.domain.member.domain.Member;
import com.moplus.moplus_server.domain.member.service.MemberService;
import com.moplus.moplus_server.global.error.exception.ErrorCode;
import com.moplus.moplus_server.global.security.exception.JwtInvalidException;
import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken;
import com.moplus.moplus_server.global.security.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.security.SignatureException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class JwtTokenProvider implements AuthenticationProvider {

private final JwtUtil jwtUtil;
private final MemberService memberService;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Claims claims = getClaims(authentication);
final Member member = getMemberById(claims.getSubject());

return new JwtAuthenticationToken(
member,
"",
List.of(new SimpleGrantedAuthority(member.getRole().getValue())
));
}

private Claims getClaims(Authentication authentication) {
Claims claims;
try {
claims = jwtUtil.getAccessTokenClaims(authentication);
} catch (ExpiredJwtException expiredJwtException) {
throw new JwtInvalidException(ErrorCode.EXPIRED_TOKEN.getMessage());
} catch (SignatureException signatureException) {
throw new JwtInvalidException(ErrorCode.WRONG_TYPE_TOKEN.getMessage());
} catch (MalformedJwtException malformedJwtException) {
throw new JwtInvalidException(ErrorCode.UNSUPPORTED_TOKEN.getMessage());
} catch (IllegalArgumentException illegalArgumentException) {
throw new JwtInvalidException(ErrorCode.UNKNOWN_ERROR.getMessage());
}
return claims;
}

private Member getMemberById(String id) {
try {
return memberService.getMemberById(Long.parseLong(id));
} catch (Exception e) {
throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage());
}
}

@Override
public boolean supports(Class<?> authentication) {
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.moplus.moplus_server.global.security.token;

import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

public class JwtAuthenticationToken extends AbstractAuthenticationToken {

private String jsonWebToken;
private Object principal;
private Object credentials;

public JwtAuthenticationToken(String jsonWebToken) {
super(null);
this.jsonWebToken = jsonWebToken;
this.setAuthenticated(false);
}

public JwtAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}

public Object getCredentials() {
return credentials;
}

public Object getPrincipal() {
return this.principal;
}

public String getJsonWebToken() {
return this.jsonWebToken;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.moplus.moplus_server.global.security;
package com.moplus.moplus_server.global.security.utils;

import com.moplus.moplus_server.domain.member.domain.Member;
import com.moplus.moplus_server.global.properties.jwt.JwtProperties;
import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

@Slf4j
Expand Down Expand Up @@ -45,6 +48,16 @@ public String generateRefreshToken(Member member) {
.compact();
}

public Claims getAccessTokenClaims(Authentication authentication) {

return Jwts.parserBuilder()
.requireIssuer(jwtProperties.issuer())
.setSigningKey(getAccessTokenKey())
.build()
.parseClaimsJws(((JwtAuthenticationToken) authentication).getJsonWebToken())
.getBody();
}

private Key getAccessTokenKey() {
return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes());
}
Expand Down
Loading
Loading