SpringSecurity自定义Form表单使用方法讲解

 更新时间:2023年01月17日 09:51:25   作者:做网安的小王  
这篇文章主要介绍了Spring Security自定义Form表单使用方法,虽然 Spring Security 提供了默认的登录表单,实际项目里肯定是不可以直接使用的,当然 Spring Security 也提供了自定义登录表单的功能

背景

本系列教程,是作为团队内部的培训资料准备的。主要以实验的方式来体验SpringSecurity的各项Feature。

新建一个SpringBoot项目,起名springboot-security-form,核心依赖为WebSpringSecurityThymeleaf

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies> 

实验-HttpBasic

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {// There is no PasswordEncoder mapped for the id "null"PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();String yourPassword = "123";System.out.println("Encoded password: " + encoder.encode(yourPassword));// Config account info and permissionsauth.inMemoryAuthentication().withUser("dev").password(encoder.encode(yourPassword)).authorities("p1").and().withUser("test").password(encoder.encode(yourPassword)).authorities("p2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().httpBasic();
} 

实验-自定义登录页面

登录页面配置

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login");
} 

后端登录页面接口

@Controller
public class LoginController {@GetMapping("/login")public String login() {return "login";}@GetMapping(value = "/user/add")@ResponseBodypublic String accessResource1() {return " Access Resource 1: Add User";}@GetMapping(value = "/user/query")@ResponseBodypublic String accessResource2() {return " Access Resource 2: Query User";}
} 

前端模板

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Login</title>
</head>
<body>
<form action="login" method="post"><span>用户名</span><input type="text" name="username" /> <br><span>密码</span><input type="password" name="password" /> <br><input type="submit" value="登录">
</form>
</body>
</html> 

Note:此时,需要先关闭CSRF,.csrf().disable(),否则报403;

实验-自定义登录接口

默认登录页面接口与登录数据提交接口是同一个:/login,顺着.loginPage,进入FormLoginConfigurer,源码如下:

@Override
public FormLoginConfigurer<H> loginPage(String loginPage) {return super.loginPage(loginPage);
} 

继续进入父类的loginPage方法,

protected T loginPage(String loginPage) {setLoginPage(loginPage);updateAuthenticationDefaults();this.customLoginPage = true;return getSelf();
} 

继续跟踪进入方法updateAuthenticationDefaults();,可以看到,如果没有配置loginProcessingUrl,那么loginProcessingUrlloginPage便相同。

protected final void updateAuthenticationDefaults() {if (loginProcessingUrl == null) {loginProcessingUrl(loginPage);}if (failureHandler == null) {failureUrl(loginPage + "?error");}final LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(LogoutConfigurer.class);if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");}
} 

下面我们自定义登录数据提交接口为/formLogin,此时相应的前端action也要修改。

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin");
} 
<form action="formLogin" method="post"><span>用户名</span><input type="text" name="username" /> <br><span>密码</span><input type="password" name="password" /> <br><input type="submit" value="登录">
</form> 

实验-自定义登录数据参数

前面我们自定义了登录页面,在form表单中设置用户名、密码分别为username, password,那为什么这样写呢,可以改成别的嘛?可以倒是可以,但是不能随便改;

如果这里我们把username改为name,再次尝试登录,后端接口将报错:org.springframework.security.authentication.BadCredentialsException: Bad credentials。。可是实际项目中我们的用户名密码就是不叫这个名字呢?我们可以进行配置.usernameParameter("name")

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name");
} 
<form action="formLogin" method="post"><span>用户名</span><input type="text" name="name" /> <br><span>密码</span><input type="password" name="password" /> <br><input type="submit" value="登录">
</form> 

默认的用户名、密码分别为username, password,我们看下SpringSecurity的源码:

public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
	/**
	 * Creates a new instance
	 * @see HttpSecurity#formLogin()
	 */
	public FormLoginConfigurer() {
		super(new UsernamePasswordAuthenticationFilter(), null);
		usernameParameter("username");
		passwordParameter("password");
	}
} 

实验-自定义登录失败、成功处理器

问题:就以上个实验3中的报错信息为例,或当用户名、密码输错后,如何在后台看到错误信息?

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name").failureHandler(new AuthenticationFailureHandler(){@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {exception.printStackTrace();request.getRequestDispatcher(request.getRequestURL().toString()).forward(request, response);}});
} 

常见的认证异常,这里可以看到AuthenticationException共有18个子类:

上述增加了在认证失败时的处理:输出错误信息。同理,如果想在登录成功时直接进行一些处理(eg: 数据初始化等),可以使用以下配置:

.successHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException {System.out.println("Login Successfully~");// do something here: initial work or forward to different url regarding different roles...request.getRequestDispatcher("").forward(request, response);}
}) 

实验-自定义登录成功跳转页面

