Spring Security 表单认证

        在上一篇文章中,我们初步引入了 Spring Security,并使用其默认生效的 HTTP 基本认证保护 URL 资源,在本篇文章中我们使用表单认证来保护 URL 资源。

一、默认表单认证:

        首先,新建一个 configuration 包用于存放通用配置;然后新建一个 WebSecurityConfig 类,使其继承 WebSecurityConfigurerAdapter ,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

}

        在给 WebSecurityConfig 类上加上 @EnableWebSecurity 注解后,便会自动被 Spring 发现并注册(点击 @EnableWebSecurity 注解可以看到 @Configuration 注解已经存在,所以此处不需要额外添加)。

        我们接着查看 WebSecurityConfigurerAdapter 类对 configure(HttpSecurity http)方法的定义。如下所示:

protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this 
			will potentially override subclass configure(HttpSecurity).");

		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

        可以看到 WebSecurityConfigurerAdapter 已经默认声明了一些安全特性:

                a、验证所有用户请求。

                b、允许用户使用表单登录进行身份验证(Spring Security 提供了一个简单的表单登录页面)。

                c、允许用户使用 HTTP 基本认证。

        现在重启服务,应用新的安全配置。可以预见,在下次访问 localhost:8080 时,系统会要求我们进行表单认证。如下图所示:

         在上图中我们可以发现,我们访问的地址自动跳转到 localhost:8080/login ,这正是 Spring Security 的默认登录页,只需要输入正确的用户名和密码便可跳转到回原来的访问地址。

二、自定义表单认证:

1、初步配置自定义表单登录页:

        虽然自动生成的表单登录页可以方便、快速地启动,但是大多数应用程序更希望提供自己的表单登录页,此时就需要覆盖 configure() 方法,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests()
			.anyRequest().authenticated()
			.and()	
		.formLogin().
			loginPage("/myLogin.html")
			// 使登录页不设限访问
			.permitAll()
			.and().
		csrf().disable();
	}
}

2、认识 HttpSecurity:

        HttpSecurity 实际上对应了 Spring Security 命名空间配置方式中 xml 文件内的标签。允许我们为特定的 http 请求配置安全策略。

        在 xml 文件中,声明大量配置早已司空见惯;但在 Java 配置中,按照传统的方式,我们需要这样来调用,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	protected void configure(HttpSecurity http) throws Exception{
		ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry 
			urlRegistry=http.authorizeRequests();

		ExpressionUrlAuthorizationConfigurer.AuthorizedUrl authorizedUrl =
			(ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)urlRegistry
				.anyRequest();
		authorizedUrl.authenticated();
		// more
		
		FormLoginConfigurer<HttpSecurity> formLoginConfigurer = 
			http.formLogin();
		formLoginConfigurer.loginPage("/myLogin.html");
		formLoginConfigurer.permitAll();
		// more
	}
}

        可以想象出这是多么烦琐且令人痛苦的一件事。HttpSecurity 首先被设计为链式调用,在执行每个方法后,都会返回一个预期的上下文,便于连续调用。我们不需要关心每个方法究竟返回了什么、如何进行下一个配置等细节。

        HttpSecurity 提供了很多配置相关的方法,分别对应命名空间配置中的子标签 <http>。例如,authorizeRequests()formLogin()httpBasic() 和 csrf() 分别对应 <intercept-url><form-login><http-basic><csrf> 标签。调用这些方法之后,除非使用 and() 方法结束当前标签,上下文才会回到 HttpSecurity ,否则链式调用的上下文将自动进入对应的标签域

        authorizeRequests() 方法实际上返回了一个 URL 拦截注册器,我们可以调用它提供的 anyRequest()antMatchers() 和 regexMatchers() 等方法来匹配系统的 URL ,并为其指定安全策略。

        formLogin() 和 httpBasic() 方法都声明了需要 Spring Security 提供的表单认证方式,分别返回对应的配置器。其中,formLogin.loginPage("/myLogin.html") 指定自定义的登录页为 /myLogin.html ,同时,Spring Security 会用 /myLogin.html 注册一个 POST 路由,用于接收登录请求。

        csrf() 方法是 Spring Security 提供的跨站请求伪造防护功能,当我们继承 WebSecurityConfigurerAdapter 会默认开启 csrf() 方法,关于 csrf() 方法的更多内容会在后面的章节专门探讨,以使测试进程更加顺利。

        重新启动服务后在此访问 localhost:8080 ,页面会自动跳转到 localhost:8080/myLogin.html。由于 /myLogin.html 无法定位到页面资源,所以会显示一个 404 页面,如下所示:

