安全框架 Spring Security 自定义异常(四)

537 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

目前的登录接口,在账号密码不存在或错误的情况下,会直接返回 401 请求码异常。如图:

image.png
前端封装的 axios 拦截,适配最好请求码都是 200,然后根据返回的 code 进行判断,也就是正常返回数据,如下:

例如用户名密码错误

{
    "code": 401,
    "msg": "用户名密码错误",
    "data": null
}

这样前端只需要判断 code 就能来进行提示不同格式 message 的信息。 同时也更好的判断后续的操作。

编写 JwtAuthenticationEntryPoint

文件:JwtAuthenticationEntryPoint.java

package com.example.auth.exception;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;

@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        log.error("权限校验失败:{}", authException.getLocalizedMessage(), authException);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        LinkedHashMap<String, Object> resMap = new LinkedHashMap<>(4);
        String msg = "用户未登录或者token失效";
        int code = 0;
        if (authException instanceof BadCredentialsException) {
            code = 500;
            msg = "用户名或密码错误!";
        }
        resMap.put("code", code);
        resMap.put("msg", msg);
        resMap.put("data", null);
        writer.write(new ObjectMapper().writeValueAsString(resMap));
        writer.flush();
        writer.close();
    }
}

更改安全配置类

默认情况下,Spring Security 提供的 BasicAuthenticationEntryPoint 会向客户端返回 401 Unauthorized 响应的完整页面。这种异常显示方式在 html 浏览器挺好,直接返回页面,不过,在 Restful 的情况下,不太适合。

我们现在来自定义一个异常返回端点,返回 Restful 风格的。

新建文件: JwtAuthenticationEntryPoint.java

package com.example.auth.exception;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;

@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        log.error("权限校验失败:{}", authException.getLocalizedMessage(), authException);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        LinkedHashMap<String, Object> resMap = new LinkedHashMap<>(4);
        String msg = "用户未登录或者token失效";
        int code = 401;
        if (authException instanceof BadCredentialsException) {
            code = 500;
            msg = "用户名或密码错误!";
        }
        resMap.put("code", code);
        resMap.put("msg", msg);
        resMap.put("data", null);
        writer.write(new ObjectMapper().writeValueAsString(resMap));
        writer.flush();
        writer.close();
    }
}

在文件 SecurityConfiguration.java 注入 Bean ->

private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Autowired
public void setJwtAuthenticationEntryPoint(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) {
    this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
}

然后在 filterChain 方法中的 http 链式末尾增加如下配置 ->

http.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint);

开始测试

打开 postman,请求登录

image.png

现在我们使用错误的 token 来访问被保护的资源

image.png

符合前端需要的预期效果,可以根据 code 码直接走不同的逻辑

总结

  1. 自定义 AuthenticationEntryPoint返回 Restful 风格
  2. 安全配置类设置自定义 AuthenticationEntryPoint