Spring Boot Cross Domain 처리

web/Spring|2018. 10. 4. 23:55

웹 브라우저에서 같은 도메인을 사용하는 서비스에 대한 요청은 정상적으로 처리가 되나 도메인이 같지 않은 서비스에 요청을 할경우에 오류가 발생한다.

이는 동일 출처 정책(Same Origin Policy) 라는 정책을 두어 다른 도메인의 서버에 요청하는 것을 보안 문제로 간주하고 이를 차단하는 것 때문에 발생한다.

Spring Boot에서 간단한 방식으로 해결할 수 있다.



1. 요청에 @CrossOrigin 애노테이션 붙히기

1
2
3
4
5
@GetMapping("/greet")
@CrossOrigin
public Greet greeting() {
    return new Greet("test");
}
cs


2. 특정 도메인에서 오는 요청만 받고 싶을 경우.

-> 특정 도메인에서 오는 요청만 받고 싶은경우에는 도메인 주소를 기입해주면 된다.

1
2
3
4
5
@GetMapping("/greet")
@CrossOrigin("http://www.naver.com")
public Greet greeting() {
    return new Greet("test");
}
cs


3. 서비스 전체에 대한 요청 허용

-> 하나하나 모든 요청에 마다 @CroossOrigin 애노테이션을 부여하는건 정말 의미없는 짓이다. 그래서 모든 부분에 대한 요청을 처리하기 위해서는 WebMvcConfigurer 빈을 사용하고 addCorsMappings(CorsRegistry registry) 매소드를 재정의하면 애플리케이션 전체에 걸쳐 효력을 미치는 CORS를 적용할 수 있다.

해당 설정에는 다음 내용이 포함되어 있다.

이름
내용
Access-Control-Allow-Orgin
요청을 보내는 도메인 주소 (모든 도메인 대상인 경우 *)
Access-Control-Allow-Methods
요청 메소드 (Default : GET, POST, HEAD)
Access-Control-Max-Age
클라이언트에서 pre-flight의 요청 결과를 저장할 시간 지정. 해당 시간 동안은 pre-flight를 다시 요청하지 않는다. 
Access-Control-Allow-Headers
요청을 허용하는 헤더

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.wedul.microservice.rabbit.dto.config;
 
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
/**
 * microservice
 *
 * @author wedul
 * @since 2018. 8. 24.
 **/
@Configuration
public class ServletContextConfig implements WebMvcConfigurer {
 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/")
                .allowedOrigins("http://www.naver.com")
                .allowedMethods(HttpMethod.GET.name())
                .allowCredentials(false)
                .allowedHeaders("Content-Type""x-xsrf-token")
                .maxAge(200);
    }
}
 
cs


댓글()

Fake SMTP로 메일 전송 테스트

IT 지식/기타지식|2018. 10. 4. 23:51

간단하게 rabbitMQ 코드 만들어보는 도중에 메일전송기능이 필요했다.

그런데 메일을 계속 보낼수도 없기 때문에 메일이 잘 전송되고 있는지 확인할 수 있는 프로그램이 있는지 찾아봤다. 


그중에 Fake SMTP 라는 프로그램이 있어서 소개해본다.

프로그램은 하단에 첨부된 링크에서 다운받을 수 있다. 실행방법은 다운받은 파일위치에서  java -jar fakeSMTP-2.0.jar 명령어로 실행시키면 GUI 화면이 나온다.

스프링 부트에서 JavaMailSender와 간단한 설정을 통해 메일 전송 여부를 테스트 할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// application.properties
spring.mail.host=localhost
spring.mail.port=2525
 
// service
/**
 * microservice
 *
 * @author wedul
 * @since 2018. 8. 25.
 **/
@Component
public class Mailer {
 
    @Autowired
    private JavaMailSender javaMailSender;
 
    public void sendMail(String email) {
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setTo(email);;
        mailMessage.setSubject("Registration");
        mailMessage.setText("Successfully Registered");
        javaMailSender.send(mailMessage);
    }
}
 
cs


