Spring Security实现用户认证二:前后端分离时自定义返回Json内容
- 1 前后端分离
- 2 准备工作
- 依赖
- WebSecurityConfig配置类
- 2 自定义登录页面
- 2.1 Spring Security的默认登录页面
- 2.2 自定义配置formLogin
- 3 自定义登录成功处理器
- 4 自定义登录失败处理器
- 5 自定义登出处理器
- 6 自定义回话过期处理器
- 7 定义认证失败接入点
1 前后端分离
在现在大多数web开发中,采用的开发模式均是前后端分离类型的。往往后端给前端返回的数据格式为json类型:
{"code":"200","message":"success","data":"登录成功"
}
2 准备工作
依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId>
</dependency>
WebSecurityConfig配置类
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {//为存储在内存中的基于用户名/密码的认证提供支持。@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(User.withDefaultPasswordEncoder().username("root").password("root").roles("USER").build());return manager;}@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {// ResultData<String> result = ResultData.success("登录成功");Map<String, Object> result = new HashMap<>();result.put("code", "200");result.put("message", "登录成功");result.put("data", "");//将结果转换成字符串String json = JSON.toJSONString(result);//返回json数据response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}
2 自定义登录页面
2.1 Spring Security的默认登录页面
.httpBasic(withDefaults())
.formLogin(withDefaults())
:
2.2 自定义配置formLogin
注释掉原来的formLogin和httpBasic。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeRequests(authorize ->authorize.anyRequest().authenticated());
// .formLogin(withDefaults())
// .httpBasic(withDefaults());http.formLogin(login ->login.loginPage("/login").permitAll() // 一定加上这个,不然会一直重定向,导致报错);return http.build();
}
在resources下创建目录templates
,用来存放thymeleaf写的页面,在这个目录下面创建login.html
页面
<html xmlns:th="https://www.thymeleaf.org">
<head><title>login</title>
</head>
<body>
<h1>Login</h1>
<div th:if="${param.error}">错误的用户名或密码.
</div>
<form th:action="@{/login}" method="post"><div><input type="text" name="username" placeholder="请输入用户名"></div><div><input type="password" name="password" placeholder="请输入密码"></div><input type="submit" value="登录">
</form>
</body>
</html>
创建LoginController
:
@GetMapping("/login")
public String login(){return "login";
}
效果如下:
3 自定义登录成功处理器
定义MyAuthenticationSuccessHandler
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {Map<String, Object> result = new HashMap<>();result.put("code", "200");result.put("message", "登录成功");result.put("data", "");//将结果转换成字符串String json = JSON.toJSONString(result);//返回json数据response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}
添加配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// ...http.formLogin(login -> {login.loginPage("/login").permitAll();login.successHandler(new MyAuthenticationSuccessHandler());});return http.build();
}
效果
4 自定义登录失败处理器
定义MyAuthenticationFailureHandler
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {Map<String, Object> result = new HashMap<>();result.put("code", "401");result.put("message", "登录失败");result.put("data", "");//将结果转换成字符串String json = JSON.toJSONString(result);//返回json数据response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}
添加配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// ...http.formLogin(login -> {login.loginPage("/login").permitAll();login.successHandler(new MyAuthenticationSuccessHandler());login.failureHandler(new MyAuthenticationFailureHandler());});return http.build();
}
效果
5 自定义登出处理器
定义MyLogoutSuccessHandler
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {Map<String, Object> result = new HashMap<>();result.put("code", "200");result.put("message", "登出成功");//将结果转换成字符串String json = JSON.toJSONString(result);//返回json数据response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}
添加配置
为了能够正确登出,需要将csrf配置关闭,否则只接受post请求登出。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// ...http.logout(logout -> {logout.logoutSuccessHandler(new MyLogoutSuccessHandler());});http.csrf(csrf->csrf.disable());return http.build();
}
效果
6 自定义回话过期处理器
这个当会话过期或者超出登录数量时。
定义MyLogoutSuccessHandler
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {Map<String, Object> result = new HashMap<>();result.put("code", "200");result.put("message", "登出成功");//将结果转换成字符串String json = JSON.toJSONString(result);//返回json数据response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}
}
添加配置
将最大的session数限制为1,则只允许一个session在线。expiredSessionStrategy指定过期策略。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// ...http.sessionManagement(session ->{session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy());});return http.build();
}
先登录一个浏览器,在登录另一个浏览器,在刷新第一个浏览器。
效果
7 定义认证失败接入点
创建:MyAuthenticationEntryPoint
实现 AuthenticationEntryPoint
接口
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {String localizedMessage = authException.getLocalizedMessage();Map<String, Object> result = new HashMap<>();result.put("code", "500");result.put("message", localizedMessage);String jsonString = JSON.toJSONString(result);response.setContentType("application/json;charset=UTF-8");response.getWriter().println(jsonString);}
}
配置WebSecurityConfig
:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// ...http.exceptionHandling(exception ->{exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());});return http.build();
}
在我们没有登录时。直接访问主页,会发生异常。
效果