Esta é a postura correta da autenticação de login unificada SpringBoot, manipulação de exceção e formato de dados

Este artigo aprenderá o módulo de processamento de funções unificadas do Spring Boot, que também é a parte real do AOP

  • Interface de implementação de verificação de permissão de login do usuário  HandlerInterceptor + WebMvcConfigurer

  • O tratamento de exceções usa anotações  @RestControllerAdvice + @ExceptionHandler

  • O formato de dados retorna usando anotações  @ControllerAdvice e implementa a interface @ResponseBodyAdvice

1. Verificação unificada da autoridade de login do usuário

O processo de desenvolvimento e melhoria da autoridade de login do usuário

  • Verificação inicial do login do usuário: Obtenha a sessão e as informações do usuário na sessão em cada método. Se houver um usuário, o login será considerado bem-sucedido, caso contrário, o login falhará

  • A segunda versão da verificação de login do usuário: forneça um método unificado, chame o método unificado de verificação de identidade de login do usuário em cada método que precisa ser verificado para julgar

  • A terceira versão da validação de login do usuário: use o Spring AOP para executar a validação unificada de login do usuário

  • A quarta versão da verificação de login do usuário: usando interceptadores Spring para obter verificação de login unificada para usuários

1.1 A verificação inicial da autoridade de login do usuário

 

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/a1")
    public Boolean login (HttpServletRequest request) {
        // 有 Session 就获取,没有就不创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,进行业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }

    @RequestMapping("/a2")
    public Boolean login2 (HttpServletRequest request) {
        // 有 Session 就获取,没有就不创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,进行业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }
}

O código escrito desta forma tem a mesma autoridade de verificação de login do usuário em cada método. As desvantagens são:

  • Cada método deve escrever o método de verificação de login do usuário separadamente, mesmo que seja encapsulado em um método público, ainda precisa passar parâmetros e chamar e julgar no método

  • Quanto mais controladores são adicionados, mais métodos são chamados para verificação de login do usuário, o que aumenta o sucesso de modificações e manutenções posteriores

  • Esses métodos de verificação de login do usuário não têm quase nada a ver com o negócio a ser implementado agora, mas cada método deve ser escrito novamente, portanto, é uma solução muito boa fornecer um método AOP público para o método unificado de verificação de autoridade de login do usuário.

1.2 Autenticação unificada de login do usuário Spring AOP

Autenticação unificada de login do usuário, o primeiro pensamento do método de implementação é usar a pré-notificação Spring AOP ou a notificação envolvente para alcançar

 

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义切点方法 Controller 包下、子孙包下所有类的所有方法
    @Pointcut("execution(* com.example.springaop.controller..*.*(..))")
    public void  pointcut(){}
    
    // 前置通知
    @Before("pointcut()")
    public void doBefore() {}
    
    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        System.out.println("Around 方法开始执行");
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行");
        return obj;
    }
}

No entanto, se a função de verificação de permissão de login do usuário for implementada apenas no aspecto do código Spring AOP acima, haverá dois problemas:

  • Não há como obter  HttpSession e solicitar o objeto

  • Precisamos interceptar alguns métodos, mas não outros métodos. Por exemplo, o método de registro e o método de login não são interceptados, ou seja, as regras de interceptação reais são muito complicadas e expressões de aspecto simples não podem atender aos requisitos de interceptação.

1.3 Interceptores de Mola

Em resposta ao problema do Spring AOP no código acima, o Spring fornece um interceptor de implementação específico: HandlerInterceptor, a implementação do interceptor tem duas etapas:

1. Crie um interceptor personalizado e implemente  HandlerInterceptor o método preHandle na interface no Spring

2. Adicione o interceptor personalizado à configuração da estrutura e defina as regras de interceptação

  • @Configuration Anotar a classe atual 

  • implementar  WebMvcConfigurer a interface

  • addInterceptors método de substituição 

Nota: Vários interceptores podem ser configurados em um projeto ao mesmo tempo 

(1) Crie um interceptor personalizado

/**
 * @Description: 自定义用户登录的拦截器
 * @Date 2023/2/13 13:06
 */