http://nilhcem.com/FakeSMTP/download.html#

댓글()

스프링 웹플럭스(spring webflux)를 활용한 간단한 리액티브 마이크로 서비스

web/마이크로서비스|2018. 10. 4. 23:50

자바 리액티브 프로그래밍은 리액티브 스트림 명세를 바탕으로 하고 있다.  

리액티브 스트림 명세에는 컴포넌트 사이의 비동기 스트림 처리나 이벤트 흐름을 Non Blocking 방식으로 처리하기 위한 문법을 정의한다. 

일반적인 옵저버 패턴과 달리 리액티브 스트림에는 시퀀스의 처리, 완료 알림, 실패시 backpressure 적용 등이 추가된다. backpressure는 받는 컴포넌트에서 보내는 컴포넌트에게 얼마만큼의 데이터를 소화할 수 있다고 알려줄 수 있다.  그래서 받는 컴포넌트에서 처리될 준비가 됐을 때만 데이터를 받을 수 있다. 

그래서 서로 속도가 다른 컴포넌트 사이의 통신을 할 때 유리하다. 

스프링 프레임워크 5 web flux는 Reactor 리액티브 스트림 명세를 기반으로 되어있다.  



간단한 Spring WebFlux 프로젝트

인텔리제이에서 New Project - Reactive web 선택  
Spring-boot-starter-webflux 가 web 대신 추가되어있는 것을 확인할 수 있다.

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
cs


간단한 RestController를 만들고 Mono를 응답바디로 지정해보자.

1
2
3
4
@RequestMapping("/")
public Mono<Greet> greet() {
    return Mono.just(new Greet("Hello World"));
}
cs

여기서 Body로 Mono를 반환하는데 이는 비동기 논블로킹 모드에서 Mono가 일을 마친 후에만 Greet 객체가 직렬화 된다는 것을 의미한다.

테스트 코드
RestTemplate와 비슷한 WebClient를 사용하여 간단한 web flux를 테스트해보자.


1
2
3
4
5
6
7
8
9
10
11
12
@Autowired
WebTestClient webTestClient;
 
@Test
public void contextLoads() {
    webTestClient.get().uri("/")
            .accept(MediaType.APPLICATION_JSON)
            .exchange() // response값을 가져오기 위해서 사용하는 메서드가 exchange()
            .expectStatus().isOk()
            .expectBody(Greet.class).returnResult()
            .getResponseBody().getName().equals("Hello World");
}
cs


댓글()

리액티브 스트림의 이해

web/마이크로서비스|2018. 10. 4. 23:47

리액티브 스트림은 총 4개의 인터페이스로 구성되어 있다. 

ㅁ 발행자(Publisher) 

- 데이터의 소스를 가지고 있으며 Subscriber의 요청이 오면 데이터를 발행한다. 구독자는 발행자에 대한 구독을 추가할 수 있다. Subscribe 메소드를 통해서 구독자를 추가할수 있다.

1
2
3
public interface Publisher<T> {
    public void subscribe(Subscriber<? Super T> s);
}
cs


ㅁ 구독자 (Subscriber) 

-  구독자는 데이터 스트림을 소비하기 위해 발행자를 구독한다. 구독자는 다양한 메서드를 제공하는데 대부분의 메서드가 콜백으로 등록되어 사용된다.

1
2
3
4
5
6
public interface Subscriber<T> {
  public void onSubscribe(Subscription s);
  public void onNext(T t);
  public void onError(Throwable t);
  public void onComplete();
}
cs


ㅁ 구독 (subscription) 

단 하나의 발행자와 단 하나의 구독자를 연결해주며 그 둘 사이에서만 데이터 교환을 중재한다. 데이터 교환은 구독자의 request 메서드 호출로 실행되고 cancel로 종료된다.

