文章目录
- 1. SecurityConfig 配置登录表单
- 2. 登录成功后的处理:AuthenticationSuccessHandler
- 3. 登录失败后的处理:AuthenticationFailureHandler
- 4. 注销登录
- 5. 注销登录成功后的处理:LogoutSuccessHandler
1. SecurityConfig 配置登录表单
定义一个登录页面:login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h2>自定义登录页面</h2>
<form action="/user/login" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
定义2个测试接口,作为受保护的资源,当用户登录成功后,就可以访问受保护的资源 :
@RestController
public class UserResource {
@RequestMapping("/hello")
public String test(){
return "hello";
}
@RequestMapping("/index")
public String test1(){
return "index";
}
}
提供 SpringSecurity 的配置类 :
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// authorizeRequests() 方法表示开启权限配置
http.authorizeRequests()
// 表示所有的请求都要认证后才能访问
.anyRequest().authenticated()
// and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
.and()
// 开启表单登录配置
// loginProcessingUrl ,usernameParameter ,passwordParameter
// 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
.formLogin()
// 配置登录页面地址
.loginPage("/login.html")
// 配置登录接口地址
.loginProcessingUrl("/user/login")
// 配置登录成功的跳转地址
.defaultSuccessUrl("/index")
// 配置登录失败的跳转地址
.failureUrl("/login.html")
// 登录用户名的参数名称
.usernameParameter("username")
// 登录密码的参数名称
.passwordParameter("password")
// 跟登录相关的页面和接口不做拦截,直接通过
.permitAll()
.and()
// 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
// 但是我们这里为了测试方便,先将CSRF防御机制关闭
.csrf().disable();
}
}
配置完成后,启动 SpringBoot 项目,浏览器地址栏中输入 http://localhost:8080/index,会自动跳转到http://localhost:8080/login.html页面:
输入用户名和密码进行登录,登录成功后,会自动跳转到登录前访问的 http://localhost:8080/index 页面,经过上面的配置,已经成功的自定义了一个登录页面出来,用户在登录成功之后,就可以访问受保护的资源了。
2. 登录成功后的处理:AuthenticationSuccessHandler
defaultSuccessUrl 表示用户登录成功之后,会自动重定向到登录之前的地址上,如果用户本身就是直接访问的登录页面,则登录成功后就会重定向到defaultSuccessUrl 指定的页面中。例如,用户在未认证的情况下,访问了/hello 页面;而用户如果一开始就访问登录页面,则登录成功后会自动重定向到defaultSuccessUrl 所指定的页面中。
第一种情况:用户登录成功之后,会自动重定向到登录之前的地址上
浏览器地址栏中输入 http://localhost:8080/hello,会自动跳转到 http://localhost:8080/login.html 页面:
输入用户名和密码 登录成功后,会自动跳转到登录之前访问的 http://localhost:8080/hello,而不是defaultSuccessUrl 所指定的 http://localhost:8080/index 页面。
第二种情况:一开始就访问登录页面,则登录成功后会自动重定向到defaultSuccessUrl 所指定的页面中
浏览器直接访问 http://localhost:8080/login.html 页面 :
输入用户名和密码登录成功后,自动跳转到了defaultSuccessUrl 所指定的 http://localhost:8080/index 页面;
那么如果想登录前不访问登录页面,而登录后又想跳转到指定的页面呢 ?
当用户登录成功后,除了defaultSuccessUrl() 方法可试下登录成功后的跳转之外,successForwardUrl() 方法也可以实现登录成功后的跳转:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.successForwardUrl("/index")
.failureUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
}
successForwardUrl 不会来了用户之前的访问地址,只要用户登录陈宫,就会通过服务器跳转到 successForwardUrl 所指定的页面。
defaultSuccessUrl 有一个重载的方法,如果重载方法的第2个参数传true ,则defaultSuccessUrl 的效果与successForwardUrl 类似,即不用考虑用户之前的访问地址,只要登录成功,就重定向到defaultSuccessUrl 所指定的页面。不同之处在于defaultSuccessUrl 是通过重定向实现的跳转,而 successForwardUrl 是通过服务器端跳转实现的。
无论是 defaultSuccessUrl 还是 successForwardUrl 最终配置的都是 AuthenticationSuccessHandler 接口的实例。SpringSecurity 中专门提供了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);
}
// 处理登录成功的具体事项,其中authentication参数保存了登录成功的用户信息
void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
}
AuthenticationSuccessHandler 接口有三个实现类:
(1) SimpleUrlAuthenticationSuccessHandler 通过handler() 方法实现请求的重定向;
(2) ForwardAuthenticationSuccessHandler 实现服务端的跳转;
(3) SavedRequestAwareAuthenticationSuccessHandler 在 SimpleUrlAuthenticationSuccessHandler 的基础上增加了请求缓存的功能,可以记录之前请求的地址,进而登录成功后重定向到一开始访问的地址;
SavedRequestAwareAuthenticationSuccessHandler 在 SimpleUrlAuthenticationSuccessHandler 的基础上增加了请求缓存的功能,可以记录之前请求的地址,进而登录成功后重定向到一开始访问的地址;
AuthenticationSuccessHandler 默认的三个实现类,无论哪一个都是用来出来页面跳转的。但是在前后端分离的开发中,用户登录成功后,不再需要页面跳转了,只需要给前端返回一个 JSON 数据即可,告诉前端登录成功还是失败,前端收到消息后自行处理,像这样的请求我们可以通过自定义AuthenticationSuccessHandler 的实现类来完成:
public class MyAthenticationSuccessHandler implements AuthenticationSuccessHandler {
// 重写onAuthenticationSuccess()方法
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
// 返回一段登录成功的Json字符串给前端
response.getWriter().write(json);
}
}
在 SecurityConfig 中配置自定义的 MyAthenticationSuccessHandler
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.successHandler(new MyAthenticationSuccessHandler())
.failureUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
}
重启项目,访问: http://localhost:8080/index
输入用户名和 密码,登录成功后,返回一段字符串:
上面 MyAthenticationSuccessHandler 可以简写在 SecurityConfig 类中:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.successHandler(jsonAthenticationSuccessHandler())
.failureHandler("/login.html"
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
}
3. 登录失败后的处理:AuthenticationFailureHandler
为了在前端页面展示登录失败的异常信息,首先在项目的pom.xml文件中引入thymeleaf依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
然后再resources/template目录下创建 mylogin.html 文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h2>自定义登录页面</h2>
<form action="/user/login" method="post">
<div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div>
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
mylogin.html 和前面的 login.html 基本类似,前面的login.html是静态页面,这里是模板页面,mylogin.html 页面中加了一个div,用来展示登录失败的异常信息,登录失败的异常信息会放在request中返回给前端。
动态页面不能直接访问,需要一个访问控制器:
@Controller
public class LoginController {
@RequestMapping("/mylogin.html")
public String login(){
return "mylogin";
}
}
在SecurityConfig中配置登录页面和登录失败时的跳转页面:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/mylogin.html")
.loginProcessingUrl("/user/login")
.successHandler(new MyAthenticationSuccessHandler())
.failureUrl("/mylogin.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
}
failureUrl 表示登录失败后重定向到 login.html 页面。重定向是一种客户端跳转,重定向不方便携带请求失败的异常信息(只能放在URL中)。如果希望能够在前端页面展示请求失败的异常信息,可以使用下面这种方式:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.successHandler(new MyAthenticationSuccessHandler())
.failureForwardUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
}
failureForwardUrl 从名字上就可以看出,这种跳转是一种服务器跳转,服务器跳转的好处是可以携带登录异常信息。如果登录失败,自动跳转到登录页面后,就可以将错误信息展示出来,如图:输入错误的用户名或密码
无论是 failureUrl 还是 failureForwardUrl ,最终所配置的都是 AuthenticationFailureHandler 接口的实现:
public interface AuthenticationFailureHandler {
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException;
}
AuthenticationFailureHandler 接口只有一个方法,用来处理登录失败的请求,exception表示登录失败的异常信息。
AuthenticationFailureHandler 接口共有5个实现类:
(1) SimpleUrlAuthenticationFailureHandler 默认的处理逻辑就是通过充电功能项跳转到登录页面,当然也可以通过配置 forwardToDestination 属性将重定向改为服务器跳转,failureUrl 方法的底层实现逻辑就是SimpleUrlAuthenticationFailureHandler ;
(2) ExceptionMappingAuthenticationFailureHandler 可以实现根据不同的异常类型,映射不同的路径;
(3) ForwardAuthenticationFailureHandler 表示通过服务器端跳转来重新回到登录页面,failureForwardUrl 方法的底层实现逻辑就是 ForwardAuthenticationFailureHandler ;
(4) AuthenticationEntryPointFailureHandler 可以通过AuthenticationEntryPoint来处理登录异常;
(5) DelegatingAuthenticationFailureHandler 可以实现为不同的异常类型配置不同的登录失败处理回调;
看一下 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() {
}
// 构造对象时传入登录失败时要跳转的地址 defaultFailureUrl
public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
this.setDefaultFailureUrl(defaultFailureUrl);
}
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// 如果登录失败时要跳转的地址defaultFailureUrl==null,直接通过response返回异常信息
if (this.defaultFailureUrl == null) {
this.logger.debug("No failure URL set, sending 401 Unauthorized error");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
} else {
// 调用 saveException() 方法
this.saveException(request, exception);
// 如果 forwardToDestination 为true,就通过服务器端跳转到登录页面
if (this.forwardToDestination) {
this.logger.debug("Forwarding to " + this.defaultFailureUrl);
request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
} else {
// 否则,重定向回到登录页面
this.logger.debug("Redirecting to " + this.defaultFailureUrl);
this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
}
}
}
protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
// 如果forwardToDestination 属性为true,表示通过服务器端跳转回到登录页面
// 把异常信息放在request中
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 setUseForward(boolean forwardToDestination) {
this.forwardToDestination = forwardToDestination;
}
// ....
}
因此,假如不使用 failureForwardUrl 方法,同时又想在登录失败后通过服务端跳转到登录页面,那么可以自定义 SimpleUrlAuthenticationFailureHandler 配置,并将 forwardToDestination 属性置为 true:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/mylogin.html")
.loginProcessingUrl("/user/login")
.successHandler(jsonAthenticationSuccessHandler())
.failureHandler(failureHandler())
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private SimpleUrlAuthenticationFailureHandler failureHandler() {
SimpleUrlAuthenticationFailureHandler handler
= new SimpleUrlAuthenticationFailureHandler("/mylogin.html");
// 将 forwardToDestination 属性置为 true
handler.setUseForward(true);
return handler;
}
}
这样配置之后,如果用户再次登录失败,就会通过服务端跳转重新回到登录页面,登录页面也会展示相应的错误信息,效果和 failureForwardUrl 一样。
如果是前后端分离开发,登录失败就不需要页面跳转了,只需要返回Json字符串给前端即可,此时可以通过自定义AuthenticationFailureHandler 的实现类来完成:
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "500",
"message", "登录失败" + exception.getMessage()
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
}
}
在 SecurityConfig 中进行配置:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/mylogin.html")
.loginProcessingUrl("/user/login")
.successHandler(new MyAthenticationSuccessHandler())
.failureHandler(new MyAuthenticationFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
}
配置完成后,当用户再次登录失败,就不会进行页面跳转了,而是直接返回 json 字符串:
上面的 MyAuthenticationFailureHandler 简写在SecurityConfig中:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/mylogin.html")
.loginProcessingUrl("/user/login")
.successHandler(jsonAthenticationSuccessHandler())
.failureHandler(jsonAuthenticationFailureHandler())
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.csrf().disable();
}
private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "500",
"message", "登录失败" + exception.getMessage()
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
}
4. 注销登录
SpringSecurity中提供了默认的注销登录页面,我们也可以根据自己的需求对注销登录进行定制。
在 SecurityConfig 中配置注销登录:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// authorizeRequests() 方法表示开启权限配置
http.authorizeRequests()
// 表示所有的请求都要认证后才能访问
.anyRequest().authenticated()
// and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
.and()
// 开启表单登录配置
// loginProcessingUrl ,usernameParameter ,passwordParameter
// 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
.formLogin()
// 配置登录页面地址
.loginPage("/mylogin.html")
// 配置登录接口地址
.loginProcessingUrl("/user/login")
// 配置登录成功的跳转地址
.successHandler(jsonAthenticationSuccessHandler())
// 配置登录失败的跳转地址
.failureHandler(jsonAuthenticationFailureHandler())
// 登录用户名的参数名称
.usernameParameter("username")
// 登录密码的参数名称
.passwordParameter("password")
// 跟登录相关的页面和接口不做拦截,直接通过
.permitAll()
.and()
// 开启注销登录配置
.logout()
// 指定注销登录请求地址,默认是GET请求,路径为/logout
.logoutUrl("/logout")
// 表示是否使session失效,默认为true
.invalidateHttpSession(true)
// 表示是否清除认证信息,默认为true
.clearAuthentication(true)
// 表示注销登录后的跳转地址
.logoutSuccessUrl("/mylogin.html")
.and()
// 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
// 但是我们这里为了测试方便,先将CSRF防御机制关闭
.csrf().disable();
}
private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "500",
"message", "登录失败" + exception.getMessage()
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
}
启动项目访问 http://localhost:8080/logout,自动跳转到登录页面:
登录成功后在浏览器输入 http://localhost:8080/logout 就可以发起注销登录请求了,注销登录成功后,会自动跳转到 mylogin.html 页面。
如果项目有需要还可以配置多个注销登录的请求,同时还可以指定请求的方法:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// authorizeRequests() 方法表示开启权限配置
http.authorizeRequests()
// 表示所有的请求都要认证后才能访问
.anyRequest().authenticated()
// and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
.and()
// 开启表单登录配置
// loginProcessingUrl ,usernameParameter ,passwordParameter
// 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
.formLogin()
// 配置登录页面地址
.loginPage("/mylogin.html")
// 配置登录接口地址
.loginProcessingUrl("/user/login")
// 配置登录成功的跳转地址
.successHandler(jsonAthenticationSuccessHandler())
// 配置登录失败的跳转地址
.failureHandler(jsonAuthenticationFailureHandler())
// 登录用户名的参数名称
.usernameParameter("username")
// 登录密码的参数名称
.passwordParameter("password")
// 跟登录相关的页面和接口不做拦截,直接通过
.permitAll()
.and()
// 开启注销登录配置
.logout()
// 指定注销登录请求地址和请求方式
// 配置两个注销登录的请求,使用任意一个请求都可以完成登录注销
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout","GET"),
new AntPathRequestMatcher("/logout2","POST")
)
)
// 表示是否使session失效,默认为true
.invalidateHttpSession(true)
// 表示是否清除认证信息,默认为true
.clearAuthentication(true)
// 表示注销登录后的跳转地址
.logoutSuccessUrl("/mylogin.html")
// 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
// 但是我们这里为了测试方便,先将CSRF防御机制关闭
.and()
.csrf().disable();
}
private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "500",
"message", "登录失败" + exception.getMessage()
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
}
5. 注销登录成功后的处理:LogoutSuccessHandler
如果项目是前后端分离的项目,注销成功后就不需要页面跳转了,只需要将注销成功的信息返回给前端即可,此时我们可以自定义返回内容,实现成功退出登录的接口LogoutSuccessHandler,自定义一个注销登录类
public interface LogoutSuccessHandler {
void onLogouauthenticationtSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
}
public class MyLogoutSuccessfulHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "注销登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
}
}
在 SecurityConfig 中配置:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// authorizeRequests() 方法表示开启权限配置
http.authorizeRequests()
// 表示所有的请求都要认证后才能访问
.anyRequest().authenticated()
// and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
.and()
// 开启表单登录配置
// loginProcessingUrl ,usernameParameter ,passwordParameter
// 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
.formLogin()
// 配置登录页面地址
.loginPage("/mylogin.html")
// 配置登录接口地址
.loginProcessingUrl("/user/login")
// 配置登录成功的跳转地址
.successHandler(jsonAthenticationSuccessHandler())
// 配置登录失败的跳转地址
.failureHandler(jsonAuthenticationFailureHandler())
// 登录用户名的参数名称
.usernameParameter("username")
// 登录密码的参数名称
.passwordParameter("password")
// 跟登录相关的页面和接口不做拦截,直接通过
.permitAll()
.and()
// 开启注销登录配置
.logout()
// 指定注销登录请求地址和请求方式
.logoutSuccessHandler(new MyLogoutSuccessfulHandler())
// 表示是否使session失效,默认为true
.invalidateHttpSession(true)
// 表示是否清除认证信息,默认为true
.clearAuthentication(true)
// 表示注销登录后的跳转地址
.logoutSuccessUrl("/mylogin.html")
// 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
// 但是我们这里为了测试方便,先将CSRF防御机制关闭
.and()
.csrf().disable();
}
private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "500",
"message", "登录失败" + exception.getMessage()
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
}
启动项目,登录成功后,注销登录访问 localhost:8080/logout,会将注销登录的成功信息返回给前端:
将上面的配置方式简写为:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// authorizeRequests() 方法表示开启权限配置
http.authorizeRequests()
// 表示所有的请求都要认证后才能访问
.anyRequest().authenticated()
// and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
.and()
// 开启表单登录配置
// loginProcessingUrl ,usernameParameter ,passwordParameter
// 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
.formLogin()
// 配置登录页面地址
.loginPage("/mylogin.html")
// 配置登录接口地址
.loginProcessingUrl("/user/login")
// 配置登录成功的跳转地址
.successHandler(jsonAthenticationSuccessHandler())
// 配置登录失败的跳转地址
.failureHandler(jsonAuthenticationFailureHandler())
// 登录用户名的参数名称
.usernameParameter("username")
// 登录密码的参数名称
.passwordParameter("password")
// 跟登录相关的页面和接口不做拦截,直接通过
.permitAll()
.and()
// 开启注销登录配置
.logout()
// 指定注销登录请求地址和请求方式
// 配置两个注销登录的请求,使用任意一个请求都可以完成登录注销
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout","GET"),
new AntPathRequestMatcher("/logout2","POST")
)
)
// 表示是否使session失效,默认为true
.invalidateHttpSession(true)
// 表示是否清除认证信息,默认为true
.clearAuthentication(true)
// 不再页面跳转,将注销成功的信息返回给前端
.logoutSuccessHandler(jsonLogoutSuccessfulHandler())
// 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
// 但是我们这里为了测试方便,先将CSRF防御机制关闭
.and()
.csrf().disable();
}
private LogoutSuccessHandler jsonLogoutSuccessfulHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "注销登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "500",
"message", "登录失败" + exception.getMessage()
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
}
如果希望为不同的注销地址返回不同的结果,也是可以的,配置如下:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// authorizeRequests() 方法表示开启权限配置
http.authorizeRequests()
// 表示所有的请求都要认证后才能访问
.anyRequest().authenticated()
// and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
.and()
// 开启表单登录配置
// loginProcessingUrl ,usernameParameter ,passwordParameter
// 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
.formLogin()
// 配置登录页面地址
.loginPage("/mylogin.html")
// 配置登录接口地址
.loginProcessingUrl("/user/login")
// 配置登录成功的跳转地址
.successHandler(jsonAthenticationSuccessHandler())
// 配置登录失败的跳转地址
.failureHandler(jsonAuthenticationFailureHandler())
// 登录用户名的参数名称
.usernameParameter("username")
// 登录密码的参数名称
.passwordParameter("password")
// 跟登录相关的页面和接口不做拦截,直接通过
.permitAll()
.and()
// 开启注销登录配置
.logout()
// 指定注销登录请求地址和请求方式
// 配置两个注销登录的请求,使用任意一个请求都可以完成登录注销
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout","GET"),
new AntPathRequestMatcher("/logout2","POST")
)
)
// 表示是否使session失效,默认为true
.invalidateHttpSession(true)
// 表示是否清除认证信息,默认为true
.clearAuthentication(true)
// 不再页面跳转,将注销成功的信息返回给前端
.defaultLogoutSuccessHandlerFor(
(request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "logout 注销登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
},new AntPathRequestMatcher("/logout","GET")
)
.defaultLogoutSuccessHandlerFor(
(request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "logout2 注销登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
},new AntPathRequestMatcher("/logout2","POST")
)
// 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
// 但是我们这里为了测试方便,先将CSRF防御机制关闭
.and()
.csrf().disable();
}
private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "500",
"message", "登录失败" + exception.getMessage()
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
Map<String, String> respMap = Map.of(
"code", "200",
"message", "登录成功"
);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(respMap);
response.getWriter().write(json);
};
}
}
defaultLogoutSuccessHandlerFor 方法可以注册过个不同的注销成功回调函数,该方法第一个参数时注销成功回调,第二个承诺书是具体的注销请求。当用户注销成功后,使用了哪个注销请求,就给出对应的响应信息。