반응형

트러블

지금만들고있는 프로젝트에서 특정 API는 USER권한을 가진 계정만 접근이 가능하게 세팅을 해두었다. 

그런데 분명히USER 권한을 가진계정으로 로그인을 하고 SecurityContextHolder에 "USER" 가 저장되는것 까지 디버그를 찍어서 확인해보는데도 해당 API로 접근하지않고 권한이없어서 login으로 리디렉션 되는 문제가있었다. 

 

어제 이 문제로 몇시간을 붙잡고있었는데 자고일어나서 다시 해보니 해결방법이 쉽게 나왔다. 

(이래서 휴식이 중요한듯하다 안되는거붙잡고있어도 안되는건 어쩔수가 없나보다.)

 

원인

SpringSecurity에서 역할을 검사할때 내부적으로 ROLE_ 접두사를 사용한다고 한다. 

그래서 정확한 문제가 뭐였냐 하면 내가보내는 역활은 USER 였고 

Security가 검사하는 역활은 ROLE_USER 였다.. (허무하다)

 

해결

그래서 Security에서 역활을 불러오는 메서드인 getAuthority( ) 에 "ROLE_" + role 로 접두사로 ROLE_ 을 붙이게 해줬더니 해결이 되었다...

 

한줄요약

 

SpringSecurity Role 검사는 ROLE_역할이름 으로 검사한다. 혹시 역할이 정확하게 들어갔는데 통과되지않는다면 ROLE_ 접두사를 붙였는지 잘 확인해보자.

반응형
반응형

 

트러블

도메인과 서브도메인에 프론트와 백을 호스팅해준후에

로그인이 잘되고 쿠키도 잘 받아올수있었다. 

그런데 이제 로그아웃할때 로그인시 받았던 쿠키를 삭제해주는 함수가 먹히지않았다. 

deleteCookie('Authorization');

 

cookies-next 라이브러리의 deleteCookie 함수인데 로컬에서 할때는 쿠키이름만 넣고도 잘 지워져서 호스팅후에도 문제없이 잘동작할줄 알았는데 로그아웃을 아무리 눌러도 쿠키가 지워지지않았다. 

 

원인

찾아보니 쿠키를 삭제할때 서버에서 받은쿠키의경우 domain 이나 path 같은 여러 세팅들도 넣어줘야 삭제가 된다는것을 알았다. 

 

해결

변경후 쿠키삭제 코드

 const deleteCookie = (name:string) => {
      setCookie(name, '', { 
        domain: '.soolae-server.shop', 
        path: '/', 
        maxAge: -1 
      });
    };
    
    
    deleteCookie('Authorization');

쿠키를 만료시간으로 세팅시켜서 쿠키를 없애주는 deleteCookie함수를 새로 만들어주었다. 

가운데 ' ' 이 공백으로 쿠키의 내용을 지우고 세팅을 도메인으로 설정해주면서 maxAge를 -1로 바꿔서 즉시 만료되도록해서 쿠키를 지워주게 된다.

 

요약 

서버에서 받은 쿠키는 도메인이나 path 의 설정이있어서 deleteCookie같은 메서드로 지워지지않는다.

지우기위해서 옵션을 넣어주거나 해당쿠키의 만료시간을 변경시키는방법으로 지워줄수있다.

반응형
반응형

트러블

칵테일 프로젝트를 진행하면서 로컬에서는 쿠키를 서로 잘 주고받았는데 호스팅을하고나니

서버에서 보낸쿠키를 vercel호스팅한 페이지에서 받을수없는 문제가 발생했다.

쿠키는 로그인에 필요해서 필수적으로 해결이 되어야한다. 

 

원인

찾아보니 서로 다른 도메인끼리는 쿠키교환이 안된다고한다.

그래서 필요한게 서브도메인이라는 개념인데

naver.com 이 도메인이라면

blog.naver.com 은 서브도메인이되는개념으로

이런 도메인구조에서는 서로 같은 도메인으로 인식되고 실제로 하나의 도메인만 사용하기때문에

