oauth2对用户状态进行修改与校验(一)—— 认证成功/失败监听

项目中登录的时候使用的是oauth2获取token进行登录,最近要加入账户锁定机制,连续输错几次密码以后锁定一段时间,如果连续错误次数太多则冻结。

梳理了一下功能:

  1. 用户状态分为四种:正常、暂时封禁、冻结、停用(停用是人员离职一类的,将账号停用)
  2. 登录时,先获取用户状态,若非正常用户则直接提示对应信息
  3. 登录失败时,需要能够进入自定义的失败方法,对用户账户失败次数+1,记录本次登录时间,修改用户状态(若需要)
  4. 登录成功时,将用户状态修改为正常,连续错误次数置0,记录登录时间。

监听登录成功/失败方法

参考链接:spring-security-oauth2 登录或者认证成功后 做一些操作, 比如登录日志

把源码记录一下,主要的认证方法

  • 在ResourceOwnerPasswordTokenGranter类的getOAuth2Authentication方法中获取用户名与密码,进入authenticate方法校验
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

   Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
   // 获取用户名&密码
   String username = parameters.get("username");
   String password = parameters.get("password");
   // 拿完密码就移除
   parameters.remove("password");

   Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
   ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
   try {
      // 在这个方法里面进行用户认证,以及成功/失败的事件发布
      userAuth = authenticationManager.authenticate(userAuth);
   }
   catch (AccountStatusException ase) {
      // ...
   }
   // 省略...
  • 接着进入ProviderManager的authenticate方法
public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
   // ...
   
   // 提供了一系列AuthenticationProvider进行验证
   for (AuthenticationProvider provider : getProviders()) {
      // ....
      try {
      	 // 认证
         result = provider.authenticate(authentication);
         // ...
      }
      catch (AccountStatusException e) {
         // 捕获异常,注意这个方法,里面会发布失败事件
         prepareException(e, authentication);
         throw e;
      }
      catch (InternalAuthenticationServiceException e) {
         // 捕获异常,注意这个方法,里面会发布失败事件
         prepareException(e, authentication);
         throw e;
      }
      catch (AuthenticationException e) {
         lastException = e;
      }
   }
   
   // ...
   
   if (result != null) {
      // ...
      if (parentResult == null) {
          // 发布登录成功事件
          eventPublisher.publishAuthenticationSuccess(result);
      }
      return result;
   }
   throw lastException;
}

@SuppressWarnings("deprecation")
private void prepareException(AuthenticationException ex, Authentication auth) {
   // 在此处发布认证失败事件
   eventPublisher.publishAuthenticationFailure(ex, auth);
}

由于这两个事件的发布,因此可以实现ApplicationListener来获取到登录成功与失败

登录成功:
@Component
public class SuccessEvent implements ApplicationListener<AuthenticationSuccessEvent> {
    @Autowired
    private UserDetailsServiceMapper userDetailsServiceMapper;

    /**
     * 获取token成功事件
     * @param event
     */
    @Override
    public void onApplicationEvent(AuthenticationSuccessEvent event) {
        if (!event.getSource().getClass().getName().equals("org.springframework.security.authentication.UsernamePasswordAuthenticationToken")) {
            return;
        }

        if (event.getAuthentication().getPrincipal() instanceof JdbcUserDetails) {
            // 这个方式稍微有一点麻烦,不止是登陆事件,一些其他的事件,包括自定义的filter也可能进入该方法
            // 所以需要这些判断保证只有用户登录成功才进入这里。
            JdbcUserDetails userDetails = (JdbcUserDetails) event.getAuthentication().getPrincipal();
            User user = userDetailsServiceMapper.selectUserById(Long.valueOf(userDetails.getUserId()));
	    // 更新用户状态
            updateUserAccountById(user);
        }
    }
}

登录失败与成功类似,可以直接implement失败事件,这里换了种方式,直接监听AuthenticationFailureBadCredentialsEvent 异常。

  • 有点神奇,没有仔细看源码,感觉上是filter与oauth是分了两部分的。在自定义filter中抛出该异常并不会被监听到,需要继承BasicErrorController来捕获,这里不写了,只有获取token相关流程的异常会被这个类监听到。
登录失败
@Component
public class failureEvents {
    /**
     * @param failure
     */
    @EventListener
    public void onFailure(AuthenticationFailureBadCredentialsEvent failure) {
        if (failure == null) {
            return;
        }
        if (failure.getSource() instanceof UsernamePasswordAuthenticationToken) {
            UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) failure.getSource();
            LinkedHashMap details = (LinkedHashMap) authenticationToken.getDetails();
            // 更新用户状态
            updateUserAccount(username);
        }
    }
}
后面还有一部分,是发送请求后,应该第一步做用户状态的检查及异常返回,这里踩坑了,有空再写~
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值