Spring Security —04—自定义认证

4.1 自定义资源权限规则

  • /index:主页,公共资源,可以直接访问

  • /hello/* :受保护资源,需要权限管理(认证授权之后才能访问),默认所有资源都是受保护的,需要认证授权之后才能访问

在项目中添加如下配置就可以实现对资源权限规则设定:

 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     /**
      * 说明: SpringBoot2.6.2版本
      *    permitAll() 代表放行该资源,该资源为公共资源,无需认证和授权可以直接访问
      *    anyRequest().authenticated() 代表所有请求,必须认证之后才能访问
      *    formLogin() 代表开启表单认证
      * 注意: 放行资源必须放在所有认证请求之前!
      */
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeHttpRequests()
                 .mvcMatchers("/index").permitAll() //放行资源写在任何前面
                 .anyRequest().authenticated()
                 .and().formLogin();
     }
 }

注意:在SpringBoot 2.7.1 中的spring-security-config-5.7.2.RELEASE中已提到WebSecurityConfigurerAdapter已过时被弃用,替代方法如下:

  • 使用 SecurityFilterChain Bean 来配置 HttpSecurity;

  • 使用 WebSecurityCustomizer Bean 来配置 WebSecurity。

替代后新代码为:

 package com.study.config;import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.web.SecurityFilterChain;/**
  * @ClassName WebSecurityConfig
  * @Description 自定义资源权限规则
  * @Author Jiangnan Cui
  * @Date 2022/7/6 12:23
  * @Version 1.0
  */
 @Configuration
 @EnableWebSecurity //添加security过滤器,此处一定要加入此注解,否则下面的httpSecurity无法装配
 public class WebSecurityConfig {
     /**
      * 说明: SpringBoot2.7.1版本
      *    permitAll() 代表放行该资源,该资源为公共资源,无需认证和授权可以直接访问
      *    anyRequest().authenticated() 代表所有请求,必须认证之后才能访问
      *    formLogin() 代表开启表单认证
      * 注意: 放行资源必须放在所有认证请求之前!
      */
     @Bean
     SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
         return httpSecurity
                 .authorizeHttpRequests()
                 .mvcMatchers("/index").permitAll()
                 .anyRequest().authenticated()
                 .and().formLogin().and().build();
     }
 }

配置 WebSecurity
新版本通过配置 WebSecurityCustomizer Bean 来配置 WebSecurity,通过源码,我们可以看到 WebSecurityCustomizer 是一个 consumer 类型的 函数式接口:

WebSecurityCustomizer
@FunctionalInterface
public interface WebSecurityCustomizer {


	/**
	 * Performs the customizations on {@link WebSecurity}.
	 * @param web the instance of {@link WebSecurity} to apply to customizations to
	 */
	void customize(WebSecurity web);

}

旧的配置为:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login");
    }
}

新的配置为:

@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring().antMatchers("/login");
    }
}

总结:新替代方案中Spring Security 就不需要再去继承WebSecurityConfigurerAdapter,然后重写 configure()方法了,直接通过 filterChain() 方法就能使用 HttpSecurity 来配置相关信息。

重启项目,测试访问index请求时直接放行,访问hello请求时需要登录后才能正常访问!!!

4.2 自定义登录界面

pom.xml引入Thymeleaf模板依赖

 <!--引入Thymeleaf依赖-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-thymeleaf</artifactId>
 </dependency>

application.properties配置Thymeleaf

 # Thymeleaf配置
 # 关闭缓存
 spring.thymeleaf.cache=false 
 # 默认前缀路径,可不配置
 spring.thymeleaf.prefix=classpath:/templates/
 # 默认文件后缀,可不配置
 spring.thymeleaf.suffix=.html 

定义登录页面LoginController

package com.study.controller;import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;/**
  * @ClassName LoginController
  * @Description TODO
  * @Date 2022/7/23 17:47
  * @Version 1.0
  */
 @Controller
 public class LoginController {
     @RequestMapping("/login.html") //注意此处是login.html而不是login
     public String login() {
         return "login";//封装为login.html
     }
 }

