Spring中的aware接口详情
353
2022-07-22
目录Spring Security登录表单配置1.引入pom依赖2.bootstrap.yml添加配置3.创建login.html4.创建配置类5.配置细节6.登陆成功7.登陆失败8.注销登录
Spring Security登录表单配置
1.引入pom依赖
创建一个Spring Boot工程,引入Web和Spring Security依赖:
xmlns:xsi="http://w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2.bootstrap.yml添加配置
工程创建完成之后,为了方便测试,需要在bootstrap.yml配置文件中添加如下配置,将登录用户名和密码固定下来:
spring:
security:
user:
name: kapcb
password: 123456
3.创建login.html
在resources/static目录下创建login.html页面,这个就是自定义登录页面:
这个login.html核心内容就是一个登陆表单,登陆表单中有三个需要注意的地方:
form标签中的action熟悉,这里是/loginSystem,表示表单要提交请求到/loginSystem接口上。用户名框的name属性值为username。密码框的name属性为password。
4.创建配置类
定义好login.html之后,接下来就定义两个测试接口,作为受保护资源。当用户登陆成功后,就可以访问受保护的资源。接口定义如下:
package com.kapcb.security.helloworld.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Description: HelloWorldController
*
* @author Kapcb
* @version 1.0
* @date 2022/6/12 22:05
* @since 1.0
*/
@RestController
public class HelloWorldController {
@GetMapping("index")
public String index() {
return "login success!";
}
@GetMapping("hello")
public String hello() {
return "Hello, World!";
}
}
最后再提供Spring Security的配置类,需要注意的是在Spring Security 5.7版本中已经废弃WebSecurityConfigurerAdapter:
package com.kapcb.security.helloworld.configuration;
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;
/**
* Title: SecurityConfiguration
* Description: SecurityConfiguration
*
* @author Kapcb
* @version 1.0
* @date 2022/6/13 22:23
* @since 1.0
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/loginSystem")
.defaultSuccessUrl("/index")
.failureUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
}
这里直接使用声明SecurityFilterChain类型的Bean的方式即可配置Spring Security登陆表单。
authorizeHttpRequests()方法表示开启权限配置。anyRequest().authenticated():表示所有请求都需要经过认证。and():方法会返回httpSecurityBuilder对象的一个子类,实际上就是HttpSecurity。所以and()方法相当于返回一个HttpSecurity实例,重新开启新一轮配置。formLogin():表示开启表单登陆配置。loginPage("/login.html"):用于配置默认登录页的请求地址。loginProcessingUrl("/loginSystem"):用于配置登录请求接口地址。defaultSuccessUrl("/index"):表示登录请求处理成功后的跳转地址。failureUrl("/login.html"):表示登陆失败跳转的地址。usernameParameter("username"):表示登陆用户名的参数名称。passwordParameter("password"):表示登录密码的参数名称。permitAll():表示跟登录相关的页面和接口不做拦截,直接允许访问。csrf().disable():表示禁用CSRF防御功能。Spring Security自带了CSRF防御机制。为了测试方便,这里先关闭了。
需要注意的是,上面的loginPage()、loginProcessingUrl()、defaultSuccessUrl()、usernameParameter()、passwordParameter()需要和之前创建的login.html中登录表单的配置一致。
完成上述配置之后,启动工程,访问http://localhost:9096/index,会自动跳转到http://localhost:9096/login.html页面。输入之前设置好的用户名密码,登陆成功之后,就可以访问到/index页面了。
5.配置细节
在前面的配置中,使用defaultSuccessUrl()方法配置了用户登陆成功之后的跳转地址。使用failureUrl()方法配置了用户登录失败之后的跳转地址。关于用户登录成功与登陆失败,除了这两个方法可以进行配置之外,还有另外两个方法也可配置。
6.登陆成功
当用户登录成功之后,除了使用defaultSuccessUrl()方法可以实现登录成功后的跳转之外,successForwardUrl()方法也可以配置实现登录成功之后的跳转,配置代码如下:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/loginSystem")
.successForwardUrl("/index")
.failureUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
}
defaultSuccessUrl()和successForwardUrl()方法配置的区别如下:
defaultSuccessUrl()方法表示当用户登陆成功之后,会自动重定向到登录之前用户访问的地址上。如果用户本身就是直接访问的登陆页面,则登录成功之后就会重定向到defaultSuccessUrl()指定的页面。successForwardUrl()方法则不会考虑用户登录之前访问的地址,只要用户登陆成功,就会通过服务器端跳转到successForwardUrl()所指定的页面。defaultSuccessUrl()方法有一个重载方法,如果重载方法的第二个参数传入true,则defaultSuccessUrl()方法的效果与successForwardUrl()方法类似,即不考虑用户之前的访问地址,只要登陆成功,就重定向到defaultSuccessUrl()所指定的页面。不同之处在于,defaultSuccessUrl()方法是通过重定向实现的客户端跳转,successForwardUrl()则是通过服务端跳转实现的。
无论是defaultSuccessUrl()还是successForwardUrl()方法配置登录成功跳转页面,最终所配置的都是AuthenticationSuccessHandler接口的实例。
在Spring Security中专门提供了AuthenticationSuccessHandler接口来处理用户登陆成功事项,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方法,此方法是Spring Security 5.2版本中添加进来的,在处理特定的认证请求Authentication Filter中会用到。另外一个非default方法,则用来处理登陆成功的具体事项,其中Authentication参数保存了登陆成功的用户信息。
AuthenticationSuccessHandler接口在Spring Security源码中一共有三个实现类:
SimpleUrlAuthenticationSuccessHandler继承自AbstractAuthenticationTargetUrlRequestHandler。通过AbstractAuthenticationTargetUrlRequestHandler中的handler()方法实现请求重定向。SavedRequestAwareAuthenticationSuccessHandler在SimpleUrlAuthenticationSuccessHandler的基础上增加了请求缓存的功能,可以记录用户登陆之前请求的地址,进而在登陆成功之后重定向到一开始访问的地址。ForwardAuthenticationSuccessHandler的实现比较简单,就是一个服务端跳转。
通过defaultSuccessUrl()方法来配置登陆成功之后重定向的请求地址时,实际上对应的实现类就是SavedRequestAwareAuthenticationSuccessHandler。SavedRequestAwareAuthenticationSuccessHandler源码如下:
public class SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
private RequestCache requestCache = new HttpSessionRequestCache();
public SavedRequestAwareAuthenticationSuccessHandler() {
}
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);
} else {
String targetUrlParameter = this.getTargetUrlParameter();
if (!this.isAlwaysUseDefaultTargetUrl() && (targetUrlParameter == null || !StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
this.clearAuthenticationAttributes(request);
String targetUrl = savedRequest.getRedirectUrl();
this.getRedirectStrategy().sendRedirect(request, response, targetUrl);
} else {
this.requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}
SavedRequestAwareAuthenticationSuccessHandler中的核心方法就是onAuthenticationSuccess()。
从RequestCache中获取缓存下来的请求,如果没有获取到缓存的请求地址,就代表用户在登录之前并没有访问其它页面。此时直接调用父类的onAuthenticationSuccess()方法来处理,最终重定向到defaultSuccessUrl()方法指定的地址。接下里会获取一个targetUrlParameter,这个地址是用户显示指定的、希望登录成功之后重定向的地址。例如用户发送的登录请求是http://127.0.0.1:9096/loginSystem?target=/hello,这就表示当用户登陆成功之后,希望主动重定向到/hello接口。targetUrlParameter()方法就是获取重定向地址参数的key,就是上面举例中的target。获取到target之后就可以获取到重定向的地址了。如果targetUrlParameter存在,或者开发者设置了isAlwaysUseDefaultTargetUrl()为true,此时缓存下来的请求就失去了意义。此时会直接移除掉缓存的请求地址,直接调用父类的onAuthenticationSuccess()方法完成重定向。targetUrlParameter存在,则直重定向到targetUrlParameter指定的地址。isAlwaysUseDefaultTargetUrl为true,则直接重定向到defaultSuccessUrl指定的地址。如果targetUrlParameter存在并且isAlwaysUseDefaultTargetUrl为true,则直接重定向到defaultSuccessUrl指定的地址。如果上述条件均不满足,那么最终会从缓存请求SavedRequest对象中获取重定向地址,然后进行重定向操作。
这就是SavedRequestAwareAuthenticationSuccessHandler的实现逻辑,开发者也可配置自己的SavedRequestAwareAuthenticationSuccessHandler,如下:
package com.kapcb.security.helloworld.configuration;
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;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
/**
* Title: SecurityConfiguration
* Description: SecurityConfiguration
*
* @author Kapcb
* @version 1.0
* @date 2022/6/13 22:23
* @since 1.0
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/loginSystem")
.successHandler(successHandler())
.failureUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
protected SavedRequestAwareAuthenticationSuccessHandler successHandler() {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setDefaultTargetUrl("/index");
successHandler.setTargetUrlParameter("target");
return successHandler;
}
}
在上面的配置代码中,制定了targetUrlParameter为target,这样用户就可以在登录请求中,通过target来指定跳转地址。修改一下上面的login.html中的form表单:
修改form表单的action属性为/loginSystem?target=/hello。当用户登陆成功之后,就始终会跳转到/hello接口了。
在使用successForwardUrl()方法设置登陆成功后重定向的地址时,实际上对应的实现类是ForwardAuthenticationSuccessHandler,ForwardAuthenticationSuccessHandler类的源码非常简单,就是一个服务端转发,ForwardAuthenticationSuccessHandler源码如下:
public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final String forwardUrl;
public ForwardAuthenticationSuccessHandler(String forwardUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> {
return "'" + forwardUrl + "' is not a valid forward URL";
});
this.forwardUrl = forwardUrl;
}
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
request.getRequestDispatcher(this.forwardUrl).forward(request, response);
}
}
主要功能就是调用request.getRequestDispatcher(this.forwardUrl).forward(request, response)方法实现服务端请求转发。
启动服务之后访问登录页面,登录成功后即可自动重定向到/hello接口。
AuthenticationSuccessHandler默认的三个实现类,无论是哪一种,都是用来处理页面跳转的。随着前后端分离架构的盛行,页面跳转并不能满足我们的业务需求,用户登录成功后,后端返回jsON数据给前端即可。告诉前端当前用户是登陆成功还是失败即可,前端拿到后端响应结果后自行处理即可。像这种需求,可以使用自定义AuthenticationSuccessHandler的实现类完成。
package com.kapcb.security.helloworld.handler;
import com.alibaba.fastjson.JSON;
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;
/**
* Title: CustomizeAuthenticationSuccessHandler
* Description: CustomizeAuthenticationSuccessHandler
*
* @author Kapcb
* @version 1.0
* @date 2022/6/16 23:47
* @since 1.0
*/
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
Map
resultMap.put("code", 200);
resultMap.put("msg", null);
resultMap.put("data", "1111111");
String jsonResult = JSON.toJSONString(resultMap);
response.getWriter().write(jsonResult);
response.getWriter().close();
}
}
修改配置类:
package com.kapcb.security.helloworld.configuration;
import com.kapcb.security.helloworld.handler.CustomizeAuthenticationSuccessHandler;
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;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
/**
* Title: SecurityConfiguration
* Description: SecurityConfiguration
*
* @author Kapcb
* @version 1.0
* @date 2022/6/13 22:23
* @since 1.0
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
}
在使用自定义AuthenticationSuccessHandler的实现类来完成登录成功之后的处理逻辑之后,还需要同步修改login.html。
将form表单中的action属性修改为之前的/loginSystem。因为此时使用的是自定义的AuthenticationSuccessHandler。逻辑与SavedRequestAwareAuthenticationSuccessHandler是不一样的。
所有的改动完成之后,重启工程。当用户登录成功之后,就不会在进行页面跳转了,而是返回了一段JSON字符串到页面上。
7.登陆失败
Spring Security登陆失败的处理逻辑。为了方便在前端页面展示登录失败的异常信息,首先在项目的pom.xml文件中引入thymeleaf模板引擎的依赖:
在resources目录下创建templates文件夹,新建loginTemplate.html文件:
loginTemplate.html文件和前面的login.html文件基本类似。login.html在static目录下,属于静态页面,loginTemplate.html是template模板页面。loginTemplate.html在form标签内新增了
loginTemplate.html属于动态页面,所以就不能像访问static/login.html那样直接访问,需要后端为其提供访问控制器:
package com.kapcb.security.helloworld.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Description: RouterController
*
* @author Kapcb
* @version 1.0
* @date 2022/6/17 23:32
* @since 1.0
*/
@Controller
@RequestMapping("/router")
public class RouterController {
@RequestMapping("loginTemplate")
public String loginTemplate() {
return "loginTemplate";
}
}
最后在Spring Security配置类中配置登陆页面:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureUrl("/router/loginTemplate")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
}
failureUrl()表示登陆失败后重定向到/router/loginTemplate接口请求地址,也就是loginTemplate.html页面。重定向是一种客户端跳转,重定向不方便携带请求失败的异常信息,只能放在URL中。
如果希望在前端展示请求失败的异常信息,可以使用下面这种方式:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureForwardUrl("/router/loginTemplate")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
}
failureForwardUrl()方法从名字上就可以看出,这种跳转是一种服务器端跳转。服务器端跳转的好处是可以携带登录异常信息。如果用户登陆失败,自动跳转回登录页面后,就可以将错误信息展示出来。
无论是failureUrl()还是failureForwardUrl()方法,最终所配置的都是AuthenticationFailureHandler接口的实现类。Spring Security中提供了AuthenticationFailureHandler接口,为登陆失败下的处理方式提供顶级拓展。AuthenticationFailureHandler源码如下:
public interface AuthenticationFailureHandler {
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException;
}
AuthenticationFailureHandler接口中只定义了一个onAuthenticationFailure()方法,用于处理登陆失败的请求。AuthenticationException表示登陆失败的异常信息。Spring Security中为AuthenticationFailureHandler一共提供了五个实现类:
SimpleUrlAuthenticationFailureHandler:默认的处理逻辑就是通过重定向跳转到登陆页面,当然也可以通过配置forwardToDestination属性将重定向改为服务器端跳转,failureUrl()方法底层实现逻辑就是SimpleUrlAuthenticationFailureHandler。ExceptionMappingAuthenticationFailureHandler:可以实现根据不同异常类型,映射到不同路径。ForwardAuthenticationFailureHandler:表示通过服务器端跳转来重新回到登陆页面,failureForwardUrl()方法底层实现逻辑就是ForwardAuthenticationFailureHandler。AuthenticationEntryPointFailureHandler:是Spring Security 5.2新引入的处理类,可以通过AuthenticationEntryPoint来处理登陆异常。DelegatingAuthenticationFailureHandler:可以实现为不同的异常类型配置不同的登录失败处理回调。
举个简单的例子。假设不使用failureForwardUrl()方法进行登陆失败的处理逻辑配置,同时又想在登陆失败后通过服务器端跳转回到登陆页面,那么可以自定义SimpleUrlAuthenticationFailureHandler配置,并将forwardToDestination属性设置为true,代码如下:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureHandler(failureHandler())
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
protected SimpleUrlAuthenticationFailureHandler failureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setDefaultFailureUrl("/loginFail");
failureHandler.setUseForward(true);
return failureHandler;
}
}
这样配置之后,用户再次登陆失败,就会通过服务端跳转重新回到登陆页面,同时页面上也会展示相应的错误信息,效果和failureForwardUrl()一样。
SimpleUrlAuthenticationFailureHandler的源码也很简单:
public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
private String defaultFailureUrl;
private boolean forwardToDestination = false;
private boolean allowSessionCreation = true;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public SimpleUrlAuthenticationFailureHandler() {
}
public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
this.setDefaultFailureUrl(defaultFailureUrl);
}
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());
} else {
this.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("SPRING_SECURITY_LAST_EXCEPTION", exception);
} else {
HttpSession session = request.getSession(false);
if (session != null || this.allowSessionCreation) {
request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
}
}
}
public void setDefaultFailureUrl(String defaultFailureUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl), () -> {
return "'" + defaultFailureUrl + "' is not a valid redirect URL";
});
this.defaultFailureUrl = defaultFailureUrl;
}
protected boolean isUseForward() {
return this.forwardToDestination;
}
public void setUseForward(boolean forwardToDestination) {
this.forwardToDestination = forwardToDestination;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
protected RedirectStrategy getRedirectStrategy() {
return this.redirectStrategy;
}
protected boolean isAllowSessionCreation() {
return this.allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
}
SimpleUrlAuthenticationFailureHandler提供了无参和有参构造器。使用有参构造器构造SimpleUrlAuthenticationFailureHandler对象时,可传入defaultFailureUrl。defaultFailureUrl也就是登陆失败时要跳转的地址。
onAuthenticationFailure()方法中,首先会判断defaultFailureUrl是否为null,如果为null则直接通过response返回401状态码Unauthorized。
如果defaultFailureUrl不为空,则调用saveException()方。在saveException()方法中,如果forwardToDestination属性为true,表示此时需要通过服务器端跳转回登录首页,此时就将异常信息放到request中。在回到onAuthenticationFailure()方法中,如果forwardToDestination属性为true,就通过服务器端跳转回到登录页面,否则通过重定向回到登陆页面。
如果是前后端分离开发,登陆失败时就不需要进行页面跳转了,只需要返回登录失败的JSON响应给前端即可。这种场景下通过AuthenticationFailureHandler的实现类来完成,实现代码如下:
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Map
resultMap.put("code", 500);
resultMap.put("msg", exception.getMessage());
resultMap.put("data", null);
String jsonResult = JSON.toJSONString(resultMap);
response.getWriter().write(jsonResult);
response.getWriter().close();
}
}
在Spring Security配置类中配置自定义登陆失败处理器:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureHandler(customizeFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
protected CustomizeAuthenticationFailureHandler customizeFailureHandler() {
return new CustomizeAuthenticationFailureHandler();
}
}
配置完成后,当用户再次登陆失败,就不会进行页面跳转了,而是直接返回JSON字符串。
8.注销登录
Spring Security中提供了默认的注销页面,开发者也可以根据自己的需求对注销登录进行定制。
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureHandler(customizeFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.clearAuthentication(true)
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
protected CustomizeAuthenticationFailureHandler customizeFailureHandler() {
return new CustomizeAuthenticationFailureHandler();
}
}
logout():表示开启注销登录配置。logoutUrl():表示指定了注销登录请求地址,默认是GET请求,路径为.logout。invalidateHttpSession():表示是否使session失效,默认为true。clearAuthentication():表示是否清除认真信息,默认为true。logoutSuccessUrl():表示注销登录后的跳转地址。
配置完成后,再次启动工程,登陆成功后,在浏览器中输入http://127.0.0.1:9096/logout就可以发起注销登录请求了。注销成功后,会自动跳转到loginTemplate.html页面。
开发者也可以配置多个注销登录的请求,同时还可以指定请求的方法:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureHandler(customizeFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout1", HttpMethod.GET.name()),
new AntPathRequestMatcher("/logout2", HttpMethod.POST.name())
))
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/router/loginTemplate")
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
protected CustomizeAuthenticationFailureHandler customizeFailureHandler() {
return new CustomizeAuthenticationFailureHandler();
}
}
在logoutRequestMatcher()的配置表示注销请求路径,分别有两个:
第一个是/logout1,请求方法是GET。第二个是/logout2,请求方法是POST
使用其中的任意一个请求都可以完成登陆注销。
如果项目采用的是前后端分离架构,注销成功后就需要页面跳转了。只需要将注销成功的信息返回给前端即可,此时可以自定义返回内容:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureHandler(customizeFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout1", HttpMethod.GET.name()),
new AntPathRequestMatcher("/logout2", HttpMethod.POST.name())
))
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessHandler((request, response, auth) -> {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Map
resultMap.put("code", 200);
resultMap.put("msg", "logout success!");
resultMap.put("data", null);
String jsonResult = JSON.toJSONString(resultMap);
response.getWriter().write(jsonResult);
response.getWriter().close();
})
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
protected CustomizeAuthenticationFailureHandler customizeFailureHandler() {
return new CustomizeAuthenticationFailureHandler();
}
}
配置logoutSuccessHandler()和logoutSuccessUrl()类似于之前的successHandler和defaultSuccessUrl之间的关系,只是类不同。
配置完成之后,重启项目,登陆成功后再注销登录。无论是/logout1还是/logout2进行注销,只要注销成功,就会返回JSON。
如果开发者希望为不同的注销地址返回不同的结果,如下配置即可:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests()
.antMatchers("/loginFail")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/router/loginTemplate")
.loginProcessingUrl("/loginSystem")
.successHandler(customizeSuccessHandler())
.failureHandler(customizeFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout1", HttpMethod.GET.name()),
new AntPathRequestMatcher("/logout2", HttpMethod.POST.name())
))
.invalidateHttpSession(true)
.clearAuthentication(true)
.defaultLogoutSuccessHandlerFor((request, response, auth) -> {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Map
resultMap.put("code", 200);
resultMap.put("msg", "logout1 success!");
resultMap.put("data", null);
String jsonResult = JSON.toJSONString(resultMap);
response.getWriter().write(jsonResult);
response.getWriter().close();
}, new AntPathRequestMatcher("/logout1", HttpMethod.GET.name()))
.defaultLogoutSuccessHandlerFor((request, response, auth) -> {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Map
resultMap.put("code", 200);
resultMap.put("msg", "logout2 success!");
resultMap.put("data", null);
String jsonResult = JSON.toJSONString(resultMap);
response.getWriter().write(jsonResult);
response.getWriter().close();
}, new AntPathRequestMatcher("/logout2", HttpMethod.POST.name()))
.permitAll()
.and()
.csrf().disable().build();
}
protected CustomizeAuthenticationSuccessHandler customizeSuccessHandler() {
return new CustomizeAuthenticationSuccessHandler();
}
protected CustomizeAuthenticationFailureHandler customizeFailureHandler() {
return new CustomizeAuthenticationFailureHandler();
}
}
通过defaultLogoutSuccessHandlerFor()方法可以注册多个不同的注销成功回调函数,该方法第一个参数是注销成功回调,第二个参数则是具体的注销登录请求。当用户注销成功之后,请求哪个注销地址,就会返回对应的响应信息。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~