@Component
public class LoginIntercept implements HandlerInterceptor {
    // 返回 true 表示拦截判断通过,可以访问后面的接口
    // 返回 false 表示拦截未通过,直接返回结果给前端
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        // 1.得到 HttpSession 对象
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 表示已经登录
            return true;
        }
        // 执行到此代码表示未登录,未登录就跳转到登录页面
        response.sendRedirect("/login.html");
        return false;
    }
}

(2) Adicione o interceptador personalizado à configuração do sistema e defina as regras de interceptação

  • addPathPatterns: indica a URL que precisa ser interceptada e **indica que todos os métodos são interceptados

  • excludePathPatterns: Indica a URL que precisa ser excluída

Descrição: As regras de interceptação podem interceptar URLs usadas neste projeto, incluindo arquivos estáticos (arquivos de imagem, arquivos JS e CSS, etc.).

/**
 * @Description: 将自定义拦截器添加到系统配置中,并设置拦截的规则
 * @Date 2023/2/13 13:13
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Resource
    private LoginIntercept loginIntercept;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入
        registry.addInterceptor(loginIntercept).
                addPathPatterns("/**").    // 拦截所有 url
                excludePathPatterns("/user/login"). //不拦截登录注册接口
                excludePathPatterns("/user/reg").
                excludePathPatterns("/login.html").
                excludePathPatterns("/reg.html").
                excludePathPatterns("/**/*.js").
                excludePathPatterns("/**/*.css").
                excludePathPatterns("/**/*.png").
                excludePathPatterns("/**/*.jpg");
    }
}

1.4 Exercício: Interceptador de Login

Exigir

  • As páginas de login e registro não são bloqueadas, outras páginas são bloqueadas

  • Após o login ser gravado com sucesso na sessão, a página interceptada pode ser acessada normalmente

Em 1.3, um interceptador personalizado foi criado e adicionado à configuração do sistema, e as regras para interceptação foram definidas

(2) Crie  controller um pacote, crie-o no pacote  UserControllere escreva o código comercial da página de login e da página inicial 

 

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public boolean login(HttpServletRequest request,String username, String password) {
        boolean result = false;
        if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
            if(username.equals("admin") && password.equals("admin")) {
                HttpSession session = request.getSession();
                session.setAttribute("userinfo","userinfo");
                return true;
            }
        }
        return result;
    }

    @RequestMapping("/index")
    public String index() {
        return "Hello Index";
    }
}

(3) Execute o programa, visite a página e compare o efeito antes e depois do login

 

1.5 Princípio de Implementação do Interceptador

Com o interceptor, o processamento de negócio correspondente será realizado antes de chamar o Controller. O processo de execução é mostrado na figura abaixo

Análise do código-fonte do princípio de implementação

Toda  a execução será  realizada Controller por meio de um agendador DispatcherServlet

 

 E todos os métodos serão executados  DispatcherServlet no  doDispatch método de agendamento, doDispatch a análise do código fonte é a seguinte:

 

 Por meio da análise do código-fonte, pode-se perceber que o interceptor no Sping também é realizado por meio da ideia de proxy dinâmico e notificação surround

1.6 Adicionando um prefixo de acesso unificado

Adicione o prefixo da API a todos os endereços de solicitação, c significa todos

 

@Configuration
public class AppConfig implements WebMvcConfigurer {
    // 所有的接口添加 api 前缀
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", c -> true);
    }
}

2. Tratamento unificado de exceções

Adicione  @ControllerAdvice a classe de notificação do controlador à classe atual

Adicione ao método  @ExceptionHandler(xxx.class), indicando o manipulador de exceção, e adicione o código de negócio retornado pela exceção

 

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/index")
    public String index() {
        int num = 10/0;
        return "Hello Index";
    }
}

No pacote de configuração, crie  MyExceptionAdvice a classe

@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类)
public class MyExceptionAdvice {
    @ExceptionHandler(ArithmeticException.class)
    public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state",-1);
        result.put("data",null);
        result.put("msg" , "算出异常:"+ e.getMessage());
        return result;
    }
}

 