도메인도 하나만있어도되고 쿠키교환도 가능하다는 점을 알게되었다.

 

www.soolae-server.shop 에 vercel 이연결되어서 서브도메인에 vercel을 연결해주었다.

 

Sool lae

1. 하이볼 잔에 얼음을 채운다. 2. 보드카 40ml, 복숭아 리큐르 20ml, 오렌지 주스 40ml, 크랜베리 주스 40ml를 붓는다. 3. 살살 저어준 뒤 오렌지 슬라이스로 장식한다.

www.soolae-server.shop

 

Vercel에 도메인 연결하기

 

vercel 에 호스팅한 프로젝트로 들어가서 settings - Domains 에들어가준다.

돋보기가 있는부분에 연결할 도메인을 입력하고 Add를 눌러준다.
아마 정상적으로 호스팅되는 도메인을 입력하면 선택하는게 3개나올텐데

제일 아래쪽에 있는걸 선택하면 현재입력한 도메인만 생성해준다.

제일아래 Add 도메인 을 선택하고 Add

나는 www.soolae-server.shop 만 이용할 계획이므로 제일 아래를 선택한다 다른 옵션들은 서브 도메인에서 도메인으로 리다이렉트시키거나 도메인에서 서브도메인으로 리다이렉트 시켜주는 옵션인것같다. 

 

도메인을 추가해주고나면 빨간색으로 Invalid Configuration이라고 뜰텐데

Value에 나와있는 ipv4 주소를 Route53에 추가해놓은 도메인으로가서 레코드추가해주면 끝이다. 

 

Route53의 호스팅영역 생성방법은 이 포스팅을 참고

 

(트러블 슈팅) - https 요청에러

프로젝트를 서버에올리고 진행하면서 문제가 생겼다.  get 요청으로 데이터는 잘받아와지는데 post요청으로 동작하는 로그인이 동작하지않았다 .  우선 내 프로젝트환경은 클라이언트부분은 n

colazoa.tistory.com

 

레코드 이름에 서브도메인이랑 똑같이 www 를 붙여주고 레코드 유형은 A 

값에는 복사한 Value인 ipv4 주소를 넣어주고 레코드를 생성해주면 Route53에서 해줘야하는일은 끝난다.

 

이제 다시 vercel로 돌아와서 확인해보면 

이렇게 나오면 도메인이 잘 등록된것이다. 

 

서브도메인에 클라이언트부분을 호스팅하면서 본래 도메인에 호스팅해놓은 api를 다시 옮기지않아도 되고 인증서도 그대로 쓸수있게되어서 예상했던것보다 시간을 많이 단축할수있었다.

도메인이 같아지면서 서버에서 쿠키를 만드는 코드도 수정해주었다.

Java

ResponseCookie cookie = ResponseCookie.from("Authorization",token)
        .path("/")
        .maxAge(60*60*1000)
        .secure(true)
        .domain(".soolae-server.shop")
        .sameSite("None")
        .build();
response.addHeader("Set-Cookie", cookie.toString());

이렇게 코드를 수정하면서 호스팅해놓은 페이지에서도 로그인시에 쿠키를 잘받아올수있게되었다. 

 

그러나 추가적인 문제가 발생했는데. 이제는 쿠키가 지워지지않는다..

 

 

쿠키가 안지워지는경우(트러블 슈팅)

도메인과 서브도메인에 프론트와 백을 호스팅해준후에로그인이 잘되고 쿠키도 잘 받아올수있었다. 그런데 이제 로그아웃할때 로그인시 받았던 쿠키를 삭제해주는 함수가 먹히지않았다. delete

colazoa.tistory.com

 

이렇게 해결해 주었다. 

 

이제 로그인도 잘되고 큰 에러없이 프로젝트가 잘돌아가는것같다.

반응형
반응형

트러블

프로젝트를 서버에올리고 진행하면서 문제가 생겼다. 

 

get 요청으로 데이터는 잘받아와지는데 post요청으로 동작하는 로그인이 동작하지않았다 . 

 