1
2
3
4
public interface Subscription {
  public void request(long n);
  public void cancel();  
cs


ㅁ 프로세서 (procesor) 

프로세서는 처리 단계를 나타내며, 발행자 인터페이스와 구독자 인터페이스를 모두 상속한다. 프로세서는 발행자와 구독자 사이의 계약을 반드시 준수해야한다. 프로세서는 발행자와 구독자를 연결해서 chaining(메서드가 반환하는 객체를 다른 변수에 할당하지 않고 객체가 가지고 있는 메소드를 호출하는 것)을 할 수도 있다.

1
2
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
cs



리액터에는 발행자 플로세서로 사용되는 flux와 mono가 있다. flux는 0 또는 N개의 이벤트를 발행할 수 있고, Mono는 0 또는 1 개의 이벤트만 발행할 수 있다. 그래서 다수의 데이터 요소 또는 값의 리스트를 스트림으로 전송할 때만 사용된다.

댓글()

Facebook Javascript plugin과 spring security를 이용한 페이스북 로그인

web/Spring|2018. 10. 4. 00:57

개인적으로 공부겸 만들고 있는 Wedul Pos에는 아이디와 패스워드를 사용해서 로그인하는 방식을 제공했다.

하지만 페이스북 로그인 방식을 추가해보고 싶어서 facebook 개발자 사이트에 가입하여 정보를 얻고 추가해봤다.

우선 페이스북 로그인 방식을 처리하는 방식은  Facebook Javascript plugin을 사용하여 spring security에서 인증을 하는 방식과 /sign/facebook 요청만 front에서 보내면 server에서 모든 처리를 진행하는 방식 두가지가 있다.

그 중에 첫번째 javascript plugin을 이용하는 방식을 사용해서 구현해보자.

1. facebook developer 사이트에서 javascript 내용 얻기
https://developers.facebook.com/docs/facebook-login/web#confirm 페이지에서 자바스크립트 플러그인에 대한 사용법과 소스를 받을 수 있다. App Id의 경우에는 개발자 사이트에 등록하면 받을 수 있다.

몇 가지 부분만 간단하게 설명을해보자.

※초기화
Facebook javascript 플러그인 사용을 위한 초기화.
{your-app-id}에는 발급받은 App Id를 version에는 최신 버전을 쓰면 된다. 지금은 v3.1이 최신이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  window.fbAsyncInit = function() {
    FB.init({
      appId      : '{your-app-id}',
      cookie     : true,  // enable cookies to allow the server to access 
                          // the session
      xfbml      : true,  // parse social plugins on this page
      version    : 'v2.8' // use graph api version 2.8
    });
 
    // Now that we've initialized the JavaScript SDK, we call 
    // FB.getLoginStatus().  This function gets the state of the
    // person visiting this page and can return one of three states to
    // the callback you provide.  They can be:
    //
    // 1. Logged into your app ('connected')
    // 2. Logged into Facebook, but not your app ('not_authorized')
    // 3. Not logged into Facebook and can't tell if they are logged into
    //    your app or not.
    //
    // These three cases are handled in the callback function.
 
    FB.getLoginStatus(function(response) {
      statusChangeCallback(response);
    });
 
  };
 
  // Load the SDK asynchronously
  (function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = "https://connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
  }(document'script''facebook-jssdk'));
cs


※ 로그인 상태 체크

로그인 상태 체크를 위해 사용된다. 전달되는 response에는 현재 상태와 accessToken, appId등을 담고있다.

1
2
3
FB.getLoginStatus(function(response) {
    statusChangeCallback(response);
});
cs


1
2
3
4
5
6
7
8
9
10
11
// 상태값. 자세한 설명은 개발자 페이지 참고
{
    status: 'connected',
    authResponse: {
        accessToken: '...',
        expiresIn:'...',
        reauthorize_required_in:'...'
        signedRequest:'...',
        userID:'...'
    }
}
cs


 로그인 요청

로그인 요청 호출 스크립트이다.
같이 입력되어있는 scope에 경우에는 로그인시에 접근가능한 내용에 대한 권한 요청이다. 아래 내용에서 public profile에 대한 권한과 email 정보에 대한 권한을 요청한다.

1
2
3
FB.login(function(response) {
  // handle the response
}, {scope: 'public_profile,email'});
cs