在 templates 中定义登录界面login.html

 <!DOCTYPE html>
 <html lang="en" xmlns:th="https://www.thymeleaf.org">
 <head>
     <meta charset="UTF-8">
     <title>登录页面</title>
 </head>
 <body>
 <h1>用户登录</h1>
 <form method="post" th:action="@{/doLogin}">
     用户名:<input name="uname" type="text"/><br> <!--此处的name属性可指定其它名称,但需要在配置文件中配置-->
     密码: <input name="pwd" type="password"/><br> <!--此处的name属性可指定其它名称,但需要在配置文件中配置-->
     <input type="submit" value="登录"/>
 </form>
 </body>
 </html>

需要注意的是:

  • 登录表单 method 必须为 post,action 的请求路径可以定义为 /doLogin

  • 用户名的 name 属性为 username,用户名可以指定为除username外的其他名称

  • 密码的 name 属性为 password,密码可以指定为除password外的其他名称

配置 Spring Security 配置类WebSecurityConfigurer

package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/**
 * @ClassName WebSecurityConfigurer
 * @Description TODO
 * @Date 2022/7/21 22:34
 * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .mvcMatchers("/index").permitAll() //放行/index请求
                 .mvcMatchers("/login.html").permitAll() //放行/login.html请求
                 .anyRequest().authenticated() //其它请求需要登录认证后才能访问
                 .and()
                 .formLogin() //默认form表单页面登录
                 .loginPage("/login.html") //使用自定义登录页面登录页面登录
                 .loginProcessingUrl("/doLogin") //使用自定义登录页面时需要重新指定url,对应login.html中的action路径
                 .usernameParameter("uname") //重新指定用户名名称
                 .passwordParameter("pwd") //重新指定密码名称
                 .successForwardUrl("/index") //认证成功后跳转路径
                 //forward 跳转路径  始终在认证成功之后跳转到指定请求 地址栏不变
                 //.defaultSuccessUrl("/hello") //默认认证成功后跳转路径
                 //.defaultSuccessUrl("/hello",true) //第二个参数设置为true时总是跳转,效果同successForwardUrl一致,默认false
                 //redirect 重定向  注意:如果之前有请求过的路径,会优先跳转之前的请求路径 地址栏改变
                 .failureUrl("/login.html") //登录失败后跳转路径
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }


注意:successForwardUrl 、defaultSuccessUrl 这两个方法都可以实现成功之后跳转,其中:

  • successForwardUrl 默认使用 forward跳转 注意:不会跳转到之前请求路径

  • defaultSuccessUrl 默认使用 redirect 跳转

注意:如果之前请求路径,会有优先跳转之前请求路径,可以传入第二个参数进行修改

4.3 自定义登录成功处理

有时候页面跳转并不能满足我们,特别是在前后端分离开发中就不需要成功之后跳转页面,只需要给前端返回一个 JSON数据通知登录成功还是失败与否,不再需要使用defaultSuccessUrl和successForwardUrl这两个方法,这个时候可以通过succcessHandler方法设置自定义AuthenticationSucccessHandler 实现自定义的登陆成功处理。
在这里插入图片描述

  • 进入succcessHandler源码
/**
 * Specifies the {@link AuthenticationSuccessHandler} to be used. The default is
 * {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properties
 * set.
 * @param successHandler the {@link AuthenticationSuccessHandler}.
 * @return the {@link FormLoginConfigurer} for additional customization
 */
public final T successHandler(AuthenticationSuccessHandler successHandler) {
	this.successHandler = successHandler;
	return getSelf();
}
  • 需要传入一个AuthenticationSuccessHandler 类型的参数
    AuthenticationSuccessHandler.java
public interface AuthenticationSuccessHandler {

	/**
	 * Called when a user has been successfully authenticated.
	 * @param request the request which caused the successful authentication
	 * @param response the response
	 * @param chain the {@link FilterChain} which can be used to proceed other filters in
	 * the chain
	 * @param authentication the <tt>Authentication</tt> object which was created during
	 * the authentication process.
	 * @since 5.2.0
	 */
	default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authentication) throws IOException, ServletException {
		onAuthenticationSuccess(request, response, authentication);
		chain.doFilter(request, response);
	}

	/**
	 * Called when a user has been successfully authenticated.
	 * @param request the request which caused the successful authentication
	 * @param response the response
	 * @param authentication the <tt>Authentication</tt> object which was created during
	 * the authentication process.
	 */
	void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException;

}
  • AuthenticationSuccessHandler 接口是 Spring Security 中用于处理用户成功认证的处理器接口。它定义了两个方法用于处理用户成功认证的操作。

