생성일: 2022년 12월 1일 오후 2:14
Security
인증 Authentication - 인증 하는거
인가 Authorization - 권한 주는거
에러처리시 Security에서 커스텀 하는 클래스들
AccessDeniedHandler - 권한체크하고 권한 없으면 동작 - 권한
AuthenticationEntryPoint - 인증안된 유저가 요청했을때 - 인증
개발자가 커스텀 예외처리 하는건 스프링 영역임
But 시큐리티는 스프링 이전에 필터링 하고 DispatcherServlet 에 들어옴
그래서 exception 마다 자세히 에러처리해줌
본인은 인증만 구현함
/**
* Security exception종류
* UsernameNotFoundException :계정 없음
* InternalAuthenticationServiceException :존재하지 않는 아이디
* InsufficientAuthenticationException :자격증명 신뢰할 수 잆음(jwt)
* BadCredentialsException :비밀번호 불일치(현재email, pw불일치시 실행됨)
* AccountExpiredException :계정만료
* CredentialsExpiredException :비밀번호 만료
* DisabledException :계정 비활성화
* LockedException :계정 잠김
*/
AuthenticationEntryPoint
를 구현한 시큐리티 에러 처리 commence
오버라이드
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException {
if (e.getClass().equals(BadCredentialsException.class)) {
ApiException ex = new ApiException(ErrorCode.ERROR_USER_LOGIN_INVALID);
HashMap<String, String> errors = apiExceptionHandler.handleException(ex, httpServletResponse);
log.error("Unauthorized error: {}",errors);
}
.....(생략)
}
@ExceptionHandler
같은걸로 처리되지 않아서 HttpServletResponse
를 활용해 응답을 보냄
Handle spring security authentication exceptions with @ExceptionHandler
/**
* Security&JWT Filter Exception
*/
public HashMap<String, String> handleException(ApiException ex, HttpServletResponse res) throws IOException {
res.setContentType("application/json");
HashMap<String, String> errors = new HashMap<>();
errors.put("error_code", ex.getErrorCode().getErrorCode()+"");
errors.put("error_user_msg", ex.getErrorCode().name());
res.getWriter().println(
"{ \"error_code\": \"" + ex.getErrorCode().getErrorCode()+"" + "\""
+", \"error_user_msg\": \"" + ex.getErrorCode().name() + "\" }");
return errors;
}
JWT 에러 종류
/**
* MalformedJwtException, SignatureException :잘못된 구성의 토큰
* ExpiredJwtException :만료된 토큰
* UnsupportedJwtException :지원하지 않는 포멧의 토큰
*/
여기있는게 다는 아님 필요해보이는것만 일단 검색해서 사용했음
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
Authentication authentication = tokenProvider.getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (JwtException e) {
ApiException ex = new ApiException(ErrorCode.ERROR_USER_TOKEN);
HashMap<String, String> errors = apiExceptionHandler.handleException(ex, response);
log.error("jwt error: {} : {}", errors ,e.getMessage());
}
}
OncePerRequestFilter
를 구현하고 doFilterInternal
를 오버라이드
인증과 같은 방식, handleException
사용
getAuthentication
메서드는 TokenProvider
클래스를 만들고 토큰을 파싱해 유저정보를 가져옴
public String getAuthenticationUser(String token) {
try {
return Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(trimToken(token))
.getBody()
.getSubject();
} catch (MalformedJwtException | SignatureException e) {
log.error("{} : {}", ErrorCode.ERROR_USER_TOKEN_INVALID.getErrorCode(),
ErrorCode.ERROR_USER_TOKEN_INVALID.getLogMsg());
} catch (ExpiredJwtException e) {
log.error("{} : {}", ErrorCode.ERROR_USER_TOKEN_EXPIRED.getErrorCode(),
ErrorCode.ERROR_USER_TOKEN_EXPIRED.getLogMsg());
} catch (UnsupportedJwtException e) {
log.error("{} : {}", ErrorCode.ERROR_USER_TOKEN_SUPPORT.getErrorCode(),
ErrorCode.ERROR_USER_TOKEN_SUPPORT.getLogMsg());
}
return null;
}
public Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = getAuthenticationUser(token);
return user != null ? new UsernamePasswordAuthenticationToken(user, null,emptyList()) : null;
}
return null;
}
토큰 생성이나 get 등 토큰에 관련된 메서드를 다 여기다 만듬, 때문에 여기서 예외처리해줌
JWTAuthenticationFilter authFilter = new JWTAuthenticationFilter(this.tokenProvider);
http
...
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class);
WebSecurityConfigurerAdapter
를 구현한 시큐리티 config 파일의 설정
UsernamePasswordAuthenticationFilter 는 인증관련 클래스인데 (아이디 비번 체크함) 인증전에 토큰관련된 예외처리 먼저한다는 거임
개발시 문제 상황
헬스체크를 하는 데 http 로 ‘/’ 경로로 헬스체크했음, 시큐리티 인증 에러가남, beanstalk 이 http로 헬스체크해서 해결해야했음
그래서 에러의 이유를 찾아봄
json 이 아니라 html 로 요청 (/) 했을때 에러가남 -> 뷰리졸버를 찾는데 없어서 에러가 나고 -> /error 요청 -> resources, static 등 아래 정적파일 찾는데 -> 이것들에 관해 인증 허용이 안되서 (에러페이지에 인증X) 인증에러가남
WebSecurityConfigurerAdapter
를 구현한 클래스에 정적파일 접근 권한 줌
/*
* resources, static 아래 파일들의 접근권한 설정
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.requestMatchers(
PathRequest.toStaticResources().atCommonLocations()
);
}
에러페이지 404 도 접근허용 해줌
@Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter authFilter = new JWTAuthenticationFilter(this.tokenProvider);
http
.cors()
.and()
.csrf().disable() // Restful API 방식 사용으로 disable 처리
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
...
"/404"
).permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class);
}
참고
https://imbf.github.io/spring/2020/06/29/Spring-Security-with-JWT.html
https://hou27.tistory.com/entry/Spring-Security-JWT
https://devlog-wjdrbs96.tistory.com/429
https://atoz-develop.tistory.com/entry/Spring-BootSpring-Web-MVC-ViewController%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%B7%B0-%EB%A7%A4%ED%95%91%ED%95%98%EA%B8%B0
WebMvcConfig 는 시큐리티 필터가 먼저 막음,,, 그래서 필터에서 허용해줘야됨
https://csy7792.tistory.com/243
csrf 도 잘 설정해줘야한다고함 Spring Security csrf token 검색 ㄱ
https://hou27.tistory.com/entry/Spring-Security-JWT
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
https://kimchanjung.github.io/programming/2020/07/02/spring-security-02/