SpringSecurity表单配置之登录成功及页面跳转原理解析

 更新时间:2023年12月05日 09:58:13   作者:陈橙橙丶  
这篇文章主要介绍了SpringSecurity表单配置之登录成功及页面跳转原理解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

登录表单配置

在上一篇文章中,我们介绍了,基本认证以及默认用户名和密码以及页面SpringSecurity是怎样帮我们生成的,这里我们就来看一下登录表单的详细配置。

项目准备

导入依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

编写登录页面(login.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>登录</title>
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="external nofollow" rel="stylesheet"
    id="bootstrap-css"
    />
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js">
    </script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js">
    </script>
</head>
<style>
</style>
<body>
    <div id="login">
        <div class="container">
            <div id="login-row" class="row justify-content-center align-item-center">
                <div id="login-colum" class="col-md-6">
                    <div id="login-box" class="col-md-12">
                        <form id="login-form" class="form" action="/doLogin" method="post">
                            <h3 class="text-center text-info">登录</h3>
                            <div class="form-group">
                                <label for="username" class="text-info">用户名:</label><br>
                                <input type="text" name="uname" id="username" class="form-control">
                            </div>
                            <div class="form-group">
                                <label for="password" class="text-info">密码:</label><br>
                                <input type="text" name="passwd" id="password" class="form-control">
                            </div>
                            <div class="form-group">
                                <input type="submit" name="submit"  class="btn btn-info btn-md" value="登录">
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

提供两个测试接口

@GetMapping("/hello")
    public String hello(){
        return "hello springboot security";
    }
    @GetMapping("/index")
    public String index(){
        return "login success";
    }

自定义用户名密码

spring.security.user.name=test
spring.security.user.password=123456
spring.security.user.roles=admin,user

提供SpringSecurity配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated() 
                .and().formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/doLogin")
                .defaultSuccessUrl("/index")
                .failureUrl("/login.html")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

在SpringSecurity中,如果我们需要自定义配置,基本上都是继承WebSecurityConfigurerAdapter来实现的,当然WebSecurityConfigurerAdapter本身的配置还是比较复杂,同时也是比较丰富的,这里不细说,后续会详细介绍。

  • 首先configure方法中是一个链式配置,当然也可以不用链式配置,每个属性配置完毕后再从http重新写起
  • authorizeRequests()方法表示开启权限配置
    • anyRequest().authenticated()表示所有的请求都要认证之后才能访问
  • and()方法,该方法会返回一个HttpSecurityBuilder对象的一个子类(实际上就算HttpSecurity),所以and()方法相当于又回到HttpSecurity实例,重新开启新一轮的配置。
  • formLogin()表示开启表单登录配置:
    • loginPage:用来配置登录页面地址
    • loginProcessingUrl:用来配置接口登录接口地址
    • defaultSuccessUrl:表示登录成功后跳转地址
    • failureUrl:表示登录失败后跳转的地址
    • usernameParameter:表示登录用户名的参数名称
    • passwordParameter:表示密码的参数名称
    • permitAll:可以理解成两个and()之间的所有方法地址不需要认证拦截(白名单)。

需要注意的是loginProcessingUrl、usernameParameterpasswordParameter需要和login.html中登录表单配置一致

最后csrf().disable()表示禁用CSRF防御功能,SpringSecurity自带了CSRF防御机制,但是我们这里为了测试方便,先将CSRF防御机制关闭。

启动项目访问http://localhost:8080/index

输入配置的用户名/密码:test/123456,然后就能访问到/index接口了

配置细节

上面我们说到defaultSuccessUrl和failureUrl表示用户登录失败后的跳转地址。关于登录成功和登录失败,除了这两个方法之外,还有另外两个方法可以配置

登录成功

  • successForwardUrl
  • defaultSuccessUrl

defaultSuccessUrl前者表示当用户登录成功之后,会自动重定向到登录之前的地址上,如果用户本身就是直接访问的登录页面,则登录成功之后就会重定向到defaultSuccessUrl指定的页面中。例如用户在未认证的情况下,访问了/hello页面,此时会重定向到登录页面,登录成功后,就会自动重定向到/hello页面;而用户如果一开始就是访问到登录页面,则登录成功后就会自动重定向到defaultSuccessUrl指定的页面

successForwardUrl则不会考虑用户之前所访问地址,只要用户登录成功,就会通过服务器端跳转到successForwardUrl所指定的页面。

defaultSuccessUrl有一个重载方法,如果重载方法的第二个参数传入true,则它和successForwardUrl效果类似,即不考虑用户之前的访问地址,只要登录成功就重定向到指定页面。不同之处在于defaultSuccessUrl是通过重定向实现的跳转(客户端跳转),而successForwardUrl是通过服务器端跳转实现的。

无论是successForwardUrl还是defaultSuccessUrl,最终所有配置的都是AuthenticationSuccessHandler接口的实例。

SpringSecurity中专门提供了AuthenticationSuccessHandler接口用来处理登录成功事项

public interface AuthenticationSuccessHandler {
    default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        this.onAuthenticationSuccess(request, response, authentication);
        chain.doFilter(request, response);
    }
    void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
}

