생각해보니 Spring 을 구동하기 위해서 꼭 필요한 Java 설치에 대한 글을 올린 적이 없다.

M1 맥북 기준으로 Java를 설치해보자.

2019년 1월부터 Oracle JDK 가 유료화되었기 때문에 나는 Open JDK(무료)를 사용해보겠다.

 

 

Azul Downloads

No matter the size of your company, Azul offers competitive pricing options to fit your needs, your budget, and your ambition.

www.azul.com

 

위 링크로 접근하여, 아래와 같은 옵션으로 다운로드 받는다.

필자는 설치만 해도 jdk 가 잘 나온다.(환경변수 따로 설정하지 않았다.)

그러나 대부분의 분들이 Java 환경변수를 설정해야 된다고 하므로

설정해보자..(사실 내가 환경변수를 설정했는데 기억하지 못하는 걸수도 있다)

sudo nano ~/.zshrc

이후

#JDK version PATH
export JAVA_HOME=/Library/Java/JavaVirtualMachines/[JDK 폴더명]/Contents/Home


#Add Environment Variable
export PATH=$JAVA_HOME/bin:$PATH
 

요렇게 경로 지정하고

아래와 같이 터미널에서 입력 시 jdk 정보가 제대로 나오면 성공한 것이다.

java --version

 

스프링부트 환경에서 Swagger를 사용하게 되었는데, 아래와 같은 에러가 발생했다.

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

처음에는 bean 문제인가 뭔가 싶었는데 , 구글링해보니

Spring boot 버전 2.6이후부터

spring.mvc.pathmatch.matching-strategy 값이

ant_path_matcher 에서 path_pattern_parser 로 변경되어 오류가 계속 식별되고 있다고 한다.

고치는 방법이 따로 있다고 한다.

1.프로젝트 -> src-> main-> resources-> application.properties 로 접근

2.아래 내용 추가

3. 결과

성공 !

* 자바랑 스프링 쓸 때마다 느끼는 건데 잡에러가 많은 거 같다.. 방심하지말고 조심하자

Swagger 로 Spring Security 적용 후 REST API 테스트하던 중 갑자기 403 에러가 식별됐다..

뭐가 문제일까 알아보니.. 스프링시큐리티를 적용하면 기본적으로 보안문제로 인해

CSRF 토큰이 없으면 403에러를 일으킨다고 한다..

기존 코드부터 살펴보자

public SecurityFilterChain exceptionSecurityFilterChain(HttpSecurity http) throws Exception {
        http

                .sessionManagement((sessionManagement) ->
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )

                .authorizeRequests((authorizeRequests) ->
                authorizeRequests
                        .requestMatchers("/api-docs","/api-docs/json/**", "/swagger-resources/**",
                                "/swagger-ui/**", "webjars/**","/swagger/**","/sign-api/**").permitAll()

                        .requestMatchers(HttpMethod.GET,"/product/**").permitAll()
                        .anyRequest().hasRole("ADMIN")
                        .and()

                        .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
                );

        return http.build();
    }
 

위의 코드를 보면 CSRF 관련 설정값이 없다.

추가해주자.

public SecurityFilterChain exceptionSecurityFilterChain(HttpSecurity http) throws Exception {
        http

                .sessionManagement((sessionManagement) ->
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )

                .authorizeRequests((authorizeRequests) ->
                authorizeRequests
                        .requestMatchers("/api-docs","/api-docs/json/**", "/swagger-resources/**",
                                "/swagger-ui/**", "webjars/**","/swagger/**","/sign-api/**").permitAll()

                        .requestMatchers(HttpMethod.GET,"/product/**").permitAll()
                        .anyRequest().hasRole("ADMIN")
                        .and()

                        .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
                )
                 // 추가한 부분
                .csrf((csrf) -> csrf.requireCsrfProtectionMatcher(new CsrfRequireMatcher()))
                .csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
        return http.build();
    }
// 추가한 클래스(swagger일때 토큰 무시)
 static class CsrfRequireMatcher implements RequestMatcher {
        private static final Pattern ALLOWED_METHODS = Pattern.compile("^(GET|HEAD|POST|TRACE|OPTIONS)$");

        @Override
        public boolean matches(HttpServletRequest request) {
            if (ALLOWED_METHODS.matcher(request.getMethod()).matches())
                return false;

            final String referer = request.getHeader("Referer");
            if (referer != null && referer.contains("/swagger-ui")) {
                return false;
            }
            return true;
        }
    }

위 코드를 보면 CsrfRequireMather를 만든 이유는 swagger 환경에서 테스트할때는 CSRF 토큰 여부를 체크하지 않기 위해
적용한 것이다.

이제 정상적으로 동작한다.

만약 CSRF 를 전혀 사용하지 않는 환경이라면 위의 코드를 수정하여야 한다.

기존

 .csrf((csrf) -> csrf.requireCsrfProtectionMatcher(new CsrfRequireMatcher()))
 .csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));

변경

 .csrf((csrf) -> csrf.disable());
 

토큰 인증 로그인방식을 적용하고 있는데.. 다음과 같은 에러가 발생했다.

[2023-08-14 13:51:27.493] [ERROR] [http-nio-8081-exec-6] 
com.springboot.security.controller.SignController ExceptionHandler 호출,null,
The signing key's size is 56 bits which is not secure enough for the HS256 algorithm.  
The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HS256 MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size).  
Consider using the io.jsonwebtoken.security.Keys class's 'secretKeyFor(SignatureAlgorithm.HS256)' 
method to create a key guaranteed to be secure enough for HS256.

엄청 많은 말이지만 결국은.. 시크릿키가 너무 짧으니 재검토를 고려해보라는 뜻이다.

(고려해보라고하면 단순 경고같지만 에러가 뜨니 안 바꿀수가 없는 노릇)

내 시크릿키는 이러했다.

## JWT auth key

springboot.jwt.secret=ohks486

참 심플하지 않은가? 해서 이런식으로 엄청 길게 바꾸었다.

## JWT auth key

springboot.jwt.secret=Q4NSl604sgyHJj1q5wEkR3ycUeR4uUAt7WJraD7EN3O9DVM4yyYu7xMEbSF4XXyYJkal13eqgB8F7Bq4H

자 다시 로그인을 해보면..

JWT 로그인

음..매우 잘된다

개발환경에서는 멀쩡히 동작하던 소스코드가 Docker 로 배포하고 테스트해보니 위 제목과 같은 에러가 발생한다.

여러가지 방법이 많지만 제일 간단한 방법을 기재한다.

application.properties에 다음 코드를 추가한다.

spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect

 

JPA 를 쓰다가 저런 에러가 발생했다.

찾아보니 Entity에서 @ManyToOne 할때

FetchType을 Lazy로 설정했을 때 발생하는 문제같다.

Lazy 옵션은 필요할 때 조회를 하는 데,

필요가 없으면 조회를 안해서 비어있는 객체를 serializer 하려고 해서 발생되는 문제인것 같다.

해결방법은 3가지가 있다.

1. application 파일에 spring.jackson.serialization.fail-on-empty-beans=false 설정해주기

2. 오류가 나는 엔티티의 LAZY 설정을 EAGER로 바꿔주기

3. 오류가 나는 컬럼에 @JsonIgnore를 설정해주기

나는 2번으로 해결했더니 잘 동작한다.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uid;
@ManyToOne(fetch = FetchType.EAGER) 
@JoinColumn(name="type_uid")
private Type type;

 

+ Recent posts