- 기존에는 다음과 같이 설정시 ID가 자동으로 세팅되어 수행되었으나 ver2에서는 id가 null로 들어오는 현상발생 - github에 해당 이슈가 존재하며 Hibernate ORM 에서 아직 해당기능을 지원하지 않는 것으로 보임 1) 테스트코드에 id 세팅 부분추가 2) datasource 설정 중 url부분에 다음과 같이 추가
url: jdbc:h2:~/test;MODE=LEGACY
3. drop table의 경우 FK등의 제약사항에 따라서 순서가 필요하게 되는데 이부분 역시 잘 동작하지 않는것으로 보인다. 추가 확인이 필요하다.(22.03.24)
- 특정 ip 목록이 추가될 경우 SpEL처리를 하며, 빈 값이 넘어올 경우 전체 ip에 대해서 허용해주도록 한다.
@ConfigurationProperties(prefix = "1.2.3")
@Getter
@Setter
@ToString
public class Properties {
private SsoProperties ssoProperties;
}
public class SsoProperties {
private List<String> allowedIps = new ArrayList<>();
}
- ip목록의 경우 수시로 바뀔수 있기 때문에 별도 properties를 활용하도록 한다.
<수행결과>
- FilterSecurityInterceptor에 의해서 처리되었으며 처리 결과는 다음과 같다.
<주의사항>
- IPv6를 사용하고 있을 경우 ip형식이 다음과 같이 처리가 되기 때문에 허용주소를 "127.0.0.1"로 한 경우 403오류가 발생한다.
- IPv6로 주소를 사용하거나 JVM 기동시 -Djava.net.preferIPv4Stack=true 옵션을 사용하면 된다.
- Spring Security 의 경우 Filter Chain 의 형태로 기능을 제공하고 있으며 필요에 따라서 추가/수정/삭제가 가능하다.
<내용>
1. Spring Security - Filter Chain
- Spring 에서 모든 호출은 DispatcherServlet을 통과하게 되고 이후에 각 요청을 담당하는 Controller 로 분배된다.
- 이 때 각 Request 에 대해서 공통적으로 처리해야할 필요가 있을 때 DispatcherServlet 이전에 단계가 필요하며 이것이 Filter이다.
- Spring Security는 FilterChainProxy를 통해서 상세로직을 구현하고 있다.
- 현재 Application에서 사용중인 Filter Chain은 Debug를 통해서 쉽게 확인할 수 있다.
- FilterChain이름이 의미하듯 Filter는 순서가 매우 중요하다.
2. 우리가 가장 많이 사용하는 UserNamePassword 구조에 대해서 좀 더 살펴보겠다.
(적당한 그림을 찾지 못해서 손으로 그려보았다.)
- Request가 들어왔을때 Filter를 거치게 되고, 적절한 AuthenticationToken이 존재하지 않는다면 AuthenticationProviders를 거쳐서 생성하게 되며 UserDeatilsService를 입맛에 맞게 구현하여 사용자 정보를 가져오는 부분을 구현할 수 있다.
- Spring Securty에서는 사람들이 가장 많이 사용하는 DB인증을 다음과 같이 미리 구현해 두었다. (오른쪽 파란색 박스)
3. 인증방식 변경 ( JWT Token)
최근에는 MSA구조의 효율성을 높이기 위해서 JWT Token 방식을 많이 사용하고 있다. Request에 대한 인증을 별도 서버를 거치지 않고 검증가능하고 기본로직에 필요한 내용을 담을 수 있어서 편리하다.
현재 서비스에 적용중인 대략적인 구조이다.
- JWT AuthTokenFilter에서 해당 처리를 마친 후 나머지 FilterChain을 수행하는 구조이다.
- Token을 통해서 인증 및 인가를 위한 정보를 생성하여 SecurityContextHolder를 통해서 세팅한다.
- JWT Filter의 내용을 간단히 살펴보면 아래와 같다. (보안상 TokenProvider 로직은 개별구현하는 것을 권장한다.)
<개요> - 다음과 같이 Service #A 에서 Service #B로 데이터 조회 API를 요청하고 값을 받아오는 로직이 있다. - Service #B에서는 AWS Athena를 저장소로 사용하고 있으며 Athena JDBC42 드라이버를 사용 중 이다.
<현상> - Service #B에서 JdbcTemplate을 통하여 쿼리가 수행된 시간은 11:13:13 이고, 2021-11-04 11:13:13.482 DEBUG 9668 --- [http-nio-8200-exec-9] o.s.jdbc.core.JdbcTemplate : Executing SQL query 2021-11-04 11:13:13.482 DEBUG 9668 --- [http-nio-8200-exec-9] o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection from DataSource - 실제 쿼리 수행결과를 받아온 시간은 11:15:57 로 약 2분44초 가 소요되었다. 2021-11-04 11:15:57.998 INFO 9668 --- [http-nio-8200-exec-9] ...
- Athena 의 경우 동시에 다수의 쿼리가 수행되면 Queue에 의하여 순차적으로 수행될 수 있기 때문에 쿼리 히스토리를 조회하였다.
- 대기열 시간 1분21초 + 수행시간 0.555초를 제외하고 꽤 오랜시간이 소요되었다.
<소스분석> - AthenaJDBC42의경우 일반적인 JDBC드라이버처럼 커넥션을 맺고 Resultset을 처리하는 형태가 아니라 AWS Athena로 Http를 통해서 수행요청을 하고, 리턴값으로 ID를 받아온 뒤 일정시간 Thread Sleep하면서 조회 polling을 요청하고 Status가 Completed가 되었을때 후속처리를 하는 형태로 구성되어 있다.
- 또한 위에도 언급한것처럼 동시에 다수의 요청이 집중될경우 자체적으로 큐에 보관하여 처리하게 된다.
- 부수적으로 Athena JDBC드라이버의 SStatement내 execute, getResultSet등의 메소드를 살펴보면 대부분 synchronized로 선언이 되어있기 때문에 이에 따른 delay도 있지 않을까 예상한다.
<Thread Dump>
10개의 Thread가 같은 위치에서 대기중이다.
"http-nio-8200-exec-9" #44 daemon prio=5 os_prio=31 tid=0x00007ffcc655f800 nid=0x8c03 waiting on condition [0x000070000c638000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at com.simba.athena.athena.api.AJClient.executeQuery(Unknown Source) at com.simba.athena.athena.dataengine.AJQueryExecutor.execute(Unknown Source) at com.simba.athena.jdbc.common.SStatement.executeNoParams(Unknown Source) at com.simba.athena.jdbc.common.SStatement.executeNoParams(Unknown Source) at com.simba.athena.jdbc.common.SStatement.executeQuery(Unknown Source) - locked <0x000000078740ccf8> (a com.simba.athena.athena.jdbc42.AJ42Statement) at com.zaxxer.hikari.pool.ProxyStatement.executeQuery(ProxyStatement.java:111) at com.zaxxer.hikari.pool.HikariProxyStatement.executeQuery(HikariProxyStatement.java) at org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:439) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:376) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:452) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:462) at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:473) at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:480)
<정리> - 다수의 사용자에게서 발생하는 ad-hoc형태 처리는 적합하지 않다.(hive와 동일함)
- Global cache(Redis)를 적절히 활용하여 Service #B Layer에서 처리를 하도록 하면 효율성을 증가시킬수 있다.(일반적인 캐시전략)
<현상> - Spring @Async를 사용하여 비동기 메소드를 작성하고 기존에 CompletableFuture를 사용해서 값을 넘겨받던 부분이 어느순간부터 null 로 넘어오는 현상
<원인> - 디버깅중에 원인을 찾았다. 기존에 Logging Aspect를 이용하여 @Async 메소드에 대해서 로깅처리하던 부분이 있었다. - 기존에 @After advice를 활용하던부분을 @Around advice로 변경하면서 발생하는 문제였다. - @Around를 사용할 경우 proceed 를 수행하여 다음단계로 넘어가게 되는데, 이때 잊지말고 리턴값을 전달해주어야 한다. (이런 초보적인 실수를 ㅠㅠ)