由上述代码可以看到AuthenticationSuccessHandler接口中一共定义了两个方法,一个是default方法,此方法是SpringSecurity5.2开始加入的,在处理特定的认证请求Authentication Filter中会用到;另外一个非default方法,则用来处理登录成功的具体事项,其中authentication参数保存了登录成功的用户信息。

AuthenticationSuccessHandler接口一共有三个实现类:

在这里插入图片描述

(1)、SimpleUrlAuthenticationSuccessHandler继承自AbstractAuthenticationTargetUrlRequestHandler,通过AbstractAuthenticationTargetUrlRequestHandler中的handle方法实现请求重定向

(2)、SavedRequestAwareAuthenticationSuccessHandler在SimpleUrlAuthenticationSuccessHandler的基础之上增加了请求缓存的功能,可以记录之前请求的地址,进而在登录成功之后重定向到一开始访问的地址。

(3)、ForwardAuthenticationSuccessHandler的实现就比较容易,就是一个服务端跳转。

我们来重点看一下SavedRequestAwareAuthenticationSuccessHandler和ForwardAuthenticationSuccessHandler

public class SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
	protected final Log logger = LogFactory.getLog(this.getClass());
	private RequestCache requestCache = new HttpSessionRequestCache();
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws ServletException, IOException {
		SavedRequest savedRequest = this.requestCache.getRequest(request, response);
		if (savedRequest == null) {
			super.onAuthenticationSuccess(request, response, authentication);
			return;
		}
		String targetUrlParameter = getTargetUrlParameter();
		if (isAlwaysUseDefaultTargetUrl()
				|| (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
			this.requestCache.removeRequest(request, response);
			super.onAuthenticationSuccess(request, response, authentication);
			return;
		}
		clearAuthenticationAttributes(request);
		// Use the DefaultSavedRequest URL
		String targetUrl = savedRequest.getRedirectUrl();
		getRedirectStrategy().sendRedirect(request, response, targetUrl);
	}
	public void setRequestCache(RequestCache requestCache) {
		this.requestCache = requestCache;
	}
}

这里的核心方法就是 onAuthenticationSuccess

(1)、首先从requestCache中获取缓存下来的请求,如果没有获取到缓存请求,就说明用户在登录页面之前并没有访问其他页面,此时调用父类的方法来处理,最终会重定向到defaultSuccessUrl指定的地址

(2)、如果缓存请求不为空,则会获取一个targetUrlParameter,这个是用户显示指定的,希望登录成功重定向的地址,例如用户发送的登录请求是http://localhost:8080/doLogin?target=/hello,这就表示当用户登录成功之后。希望自动重定向到/hello这个接口,getTargetUrlParameter就是要获取重定向地址参数的key,也就是上面的target,拿到target之后,就可以获取到重定向地址了。

(3)、如果targetUrlParameter存在,或者用户设置了alwaysUseDefaultTargetUrl为true,这个时候缓存下来的请求就没有意义了。此时会直接调用父类的onAuthenticationSuccess方法完成重定向。targetUrlParameter存在,则直接重定向到targetUrlParameter指定的地址。alwaysUseDefaultTargetUrl为true,则直接重定向到defaultSuccessUrl指定的地址。如果alwaysUseDefaultTargetUrl和targetUrlParameter同时满足,则重定向到defaultSuccessUrl指定的地址。

(4)、如果前面的条件都不满足,那么最终会从缓存请求saveRequest中获取重定向地址,然后进行重定向操作。

这就是SavedRequestAwareAuthenticationSuccessHandler的实现逻辑,开发者也可以配置自己的SavedRequestAwareAuthenticationSuccessHandler,代码如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/doLogin")
                .successHandler(successHandler())
//                .defaultSuccessUrl("/index")
                .failureUrl("/login.html")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();
    }
    SavedRequestAwareAuthenticationSuccessHandler successHandler(){
        SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
        handler.setDefaultTargetUrl("/hello");
        handler.setTargetUrlParameter("target");
        return handler;
    }
}

然后在上篇文章中的表单中,修改一下action的参数