onAuthenticationSuccess(%,%,%,%)

onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) 方法是自 Spring Security 5.2.0版本开始引入的新方法。它在用户成功认证后被调用。该方法主要用于处理认证成功后的逻辑,例如设置认证成功后的跳转页面、写入认证成功的日志等。

参数:

request:发起认证请求的 HttpServletRequest 对象。
response:认证响应的 HttpServletResponse 对象。
chain:可以用来继续执行过滤链中的其他过滤器。
authentication:在认证过程中创建的 Authentication 对象。

onAuthenticationSuccess(%,%,%)

onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication),实际上在执行过程中就是执行这个方法的实现,然后继续执行过滤链中的其他过滤器。它在用户成功认证后被调用,主要用于处理认证成功后的逻辑。

参数:

request:发起认证请求的 HttpServletRequest 对象。
response:认证响应的 HttpServletResponse 对象。
authentication:在认证过程中创建的 Authentication 对象。

  • successForwardUrl和defaultSuccessUrl

其中根据接口的描述信息也可以得知,登录成功后会自动回调这个方法,进一步查看它的默认实现,你会发现successForwardUrl(ForwardAuthenticationSuccessHandler)defaultSuccessUrl(SavedReguestAwareAuthenticationSuccessHandler)也是由AuthenticationSuccessHandler它的子类实现的:

在这里插入图片描述

  • 通过实现 AuthenticationSuccessHandler 接口,实现覆盖 onAuthenticationSuccess() 方法,您可以自定义用户成功认证后的处理逻辑。根据具体的需求,编写相应的代码来处理认证成功后的操作,比如重定向到指定页面、写入日志等。

  • 在config包中自定义 AuthenticationSuccessHandler接口实现类MyAuthenticationSuccessHandler

