Spring Security를 ​​사용하면 JSESSIONID를 URL에 부여 할 수 없습니다.

JSESSIONID 를 URL 에 포함하고 있는 것과 같은 어플리케이션으로 Spring Security 의 버전 업을 실시하면(자) 에러가 발생하게 되었다.
그 원인에 대해 조사했으므로 정리한다.

네, 지금 JSESSIONID를 URL에 포함시키지 않겠습니까?

환경


  • Spring Boot 2.1.6.RELEASE
  • (Spring Security 5.1.5.RELEASE)
  • Thymeleaf 3.0.11.RELEASE

  • 사건을 재현



    이벤트를 재현하려면 몇 가지 준비가 필요합니다.

    JSESSIONID를 URL로 관리



    쿠키가 이용 가능한 브라우저의 경우는 쿠키를 이용해 JSESSIONID 를 관리해 버린다.
    URL을 강제로 관리하도록 서블릿 컨테이너 설정을 변경합니다.

    Spring Boot를 사용했을 경우는, 이하와 같이 ServletContextInitializer 를 Bean 정의하는 것으로 설정할 수 있다.
    @Bean
    public ServletContextInitializer servletContextInitializer() {
        return servletContext -> servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.URL));
    }
    

    URL Rewriting 사용



    Spring Security에서는 URL Rewriting을 무효화하는 기능이 디폴트로 설정되어 있다.
    다음과 같이 WebSecurityConfigurerAdapter 를 상속한 클래스에서 설정을 변경한다.
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.sessionManagement().enableSessionUrlRewriting(true);
        }
    }
    

    로그인 페이지 만들기



    Spring Security 가 디폴트로 제공하고 있는 로그인 페이지에서는 URL Rewriting 를 이용할 수 없기 때문에, 로그인 페이지를 만든다.

    login.html
    <!DOCTYPE html>
    <html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>login</title>
    </head>
    <body>
    <form th:action="@{/login}" method="post">
        <div>
            <label>ユーザー名: <input type="text" name="username"/></label>
        </div>
        <div>
            <label>パスワード: <input type="password" name="password"/></label>
        </div>
        <input type="submit" value="login"/>
    </form>
    </body>
    </html>
    

    또한 이 페이지를 표시하기 위한 Controller 메서드를 만든다.
    @Controller
    public class HelloController {
        @GetMapping("/login")
        public String login() {
            return "login";
        }
    }
    

    마지막으로 설정을 추가합니다.
    그런 다음 로그인에 사용할 사용자 이름과 암호를 지정합니다.
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated()
                    .and()
                    .formLogin().loginPage("/login")
                    .and()
                    .sessionManagement().enableSessionUrlRewriting(true);
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("user").password("{noop}user").roles("ADMIN");
        }
    }
    

    기본적으로 DelegatingPasswordEncoder가 사용되므로 암호에는 prefix가 필요합니다. 이번은 평문이므로 {noop}를 부여하고 있다.
    DelegatingPasswordEncoder에 대해 조사한 것은 아래에 정리되어 있다.
    htps : // 이 m / d-o sh / ms / b b52152318391 5 5 07 아

    로그인 후 페이지 만들기



    이것도 html과 Controller 메소드를 만든다.

    hello.html
    <!DOCTYPE html>
    <html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>Hello</title>
    </head>
    <body>
    <h1>ログインできたよ</h1>
    </body>
    </html>
    
    @Controller
    public class HelloController {
    
        @GetMapping("/")
        public String index() {
            return "hello";
        }
    
        @GetMapping("/login")
        public String login() {
            return "login";
        }
    }
    
    

    이벤트 확인



    응용 프로그램을 시작하고 액세스하면 오류가 발생합니다.
    (정확하게는 에러가 발생하여 에러 화면으로 리디렉션하지만, 거기에서도 에러가 발생하여 리디렉션이 루프하고 있다.)


    로그를 보면 RequestRefectedException 가 발생하고 있는 것을 확인할 수 있다.
    org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
    

    원인



    Spring Security 는 HttpFirewall 에 의해, 요구의 체크를 실시하고 있어, 그 구현의 1 개인 StrictHttpFirewall 에서는, 세미콜론을 포함한 URL 를 거부하게 되어 있다.
    JSESSIONID 를 URL 에 포함하는 경우는 세미콜론이 URL 에 부여되기 (위해)때문에, StrictHttpFirewall 에 의해 거부되어 예외가 발생하고 있다.

    Spring Security 의 버젼에 의해 디폴트로 이용하는 HttpFirewall 가 다르고, 한때는 DefaultHttpFirewall 가 이용되고 있었다.
    이 클래스는 URL에 세미콜론이 포함되어 있어도 요청을 거부하지 않습니다.
    이번은 버전을 들었을 때에, 디폴트의 HttpFirewall 가 바뀌어서 에러가 발생하게 되어 버렸다.

    대처 1


    StrictHttpFirewall 는 세미콜론을 허용하도록 설정을 변경할 수 있습니다.
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        // 諸々省略・・・
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            StrictHttpFirewall firewall = new StrictHttpFirewall();
            firewall.setAllowSemicolon(true);
            web.httpFirewall(firewall);
        }
    
    }
    

    대처 2


    DefaultHttpFirewall 를 이용하도록 설정을 변경한다.
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        // 諸々省略・・・
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            DefaultHttpFirewall firewall = new DefaultHttpFirewall();
            web.httpFirewall(firewall);
        }
    }
    

    조치 확인



    조치 1 또는 2를 수행 한 후 액세스하면 로그인 화면을 볼 수 있습니다.


    사용자 이름 : user, 암호 : user로 로그인 할 수 있습니다.


    덧붙여서 로그인 전후로 JSESSIONID 가 바뀌고 있는 것은, Spring Security 의 세션 고정화 공격 대책이 유효하게 되어 있기 때문.
    htps : // / cs. sp 링 g. 이오 / sp 린 g - 쿠시 ty / 해 / 도 CS / 5.1.6. Ree Ase / Reffe Rense / HTML ml g / / N-s-Seshi On-Fu Chion

    사이고에게



    세션을 URL로 관리해서는 안됩니다.

    좋은 웹페이지 즐겨찾기