SpringBoot+Freemarker+Security环境下自建/login登录页面,并解决一直302跳转登录首页、无法登录成功的问题

本文介绍了如何在SpringBoot项目中集成WebSocket和Security实现群聊功能,并自定义登录页面。在开发过程中遇到登录后始终302重定向回登录页的问题,原因是SpringSecurity的CSRF防护机制。解决方案包括禁用CSRF验证或在前端页面添加_csrf字段以满足Security的安全要求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 开发步骤

最近在学习SpringBoot WebSocket编写群聊天的功能,需要用到用户体系,为了方便直接引入了Security包,具体pom如下:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    
    <dependencies>
        <!-- freemarker -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <!-- security安全 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

用FreeMarker开发了一个自己的登录页面login.ftl,放在templates文件夹下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>登录</title>
</head>
<body>
<form action="/login123" method="post">
    <div><label>账号:<input type="text" name="username"/></label></div>
    <div><label>密码:<input type="password" name="password"/></label></div>
    <div><input type="submit" value="登录"/></div>
</form>
</body>
</html>

重写WebSecurityConfig来替换SpringBoot默认的配置,使得我们上面开发的登录页面生效

//@EnableWebSecurity        //SpringBoot自带此注解,不再需要添加
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //设置拦截规则
                .antMatchers("/", "/login")      //对/ 和 /login不拦截
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                //开启默认登录页面
                .formLogin()
                .loginPage("/login")                    //替换Security默认的登录页面到我们自己开发的login.tfl页面
                .loginProcessingUrl("/login123")      //登录提交form action, 也会自动影响Security自带Http Basic登录页的action请求地址
                .defaultSuccessUrl("/chat", true)       //登录成功后,默认后续跳转页面
                .permitAll()
                .and()
                .logout()		//设置注销
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置两个用户
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user1")
                .password(new BCryptPasswordEncoder().encode("111")).roles("USER").and()
                .withUser("user2")
                .password(new BCryptPasswordEncoder().encode("111")).roles("USER");

        //security5.0以后默认需要加密传输,不能明文
//        auth.inMemoryAuthentication()
//                .withUser("user1").password("111").roles("USER").and()
//                .withUser("user2").password("222").roles("USER");
    }

    @Override
    public void configure(WebSecurity web) {
        //设置不拦截规则,一般用于静态资源
        web.ignoring().antMatchers("/js/**", "pic/");
    }
}

以上代码的重点在.loginPage("/login"),登录界面被替换为view:login。这类似于@Controller方法中返回的String,会被SpringBoot替换为对应的view视图。
/login与页面login.ftl的对应关系,则通过重写WebMvcConfigurer类实现:

//@EnableWebMvc         //SpringBoot下最好不要开启此注解,否则默认SpringMVC配置将失效被替换成本类方法
@Configuration
public class WSWebMvcConfig implements WebMvcConfigurer {

    /**
     * 直接映射url到对应的view:XXX.tfl模板文件
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/ws").setViewName("/freemarker/ws");
        registry.addViewController("/chat").setViewName("/freemarker/chat");
        registry.addViewController("/login").setViewName("/freemarker/login");
    }
}

二. 一直302跳转登录首页问题描述

以上项目启动后,访问/login的URL(我的是:https://localhost:8443/login),可以出现自定义页面
在这里插入图片描述

但输入用户名user1、密码111之后,登录不成功,页面跳转后还是在/login页面内,并且没有明显的报错
通过F12可以看到,http请求被302重定向了,location仍然为登录页
在这里插入图片描述

三. 分析原因

注释掉上面的.loginPage("/login")代码,重新启动服务,/login登录页面会恢复Security默认的登录页面
在这里插入图片描述
尝试登录,可以成功登录并进入后续/chat页面。F12调试模式下,与原先自开发页面请求进行对比,可以发现form请求中多了一个字段:_csrf
在这里插入图片描述

此参数是Security模块为了防止CSRF跨域请求漏洞而添加的伪随机数参数,action调用的/login接口也会对此进行校验,防止漏洞攻击。

至此我们可以定位到之前登录不成功的原因:Spring Security默认是开启crsf验证的,自开发的登录接口被Security模块的CSRF防范机制给拦截了

四. 解决办法

方法一. 去掉Security的CSRF验证机制:
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //设置拦截规则
                .antMatchers("/", "/login")      //对/ 和 /login不拦截
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                //开启默认登录页面
                .formLogin()
                .loginPage("/login")                    //替换Security默认的登录页面到我们自己开发的login.tfl页面
                .loginProcessingUrl("/login123")        //登录提交form action, 也会自动影响Security自带Http Basic登录页的action请求地址
                .defaultSuccessUrl("/chat", true)       //登录成功后,默认后续跳转页面
                .permitAll()
                .and()
                .logout()		//设置注销
                .permitAll()
                .and()
                .csrf().disable();      //Security自带CSRF防攻击,导致页面验证不通过,一直跳转302,这里关闭
    }

注意最后一行,增加.csrf().disable()代码段来关闭CSRF验证。改后重启服务,即可登录成功。

CSRF验证相关原理在:org.springframework.security.web.csrf.CsrfFilter这个过滤器源码中

方法二. 可以修改前端页面,像原生login界面一样增加_csrf字段,上网查到操作如下(thymeleaf):
 <!-- 表单提交用户信息,注意字段的设置,直接是*{} --> 
 <form action="/login" method="post" enctype="application/x-www-form-urlencoded"> 
 	<!--用于验证跨域伪造csrf--> 
 	<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> 
 	用户名:<input type="text" name="username"/><br/> 
 	密码:<input type="text" name="password"/><br/> 
 	<input type="submit" value="登录"/>
</form> 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭Albert

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值