1. Spring Security X-Frame-Options 이슈
Default Security Headers
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
cf) Strict-Transport-Security는 HTTPS 요청일때만 추가된다.
X-Frame-Options: DENY
response header에 X-Frame-Options를 갖고 있는 모든 사이트는 iframe 안에서 렌더링 되지 못하도록 브라우저가 막는다.
customize
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://example.com/
SAMEORIGIN : 같은 도메인일때만 ifrmae을 허용한다.
<http>
<!-- ... -->
<headers>
<frame-options
policy="SAMEORIGIN" />
</headers>
</http>
http
// ...
.headers()
.frameOptions()
.sameOrigin();
ALLOW-FROM : 구체적인 도메인 지정
Solution
<http>
<!-- ... -->
<headers disabled="true" />
</http>
http.headers().frameOptions().disable()
2. HTTP Strict Transport Security (HSTS)
https://docs.spring.io/spring-security/site/docs/4.0.x/reference/html/headers.html#headers-hsts
웹 브라우저가 HTTPS 프로토콜만을 사용해서 서버와 통신하도록 하는 기능을 한다. 만약 HTTPS로 접속에 실패하면 사이트 접근에 실패하게 된다. 서버가 HTTP 응답 헤더에 Strict-Transport-Security
를 내려주면 브라우저는 그 사이트에 접속할 때 무조건 HTTPS로만 연결한다.
HSTS가 적용되기 위해서는 서버도 헤더를 내려줘야하고 브라우저도 그 헤더에 따른 동작을 해야하는 것이다.
HSTS를 사용하는 대신 서버에서 HTTP 접속을 HTTPS로 redirect 시키는 방법이 있지만 일단 한번 HTTP 연결을 거쳐가는 것이기 때문에 쿠키 정보 탈취 등 보안에 취약하다.
Strict-Transport-Security: max-age=31536000 ; includeSubDomains; preload
max-age : 지정 시간(단위 초)만큼 HTTPS를 사용
includeSubdomains : HSTS를 서브 도메인에도 적용
preload : 브라우저가 해당 사이트를 HSTS 적용 preload list에 추가하도록 함
preload list
HTTPS로 웹 사이트에 접속하기 위해 적어도 한번 웹 서버와 통신을 해야하는데, 통신 해보기 전에 미리 HTTPS로 접속하도록 목록을 미리 만들어둔 것이다.
이는 브라우저가 지원해주는 기능이고 브라우저 안에 기본적으로 내장되어 있는 사이트 목록들이 있다. 그리고 서버가 헤더에 preload값을 내려주면 이 목록에 해당 사이트도 추가하게 되는 것이다(브라우저에 캐시됌). preload에 추가된 사이트는 서버가 HSTS헤더를 삭제해도 브라우저에는 설정이 유지된다.
만약 내장되는 preload list에 추가되면 삭제되기까지(목록 삭제 및 사용자 브라우저 업데이트) 시간이 오래 걸리므로 추가는 신중하게 해야한다. preload list는 크롬이 관리하고 있고 대부분의 브라우저들(파이어폭스, 오페라, 사파리, IE11, Edge)이 이 목록을 같이 사용한다.
만약 수동으로 해제하고자 한다면 크롬은 chrome://net-internals/#hsts
에 들어가서 Delete Domain에서 삭제할 도메인을 입력하고 삭제하면 된다.
HSTS 설정 코드
<http>
<!-- ... -->
<headers>
<hsts include-subdomains="true" max-age-seconds="31536000" />
</headers>
</http>
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers()
.httpStrictTransportSecurity()
.includeSubdomains(true)
.maxAgeSeconds(31536000);
}
}
HSTS disable 코드
<http>
<!-- ... -->
<headers>
<hsts disable="true"/>
</headers>
</http>
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers()
.httpStrictTransportSecurity().disable();
}
}
3. List에 있는 value를 Mybatis foreach로 insert 하기
public class TestDto {
private List<String> list;
// getter & setter
}
<insert id="insertTest" parameterType="com.test.dto.TestDto">
INSERT
INTO test_table (id, order)
VALUES
<foreach collection="list" item="value" index="order" open="" separator="," close="">
(#{value}, #{order})
</foreach>
</insert>
4. 컨트롤러에서 List 객체 받는 방법 정리
방법1
$.ajax({
url: "/test1",
type: "post",
contentType: 'application/json;charset=UTF-8',
data: JSON.stringify(list)
}).done(function (data, status, xhr) {
...
@RequestMapping(value = "/test1", produces = {"application/json;charset=UTF-8"}, method = RequestMethod.POST)
public void test1(@RequestBody List<String> list) {
...
}
방법2
$.ajax({
url: "/test2",
type: "post",
contentType: 'application/json;charset=UTF-8',
data: JSON.stringify({
name: yangbongsoo,
age: 29,
list: list
})
}).done(function (data, status, xhr) {
...
@RequestMapping(value = "/test2", produces = {"application/json;charset=UTF-8"}, method = RequestMethod.POST)
public void test2(@RequestBody TestDto testDto) {
...
}
public class TestDto {
private String name;
private int age;
private List<String> list;
//getter and setter
}
5. Mac에서 Spring Boot의 시작이 느릴 때
Mac에서 Spring Boot의 시작이 느리다면 아래와 같이 hostname을 지정해 본다.
sudo scutil --set HostName MyMacBook
Spring Boot의 StartupInfoLogger 에서는 InetAddress.getLocalHost().getHostName();를 호출한다.
Mac에서는 Hostname이 지정되어 있지 않을 경우 해당 메서드 호출에 몇초가 걸리는 것으로 파악된다.
6. Tomcat8 UMASK 이슈
스프링 이슈는 아니지만 따로 적을 곳이 없어서 여기에 적음
파일 업로드를 할 때 업로드된 파일 권한이 위에서 아래로 바뀌는 현상 발견
-rw-r--r--
-rw-r-----
원인을 찾아보니 tomcat 7.0.68 catalina.sh에서는 UMASK가 주석처리 되어있었는데
#JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask`"
tomcat 8.5.32 catalina.sh에서 UMASK에 대한 부분이 바꼈다.
# Set UMASK unless it has been overridden
if [ -z "$UMASK" ]; then
UMASK="0027"
fi
umask $UMASK
...
# Make the umask available when using the org.apache.catalina.security.SecurityListener
JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask`"
문제가 되는 이유는, 아파치 httpd.conf에서 User nobody를 할 경우에 640이면 파일 read를 못하게 된다.
기존 tomcat7에서는 read에 문제가 되지 않았던 부분이다.
file 666 - 022 = 644(-rw-r--r--)
file 666 - 027 = 640(-rw-r-----)
cf) 027은 000 010 111
이고 보수로 전환하면 111 101 000
이 된다.
UMASK 보수 값과 파일 기본 허가권을 AND 연산하면
110 110 110
111 101 000
-----------
110 100 000
640이 된다.
7. Spring, Ajax File upload(FormData)
commons-fileupload 의존성을 추가한다.
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
servlet-context.xml에서 CommonsMultipartResolver를 빈으로 등록한다.
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!--10MB -->
</bean>
컨트롤러에서 MultipartFile 객체로 받는다.
@RequestMapping(value = "/file/insert", method = RequestMethod.POST)
public AjaxResponse insertBoardWorksEditorArticleFile(@RequestParam("files") List<MultipartFile> files) throws Exception {
...
}
FormData를 이용하는 방법은 두가지다.
1.form 태그 있을 경우
<form id="fileUploadForm" method="post" enctype="multipart/form-data">
<input type="file" name="files" id="filesToUpload" multiple="multiple" onchange="makeFileList();" style="display: none">
</form>
function makeFileList() {
var form = $('#fileUploadForm')[0];
var formData = new FormData(form);
$.ajax({
url: '/file/insert',
type: 'POST',
enctype: 'multipart/form-data',
processData: false,
cache: false,
contentType: false,
data: formData
}).done(function (data, status, xhr) {
}).fail(function (xhr, status, error) {
});
}
enctype: 'multipart/form-data' 추가 안해도 정상 동작함
processData: false false를 안해 주면 jquery Uncaught TypeError: Illegal invocation 발생(false를 해줌으로써 jQuery가 자동으로 데이터를 쿼리 문자열로 변환하지 않도록 방지)
contentType: false 만약 contentType을 아예 안보내면 "application/x-www-form-urlencoded; charset=UTF-8" 로 지정되서 500 에러 발생(MultipartException: Current request is not a multipart request) 그리고 contentType을 multipart/form-data로 직접 명시하면 405 에러 발생
2.form 태그 없을 경우
<input type="file" name="files" id="filesToUpload" multiple="multiple" onchange="makeFileList();" style="display: none">
function makeFileList() {
var input = document.getElementById('filesToUpload');
var formData = new FormData();
for (var i=0; i<input.files.length; i++) {
formData.append('files', input.files[i]);
}
$.ajax({
url: '/file/insert',
type: 'POST',
enctype: 'multipart/form-data',
processData: false,
cache: false,
contentType: false,
data: formData
}).done(function (data, status, xhr) {
}).fail(function (xhr, status, error) {
});
}
cf) HTTP Method POST 방식으로 전송할 때는 body의 데이터를 설명하는 content-type를 꼭 추가해줘야 한다.
application/x-www-form-urlencoded은 default content-type이다(key=value&key=value 와 같은 데이터를 전달).
서블릿 컨테이너는 request의 body를 읽어 Map 형태로 변환한다.
그리고 반드시 content-type에 body의 인코딩을 추가해줘야 한다(ex charset=UTF-8).
'Spring' 카테고리의 다른 글
Spring Web MVC 구조 논의 1편 (0) | 2021.06.06 |
---|---|
Spring Cloud Gateway CORS 주의사항 (4) | 2021.01.24 |
useInsecureTrustManager 옵션 (0) | 2021.01.20 |
request body memory leak (0) | 2021.01.20 |
WebClient 사용할때 주의 (1편) (2) | 2021.01.18 |