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;
}
- 遍历配置的认证提供者列表,通过调用
provider.authenticate(authentication)
方法对认证请求进行认证,并返回认证结果。如果找到了支持请求类型的认证提供者并成功认证,则将结果保存在 result 变量中,并中断遍历。 - 如果找不到支持请求类型的认证提供者或认证失败,则将 authentication 异常保存在 lastException 中。
- 如果设置了父认证管理器(parent),则将认证请求转发给父认证管理器进行认证。 如果父认证管理器成功进行了认证,则将结果保存在
parentResult 变量中,并将其赋值给 result。 - 如果 result 不为空,则表示认证成功,将认证结果发布为
AuthenticationSuccessEvent 事件,然后返回 result。 - 如果 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);
}
-
AbstractAuthenticationProcessingFilter 是 Spring Security 过滤器链中用于处理认证请求的过滤器之一。当认证过程中出现失败时,就会调用该方法来处理认证失败的情况。
-
在该方法中,首先调用 SecurityContextHolder.clearContext() 方法来清除 SecurityContextHolder 中的认证信息。这是为了确保不会保留已经失败的认证信息。
-
然后,通过日志记录认证失败的原因,其中使用了 logger.trace() 方法来输出日志消息。
-
接下来,调用 rememberMeServices.loginFail(request, response) 方法来处理记住我(Remember Me)功能。这个方法通知 RememberMeServices 实现类记录认证失败,以确保下次请求时不会自动进行认证。
-
最后,调用 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