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

一、前言

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

    这两天看隔壁组项目,由于我自己项目和他们项目一样使用的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 隐藏域




 ...
    
 ...

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

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

    
    

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

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

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

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


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

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

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

A、.do应用解决

原代码如下:

页面

控制层

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.music.league.service.SignManager;

@Controller
public class SignController {
	
	@Resource
	SignManager signManager;
	
	@RequestMapping(value="/login",method = RequestMethod.GET)
	public String sign(){
		System.out.println("Judge!!");
		return "login";
	}
	@RequestMapping(value="/login",method = RequestMethod.POST)
	public String sign(HttpServletRequest request){
		System.out.println("登录方法入参:"+request.getParameter("userName")+":"+request.getParameter("password"));
		return "welcome";
	}
}

SpringMVC配置

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
	@Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }
	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
}

Spring security配置

 http.authorizeRequests()
		.anyRequest().authenticated()
		.and().formLogin()
		.loginPage("/login")
		//设置默认登录成功跳转页面
		.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 2

Invalid username and password.

3

You have been logged out.

4

5

    看到官方这么贴心,然后我的表单整体样式和它基本一致,就再次仔细看了一下。发现除了我的页面写的是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,否则无法通过表达提交

你可能感兴趣的:(Springboot,thymeleaf,spring,security)