로그인 요청 스크립트가 호출되면 다음과 같이 페이스북에서 제공하는 로그인 화면이 보여진다.


※로그아웃 요청

로그인 되어있는 사용자에 대해 로그아웃을 요청한다.
1
2
3
FB.logout(function(response) {
   // Person is now logged out
});
cs

단 로그아웃을 할때는 먼저 세션의 status를 확인하고 진행해야한다. 그렇지 않고 무작정 FB.logout을 통해 로그아웃을 시도하면 다음과 같은 오류가 발생한다.
FB.logout() called without an access token.

그래서 이런식으로 계정의 상태를 확인하고 진행하자.

1
2
3
4
5
6
7
8
9
10
11
12
// 페이스북 계정 로그아웃
var faceBookLogOut = function(callback) {
    FB.getLoginStatus(function(response) {
      if (response.status === 'connected') {
          FB.logout(function(response) {
              callback();
          });
      } else {
          callback();
      }
    });
};
cs


※ 사용자 정보 요청

로그인이 된 후에 현재 로그인된 사용자의 로그인 상태를 요청하는 스크립트이다.
response의 값은 {"name": "정철", "email" : "rokking1@naver.com", "userID" : "dkfdkfjalkd"} 와 같이 전달된다.

나는 로그인 후에 이 스크립트를 사용하여 전달받은 사용자 정보를 server로 전송하여 DB에 값을 저장하고 로그인 처리하였다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var loginFacebookLoginUserInfo = function() {
    // 로그인한 사용자의 정보 얻기
    FB.api('/me', {fields: 'name,email'},  function(response) {
        let param = {};
        param.snsId = response.id;
        param.nickname = response.name;
        param.email = response.email;
 
        // 사용자 소셜 로그인 요청
        Common.sendAjax({
            url: Common.getFullPath('user/login/facebook'),
            param,
            type: 'POST',
            success: () => {
                Common.pageMove('');
            },
            failed: () => {
                alert(Common.getMessage('user.login.message.checkAccount'));
            }
        });
    });
};
cs


2. Spring Security에서 로그인 처리하기.

1번 스크립트를 통해 페이스북 로그인을 하였다. 그리고 로그인이 성공한 뒤에 받은 사용자 정보를 wedul pos 서버에 전달하여 로그인 처리하였다.

※ 사용자 정보 전달 및 저장

로그인된 사용자 정보를 전달받고 DB에 값을 저장한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// UserController.java
 
/**
   * facebook으로 로그인
   *
   * @param reqDto the req dto
   * @return the response entity
   * @throws Exception the exception
*/
@RequestMapping("/login/facebook")
public ResponseEntity<?> loginfacebook(HttpServletRequest request, UserDto reqDto) throws Exception {
    return ResponseEntity.ok(userService.facebookLogin(request, reqDto));
}
 
// UserService.java (DB에 사용자 정보 저장)
private UserDto insertSnsUser(UserDto reqDto) throws Exception {
  UserDto userDto = selectUser(reqDto);
 
  if (null == userDto) {
    if (insertUser(reqDto)) {
        return reqDto;
    } else {
        return null;
    }
  }
 
  return userDto;
}
 
cs

그리고 Spring Security에 해당 사용자의 로그인 처리를 위해서 UsernamePasswordAuthenticationToken token을 생성하고 securityContext에 Autithentication을 적용해 주었다. 