action="/doLogin?target=http://www.baidu.com"

这样当我们登录成功之后就可以跳转到百度了,如果不指定action的target就会跳转到我们上面默认的/hello接口

当我们通过successForwardUrl来设置登录成功后重定向地址时,实际上对应的实现类就是ForwardAuthenticationSuccessHandler,它的源码特别简单,就是一个服务端转发,如下:

public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
	private final String forwardUrl;
	/**
	 * @param forwardUrl
	 */
	public ForwardAuthenticationSuccessHandler(String forwardUrl) {
		Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> "'" + forwardUrl + "' is not a valid forward URL");
		this.forwardUrl = forwardUrl;
	}
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		request.getRequestDispatcher(this.forwardUrl).forward(request, response);
	}
}

上述代码可以看到,主要共嗯那个就是调用getRequestDispatcher方法进行服务端转发,AuthenticationSuccessHandler默认的三个实现类,无论是哪一个,都是用来处理页面跳转的。有时候页面跳转并不能满足我们的需求,特别是现在的前后的分离开发中,用户登录成功之后,就不需要跳转页面了,只需要给前端返回一个JSON数据即可,告诉前端登录成功还是失败,前端收到消息后自行处理,像这样的需求,我们可以通过自定义AuthenticationSuccessHandler的实现类来完成,如下:

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        HashMap<String,Object> resp = new HashMap<>();
        resp.put("status",200);
        resp.put("msg","登录成功");
        ObjectMapper om = new ObjectMapper();
        final String writeValueAsString = om.writeValueAsString(resp);
        response.getWriter().write(writeValueAsString);
    }
}

然后在WebSecurity中配置

http.successHandler(new MyAuthenticationSuccessHandler());

配置完成之后,此时登录成功,就不会进行页面添砖了,而是返回一段JSON字符串。

关于登录失败的,会在下一篇中详细讲解。

到此这篇关于SpringSecurity表单配置之登录成功及页面跳转原理的文章就介绍到这了,更多相关SpringSecurity登录成功跳转内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详述IntelliJ IDEA 中自动生成 serialVersionUID 的方法(图文)

    详述IntelliJ IDEA 中自动生成 serialVersionUID 的方法(图文)

    本篇文章主要介绍了详述IntelliJ IDEA 中自动生成 serialVersionUID 的方法(图文),具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-11-11
  • Java8函数式编程应用小结

    Java8函数式编程应用小结

    Java8非常重要的就是引入了函数式编程的思想,使得这门经典的面向对象语言有了函数式的编程方式,弥补了很大程度上的不足,函数式思想在处理复杂问题上有着更为令人称赞的特性,本文给大家介绍Java8函数式编程应用小结,感兴趣的朋友一起看看吧
    2023-12-12
  • java开发中常遇到的各种难点以及解决思路方案

    java开发中常遇到的各种难点以及解决思路方案

    Java项目是一个复杂的软件开发过程,其中会涉及到很多技术难点,这篇文章主要给大家介绍了关于java开发中常遇到的各种难点以及解决思路方案的相关资料,需要的朋友可以参考下
    2023-07-07
  • Java中Timer的schedule()方法参数详解

    Java中Timer的schedule()方法参数详解

    今天小编就为大家分享一篇关于Java中Timer的schedule()方法参数详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • java数据结构和算法学习之汉诺塔示例

    java数据结构和算法学习之汉诺塔示例

    这篇文章主要介绍了java数据结构和算法中的汉诺塔示例,需要的朋友可以参考下
    2014-02-02
  • javaweb中Http协议详解

    javaweb中Http协议详解

    HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程。这篇文章主要为大家详细介绍了javaweb中的Http协议,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • JAVA基于静态数组实现栈的基本原理与用法详解

    JAVA基于静态数组实现栈的基本原理与用法详解

    这篇文章主要介绍了JAVA基于静态数组实现栈的基本原理与用法,结合实例形式详细分析了JAVA基于静态数组实现栈相关原理、用法与操作注意事项,需要的朋友可以参考下
    2020-03-03
  • Spring Boot 简介(入门篇)

    Spring Boot 简介(入门篇)

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。下面通过本文给大家介绍spring boot相关知识,需要的的朋友参考下吧
    2017-04-04
  • Java之SpringBoot定时任务案例讲解

    Java之SpringBoot定时任务案例讲解

    这篇文章主要介绍了Java之SpringBoot定时任务案例讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • SpringBoot实现在一个模块中引入另一个模块

    SpringBoot实现在一个模块中引入另一个模块

    这篇文章主要介绍了SpringBoot实现在一个模块中引入另一个模块的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10

最新评论