当前位置:   article > 正文

Spring boot+Spring security5.0+thymeleaf登录表单POST方式无法提交403或自动转为GET方式的解决_thymeleaf按钮提交没有反应

thymeleaf按钮提交没有反应

一、前言

    我这几天看到查看这篇博文的人比较多,特意更新了两种解决第二个问题办法。

    这两天看隔壁组项目,由于我自己项目和他们项目一样使用的Spring boot基础框架,想看看有什么值得学习的地方,结果就看到人家的登录表单可以正常分GET和POST提交,也没做什么特别的处理,唯一的区别就是人是用Ajax中并submit方法提交的。当时我的项目在登录模块也分GET和POST两种请求方式的控制层方法。但我的POST方法直接通过表单形式提交的话会有本文标题的问题。

二、问题分析与解决

    先了解一下web请求链,由于项目采用Spring security做权限控制,系统的访问流程如下(英文原版文档见9.4Authentication in a Web Application):

  1. 您访问主页,然后单击链接。
  2. 请求转到服务器,服务器确定您已请求受保护的资源。
  3. 由于您目前尚未通过身份验证,因此服务器会发回一个响应,指示您必须进行身份验证。响应将是HTTP响应代码,或重定向到特定网页。
  4. 根据身份验证机制,您的浏览器将重定向到特定的网页,以便您可以填写表单,或者浏览器将以某种方式检索您的身份(通过BASIC身份验证对话框,cookie,X.509证书等) )。
  5. 浏览器将向服务器发回响应。这将是包含您填写的表单内容的HTTP POST,或者包含您的身份验证详细信息的HTTP标头。
  6. 接下来,服务器将决定所呈现的凭证是否有效。如果它们有效,则下一步将会发生。如果它们无效,通常会要求您的浏览器再次尝试(因此您将返回上面的第二步)。
  7. 将重试您进行身份验证过程的原始请求。希望您已通过足够授权的权限进行身份验证以访问受保护资源。如果您有足够的访问权限,请求将成功。否则,您将收到HTTP错误代码403,这意味着“禁止”。

     这里说的很清楚是可以POST请求方式传到后台的。结合文档中关于CSRF的介绍,基本可以确定是CRSF机制转发后POST变成了GET(这句没错,但是有坑)。

    处理这种CSRF问题(此处可以解决POST请求报403的错误)有多种解决方案,如下:

    第一种方法,也是官方推荐使用的。form 表单使用 th:action 属性, thymeleaf 会自动在 form 表单中生成 _csrf 隐藏域

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. ...
  5. <form class="form-signin" th:action="@{login}" action="login" method="post">
  6. ...
  7. </form>
  8. ...

    第二种方法,手动添加隐藏域。

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

     第三种方法,加在请求头部分

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

    第四种办法,直接禁掉CSRF.。这个方法太极端。禁用方式不放出来了,总之强烈不推荐。

    第五种办法,增加例外,让CSRF直接通过。

http.csrf().ignoringAntMatchers("/login")

POST请求403的问题通过设置以上参数就可以解决了


下面解决POST登录表单直接提交后台接收时变成GET的问题

    还记得上面讲到CRSF时说的坑吗?上面我们怀疑是POST表单提交后经由CRSF机制转发后最终提交给后台的是GET请求方式,由于不能正确提交登录信息,导致不管怎样反复会跳到登录页面。

   在说这个问题前,先列举两个它的野路子解法。最后再分析官方解法。

A、.do应用解决

原代码如下:

页面

  1. <form class="form-signin" th:action="@{login}" action="login" method="post">
  2. <div class="form-group">
  3. <label for="username">账号</label>
  4. <input type="text" class="form-control" name="username" value="" placeholder="账号"/>
  5. </div>
  6. <div class="form-group">
  7. <label for="password">密码</label>
  8. <input type="password" class="form-control" name="password" placeholder="密码"/>
  9. </div>
  10. <input type="submit" id="login" value="Login" class="btn btn-primary"/>
  11. </form>

控制层

  1. import javax.annotation.Resource;
  2. import javax.servlet.http.HttpServletRequest;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import com.music.league.service.SignManager;
  7. @Controller
  8. public class SignController {
  9. @Resource
  10. SignManager signManager;
  11. @RequestMapping(value="/login",method = RequestMethod.GET)
  12. public String sign(){
  13. System.out.println("Judge!!");
  14. return "login";
  15. }
  16. @RequestMapping(value="/login",method = RequestMethod.POST)
  17. public String sign(HttpServletRequest request){
  18. System.out.println("登录方法入参:"+request.getParameter("userName")+":"+request.getParameter("password"));
  19. return "welcome";
  20. }
  21. }