经历千难万险,终于要登录成功了。进来之后要跳转到哪里呢?看你喽~想跳哪里跳哪里。。

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name").failureHandler(new AuthenticationFailureHandler(){@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {exception.printStackTrace();request.getRequestDispatcher(request.getRequestURL().toString()).forward(request, response);}}).successForwardUrl("/ok"); // custom login success page, a POST request
} 
@Controller
public class LoginController {...@PostMapping(value = "/ok")@ResponseBodypublic String ok() {return "ok";}
} 

通过.successForwardUrl("/ok")配置了登录成功之后要跳转的页面路径或接口,同时需要在后端新增/ok接口。

Note:

  • 注意这里successForwardUrl的接口必须为POST接口;
  • 除了.successForwardUrl("/ok");,还可以使用.defaultSuccessUrl("/ok");或者.defaultSuccessUrl("/ok", true); 第二个参数true表示不管是从哪个地址进来,登录后全部跳转到指定的地址,此时与successForwardUrl效果相同,默认为false
  • 当然,除了登录成功后的跳转,还有登录失败后的跳转:failureForwardUrl

实验-自定义退出接口

默认的退出接口是/logout,可进行配置:

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name").failureHandler(new AuthenticationFailureHandler(){@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {exception.printStackTrace();request.getRequestDispatcher(request.getRequestURL().toString()).forward(request, response);}}).successForwardUrl("/ok") // custom login success page, a POST request.and().logout().logoutUrl("/leave");} 

上述配置将退出接口改为/leave。在默认的退出过程中,还做了诸如清除认证信息和使Session失效等工作:

public class SecurityContextLogoutHandler implements LogoutHandler {
	protected final Log logger = LogFactory.getLog(this.getClass());
	private boolean invalidateHttpSession = true;
	private boolean clearAuthentication = true;
	// ~ Methods
	// ==========================================
	/**
	 * Requires the request to be passed in.
	 *
	 * @param request from which to obtain a HTTP session (cannot be null)
	 * @param response not used (can be <code>null</code>)
	 * @param authentication not used (can be <code>null</code>)
	 */
	public void logout(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) {
		Assert.notNull(request, "HttpServletRequest required");
		if (invalidateHttpSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				logger.debug("Invalidating session: " + session.getId());
				session.invalidate();
			}
		}
		if (clearAuthentication) {
			SecurityContext context = SecurityContextHolder.getContext();
			context.setAuthentication(null);
		}
		SecurityContextHolder.clearContext();
	}
} 

到此这篇关于SpringSecurity自定义Form表单使用方法讲解的文章就介绍到这了,更多相关SpringSecurity Form表单内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Struts2 Result 参数详解

    Struts2 Result 参数详解

    这篇文章主要讲解Struts2 Result的参数,讲的比较详细,希望能给大家做一个参考。
    2016-06-06
  • Spring Cloud Feign原理详解

    Spring Cloud Feign原理详解

    Feign 主要是帮助我们方便进行rest api服务间的调用,本文从Spring Cloud对feign封装的源码中去了解其主要实现机制
    2021-06-06
  • IntelliJ IDEA 15款超级牛逼插件推荐(自用,超级牛逼)

    IntelliJ IDEA 15款超级牛逼插件推荐(自用,超级牛逼)

    这篇文章主要给大家推荐介绍了IntelliJ IDEA 15款超级牛逼插件,这15款插件都是自用的,真的非常推荐,需要的朋友可以参考下
    2020-11-11
  • Java使用条件语句和循环结构确定控制流(实例)

    Java使用条件语句和循环结构确定控制流(实例)

    下面小编就为大家带来一篇Java使用条件语句和循环结构确定控制流(实例)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Java 如何同时返回多个不同类型

    Java 如何同时返回多个不同类型

    这篇文章主要介绍了Java 同时返回多个不同类型的方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • spring中bean id相同引发故障的分析与解决

    spring中bean id相同引发故障的分析与解决

    最近在工作中遇到了关于bean id相同引发故障的问题,通过查找相关资料终于解决了,下面这篇文章主要给大家介绍了因为spring中bean id相同引发故障的分析与解决方法,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-09-09
  • java对象与json对象间的相互转换的方法

    java对象与json对象间的相互转换的方法

    本篇文章主要介绍了java对象与json对象间的相互转换的方法,详细介绍了json字符串和java对象相互转换,有兴趣的可以了解一下
    2017-01-01
  • 浅谈Spring Data Redis读不到设进去的值

    浅谈Spring Data Redis读不到设进去的值

    本文主要介绍了Spring Data Redis怎么读不到我刚才设进去的值,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • SpringBoot YAML语法基础详细整理

    SpringBoot YAML语法基础详细整理

    YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言),本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • Java数据结构之KMP算法的实现

    Java数据结构之KMP算法的实现

    这篇文章主要为大家详细介绍了Java数据结构中KMP算法的原理与实现,文中的示例代码讲解详细,对我们学习Java有一定的帮助,需要的可以参考一下
    2022-11-11

最新评论