package com.study.config;import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;/**
  * @ClassName MyAuthenticationSuccessHandler
  * @Description TODO
  * @Date 2022/7/23 20:30
  * @Version 1.0
  */
 public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
     @Override
     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
         Map<String, Object> result = new HashMap<String, Object>();
         result.put("msg", "登录成功");//打印登录成功信息
         result.put("status", 200);//打印状态码
         result.put("authentication", authentication);//打印认证信息
         response.setContentType("application/json;charset=UTF-8");//设置响应类型
         String s = new ObjectMapper().writeValueAsString(result);//json格式转字符串
         response.getWriter().println(s);//打印json格式数据
     }
 }
  • 在WebSecurityConfigurer中配置MyAuthenticationSuccessHandler
 package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/**
  * @ClassName WebSecurityConfigurer
  * @Description TODO
  * @Date 2022/7/21 22:34
  * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         ......
                 //.successForwardUrl("/index") //认证成功后跳转路径 forward 跳转路径  始终在认证成功之后跳转到指定请求 地址栏不变
                 //.defaultSuccessUrl("/hello") //默认认证成功后跳转路径 redirect 重定向  注意:如果之前有请求过的路径,会优先跳转之前的请求路径 地址栏改变
                 //.defaultSuccessUrl("/hello",true) //第二个参数设置为true时总是跳转,效果同successForwardUrl一致,默认false
                 .successHandler(new MyAuthenticationSuccessHandler())//认证成功时处理,前后端分离解决方案
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }
  • 访问路径:http://localhost:8080/hello,输入用户名密码登录后,输出结果:
    在这里插入图片描述

4.4 显示登录失败信息

ProviderManager.java

@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}
  1. 遍历配置的认证提供者列表,通过调用provider.authenticate(authentication)方法对认证请求进行认证,并返回认证结果。如果找到了支持请求类型的认证提供者并成功认证,则将结果保存在 result 变量中,并中断遍历。
  2. 如果找不到支持请求类型的认证提供者或认证失败,则将 authentication 异常保存在 lastException 中。
  3. 如果设置了父认证管理器(parent),则将认证请求转发给父认证管理器进行认证。 如果父认证管理器成功进行了认证,则将结果保存在
    parentResult 变量中,并将其赋值给 result。
  4. 如果 result 不为空,则表示认证成功,将认证结果发布为
    AuthenticationSuccessEvent 事件,然后返回 result。
  5. 如果 result为空,则表示认证失败。在这种情况下,将根据具体情况创建和抛出适当的 AuthenticationException 异常。

认证过程中没有父认证管理器或者父认证管理器也无法认证请求。在这种情况下,会调用 prepareException(lastException, authentication) 方法来准备将要抛出的异常,这个异常会继续向上传播给调用方,让调用方处理这个认证异常。调用方可以选择捕获并处理这个异常,或者继续将它向上层抛出,最终由上层进行处理。通过断点的方式会进入他的上层AbstractAuthenticationProcessingFilter并捕获异常,进入unsuccessfulAuthentication方法
在这里插入图片描述
unsuccessfulAuthentication方法
AbstractAuthenticationProcessingFilter.java

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) throws IOException, ServletException {
		SecurityContextHolder.clearContext();
		this.logger.trace("Failed to process authentication request", failed);
		this.logger.trace("Cleared SecurityContextHolder");
		this.logger.trace("Handling authentication failure");
		this.rememberMeServices.loginFail(request, response);
		this.failureHandler.onAuthenticationFailure(request, response, failed);
	}
  1. AbstractAuthenticationProcessingFilter 是 Spring Security 过滤器链中用于处理认证请求的过滤器之一。当认证过程中出现失败时,就会调用该方法来处理认证失败的情况。

  2. 在该方法中,首先调用 SecurityContextHolder.clearContext() 方法来清除 SecurityContextHolder 中的认证信息。这是为了确保不会保留已经失败的认证信息。

  3. 然后,通过日志记录认证失败的原因,其中使用了 logger.trace() 方法来输出日志消息。

  4. 接下来,调用 rememberMeServices.loginFail(request, response) 方法来处理记住我(Remember Me)功能。这个方法通知 RememberMeServices 实现类记录认证失败,以确保下次请求时不会自动进行认证。

  5. 最后,调用 failureHandler.onAuthenticationFailure(request, response, failed) 方法来处理认证失败的具体操作。failureHandler 是认证失败处理器,用于在认证失败时执行特定的操作,例如返回认证失败的响应或重定向到登录页面。
    进入onAuthenticationFailure方法
    AuthenticationFailureHandler接口的实现类SimpleUrlAuthenticationFailureHandler

在认证失败时,Spring Security 会调用默认的认证失败处理器 SimpleUrlAuthenticationFailureHandler中的该方法来处理认证失败的情况。

@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
			//通过检查 defaultFailureUrl 是否为空来确定认证失败后的处理方式。
			//如果 defaultFailureUrl 为 null,表示没有配置特定的认证失败处理路径,则会发送 401 Unauthorized 错误响应。
			//这里使用 response.sendError() 方法发送 HTTP 状态码 401 和对应的错误原因。
		if (this.defaultFailureUrl == null) {
			if (this.logger.isTraceEnabled()) {
				this.logger.trace("Sending 401 Unauthorized error since no failure URL is set");
			}
			//如果 defaultFailureUrl 不为 null,则会调用 saveException(request, exception) 方法来保存认证失败的异常信息。
			else {
				this.logger.debug("Sending 401 Unauthorized error");
			}
			response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
			return;
		}
		saveException(request, exception);
		//如果 forwardToDestination 为 true,则会通过 request.getRequestDispatcher().forward() 方法进行转发,
		//将请求转发到 defaultFailureUrl 指定的位置进行处理
		if (this.forwardToDestination) {
			this.logger.debug("Forwarding to " + this.defaultFailureUrl);
			request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
		}
		//如果 forwardToDestination 为 false,
		//则会通过重定向策略(redirectStrategy)将请求重定向到 defaultFailureUrl 指定的位置。
		else {
			this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
		}
	}

对于抛出来的异常会执行saveException(request, exception);方法
进入saveException(request, exception);

protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
	//检查 forwardToDestination 的值。如果为 true,则将认证失败的异常信息设置到请求的属性中,
	//通过 request.setAttribute() 方法来保存。
	if (this.forwardToDestination) {
		request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
		return;
	}
	HttpSession session = request.getSession(false);
	//如果 forwardToDestination 为 false,说明不启用转发,那么首先尝试获取当前请求对应的会话(如果存在的话)。
	//如果会话存在或允许创建会话(allowSessionCreation 为 true),则将认证失败的异常信息设置到会话的属性中,通过 request.getSession().setAttribute() 方法来保存。
	if (session != null || this.allowSessionCreation) {
		request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
	}
}

将认证失败的异常信息保存到请求或会话中,以便后续处理认证失败时可以获取到异常信息,并根据需要进行进一步的处理。如果启用转发,则将异常信息设置到请求的属性中;如果不启用转发,则将异常信息设置到会话的属性中。
在这里插入图片描述

登录失败之后

  • 启用forward重定向会将异常信息存储到 request 作用域中key为 SPRING_SECURITY_LAST_EXCEPTION命名属性中;

  • 命名属性中; 启用redirect重定向会将异常信息存储到session作用域中key为
    SPRING_SECURITY_LAST_EXCEPTION 命名属性中;

    注意:failureUrl、failureForwardUrl 关系类似于之前提到的 successForwardUrl 、defaultSuccessUrl 方法

  • failureUrl:失败以后的redirect重定向跳转

  • failureForwardUrl:失败以后的 forward 跳转

启动项目,测试路径:http://localhost:8080/hello,输入错误的用户名和密码时会输出如下错误信息,注意login.html与WebSecurityConfigurer对failureForwardUrl或failureUrl的配置一致:

(1)failureForwardUrl:forward跳转,request作用域

 package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/**
  * @ClassName WebSecurityConfigurer
  * @Description TODO
  * @Date 2022/7/21 22:34
  * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .......
                 .successHandler(new MyAuthenticationSuccessHandler())//认证成功时处理,前后端分离解决方案
                 .failureForwardUrl("/login.html")//认证失败之后,forward跳转
                 //.failureUrl("/login.html") //默认认证失败之后,redirect跳转
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }
 <!DOCTYPE html>
 <html lang="en" xmlns:th="https://www.thymeleaf.org">
 <head>
     <meta charset="UTF-8">
     <title>登录页面</title>
 </head>
 <body>
 <h1>用户登录</h1>
 ...
 <h2>
      <div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div><!--request作用域:forward跳转-->
     
 </h2>
 </body>
 </html>

在这里插入图片描述
(2)failureUrl:redirect跳转,session作用域

 package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/**
  * @ClassName WebSecurityConfigurer
  * @Description TODO
  * @Date 2022/7/21 22:34
  * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .......
                 .successHandler(new MyAuthenticationSuccessHandler())//认证成功时处理,前后端分离解决方案
                 //.failureForwardUrl("/login.html")//认证失败之后,forward跳转
                 .failureUrl("/login.html") //默认认证失败之后,redirect跳转
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }
 <!DOCTYPE html>
 <html lang="en" xmlns:th="https://www.thymeleaf.org">
 <head>
     <meta charset="UTF-8">
     <title>登录页面</title>
 </head>
 <body>
 <h1>用户登录</h1>
 ...
 <h2>
     <div th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}"></div><!--session作用域:redirect跳转-->
 </h2>
 </body>
 </html>

在这里插入图片描述

4.5 自定义登录失败处理

自定义登录失败处理和自定义登录成功处理一样,Spring Security 同样为前后端分离开发提供了登录失败的处理,这个类就是AuthenticationFailureHandler:
AuthenticationFailureHandler.java

 public interface AuthenticationFailureHandler {/**
      * Called when an authentication attempt fails.
      * @param request the request during which the authentication attempt occurred.
      * @param response the response.
      * @param exception the exception which was thrown to reject the authentication
      * request.
      */
     void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
             AuthenticationException exception) throws IOException, ServletException;}

