首页 > 其他分享 >spring security 使用过滤器认证登录时,抛出自定义异常

spring security 使用过滤器认证登录时,抛出自定义异常

时间:2024-05-17 14:51:35浏览次数:24  
标签:spring apiResult request 过滤器 new security 异常 response

前情提要

最近在做项目的改造,涉及到新增用户的离职冻结状态,当被离职/冻结后,尝试登录系统,则抛出不同的异常代码给前端,前端依据不同的异常代码提示不同的文本。所以需要对项目的认证逻辑简单调整,增加按照不同的登录用户的状态(离职/冻结)判断,如果满足指定状态,则抛出对应的异常代码。

认证逻辑

使用 jwt 认证登录请求到 sso -> sso 认证成功携带 jwt token 进入登录的项目 -> 项目解析jwt的token,得到登录的用户id,用该id去取项目维护的用户表,不同用户状态抛出不同的错误代码
例如
用户离职,抛出 AccountExpiredException 并输出对应的错误代码给前端
用户冻结,抛出 LockedException 并输出对应的错误代码给前端

处理方式

一开始想要在userdetailService中抛出自定义的异常,但是他默认抛出的是UsernameNotFoundException 需要override 一下并修改抛出的异常为他的父类异常 AuthenticationException,这样就可以抛出来 集成了AuthenticationException的各种类型的自定义异常了,例如:

userdetailservice.loadUserByUsername:

    if ("-1".equals(user.getUserState())) {
        throw new LockedException("用户被锁定");
    }
    if ("1".equals(user.getUserState()) || "3".equals(user.getUserState())) {
        throw new AccountExpiredException("用户被冻结或离职");
    }

然后是在使用spring security 配置过滤器时,需要根据使用的不同的过滤器去调整过滤器逻辑:

比如使用的是 OncePerRequestFilter,在 doFilterInternal 方法中调用 loadUserByUsername去校验用户状态时,如果报错不进行处理,异常会被记录,并且覆盖掉,例如spring security 在 SecurityConfiguration 中这么配置:

        http
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and().addFilterBefore(new OncePerRequestFilter() {
                    @Override
                    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                        try {
                            String jwt = request.getHeader(tokenName);
                            DecodedJWT decodedJWT = verifierHMAC512.verify(jwt);
                            String decodedPayload = StringUtil.decode(decodedJWT.getPayload());
                            Map<String, String> map = objectMapper.readValue(decodedPayload, valueTypeRef);
                            String userId = map.get("iss");
                            ReqUser reqUser = userDetailsService.loadUserByUsername(userId);
                            reqUser.setToken(jwt);
                            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(reqUser, null, reqUser.getAuthorities());
                            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                            SecurityContextHolder.getContext().setAuthentication(authentication);
                            ReqContext.set(reqUser);
                            filterChain.doFilter(request, response);
                        } 
						/** catch (AuthenticationException e) {
                            log.error("登录异常", e);
                            customAuthenticationEntryPoint.commence(request, response, e);
                        } **/
						finally {
                            ReqContext.clearContext();
                            SecurityContextHolder.clearContext();
                        }
                    }
                }, UsernamePasswordAuthenticationFilter.class)

如果不加上注释的代码,此时如果登录人账号状态不是正常状态,则会报错

org.springframework.security.authentication.AccountExpiredException: 用户被冻结或离职
	at com.hikvision.fiit.fpc.core.FpcUserDetailsServiceImpl.loadUserByUsername(FpcUserDetailsServiceImpl.java:65)
	at com.hikvision.fiit.fpc.config.SecurityConfiguration$2.doFilterInternal(SecurityConfiguration.java:104)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
省略...
2024-05-17 14:20:49.902 ERROR 3300 --- [nio-8088-exec-1] c.h.f.f.c.CustomAuthenticationEntryPoint : 认证失败, DF2E00186E1F44ABB579C351F5E70463
org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:189)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:140)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)

loadUserByUsername 中触发抛出异常的代码如下:

        if ("-1".equals(user.getUserState())) {
            throw new LockedException("用户被锁定");
        }
        if ("1".equals(user.getUserState()) || "3".equals(user.getUserState())) {
            throw new AccountExpiredException("用户被冻结或离职");
        }

可以看到,自己抛出的异常 是 AccountExpiredException,但是返回给前端的异常是, 自定义的异常被过滤器链中记录并吞掉了, 修改为了 InsufficientAuthenticationException,并且啥类型的异常都会被改掉。

{
    "uuid": "DF2E00186E1F44ABB579C351F5E70463",
    "code": "AUTHENTICATION_FAILED",
    "message": null,
    "data": null
}

查阅了相关的资料,需要在触发异常后手动调用异常处理类 AuthenticationEntryPoint ,就可以解决了。就是上面被注释的代码,customAuthenticationEntryPoint.commence(request, response, e);
此时再次登录,就能够返回自定义的异常信息了:

{
    "uuid": "A89936FC642A42688D2706BA5B74A21E",
    "code": "USER_NOT_FOUND_ERROR",
    "message": "离职异动",
    "data": null
}

