package shop.mtcoding.bank.config.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import shop.mtcoding.bank.config.auth.LoginUser;
// 모든 주소에서 동작(검증)
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (isHeaderVerify(request, response)) {
// 토큰 존재
String token = request.getHeader(JwtValueObject.HEADER).replace(JwtValueObject.TOKEN_PREFIX, "");
LoginUser loginUser = JwtProcess.verify(token); // id, role만 존재
// 임시 세션, 인증처럼 UserDetailsService 호출 X, UserDetails or username
Authentication authentication = new UsernamePasswordAuthenticationToken(loginUser, null,
loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private boolean isHeaderVerify(HttpServletRequest request, HttpServletResponse response) {
String header = request.getHeader(JwtValueObject.HEADER);
if (header == null || !header.startsWith(JwtValueObject.TOKEN_PREFIX)) {
return false;
} else {
return true;
}
}
}
SecurityConfig 추가
// JWT 등록
public class CustomSecurityFilterManager
extends AbstractHttpConfigurer<CustomSecurityFilterManager, HttpSecurity> {
@Override
public void configure(HttpSecurity builder) throws Exception {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
// 강제 세션 로그인을 위해 JwtAuthenticationFilter에 authenticationManager 필요
builder.addFilter(new JwtAuthenticationFilter(authenticationManager));
builder.addFilter(new JwtAuthorizationFilter(authenticationManager));
super.configure(builder);
}
}
UsernamePasswordAuthenticationToken에서 첫번째 매개변수는 UserDetails or username 타입만 가능하다.
베리어 토큰
404, 필터 통과하고 컨트롤러까지 갔다는 의미, 즉 인증 그리고 권한 통과
admin 테스트
형태를 잡아야한다.
ex 에 코드 추가
package shop.mtcoding.bank.handler.ex;
public class CustomForbiddenException extends RuntimeException {
public CustomForbiddenException(String message) {
super(message);
}
}
@RestControllerAdvice
public class CumstomExceptionHandler {
private final Logger log = LoggerFactory.getLogger(getClass());
@ExceptionHandler(CustomApiException.class)
public ResponseEntity<?> apiException(CustomApiException e) {
log.error(e.getMessage());
return new ResponseEntity<>(new ResponseDto<>(-1, e.getMessage(), null), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(CustomForbiddenException.class)
public ResponseEntity<?> forbiddenException(CustomForbiddenException e) {
log.error(e.getMessage());
return new ResponseEntity<>(new ResponseDto<>(-1, e.getMessage(), null), HttpStatus.FORBIDDEN);
}
@ExceptionHandler(CustomValidationException.class)
public ResponseEntity<?> validationApiException(CustomValidationException e) {
log.error(e.getMessage());
return new ResponseEntity<>(new ResponseDto<>(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_REQUEST);
}
}
SecurityConfig 수정
// JWT 서버 -> 세션 사용 X(무상태 서버)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
log.debug("디버그 : filterChain 빈 등록됨");
http.headers().frameOptions().disable(); // iframe 허용 X
http.csrf().disable();
http.cors().configurationSource(configurationSource());
// jSessionId 서버쪽에서 관리 안하겠다는 것
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// react, app 요청 예정
http.formLogin().disable();
// 브라우저가 팝업창을 이용해서 사용자 인증을 진행
http.httpBasic().disable();
// 필터 적용
http.apply(new CustomSecurityFilterManager());
// 인증실패
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
CustomResponseUtil.fail(response, "로그인을 진행해 주세요", HttpStatus.UNAUTHORIZED);
});
// 권한실패
http.exceptionHandling().accessDeniedHandler((request, response, e) -> {
CustomResponseUtil.fail(response, "권한이 없습니다.", HttpStatus.FORBIDDEN);
});
http.authorizeRequests()
.antMatchers("/api/s/**").authenticated()
.antMatchers("/api/admin/**").hasRole("" + UserEnum.ADMIN) // ROLE_
.anyRequest().permitAll();
return http.build();
}
accessDeniedHandler -> AccessDeniedHandler
handle 부분 3가지를 받아서 처리하면 끝
참고로 fail 부분 바꿈