根据接口的描述信息,也可以得知登录失败会自动回调这个接口的默认实现类的这个方法,进一步查看它的默认实现,你会发现failureUrl、failureForwardUrl也是由它的子类实现的。

在这里插入图片描述

  • failureUrl(ExceptionMappingAuthenticationFailureHandler)——redirect

  • failureForwardUrl(ForwardAuthenticationFailureHandler)——forward

  • config包中自定义
    AuthenticationFailureHandler接口实现类MyAuthenticationFailureHandler

 package com.study.config;import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.authentication.AuthenticationFailureHandler;import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;/**
  * @ClassName MyAuthenticationFailureHandler
  * @Description 自定义认证失败处理
  * @Date 2022/7/24 15:54
  * @Version 1.0
  */
 public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
     @Override
     public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
         Map<String, Object> result = new HashMap<String, Object>();
         result.put("msg", "登录失败:" + exception.getMessage());
         result.put("status", 500);
         response.setContentType("application/json;charset=UTF-8");
         String s = new ObjectMapper().writeValueAsString(result);
         response.getWriter().println(s);
     }
 }

  • WebSecurityConfigurer配置MyAuthenticationFailureHandler
package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/**
  * @ClassName WebSecurityConfigurer
  * @Description TODO
  * @Date 2022/7/21 22:34
  * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .......
                 //.failureForwardUrl("/login.html")//认证失败之后,forward跳转
                 //.failureUrl("/login.html") //默认认证失败之后,redirect跳转
                 .failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }


测试路径:http://localhost:8080/hello,输入错误的用户名或密码后输出如下结果:
在这里插入图片描述

4.6 注销登录

Spring Security 中也提供了默认的注销登录配置,在开发时也可以按照自己需求对注销进行个性化定制。

开启注销登录(默认开启) get方式请求logout即可注销

自定义配置注销登录

 package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;/**
  * @ClassName WebSecurityConfigurer
  * @Description TODO
  * @Date 2022/7/21 22:34
  * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .......
                 .failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
                 .and()
                 .logout()//拿到注销登录配置对象
                 .logoutUrl("/logout")//指定注销登录URL,默认请求方式必须为GET
                 .invalidateHttpSession(true)//默认开启session会话失效
                 .clearAuthentication(true)//默认清除认证标志
                 .logoutSuccessUrl("/login.html")//注销登录成功后跳转的页面
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }


测试注销路径:登录成功后,访问:http://localhost:8080/hello,通过访问:http://localhost:8080/logout,进行注销登录。

注意

  • 通过 logout() 方法开启注销配置

  • logoutUrl 指定退出登录请求地址,默认是 GET 请求,路径为 /logout

  • invalidateHttpSession 退出时是否使session 失效,默认值为 true

  • clearAuthentication 退出时是否清除认证信息,默认值为 true

  • logoutSuccessUrl 退出登录时跳转地址

配置多个注销登录请求

如果项目中有需要,开发者还可以配置多个注销登录的请求,同时还可以指定请求的方法:

 package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;/**
  * @ClassName WebSecurityConfigurer
  * @Description TODO
  * @Date 2022/7/21 22:34
  * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .......
                 .failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
                 .and()
                 .logout()
                 //.logoutUrl("/logout")//指定注销登录URL,默认请求方式必须为GET
                 .logoutRequestMatcher(new OrRequestMatcher(
                         new AntPathRequestMatcher("/aaa", "GET"),
                         new AntPathRequestMatcher("/bbb", "POST")
                 ))
                 .invalidateHttpSession(true)//默认开启会话失效
                 .clearAuthentication(true)//默认清除认证标志
                 .logoutSuccessUrl("/login.html")//注销登录成功后跳转的页面
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }

注意:

  • GET请求直接在地址栏访问,注销登录路径:http://localhost:8080/aaa

  • POST方式需要新建表单及对应的Controller,注销登录路径:http://localhost:8080/logout.html

  • logout.html

 <!DOCTYPE html>
 <html lang="en" xmlns:th="https://www.thymeleaf.org">
 <head>
     <meta charset="UTF-8">
     <title>注销页面</title>
 </head>
 <body>
 <h1>用户注销</h1>
 <form method="post" th:action="@{/bbb}">
     <input type="submit" value="注销登录"/>
 </form>
 </body>
 </html>
  • LogoutController
 package com.study.controller;import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;/**
  * @ClassName LogoutController
  * @Description TODO
  * @Date 2022/7/24 18:15
  * @Version 1.0
  */
 @Controller
 public class LogoutController {
   @RequestMapping("logout.html")
     public String logout() {
         return "logout";
     }
 }