여기서 삽질을 조금 했는데 securityContext에 Autithentication을 설정만 하면 되는줄 알았는데 그게 아니라 HttpSession에 "SPRING_SECURITY_CONTEXT" 속성에 securityContext를 넣어줘야했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public ResultDto facebookLogin(HttpServletRequest request, UserDto reqDto) throws Exception {
    UserDto userDto = insertSnsUser(reqDto);
 
    if (null == userDto) {
        return ResultDto.fail("등록된 사용자가 없습니다.");
    }
 
    // 인증 토큰 생성
    MyAuthenticaion token = new MyAuthenticaion(userDto.getSnsId(), "", Arrays.asList(new SimpleGrantedAuthority(Constant.ROLE_TYPE.ROLE_USER.toString())), userDto, EnumLoginType.FACE_BOOK);
    token.setDetails(new WebAuthenticationDetails(request));
    authProvider.authenticate(token);
 
    // Security Context에 인증 토큰 셋팅
    SecurityContext securityContext = SecurityContextHolder.getContext();
    securityContext.setAuthentication(token);
 
    // Create a new session and add the security context.
    HttpSession session = request.getSession(true);
    session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
 
    return ResultDto.success();
}
cs


자세한 소스코드는 Git을 참조.

https://github.com/weduls/wedulpos_boot

댓글()

Spring Boot application.properties 암호화 내역 복호화 방법

web/Spring|2018. 10. 4. 00:12

Spring Boot에서는 여러 설정값을 application.properties에 입력하여 사용할 수 있다.


Spring보다 편리하고 효율적이다.
하지만 DBMS 사용을 위해서 연결정보를 properties에 입력할 때 평문으로 그냥 삽입하면 정보 유출에 문제가 발생 할 수 있다. 

이를 해결하기 위해서 application.properties에서 어떻게 사용하는지 확인해보자.


1. application.properties 내용 암호화하여 입력하기

우선 application.properties에 있는 내용을 암호화 해서 삽입한다. 나는 AES256으로 암호화 하여 삽입하였다.

1
2
spring.datasource.username=K2amNtg+kL5xK23g7H3Znw==
spring.datasource.password=ivWXmtXGN289BY6q9AwRnw==
cs


2. EnvironmentPostProcessor 구현하기

EnvironmentPostProcessor 인터페이스는 application context가 올라가기전에 application을 커스터마이징 할 수 있게 지원해주는 기능을 제공한다.

postProcessEnvironment 메소드를 구현하여 원하고자 하는 속성값을 변경하여 처리할 수 있다.  이 부분에서 DBMS 연결정보를 다시 복호화 처리하도록 설정하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * JDBC 접속 ID 와 Password 암복호화에 사용하기 위해 사용하는 Processor
 *
 * @author cjung
 * @date 2018. 08. 12
 *
 */
public class EncryptionEnvironmentPostProcessor implements EnvironmentPostProcessor {
 
    private final Logger logger = LoggerFactory.getLogger(EnvironmentPostProcessor.class);
 
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Properties props = new Properties();
        try {
            props.put("spring.datasource.password", AES256Cipher.getInstance().AES_Decode(environment.getProperty("spring.datasource.password")));
            props.put("spring.datasource.username", AES256Cipher.getInstance().AES_Decode(environment.getProperty("spring.datasource.username")));
        } catch (Exception e) {
            logger.error("Fail decrypt datasource info", e);
        }
 
        environment.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
    }
 
}
cs


3. META-INF/spring.factories 에 클래스 full 경로 기입

EnvironmentPostProcessor를 구현한 클래스가 정상적으로 동작하기 위해서는 classpath에 META-INF/spring.factories 파일을 만들고 해당 클래스의 풀 경로를 기입해주어야한다.


Full path 기입

1
2
# post processor 적용
org.springframework.boot.env.EnvironmentPostProcessor=com.wedul.common.config.EncryptionEnvironmentPostProcessor
cs


참고
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/env/EnvironmentPostProcessor.html


댓글()

Spring Validation을 이용해서 요청 검증처리

web/Spring|2018. 8. 9. 23:30

대게 개발을 진행할 때 front에서 validate를 체크하고 민감한 정보에 대해서는 한번더 체크를 진행하고 작업을 했었다. 

하지만 클라이언트에서만 validation을 체크하게 되는 경우 브라우저에서 악의적인 행동에 대해서 대응하기 어려워질수 있기 때문에 백엔드에서도 Validation을 처리해야한다. 

여기서 사용되는 @valid 어노테이션들을 알아보자.

