使用get请求评估springel表达式“\u csrf.token”时出现异常

tsm1rwdh  于 2021-09-30  发布在  Java
关注(0)|答案(1)|浏览(424)

我有一个SpringBoot2.5.1应用程序,它具有SpringSecurity和thymleaf。
为了传递csrf令牌,我遵循了spring文档链接https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html#csrf-包括csrf令牌ajax
index.html包含上述链接中提到的标题

<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

下面是在所有ajax请求中包含令牌的配置

$(function () {
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $(document).ajaxSend(function (e, xhr, options) {
            xhr.setRequestHeader(header, token);
    });
});

然而,在添加了上述更改之后,现有的junit测试用例开始失败

@AutoConfigureMockMvc
@WebMvcTest(MyUserInterfaceController.class)
class MyUserInterfaceControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldGetIndexPageWithNoUnknownUserName() throws Exception {
        mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("index"))
                .andExpect(model().attribute("username", "UNKNOWN_USER"));
    }
}

在我做出与csrf相关的更改之前,上面的测试用例是通过的。然而,下面的测试用例通过了csrf更改或没有更改。

@SneakyThrows
    @Test
    @WithMockTestUser
    void shouldSaveSettlementRemark() {
        //var userDtos= ...
        mockMvc.perform(post("/users")
                .contentType(APPLICATION_JSON)
                .content(userDtos))
                .andDo(print())
                .andExpect(status().isOk());
    }

根据spring文档,如果SpringSecurityJAR在类路径中,csrf在默认情况下是启用的&因此post请求需要csrf令牌,否则请求被禁止。使用mockmvc编写的测试用例不遵循这种行为。
为什么在post请求传递时,get请求在stacktrace下失败?如何解决这个问题?

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "_csrf.token" (template: "index" - line 6, col 24)

    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)

在运行应用程序时,ui正在呈现,但在后端,即使没有启动任何用户请求,thymeleaf也会尝试呈现index.html,这会引发以下错误。

":"org.thymeleaf.TemplateEngine","message":"[THYMELEAF][http-nio-8080-exec-1] Exception processing template \"index\": An error happened during template parsing (template: \"templates/index.html\")","stack_trace":"<#24d6b41b> o.s.e.s.SpelEvaluationException: EL1007E: Property or field 'token' cannot be found on null\n\tat o.s.e.s.a.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:213)

我可以通过添加空检查来修复上述两个错误,如下所示:
这是正确的方法还是我在损害应用程序的安全性?

<meta name="_csrf" th:content="${_csrf != null} ? ${_csrf.token} : '' "/>
<meta name="_csrf_header" th:content="${_csrf != null} ? ${_csrf.headerName} : '' "/>

Web安全配置如下所示:

@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomProperties customProperties;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if (customProperties.isEnabled()) {
            log.info("authentication enabled.");
            http.authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .oauth2Login()
                    .redirectionEndpoint()
                    .baseUri(customProperties.getAuthorizationResponseUri())
                    .and()
                    .userInfoEndpoint(userInfo -> userInfo.userService(new CustomOAuth2UserService()::loadUser));
        } else {
            log.info("authentication disabled.");
            http.authorizeRequests()
                    .anyRequest()
                    .permitAll();
        }
    }
}
vc6uscn9

vc6uscn91#

默认情况下,启用csrf时,spring security会在会话启动时生成csrf令牌。如果您没有会话,将不会有csrf令牌。您的第二个测试使用经过身份验证的用户,将有一个会话,并成功。
在禁用csrf时,您可以使用以下元标记来处理这种情况:

<meta sec:authorize="isFullyAuthenticated()" th:if="${_csrf}" name="_csrf" th:content="${_csrf.token}"/>
<meta sec:authorize="isFullyAuthenticated()" th:if="${_csrf}" name="_csrf_header" th:content="${_csrf.headerName}"/>
<meta sec:authorize="isFullyAuthenticated()" th:if="${_csrf}" name="_csrf_parameter" th:content="${_csrf.parameterName}"/>

另一种方法是更改配置中的csrf令牌策略:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    // other security information ...
   .and().csrf().csrfTokenRepository(new HttpSessionCsrfTokenRepository())
}

并使用以下元标记:

<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
<meta name="_csrf_parameter" th:content="${_csrf.parameterName}"/>

但我不知道你的帖子为什么有用。如果启用了csrf,我认为您必须使用.with(csrf()),如下所示。

@SneakyThrows
@Test
@WithMockTestUser
void shouldSaveSettlementRemark() {
    //var userDtos= ...
    mockMvc.perform(post("/users")
            .with(csrf())
            .contentType(APPLICATION_JSON)
            .content(userDtos))
            .andDo(print())
            .andExpect(status().isOk());
}

相关问题