改造 Spring Boot 的 WebSecurityConfigurerAdapter 根据条件进行不同的验证

305 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情


需求

一个现有的 WEB 网站,用 Spring Boot 做的,验证是采用的 Spring Security 。
现在想改造成,能在 APP 的 Webview 中也能访问。

访问前先用 Token 获得一个 SessionID ,根据这个 SessionID 再访问现有的 WEB 页面。
前提是 APP 端采用了 Token 认证 ,所以只在最开始登录一次。
请求 SessionID 时会在 Header 里带上 Token 信息,不会再次使用用户名和密码。后面不再登录。
服务端只能拿到 Token 后,再去获取用户名和密码。

获得 SESSION ID 的处理是在和现有系统不同的服务器上的 API 服务。 通过身段验证后的 SESSION 才能正常访问 WEB 页面。 那如何通过身份验证后获得 Session ,还能不影响现在的处理?

问题

现有的系统保存密码时使用的是 BCryptPasswordEncoder ,采用的是 Hash 算法,把密码算出一个哈希值再保存到数据库里。
身份验证时,将接收到的密码再用同样的算法计算后和数据库里保存的密码进行比较。

这种方式算出的密码是不可逆的,不可能通过 Token 取到数据库里的密码再解密出原始密码,然后再拿着用户名和原始密码再去验证身份。

折腾

怎么搞?
还是没有方向,DaoAuthenticationProvider 的定制里没有 Request ,也无法根据请求过来的信息进行分支判断。

现在搞来搞去,快崩溃的时候,发现了一个方法。
有以下几个要点:

  1. 扩展 UsernamePasswordAuthenticationFilter 类里的 attemptAuthentication 方法。
    这个方法的参数里有 HttpServletRequest request ,那就能拿到请求的数据然后进行不同的分支处理了。

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
    
  2. UsernamePasswordAuthenticationToken 有两个参数和三个参数的构造方法。
    现有的处理使用的是两个参数的构造方法,然后改成使用三个参数的构造方法。

    public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)
    
  3. 扩展 DaoAuthenticationProvideradditionalAuthenticationChecks 方法(该方法都要废弃了,不过没办法,现有的系统一直在运用着)。
    验证身份时,根据 UsernamePasswordAuthenticationToken 里的信息进行分支处理。

    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
      // 根据条件进行分支处理。
      if(UsernamePasswordAuthenticationToken 的 authorities 是 null 或 空数组) {
        // 现有身份验证处理
      } else {
        // 取出 authentication 的第三个参数进行自定义的验证处理。
      }
    }
    
  4. 注册扩展的类。

    创建 UsernamePasswordAuthenticationFilter 扩展类对象的方法。
    这里假设扩展类的类名是 XxxUsernamePasswordAuthenticationFilter

    @Bean  
    XxxUsernamePasswordAuthenticationFilter newUsernamePasswordAuthenticationFilter() throws Exception {
      XxxUsernamePasswordAuthenticationFilter xxxFilter = new XxxUsernamePasswordAuthenticationFilter();
      xxxFilter.setAuthenticationSuccessHandler(successHandler);
      xxxFilter.setAuthenticationFailureHandler(failureHandler);
      xxxFilter.setFilterProcessesUrl("/validate");
      xxxFilter.setAuthenticationManager(authenticationManagerBean());
      return xxxFilter;
    }
    

    successHandler 里可以根据不同的分支返回不同的响应结果。 API 接收到请求后解析 Cookie 里的 SESSION 即可。

    注册 DaoAuthenticationProvider 的扩展类。
    这里假设是 XxxAuhthenticationProvider

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.authenticationEventPublisher(DefaultAuthenticationEventPublisher()).authenticationProvider(XxxAuhthenticationProvider());
    }
    

    注册 UsernamePasswordAuthenticationFilter 扩展类的对象:

        @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAt(newUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .xxxxx()
        ....... ;
    }