1. DTO validation 선언
우선적으로 DTO에 각 속성에 필요한 @valid 옵션들을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.wedul.springboottest.rest.dto;
 
import lombok.*;
 
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
 
/**
 * 사용자 클래스.
 * User: wedul
 * Date: 2018-08-05
 * Time: 오후 9:47
 */
@Data
@AllArgsConstructor
@EqualsAndHashCode
@NoArgsConstructor
public class UserDto {
    @NotBlank(message = "이름을 입력해주세요.")
    private String name;
 
    private int age;
 
    @NotBlank(message = "이메일을 입력해주세요.")
    @Email(message = "이메일을 양식을 지켜주세요.")
    private String email;
 
    public void updateInfo(UserDto user) {
        this.age = user.getAge();
        this.email = user.getEmail();
    }
}
cs


@Valid 데이터 종류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@AssertFalse : false 값만 통과 가능
@AssertTrue : true 값만 통과 가능
@DecimalMax(value=) : 지정된 값 이하의 실수만 통과 가능
@DecimalMin(value=) : 지정된 값 이상의 실수만 통과 가능
@Digits(integer=,fraction=) : 대상 수가 지정된 정수와 소수 자리수보다 적을 경우 통과 가능
@Future : 대상 날짜가 현재보다 미래일 경우만 통과 가능
@Past : 대상 날짜가 현재보다 과거일 경우만 통과 가능
@Max(value) : 지정된 값보다 아래일 경우만 통과 가능
@Min(value) : 지정된 값보다 이상일 경우만 통과 가능
@NotNull : null 값이 아닐 경우만 통과 가능
@Null : null일 겨우만 통과 가능
@Pattern(regex=, flag=) : 해당 정규식을 만족할 경우만 통과 가능
@Size(min=, max=) : 문자열 또는 배열이 지정된 값 사이일 경우 통과 가능
@Valid : 대상 객체의 확인 조건을 만족할 경우 통과 가능
 
출처: http://goldenraccoon.tistory.com/entry/Valid-annotation-종류 [황금너구리 블로그]
cs


2. @Request에서 전달받는 파라미터 설정
간단하게 @Valid 설정을 통해 전달받을 객체에 대한 유효성 체크를 설정할 수 있다.

1
2
3
4
5
6
7
8
9
10
 /**
   * Edit user info response entity.
   *
   * @param user the user
   * @return the response entity
 */
 @PutMapping(value = "/user/info")
 public ResponseEntity<?> editUserInfo(@RequestBody @Valid UserDto user) {
     return ResponseEntity.ok(userService.updateUser(user));
 }
cs

3. 테스트
위의 설정한 validation 설정을 위반하여 request를 보내보자

요청

응답

validation 위반 시 400에러와 함께 오류 field와 기존에 설정한 defaultMessage를 확인할 수 있다.


4. 추가적인 action에 대한 validation 설정
단순하게 regex와 null, email 등등에 대한 validation 체크 이외에 중복 체크등을 진행하기 위해서는 별도의 작업이 필요하다.

4-1)  기존의 Validation과 동일한 방식으로 표현하기 위해서 별도의 RuntimeException 클래스를 만든다.

이 예외 클래스는 Response Stauts는 400으로 전달될 수 있게 @ResponseStatus(HttpStatus.BAD_REQUEST)로 설정하고 예외가 발생한 field와 defaultMessage를 출력해주기 위해 별도의 속성을 만들어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.wedul.springboottest.rest.exception;
 
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
 
import java.util.List;
 
/**
 * Created by Leo.
 * User: wedul
 * Date: 2018-08-09
 * Time: 오후 9:06
 */
// 기존에 validation의 에러와 동일하게 400 에러가 발생하도록 추가
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class ValidationException extends RuntimeException {
 
    private Error[] errors;
 
    public ValidationException(String defaultMessage, String field){
        this.errors = new Error[]{new Error(defaultMessage, field)};
    }
 
    public ValidationException(Error[] errors) {
        this.errors = errors;
    }
 
    public Error[] getErrors() {
        return errors;
    }
 
    public static class Error {
 
        private String defaultMessage;
        private String field;
 
        public Error(String defaultMessage, String field) {
            this.defaultMessage = defaultMessage;
            this.field = field;
        }
 
        public String getDefaultMessage() {
            return defaultMessage;
        }
 
        public String getField() {
            return field;
        }
    }
 
 
}
cs


