Spring 启动 Oauth2 资源服务器 UserDetailsS​​ervice

Spring Boot Oauth2 Resource Server UserDetailsService

正在尝试让 UserDetailsS​​ervice 为我设置的 oauth2 资源服务器工作。我能够成功验证 jwt,但我似乎没有做任何事情来调用 loadUserByUsername 方法。这最初是使用 SAML 并且可以正常工作,但现在我切换到 Oauth2 并且无法正常工作。

     @Service
     public class OauthUsersDetailsServiceImpl implements UserDetailsService{
         @Override
         public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
             //some user loading junk here - this is never called
         }
     }
     @Configuration
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        @EnableWebSecurity
         public class SecurityConfig extends WebSecurityConfigurerAdapter {
            
            @Override
            protected void configure(HttpSecurity http) throws Exception
            {
                //test key for now
                SecretKeySpec key = new SecretKeySpec("private key0000000000000000000000000000000".getBytes(), "HMACSHA256");
                

                http
                    .authorizeRequests()
                    .antMatchers(/*some endpoints im excluding from auth - this all works*/)
                    .permitAll().and()
                    .authorizeRequests()
                    .anyRequest().authenticated().and()
                    .oauth2ResourceServer().jwt().decoder(NimbusJwtDecoder.withSecretKey(key).build());
            }
         }

我发现 google 可以将 class 注册为 @service 的 bean,而 spring 只会将其拾取,但它不起作用。我也尝试通过 AuthenticationManagerBuilder 添加它,但这也不起作用。我的猜测是,这方面的 jwt 有自己的 UserDetailsS​​ervice,它实现了并且优先于我的。也就是说,让我调用的正确方法是什么,还是在身份验证完成后以某种方式手动调用我的用户加载逻辑并覆盖 Principal 对象更好?我需要在调用端点之前发生这种情况,以便 PreAuthorize 可以检查由 UserDetailsS​​ervice 加载的角色。

您需要注册 UserDetailsS​​ervice 实现,然后由 DaoAuthenticationProvider 使用

// userDetailsService bean
@Autowired
private OauthUsersDetailsServiceImpl oauthUsersDetailsServiceImpl;

// 
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(oauthUsersDetailsServiceImpl);
}

想通了。希望这会帮助遇到同样问题的任何人。我不得不在链中添加一个自定义过滤器来调用我的用户详细信息服务并覆盖上下文:

public class Oauth2AuthorizationFilter extends GenericFilterBean {

        @Autowired
        private OauthUsersDetailsServiceImpl oauthUsersDetailsServiceImpl;
      
      public Oauth2AuthorizationFilter (OauthUsersDetailsServiceImpl userDetailsService) {
        this.oauthUsersDetailsServiceImpl = userDetailsService;
      }
      
      
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {

        SecurityContext context = SecurityContextHolder.getContext();
        if(context.getAuthentication() != null && !(context.getAuthentication().getPrincipal() instanceof Users)) {
          
          UserDetails user = oauthUsersDetailsServiceImpl.loadUserByUsername(((Jwt)context.getAuthentication().getPrincipal()).getClaimAsString("user_name")); 
          UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
          context.setAuthentication(authentication);
        }
        
        chain.doFilter(request, response);
      }

    }
@Override
        protected void configure(HttpSecurity http) throws Exception
        {
            //test key for now
            SecretKeySpec key = new SecretKeySpec("private key0000000000000000000000000000000".getBytes(), "HMACSHA256");
            
            http.authorizeRequests().antMatchers(/*bunch of junk...*/).permitAll().and().authorizeRequests().anyRequest().authenticated().and()
                .oauth2ResourceServer().jwt().decoder(NimbusJwtDecoder.withSecretKey(key).build());
            
            http.addFilterAfter(jwtAuthTokenFilterBean(), SwitchUserFilter.class);

        }

这终于满足了我的需要

问题是 JwtAuthenticationProvider 不调用 UserDetailService - 它假定 JWT 具有所有相关的身份验证信息 - 因此无需去 UserDetailService 获取当局等.

因此,您要做的是构建一个 JWT/Token 转换器,从 jwt 中提取用户名并使用 DaoAuthenticationProvider 进行身份验证(这将调用您的 UserDetailsService)。此外,由于密码将为空,因此您必须使用具有 noop additionalAuthenticationChecks 方法的版本覆盖 DaoAuthenticationProvider

这是适合我的代码:

@Configuration
class OAuthSecurityConfiguration() :
    WebSecurityConfigurerAdapter() {

    /*
    Override the default DaoAuthenticationProvider to prevent password validity checks since they will not be set
     */
    @Bean
    fun daoAuthenticationProvider(userDetailsService: UserDetailsService): DaoAuthenticationProvider {
        val daoAuthenticationProvider = object : DaoAuthenticationProvider() {
            override fun additionalAuthenticationChecks(
                userDetails: UserDetails,
                authentication: UsernamePasswordAuthenticationToken
            ) {
                // Do nothing as the password will be set to null
            }
        }
        daoAuthenticationProvider.setUserDetailsService(userDetailsService)
        return daoAuthenticationProvider
    }

    override fun configure(http: HttpSecurity) {
        http
            .authorizeRequests()
            .regexMatchers(
                "/customers.*",
                "/accounts.*",
                "/administrators.*"
            )
            .authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter { jwt ->
                convertJwtToUsernamePasswordToken(jwt)
            }
    }

    private fun convertJwtToUsernamePasswordToken(
        jwt: Jwt
    ): AbstractAuthenticationToken {
        val username = jwt.getClaimAsString("username") // whichever claim you use to transmit the lookup key in the token
        val userPasswordToken = UsernamePasswordAuthenticationToken(username, null)
        return authenticationManager().authenticate(userPasswordToken) as AbstractAuthenticationToken
    }
}