前后端分离注销登录配置

如果是前后端分离开发,注销成功之后就不需要页面跳转了,只需要将注销成功的信息返回前端即可,此时我们可以通过自定义LogoutSuccessHandler 实现来返回注销之后信息:
在这里插入图片描述
进入logoutSuccessHandler方法

public LogoutConfigurer<H> logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
	this.logoutSuccessUrl = null;
	this.customLogoutSuccess = true;
	this.logoutSuccessHandler = logoutSuccessHandler;
	return this;
}

需要传入一个LogoutSuccessHandler 类型的参数

public interface LogoutSuccessHandler {

	void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException;

}

编写LogoutSuccessHandler接口实现类MyLogoutSuccessHandler重写onLogoutSuccess方法

package com.study.config;import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;/**
  * @ClassName MyLogoutSuccessHandler
  * @Description 自定义认证注销处理
  * @Author Jiangnan Cui
  * @Date 2022/7/23 20:30
  * @Version 1.0
  */
 public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
     @Override
     public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
         Map<String, Object> result = new HashMap<String, Object>();
         result.put("msg", "注销成功,当前认证对象为:" + authentication);//打印认证信息
         result.put("status", 200);//打印状态码
         response.setContentType("application/json;charset=UTF-8");//设置响应类型
         String s = new ObjectMapper().writeValueAsString(result);//json格式转字符串
         response.getWriter().println(s);//打印json格式数据
     }
 }