이 생성된 ValidationException 클래스를 ErrorAttributes 인터페이스를 재 정의하여 Validation 오류가 발생하였을 때 별도의 작업을 할 수 있도록 처리해준다. (이 Bean 객체는 Application에 정의해준다.)


※ ErrorAttributes는 기본적으로 스프링 부트에서 에러 처리하는 BasicErrorController인데 이 인터페이스는 DefaultErrorAttributes 클래스를 참조하여 커스터마이징이 가능하다.
참고
https://brunch.co.kr/@sbcoba/9


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
 
 
 
@SpringBootApplication
public class SpringboottestApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SpringboottestApplication.class, args);
    }
 
    @Bean
    public ErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes() {
 
        @Override
        public Map<String, Object> getErrorAttributes(WebRequest webRequest,
                                                          boolean includeStackTrace) {
            Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
            Throwable error = getError(webRequest);
 
            // validatijon Exception에 경우 별도의 처리를 진행한 에러 데이터 추가
            if (error instanceof ValidationException) {
                errorAttributes.put("errors", ((ValidationException)error).getErrors());
            }
            return errorAttributes;
        }
    };
}
cs


그리고 서비스 코드 영역에서 중복, 금지 등등을 확인하고  ValidationException을 throw하면 front에 동일한 형태에 에러코드를 전송해 줄 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 서비스 코드
 
@Override
public ResultDto insertUser(UserDto user) {
    if (null == users) {
       return ResultDto.fail("UserList Empty");
    }
 
    if (null == user) {
        return ResultDto.fail("Request User Is Null");
    } else {
        if (users.contains(user)) {
            // 중복된 부분에 대해 ValidationException
            throw new ValidationException("name""이름이 중복됩니다.");
        }
        users.add(user);
        return ResultDto.success();
    }
}
cs

테스트 해보면 다음과 같이 중복코드에 대해서 체크가 정상적으로 되는 것을 알 수 있다.

물론 이렇게 RuntimeException을 만들지 않고 팩토리 객체를 만들어서 동일하게 실패 메시지를 만들어서 줘도 상관없다.

하지만 예측이 어려운 Runtime Exception을 잘 정의해놓으면 편하게 처리할 수 있다. 




댓글()

JSP가 포함된 Spring Boot에서 Jar 파일로 패키징 시 오류 해결방법

web/Spring|2018. 7. 5. 00:07

Spring Boot를 통해 다양한 공부를 하던 도중에 View는 편하게 사용하던 JSP를 사용하였다.  IDE 환경에서는 application.properties에서 다음과 같이 설정해주고 src/main/webapp/WEB-INF/ 밑에 jsp를 위치시키면 사용이 가능했다.

#JSP
spring.mvc.view.prefix=/WEB-INF/views/**/
spring.mvc.view.suffix=.jsp

그때 메이븐 wrapper인 mvnw를 이용해서 jar 파일로 패키징을 진행하였을 때 다음과 같이 오류가 발생합니다.

오류내용

[2018-07-01 23:02:04] [ERROR] [DirectJDKLog.java:182] Servlet.service() for servlet [jsp] threw exception
javax.servlet.ServletException: File [&#47;WEB-INF&#47;views&#47;error&#47;error.jsp] not found
	at org.apache.jasper.servlet.JspServlet.handleMissingResource(JspServlet.java:408)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:375)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:330)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
	at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:591)
	at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:527)
	at org.apache.tiles.request.servlet.ServletRequest.doInclude(ServletRequest.java:243)
	at org.apache.tiles.request.AbstractClientRequest.dispatch(AbstractClientRequest.java:54)
	at org.apache.tiles.request.render.DispatchRenderer.render(DispatchRenderer.java:47)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:259)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:397)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:238)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:221)
	at org.apache.tiles.renderer.DefinitionRenderer.render(DefinitionRenderer.java:59)
	at org.springframework.web.servlet.view.tiles3.TilesView.renderMergedOutputModel(TilesView.java:146)
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:314)
	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1325)
	at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1069)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:116)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:84)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:472)
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:395)
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:316)
	at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:395)
	at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:254)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:177)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
