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="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字符串。

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

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以使用以下配置来跳转到指定的页面: 在 Spring Security 配置中,使用`formLogin()`方法开启表单登录,在其中设置`loginPage()`方法来指定登录页面地址,如下所示: ```java @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() // 不进行权限验证的 URL .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") // 指定登录页面地址 .permitAll() .and() .logout() .permitAll(); } } ``` 在上述代码中,`/login` 路径为登录页面地址,是一个 GET 请求,访问该路径时返回登录页面。在登录页面中,用户输入用户名和密码,然后提交表单,将会通过 POST 请求发送到`/login`地址。 通过重 `configure(AuthenticationManagerBuilder auth)` 方法,可以指定用户认证方式默认使用内存中的认证方式。在下面的代码示例中,使用了一对用户名和密码 `user/userpassword`。 ```java @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() // 不进行权限验证的 URL .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") // 指定登录页面地址 .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user") .password("{noop}userpassword") .roles("USER"); } } ``` 在上述代码中,“{noop}” 是 5.x 版本的特性,用于指定密码以明文形式存储。否则的话,需要使用`PasswordEncoder`接口的实现来加密密码,不过这样做对于一个小型项目并不是必要的。 Spring Security 提供了默认的 login form 表单,不过在以下情况下,可能需要重默认的登录页面模板。 默认模板位于`/org/springframework/security/web/server/ui/login/LoginPageGeneratingWebFilter.DEFAULT_LOGIN_PAGE_TEMPLATE`。 利用 FreeMarker 模板引擎可以重该模板。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陈橙橙丶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值