본문 바로가기
Spring

개발 이슈 모음

by ybs 2021. 1. 19.
반응형

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