우선 내 프로젝트환경은

클라이언트부분은 next 로 만들어서 vercel 에 호스팅해주었고

api 부분은 aws 의 ec2에 호스팅해 두었다.

그래서 처음엔 cors에러인가 했는데 서버부분에 cors 처리를 이미 다 해두어서 cors 에러는 아니였다. 

 

원인

그래서 찾아보니

vercel로 호스팅한 페이지는 https로 되어있고 서버는 http로 호스팅이되어서 이런 에러가 발생한다는걸 발견했다. 

Mixed Content: The page at 'https://...' was loaded over HTTPS...

 

이걸 해결하기위해서 프론트에서 메타데이터에

어떤 태그를 추가해주면 해결된다고해서 추가해줘봤지만 실패했다. 

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"></meta>

 

 

결국 서버인 스프링을 https로 배포해야된다고 해서

스프링을 https로 띄우기위해서 상당히 많은 시도를 해봤다. 

그러나 가장간단한방법으로 해결했다. 

 

해결

우선은 서버에 올려야하기때문에 도메인을 구매해주었다. (도메인은 가비아에서 구매할수있다.)

 

웹을 넘어 클라우드로. 가비아

그룹웨어부터 멀티클라우드까지 하나의 클라우드 허브

www.gabia.com

 

구매한 도메인은 aws에서 사용하기위해 route53 에서 등록해주었다.

 

호스팅영역을 생성하고 레코드 생성을 눌러서 레코드를 만들어준다.

 

레코드이름부분과 값부분에 도메인을 사용할 ec2인스턴스의 public ip를 넣어주면

해당 도메인주소로 접근할때 ec2로 연결시켜준다.

 

네임서버 설정

레코드유형에 ns라고되어있는 레코드가있고 값에 네임서버 주소들이있다. 

이 주소를 도메인을 구매한곳에서 네임서버 등록을 해줘야한다. 

 

만약 나처럼 가비아에서 구매했다면 가비아에 들어가서 구매한도메인을 클릭하고들어가면

네임서버옆에 설정버튼이 있을텐데 이걸 클릭하고 들어가서 .

1 2 3 4차에 route53에 있는 네임서버 주소값을 넣어주면된다 

* 입력할때 route53에는 마침표가들어있으니 마침표는 다 빼주어야한다. 

 

이렇게 네임서버 등록까지 완료하면 이제 인증서를 받을 준비가 끝났다. 

 

SSL 발급

 

나는 certbot 을 이용해서 ssl 인증서를 발급받는 방법을 사용했다.

 

ec-2 terminal

sudo yum install certbot
sudo certbot certonly --standalone -d 구매한도메인.com

 

이렇게 명령어를 실행하면 /etc/letsencrypt/live 경로에 입력한도메인폴더가 생기고

해당폴더안에 

fullchain.pem 

privkey.pem 가 발급되어있다. 

이제 이파일들을 스프링이 읽을수있는 파일인 PKCS12 형식으로 변경해주어야한다. 

sudo openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem 
-out keystore.p12 -name ttp -CAfile chain.pem -caname root

 

이 명령어를 입력하면 keystore.p12 라는 파일이 생긴다. 

이 명령어를 입력하면 해당 키의 비밀번호를 입력하게되는데 이 파일을 사용하기 위해서 꼭 필요하니

어딘가에 메모해두자. 

 

HTTPS로 run 시키기 

실행시킬 스프링 프로젝트의 resouces에 ssl 폴더를 만들어주고 이 안에 keystore.p12 파일을 넣어준다.

 

properties 세팅 .

# application-aws.yml
server:
  port: 443  # aws 환경에서 사용할 포트
  ssl:
    key-store: classpath:ssl/keystore.p12
    key-store-type: PKCS12
    key-store-password: ${P12_KEY}
    enabled: true

 

나는 yml이 편해서 yml로 만들어주었다 .  
이렇게 세팅을 마치고 빌드한후에 실행시켜주면 

도메인으로 접근하고 api 요청에대한응답도 잘 받아오는걸 확인할수있다. 

 