[2018-07-01 23:02:04] [ERROR] [DirectJDKLog.java:182] Servlet.service() for servlet [dispatcherServlet] threw exception
javax.servlet.ServletException: File [&#47;WEB-INF&#47;views&#47;error&#47;error.jsp] not found
	at org.apache.jasper.servlet.JspServlet.handleMissingResource(JspServlet.java:408)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:375)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:330)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
	at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:591)
	at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:527)
	at org.apache.tiles.request.servlet.ServletRequest.doInclude(ServletRequest.java:243)
	at org.apache.tiles.request.AbstractClientRequest.dispatch(AbstractClientRequest.java:54)
	at org.apache.tiles.request.render.DispatchRenderer.render(DispatchRenderer.java:47)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:259)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:397)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:238)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:221)
	at org.apache.tiles.renderer.DefinitionRenderer.render(DefinitionRenderer.java:59)
	at org.springframework.web.servlet.view.tiles3.TilesView.renderMergedOutputModel(TilesView.java:146)
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:314)
	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1325)
	at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1069)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:116)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:84)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:472)
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:395)
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:316)
	at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:395)
	at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:254)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:177)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
[2018-07-01 23:02:04] [ERROR] [DirectJDKLog.java:182] Exception Processing ErrorPage[errorCode=0, location=/error]
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.apache.tiles.request.render.CannotRenderException: ServletException including path '/WEB-INF/views/error/error.jsp'.
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:116)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:84)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:472)
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:395)
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:316)
	at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:395)
	at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:254)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:177)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.tiles.request.render.CannotRenderException: ServletException including path '/WEB-INF/views/error/error.jsp'.
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:399)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:238)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:221)
	at org.apache.tiles.renderer.DefinitionRenderer.render(DefinitionRenderer.java:59)
	at org.springframework.web.servlet.view.tiles3.TilesView.renderMergedOutputModel(TilesView.java:146)
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:314)
	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1325)
	at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1069)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
	... 57 common frames omitted
Caused by: java.io.IOException: ServletException including path '/WEB-INF/views/error/error.jsp'.
	at org.apache.tiles.request.servlet.ServletUtil.wrapServletException(ServletUtil.java:63)
	at org.apache.tiles.request.servlet.ServletRequest.doInclude(ServletRequest.java:245)
	at org.apache.tiles.request.AbstractClientRequest.dispatch(AbstractClientRequest.java:54)
	at org.apache.tiles.request.render.DispatchRenderer.render(DispatchRenderer.java:47)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:259)
	at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:397)
	... 67 common frames omitted
Caused by: javax.servlet.ServletException: File [&#47;WEB-INF&#47;views&#47;error&#47;error.jsp] not found
	at org.apache.jasper.servlet.JspServlet.handleMissingResource(JspServlet.java:408)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:375)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:330)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
	at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:591)
	at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:527)
	at org.apache.tiles.request.servlet.ServletRequest.doInclude(ServletRequest.java:243)
	… 71 common frames omitted

 

스프링 부트는 공식적으로 jsp를 지원하지 않기 때문에 jar로 묶을 수 없다. 1.4.3 이하에서는 resources/META-INF/resources/WEB-INF/ 폴더 내의 내용을 src/main/webapp/WEB-INF/ 폴더 내로 복사해서 해결할 수 있지만 1.4.3부터는 그 것마저 막혔다.

그래서 1.4.3은 jar가 아닌 was로 패키징을 진행하면 해결할 수 있다.

pom.xml만 수정하고 다시 진행하면 정상적으로 jsp가 호출된다.

댓글()