3、编写表单登录页:

        表单登录页 myLogin.html 的代码如下所示:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
	<div class = "login" style="width:300px;height:300px">
		<h2>Acced Form</h2>
		<div class ="login-top"></div>
		<h1>LOGIN FORM</h1>
		<form action="myLogin.html" method="post">
			<input type="text" name="username" placeholder="username"/> 
			<input type="password" name="password" placeholder="password"/> 
			<div class="forgot" style="margin-top:20px;">
				<a href="#">forgot Password</a>
				<input type="submit" value="login">
			</div>
		</form>
		<div class="login-bottom">
			<h3>New User &nbsp;<a href ="">Register</a>&nbsp;&nbsp;</h3>
		</div>
	</div>
</body>
</html>

        在表单登录页中,仅有一个表单,用户名和密码分别为 username password,并以 POST 的方式提交到 /myLogin.html

        我们将该文件放置在 resources/static/ 下。重启服务,再次访问 localhost:8080,即可看到自定义的表单登录页,如下所示:

         输入正确的用户名和密码后,单击 login 按钮,即可成功跳转。

4、其他表单配置项:

        在自定义表单登录页之后,处理登录请求的 URL 也会相应改变,如何自定义 URL 呢?很简单, Spring Security 在表单定制里提供了相应的支持,代码如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests()
			.anyRequest().authenticated()
			.and()
		.formLogin()
			.loginPage("/myLogin.html")
			.loginProcessingUrl("/login")
			.permitAll()
			.and()
		.csrf().disable();
	}
}

        此时,有些读者可能会有疑问,因为按照惯例,在发送登录请求并认证成功之后,页面会跳转回原访问页。在某些系统中的确是跳转回原访问页的,但在部分前后端完全分离、仅靠 json 完成所有交互的系统中,一般会在登录时返回一段 json 数据,告知前端成功登录成功与否,由前端决定如何处理后续逻辑,而非由服务器主动执行页面跳转。这在 Spring Security 中同样可以实现:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests()
		.anyRequest().authenticated()
		.and()
		.formLogin()
		.loginPage("/myLogin.html")
		.loginProcessingUrl("/login")
		.successHandler(new AuthenticationSuccessHandler() {
			@Override
			public void onAuthenticationSuccess(HttpServletRequest arg0, 
					HttpServletResponse arg1, Authentication arg2)
							throws IOException, ServletException {
				arg1.setContentType("application/json;charset=UTF-8");
				PrintWriter out = arg1.getWriter();
				out.write("{\"error_code\":\"0\",\"message\":\"欢迎登录系统\"}");
			}
		})
		.failureHandler(new AuthenticationFailureHandler() {
			@Override
			public void onAuthenticationFailure(HttpServletRequest arg0,
					HttpServletResponse arg1, AuthenticationException arg2)
							throws IOException, ServletException {
				arg1.setContentType("application/json;charset=UTF-8");
				arg1.setStatus(401);
				PrintWriter out = arg1.getWriter();
				// 输出失败的原因
				out.write("{\"error_code\":\"401\",\"name\":\""+arg2.getCause()+"\","
						+ "\"message\":\""+arg2.getMessage()+"\"}}");
			}
		})
		.and()
		.csrf().disable();
	}
}

        表单登录配置模块提供了 successHandler() failureHandler() 两个方法,分别处理登录成功和登录失败的逻辑。其中,successHandler() 方法带有一个 Authentication 参数,携带当前登录用户名及其角色等信息;而 failureHandler() 方法携带一个 AuthenticationException 异常参数。具体处理方式需按照系统的情况自定义。

        在形式上,我们确实使用了 Spring Security 的表单认证功能,并且自定义了表单登录页。但实际上,这还远远不够。例如,在实际系统中,我们正常登录时使用的用户名和密码都来自数据库,这里却都写在配置上。更进一步,我们可以对每个登录用户都设定详细的权限,而并非一个通用角色。这些内容将在后面章节讲解。

猜你喜欢

转载自blog.csdn.net/xhf852963/article/details/121920422