https 가 중요하다고하지만 포트폴리오 단계에서적용해볼 생각은 한번도 해본적없었는데 우연히 https에서 http로 요청하면 에러가 발생한다는걸 알게되고 멀리 돌아왔지만 스프링을 https로 호스팅하는 방법도 알게되어서 굉장히 기쁘다. 

 

이번에 찾아보면서 nginx에 대해서도 많이 보고 aws에서도 로드밸런서나 route53에 대한부분을 많이 봤는데 nginx 는 추후에 따로 더 찾아보면서 프로젝트에 적용해보고싶어졌다. 

스프링에서는 자체적으로 tomcat을 가지고있어서 nginx같은 웹서버가 없이도 웹을 동작시킬수있지만 나중에 서버하나로는 부하가 심한경우에 nginx를 제일앞단에 사용하고 서버를 여러개 붙여서 로드밸런싱이란걸 해줄수있다는점이 상당히 흥미로웠다. 물론 지금단계에서는 웹에올려놔도 나밖에 안보지만 ... 부하가 발생한 후에 적용하면 늦는다는 생각으로 포폴이 어느정도 완성되면 공부해서 적용하고싶다.

 

 

SpringBoot에 SSL 인증서를 적용해보자 (feat. AWS EC2)

SSL, HTTPS, SpringBoot

velog.io

이 글을 보고 에러를 해결한후에 작성하는 트러블 슈팅이다. 혹시 부족하거나 잘 모르겠는부분이있다면 이 글도 참고하면 좋을것같다.

반응형
반응형

 

트러블

이번엔 프론트엔드에서 발생한 문제였다.

문제해결에 시간이 오래걸리진 않았지만 특이하다고 생각되서 트러블 슈팅에 저장해놓고 기억해두려고한다.

 

이제 서버에 페이지를올리고 어느정도 확인을 하면서 진행중인데. 모바일과 컴퓨터에서 보는화면이 달랐다.

 

크롬웹으로본 반응형 웹페이지.
모바일로 본 반응형 웹 페이지

 

분명히 서버에올라가있는 같은 페이지인데 모바일에서보면 글씨가 하얗게 날아갔다. 

하나씩 비교해보니 휴대폰이 다크모드여서 그런가 싶어서 데스크톱쪽도 다크모드로 변경하고 봤지만 똑같았다.

 

원인

그래서 모바일에서는 다크모드일때 검정색인 부분은 모두 강제로 하얗게 변경한다는 점을 발견했다. 

데스크톱에서는 다크모드일때 적용될 css를 따로 만들어서 넣어주어야하지만 

모바일에서는 별도로 다크모드 css를 적용해주지않아도

검은색은 하얀색으로 하얀색은 검정색을 반전시켜주는 효과가있었다.

그래서 color가 따로 지정되어있지않은 <p> 태그에 있는 검정글자들이 모바일에서 하얗게 변경되어서 안보였던것이다.. 

 

해결

문제는 text-color 를 적용해주면서 간단하게 해결되었다.

text-color 적용후

 

반응형
반응형

트러블

로컬에서 잘돌아가던 타임리프 페이지가 ec2 서버에 올리고 페이지를 못찾아서 에러가 발생했다..


Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/index.html]")] with root cause

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [/common/header], template might not exist or might not be accessible by any of the configured Template Resolvers (template: "common/layout" - line 6, col 11)

 

[THYMELEAF][http-nio-8081-exec-1] Exception processing template "index": An error happened during template parsing (template: "class path resource [templates/index.html]")

org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/index.html]")

 


원인

이렇게 index.html에서 타임리프 레이아웃을 사용해서 헤더를 불러서 붙여놨는데 header를 찾을수없다는 이야기였다.


layouy.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}">
<script defer th:src="@{/js/bootstrap.bundle.js}"></script>
<th:block th:replace="~{/common/header::header}"></th:block>
<body>
<th:block layout:fragment="content"></th:block>
</body>
</html>