CustomAuthenticationEntryPoint 对应的实现:

@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private final ObjectMapper objectMapper;

    public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        ApiResult<Object> apiResult = new ApiResult<>();
        String uuid = StringUtil.uuid();
        log.error("认证失败, {}", uuid, e);
        apiResult.setUuid(uuid);
        if (e instanceof LockedException) {
            apiResult.setCode(ErrorCode.USER_LOCK_ERROR.name());
            apiResult.setMessage(ErrorCode.USER_LOCK_ERROR.message());
        } else if (e instanceof AccountExpiredException) {
            apiResult.setCode(ErrorCode.USER_NOT_FOUND_ERROR.name());
            apiResult.setMessage(ErrorCode.USER_NOT_FOUND_ERROR.message());
        } else {
            apiResult.setCode(ErrorCode.AUTHENTICATION_FAILED.name());
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
        }
        String json = objectMapper.writeValueAsString(apiResult);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(json);
    }
}

也可以参考这个处理方式,将错误信息放到request中,最后取request中的message取判断哪一种错误,再返回给前端
https://blog.csdn.net/weixin_43120308/article/details/109744070

如果用的是UsernamePasswordAuthenticationFilter
那么可以参照下面的处理方法:
https://blog.csdn.net/wwang_dev/article/details/119108639
主要思路则是重新捕获被重写的异常信息,再抛出来一次

以上,就能解决该问题了~

标签:spring,apiResult,request,过滤器,new,security,异常,response
From: https://www.cnblogs.com/charler/p/18197793

相关文章

  • Spring Boot的常用注解
    在SpringBoot中,注解(Annotation)是核心特性之一,广泛用于配置和简化开发。以下是SpringBoot中一些常用的注解及其示例:1.@SpringBootApplication@SpringBootApplication是一个组合注解,包括了@Configuration、@EnableAutoConfiguration和@ComponentScan。它通常用在主类上,标识一个......
  • SpringBoot给所有的 Model添加属性
    添加全局数据@ControllerAdvice是一个全局数据处理组件,因此也可以在@ControllerAdvice中配置全局数据,使用@ModelAttribute注解进行配置,代码如下: 运行测试结果:  ......
  • Springcloud学习笔记67--springboot 整合 任务调度框架Quartz
    1.背景定时任务Job的作业类中无法注入Service等由Spring容器所管理的Bean。例如下面这种情况,TaskCronJobService就无法成功注入。importjava.util.Iterator;importjavax.annotation.Resource;importorg.quartz.Job;importorg.quartz.JobExecutionContext;importor......
  • springboot集成@DS注解实现数据源切换(转载)
    springboot集成@DS注解实现数据源切换启用@DS实现数据源切换POM内添加核心jar包yml配置"核心"-使用@DS注解最后启用@DS实现数据源切换POM内添加核心jar包 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-start......
  • springboot怎么将List集合数据转成JSON数组
    SpringBoot默认使用Jackson框架将Java对象转换成JSON格式。要转换List集合数据为JSON数组,可以采用以下两种方法:1.使用@ResponseBody注解在SpringBoot中,可以使用@ResponseBody注解标注要返回的List集合数据,让Spring自动将其转换成JSON数组。例如:@GetMapping("/list")@Respo......
  • 关于SpringBoot项目使用Hutool工具进行json序列化时出现Null值过滤或者丢失的问题(转
    ##问题描述:SpringBoot项目中,一直使用的时Hutool的json转换工具,被强制要求不能使用fastJson工具;之前都没什么问题,突然有一次使用parseObj()进行json字符串转换json对象时,突然报错:Noserializerfoundforclasscn.hutool.json.JSONNullandnopropertiesdiscoveredtocreate......
  • Springcloud学习笔记66---@Autowired注入为null的几种情况
    1.在应用的Filter或Listener中使用了@Autowired原因:因为Filter和Listener加载顺序优先于spring容器初始化实例,所以使用@Autowired肯定为null了~~解决:用ApplicationContext根据bean名称(注意名称为实现类而不是接口)去获取bean,随便写个工具类即可2.你写的代码有问题,没加@Service、......
  • Java面试题:Spring中的循环依赖,给程序员带来的心理阴影
    循环依赖通常发生在两个或多个SpringBean之间,它们通过构造器、字段(使用@Autowired)或setter方法相互依赖,从而形成一个闭环。下面是一个使用字段注入(即使用@Autowired)导致的循环依赖的示例: 示例代码: 假设我们有两个类,ClassA和ClassB,它们相互依赖:publicclassClassA{......
  • 教你如何搞定springboot集成kafka
    本文分享自华为云社区《手拉手入门springboot+kafka》,作者:QGS。安装kafka启动Kafka本地环境需Java8+以上Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。Kafka启动方式有Zookeeper和Kraft,两种方式只能选择其中一种启动,不能同时使用......
  • Springboot配置文件Properties密码加密
    1.添加依赖<dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.3</version></dependency>2.启动类添加注解@EnableEncryptableProperties......