Você também pode escrever assim, o efeito é o mesmo

 

@ControllerAdvice
public class MyExceptionAdvice {
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state",-1);
        result.put("data",null);
        result.put("msg" , "算数异常:"+ e.getMessage());
        return result;
    }
}

Se houver outra exceção de ponteiro nulo, o código acima não funcionará e um manipulador para exceções de ponteiro nulo deve ser escrito

@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e) {
    HashMap<String, Object> result = new HashMap<>();
    result.put("state",-1);
    result.put("data",null);
    result.put("msg" , "空指针异常异常:"+ e.getMessage());
    return result;
}
@RequestMapping("/index")
public String index(HttpServletRequest request,String username, String password) {
    Object obj = null;
    System.out.println(obj.hashCode());
    return "Hello Index";
}

Mas uma coisa a considerar é que, se cada exceção for escrita assim, a carga de trabalho será muito grande e haverá exceções personalizadas, portanto, definitivamente não é bom escrever assim. Como é uma exceção, basta escrever Exception diretamente . É a classe pai de todas as exceções. Se você encontrar duas exceções que não estão escritas acima, ela corresponderá diretamente a Exception

Quando há várias notificações de exceção, a ordem de correspondência é para a classe atual e suas subclasses corresponderem para cima

@ExceptionHandler(Exception.class)
public HashMap<String,Object> exceptionAdvice(Exception e) {
    HashMap<String, Object> result = new HashMap<>();
    result.put("state",-1);
    result.put("data",null);
    result.put("msg" , "异常:"+ e.getMessage());
    return result;
}

 

Você pode ver que a correspondência de prioridade ainda é a exceção de ponteiro nulo escrita anteriormente

3. Retorno do formato de dados unificado

3.1 Realização do retorno do formato de dados unificado

(1) Adicionar à classe atual @ControllerAdvice

(2) Implemente  ResponseBodyAdvice reescrevendo seu método

  • supports Method, este método indica se o conteúdo precisa ser reescrito (através deste método, alguns controladores e métodos podem ser reescritos seletivamente) e, se for reescrito, retorna true

  • beforeBodyWrite método, este método é chamado antes que o método retorne

 

@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice {

    // 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法
    // 返回 false 表示对结果不进行任何处理,直接返回
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    // 方法返回之前调用此方法
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        HashMap<String,Object> result = new HashMap<>();
        result.put("state",1);
        result.put("data",body);
        result.put("msg","");
        return result;
    }
}
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public boolean login(HttpServletRequest request,String username, String password) {
        boolean result = false;
        if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
            if(username.equals("admin") && password.equals("admin")) {
                HttpSession session = request.getSession();
                session.setAttribute("userinfo","userinfo");
                return true;
            }
        }
        return result;
    }

    @RequestMapping("/reg")
    public int reg() {
        return 1;
    }
}

 

3.2 Análise do código-fonte @ControllerAdvice

Através  @ControllerAdvice da análise do código-fonte, podemos conhecer o processo de execução da exceção unificada acima e retorno de dados unificado

(1) Primeiro, observe o código-fonte de @ControllerAdvice

 

Você pode ver  @ControllerAdvice que é derivado  @Component do componente e toda inicialização do componente chamará  InitializingBean a interface

(2) Vamos verificar quais classes de implementação initializeBean possui

Durante o processo de consulta, foi constatado que a subclasse de implementação no Spring MVC é que  RequestMappingHandlerAdapterexiste um método nela  afterPropertiesSet(), que indica o método a ser executado após todas as configurações de parâmetros serem concluídas.

 

 

(3) E há um método initControllerAdviceCache neste método, consulte este método

 

 

Verifica-se que este método irá pesquisar e usar todas as  @ControllerAdvice classes durante a execução. Quando um evento é enviado, o método Advice correspondente será chamado, como chamar o encapsulamento unificado de dados antes de retornar os dados. Por exemplo, quando ocorre uma exceção, ele é implementado chamando o método Advice anormal

Acho que você gosta

Origin blog.csdn.net/zhangjiaming_zjm/article/details/130422871
Recomendado
Clasificación