WebSecurityConfigurer配置MyLogoutSuccessHandler

package com.study.config;import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;/**
  * @ClassName WebSecurityConfigurer
  * @Description TODO
  * @Date 2022/7/21 22:34
  * @Version 1.0
  */
 @Configuration
 public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .......
                 .failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
                 .and()
                 .logout()
                 //.logoutUrl("/logout")//指定注销登录URL,默认请求方式必须为GET
                 .logoutRequestMatcher(new OrRequestMatcher(
                         new AntPathRequestMatcher("/aaa", "GET"),
                         new AntPathRequestMatcher("/bbb", "POST")
                 ))
                 .invalidateHttpSession(true)//默认开启会话失效
                 .clearAuthentication(true)//默认清除认证标志
                 //.logoutSuccessUrl("/login.html")//注销登录成功后跳转的页面
                 .logoutSuccessHandler(new MyLogoutSuccessHandler())
                 .and()
                 .csrf().disable();//此处先关闭CSRF跨站保护
     }
 }

测试路径:http://localhost:8080/aaa

测试路径:http://localhost:8080/logout.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Security 可以通过实现 AuthenticationFailureHandler 接口来自定义认证错误处理。具体步骤如下: 1. 创建一个类,实现 AuthenticationFailureHandler 接口。 2. 在类中实现 onAuthenticationFailure 方法,该方法接收三个参数:HttpServletRequest、HttpServletResponse 和 AuthenticationException。 3. 在 onAuthenticationFailure 方法中,可以根据 AuthenticationException 的类型来判断认证失败的原因,并根据需要进行处理。 4. 最后,将自定义认证错误处理器配置到 Spring Security 中。 示例代码如下: ``` public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { if (exception instanceof BadCredentialsException) { // 处理用户名或密码错误的情况 response.sendRedirect("/login?error=bad_credentials"); } else if (exception instanceof DisabledException) { // 处理账号被禁用的情况 response.sendRedirect("/login?error=disabled"); } else { // 处理其他认证失败的情况 response.sendRedirect("/login?error=unknown"); } } } ``` 配置自定义认证错误处理器的代码如下: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomAuthenticationFailureHandler customAuthenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .failureHandler(customAuthenticationFailureHandler) .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") .and() .csrf().disable(); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值