지금 헤더를 찾는부분이 절대경로로 /common/header::header 로 되어있는데 이 부분을 

 

해결

common/header::header 로 상대경로로 수정해주면 작동이잘된다.. 

 

서버에서는 프로젝트의 루트 디렉토리가 달라질수있어서 상대경로로 지정해야한다고한다.

하하하.. 몇 시간 정도해메고 ai한테도 물어봤는데 해결을 못하다가 구글링을 열심히해서 찾아냈다. 

반응형
반응형

 

트러블

클라이언트쪽에서 원래 보내던 데이터에 추가해서 객체가들어간 배열을 보내줄일이 생겼다.

처음에는 formData에 배열을 바로 직렬화해서 보냈는데 서버에서 데이터를 배열로받을수가없었다.

javaScript코드

const getIngredientsData = () => {
    const ingredientData = [];
    Array.from(ingredientRows.children).forEach((row) => {
        const ingredientName = row.children[0].value;
        const volume = row.children[1].value;
        const unit = row.children[2].value;

        ingredientData.push({
            name: ingredientName,
            volume: volume,
            unit: unit
        });
    });
    return ingredientData;
} //배열을만들고 요소를모아서 객체를만들어 배열에 push
// 만든 배열을 return해주는 함수

formData.append(`ingredients`, JSON.stringify(ingredientData[i]));

 

이렇게 클라이언트에서 보내주면 서버에서는 ingredients 필드에서 받아야하는데 

@Data
@Builder
public class CockTailRequest {
    private List<CocktailIngredientRequest> ingredients;
}

@Data
public class  CocktailIngredientRequest {
    private String name;
    private Integer volume;
    private String unit;
}

에러가 발생한다 String을 List타입으로 컨버트할수없다는에러가..

 

해결

그래서 찾아보니 formData에서 배열을 보내는방법이있었다. 같은필드이름에 인덱스를 달아서 보내주면 

자동으로 배열로 넘어간다고 한다.

그래서 클라이언트쪽 코드를 변경해줬다. 

const getIngredientsData = () => {
    const ingredientData = [];
    Array.from(ingredientRows.children).forEach((row) => {
        const ingredientName = row.children[0].value;
        const volume = row.children[1].value;
        const unit = row.children[2].value;

        ingredientData.push({
            name: ingredientName,
            volume: volume,
            unit: unit
        });
    });
    return ingredientData;
} //배열을만들고 요소를모아서 객체를만들어 배열에 push
// 만든 배열을 return해주는 함수

const ingredientData = getIngredientsData();
    for(let i=0;i<ingredientData.length;i++){
        formData.append(`ingredients[${i}]`, JSON.stringify(ingredientData[i]));
    }

배열을 반복문으로 돌려서 필드에 인덱스를 붙여주는 방법으로 보내도록 변경했다.

 

문제는 이렇게 해도 서버에서 List<String> 형태로는받아지는데

배열 내부의 객체인 CocktailIngredientRequest로는 컨버팅이 되지않았다.

이것저것 시도해보다가 시간만 너무날려서 문자열 배열로 받고 내부에서 컨버팅해줄수있게 함수를 추가해줬다.

@Builder
@Data
@Getter
    private List<String> ingredients;
   
   public List<CocktailIngredientRequest> getListIngredients() {
        ObjectMapper objectMapper = new ObjectMapper();

        return this.ingredients.stream().map(json -> {
            try {
                return objectMapper.readValue(json, CocktailIngredientRequest.class);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Error parsing JSON: " + e.getMessage());
            }
        }).collect(Collectors.toList());
    }
}

ingredients 배열내에는 Json문자열이있고 해당 Json문자열을 objectmapper로 convert해주도록 메서드를 만들었다.

 

트러블슈팅은못한것같고 트러블 회피정도는 되는듯한 해결방법이다. 

추후에 더 알아보면 클라이언트에서 객체배열을 보내고 서버에서 한번에 컨버팅해서 받는방법을 알아낼수있겠지 그때되면 이부분은 업데이트 해보겠다.

반응형

+ Recent posts