前后端分离
在如今项目中基本上都是前后端分离,也就是前端请求用户名和密码到后端后,后端返回不再是具体成功或者失败页面而是json数据,由前端自行觉得跳转某个页面。要进行改动后端认证流程,那就需要提前了解具体的用户认证流程
1. 用户认证流程
其中校验用户名密码的过滤器是UsernamePasswordAuthenticationFilter,步骤如下:
- 将用户的登录请求信息封装成UsernamePasswordAuthenticationToken,里面包含了用户名和密码信息
- 将UsernamePasswordAuthenticationToken交给AuthenticationManager进行用户名密码验证处理,处理结果无非是成功和失败
- 成功的话,经过一系列处理,最后调用AuthenticationSuccessHandler,AuthenticationSuccessHandler用于返回处理
- 同样在失败的话,最后要返回之前要调用AuthenticationFailureHandler,AuthenticationFailureHandler用于失败情形的返回处理
2. 认证成功的响应
编写ResponseJsonAuthenticationSuccessHandler类:
java
public class ResponseJsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private HttpMessageConverter<Object> converter;
public ResponseJsonAuthenticationSuccessHandler(HttpMessageConverter<Object> converter) {
this.converter = converter;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获取用户身份信息
Object principal = authentication.getPrincipal();
HashMap<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "登录成功");
result.put("data", principal);
this.converter.write(result, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
}
}
3. 认证失败响应
编写ResponseJsonAuthenticationFailureHandler类:
java
public class ResponseJsonAuthenticationFailureHandler implements AuthenticationFailureHandler {
private HttpMessageConverter<Object> converter;
public ResponseJsonAuthenticationFailureHandler(HttpMessageConverter<Object> converter) {
this.converter = converter;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//获取错误信息
String localizedMessage = exception.getLocalizedMessage();
HashMap<String, Object> result = new HashMap<>();
result.put("code", -1);
result.put("message", localizedMessage);
this.converter.write(result, MediaType.APPLICATION_JSON_UTF8, new ServletServerHttpResponse(response));
}
}
4. 注销响应
同样的,注销如果要返回Json, 需要实现LogoutSuccessHandler接口,编写ResponseJsonLogoutSuccessHandler类:
java
public class ResponseJsonLogoutSuccessHandler implements LogoutSuccessHandler {
private HttpMessageConverter<Object> converter;
public ResponseJsonLogoutSuccessHandler(HttpMessageConverter<Object> converter) {
this.converter = converter;
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获取用户身份信息
HashMap<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "注销成功");
this.converter.write(result, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
}
}
5. SecurityFilterChain配置
java
@Configuration
public class WebSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter();
http
.authorizeHttpRequests(
(authorize) ->
authorize.anyRequest()
.authenticated())
// 放开登录页面的访问权限
.formLogin( form -> {
form
.loginPage("/login").permitAll() //登录页面无需授权即可访问
.failureUrl("/login?error=true") //登录失败后跳转的页面
.successHandler(new ResponseJsonAuthenticationSuccessHandler(converter))
.failureHandler(new ResponseJsonAuthenticationFailureHandler(converter))
;
}); //使用表单授权方式
// 配置退出
http.logout(logout -> {
logout
.logoutSuccessHandler(new ResponseJsonLogoutSuccessHandler(converter));
});
// api接口测试时关闭csrf校验
http.csrf((csrf) -> csrf.disable());
return http.build();
}
}
需要注意的是,配置前后端分离也就意味着前端通过api接口访问,需要关闭csrf。 运行测试:
6. 请求未认证的接口
6.1 实现AuthenticationEntryPoint接口
当访问一个需要认证之后才能访问的接口的时候,Spring Security会使用AuthenticationEntryPoint
将用户请求跳转到登录页面,要求用户提供登录凭证。
这里我们也希望系统返回json结果
,因此我们定义类实现AuthenticationEntryPoint接口
。
java
public class ResponseJsonAuthenticationEntryPoint implements AuthenticationEntryPoint {
private HttpMessageConverter<Object> converter;
public ResponseJsonAuthenticationEntryPoint(HttpMessageConverter<Object> converter){
this.converter = converter;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//获取错误信息
// String localizedMessage = authException.getLocalizedMessage();
HashMap<String, Object> result = new HashMap<>();
result.put("code", -1);
result.put("message", "请先登录!");
this.converter.write(result, MediaType.APPLICATION_JSON_UTF8, new ServletServerHttpResponse(response));
}
}
6.2 SecurityFilterChain配置
java
http.exceptionHandling(exception -> {
exception
.authenticationEntryPoint(new ResponseJsonAuthenticationEntryPoint(converter));
});
运行测试:
7. 跨域
跨域全称是跨域资源共享(Cross-Origin Resources Sharing, CORS),它是浏览器的保护机制,只允许网页请求统一域名下的服务,同一域名指=>协议、域名、端口号都要保持一致,如果有一项不同,那么就是跨域请求。在前后端分离的项目中,需要解决跨域的问题。
在SpringSecurity中解决跨域很简单,在配置文件中添加如下配置即可:
java
// 跨域
http.cors(withDefaults());