SpringSecurity(五)表单配置-登录失败及页面跳转原理

认证失败

继上一篇内容我们继续讨论一下关于表单登录配置认证失败的时候,它默认页面的跳转原理。

前言

为了方便在前端页面中展示失败的异常信息,我们现在项目中的pom.xml文件中引入thymeleaf依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

接下来修改一下上篇文章中的页面,增加一个div用来展示异常信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qcfkpmio-1646990447899)(C:\Users\chenx\Desktop\新建文件夹\SpringSecurity\i异常信息-login.png)]

既然现在的页面是动态页面,那么就不能像静态页面那样直接访问,需要我们提供一个访问控制器。

 @GetMapping({"/","login",""})
    public String login(){
        return "login";
    }

最后在SecurityConfig中配置页面登录。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/doLogin")
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureUrl("/login.html")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();
    }

failureUrl表示登录失败后重定向到login.html页面,重定向是一种客户端跳转,重定向不方便携带请求失败的异常信息(只能放在URL中)

如果希望能够在前端展示请求失败的异常信息,可以使用下面这种方式:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/doLogin")
                .successHandler(new MyAuthenticationSuccessHandler())
//                .failureUrl("/login.html")
                .failureForwardUrl("/login.html")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();
    }

failureForwardUrl方法从名字上就可以看出,这种跳转是一个服务器端跳转,服务器端跳转的好处是可以携带登录异常信息。如果登录失败,自动跳转到登录页面后,就可以将错误信息展示出来。


无论是failureUrl还是failureForwardUrl,最终所配置的都是AuthenticationFailureHandler接口实现的,SpringSecurity中提供了AuthenticationFailureHandler接口,用来规范登录失败的实现:

public interface AuthenticationFailureHandler {

	void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException;

}

AuthenticationFailureHandler中只有一个方法,用来处理登录失败请求,最后的exception参数则表示登录失败的异常信息。SpringSecurity中为AuthenticationFailureHandler一共提供了五个实现类,如下:

在这里插入图片描述

(1)、SimpleUrlAuthenticationFailureHandler 默认的处理逻辑就算通过重定向跳转到登录页面,当然也可以通过配置forwardToDestination属性将重定向改为服务器端跳转,failureUrl方法底层的实现逻辑就是SimpleUrlAuthenticationFailureHandler

(2)、ExceptionMappingAuthenticationFailureHandler 可以实现根据不同的异常类型,映射到不同的路径。

(3)、ForwardAuthenticationFailureHandler 表示通过服务器端跳转来重新回到登录页面,failureForwardUrl方法底层实现逻辑就是ForwardAuthenticationFailureHandler

(4)、AuthenticationEntryPointFailureHandler 是SpringSecurity 5.2新引进的处理类,可以通过AuthenticationEntryPoint来处理登录异常。

(5)、DelegatingAuthenticationFailureHandler 可以实现为不同的异常类型配置不同的登录失败处理回调。

这里举一个简单的例子,如果不适用failureForwardUrl方法,同时又想在登录失败后通过服务器端跳转回到登录页面,那么可以自定义SimpleUrlAuthenticationFailureHandler配置,并将forwardToDestination属性设置为true,代码如下:

@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(new MyAuthenticationSuccessHandler())
                .failureHandler(failureHandler())
//                .failureUrl("/login.html")
//                .failureForwardUrl("/login.html")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();
    }
    
    
    SimpleUrlAuthenticationFailureHandler failureHandler(){
        SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler();
        handler.setDefaultFailureUrl("/login.html");
        handler.setUseForward(true);
        return handler;
    }
}

这样配置后,如果用户在此登录失败,就会通过服务端跳转重新回到登录页面,登录页面也会展示相应的错误信息,效果和failureForwardUrl一致。


SimpleUrlAuthenticationFailureHandler 的源码也很简单,我们也来看看(列出核心部分)

public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {

    	public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
		setDefaultFailureUrl(defaultFailureUrl);
	}

    @Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		if (this.defaultFailureUrl == null) {
			if (this.logger.isTraceEnabled()) {
				this.logger.trace("Sending 401 Unauthorized error since no failure URL is set");
			}
			else {
				this.logger.debug("Sending 401 Unauthorized error");
			}
			response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
			return;
		}
		saveException(request, exception);
		if (this.forwardToDestination) {
			this.logger.debug("Forwarding to " + this.defaultFailureUrl);
			request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
		}
		else {
			this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
		}
	}
	
    protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
		if (this.forwardToDestination) {
			request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
			return;
		}
		HttpSession session = request.getSession(false);
		if (session != null || this.allowSessionCreation) {
			request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
		}
	}
	
    	public void setUseForward(boolean forwardToDestination) {
		this.forwardToDestination = forwardToDestination;
	}

}

从这段源码中可以看到,当用户构造SimpleUrlAuthenticationFailureHandler对象的时候,就传入了defaultFailureUrl,也就是登录失败时要跳转的地址。在onAuthenticationFailure方法中,如果发现了defaultFailureUrl为null,则直接通过response返回异常信息,否则调用saveException方法,在saveException方法中,如果forwardToDestination为true,就通过服务区端跳转回到登录页面,否则通过重定向回到登录页面。

如果是前后的分离开发,登录失败就不需要页面跳转了,只需要返回JSON字符串给前端即可,此时可以通过自定义AuthenticationFailureHandler的实现类来完成,如下:

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        HashMap<String,Object> resp = new HashMap<>();
        resp.put("status",200);
        resp.put("msg",exception.getMessage());
        ObjectMapper om = new ObjectMapper();
        final String writeValueAsString = om.writeValueAsString(resp);
        response.getWriter().write(writeValueAsString);
    }
}

同样在WebSecurity中配置

http.failureHandler(new MyAuthenticationFailureHandler());

这样在登录失败后,就不会进行页面跳转了,而是直接返回JSON字符串。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 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、付费专栏及课程。

余额充值