SpringMVC配置

  1. @Configuration
  2. public class WebMvcConfig extends WebMvcConfigurerAdapter {
  3. @Override
  4. public void addViewControllers(ViewControllerRegistry registry) {
  5. registry.addViewController("/login").setViewName("login");
  6. }
  7. @Override
  8. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  9. registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
  10. }
  11. }

Spring security配置

  1. http.authorizeRequests()
  2. .anyRequest().authenticated()
  3. .and().formLogin()
  4. .loginPage("/login")
  5. //设置默认登录成功跳转页面
  6. .defaultSuccessUrl("/welcome").failureUrl("/login?error").permitAll()

把上述几处login的请求,除了控制层的POST方式登录方法和 th:action="@{login},其他的login都改成login.do。这样做是为了通过除请求Method属性外的第二种办法去区分开get和post请求的不同。

B、参数差异

   这种方法是基于上面web请求链第二步请求本身无参的性质硬搞。当login方法无参时,自动处理走GET,大家和和美美。当有参时,手动写逻辑去掉POST。不过可能会有安全问题,所以不太推荐。   


听说用ModelAndView也可以解决,我简单试了一下,好像不行哦。anyway,野路子解法到此为止。


正经的问题本质原因,如下:

    在翻看Spring security5.0官方文档的时候,发现文档中提到Spring security特别为大家提供了一个登录验证表单(具体哪句找不到了,文档连接点我),倾力奉献撒!继续读文档,通过前后文的联系,官方的表单页面代码大概是这样的(这段代码在文档5.3节末尾):

  1. <c:url value="/login" var="loginUrl"/>
  2. <form action="${loginUrl}" method="post"> 1
  3. <c:if test="${param.error != null}"> 2
  4. <p>
  5. Invalid username and password.
  6. </p>
  7. </c:if>
  8. <c:if test="${param.logout != null}"> 3
  9. <p>
  10. You have been logged out.
  11. </p>
  12. </c:if>
  13. <p>
  14. <label for="username">Username</label>
  15. <input type="text" id="username" name="username"/> 4
  16. </p>
  17. <p>
  18. <label for="password">Password</label>
  19. <input type="password" id="password" name="password"/> 5
  20. </p>
  21. <input type="hidden" 6
  22. name="${_csrf.parameterName}"
  23. value="${_csrf.token}"/>
  24. <button type="submit" class="btn">Log in</button>
  25. </form>

    看到官方这么贴心,然后我的表单整体样式和它基本一致,就再次仔细看了一下。发现除了我的页面写的是loginName其他没区别。于是抱着试一试的态度改成username。MMP,完美的POST请求进入控制层POST请求登录方法。MMP...要不要限制这么死?CRSF底层实现我找不到,但问题就是在这个特殊的登录,官方给登录做了特别处理。

    经过进一步的验证发现即使实体中写的是userName或者loginName,只要你想在登录模块直接通过表单方式提交的话,就必须是username。

    如果改了名字还是无效,那么还有两个解决方法。就是改为小写的username后,执行下面两个方法之一。推荐第二个。

    第一个办法是手动给指定一下登录请求的处理。就是在loginPage后面加一个loginProcessingUrl,内容是你登陆方法的控制层RequestMapping中的value和登录方法。如果你没有写RequestMapping的话,那就是控制层的Spring自动转换值,一般是去掉Contrller的驼峰命名。比如SignController,这里写sign就行。

    第二个办法是把defaultSuccessUrl改为successForwardUrl,这个办法的原理就是把直接跳转页面改为跳转后台方法。defaultSuccessUrl("/login")改为successForwardUrl("/sign/login")。建议用这个,因为这个依旧会照常按security过滤器链自动加载权限,第一个需要手动添加权限,否则一直是匿名。

.loginPage("/login").loginProcessingUrl("/sign/login")

三、注意事项

    .do的应用解决也要把登录名改成小写的username,官方的3.x版本文档写的是must,否则无法通过表达提交

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/652303
推荐阅读
相关标签
  

闽ICP备14008679号