4

试图让 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 上发现我可以使用 @service 将类注册为一个 bean,spring 会选择它,但它不起作用。我也尝试通过 AuthenticationManagerBuilder 添加它,但这也不起作用。我的猜测是,它的 jwt 方面有它自己的 UserDetailsS​​ervice ,它已经实现并且优先于我的。也就是说,让我调用的正确方法是什么,或者在身份验证完成后以某种方式手动调用我的用户加载逻辑并覆盖 Principal 对象是否更好?我需要在调用端点之前执行此操作,以便 PreAuthorize 可以检查 UserDetailsS​​ervice 加载的角色。

4

3 回答 3

2

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

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);

        }

这终于做了我需要的

于 2020-10-24T17:21:02.140 回答
1

问题是JwtAuthenticationProvider不调用UserDetailService- 它假定 JWT 具有所有相关的身份验证信息 - 所以没有必要去UserDetailService获取授权等。

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

这是对我有用的代码:

@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
    }
}
于 2021-12-19T04:40:35.620 回答
0

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

// userDetailsService bean
@Autowired
private OauthUsersDetailsServiceImpl oauthUsersDetailsServiceImpl;

// 
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(oauthUsersDetailsServiceImpl);
}
于 2020-10-24T14:35:53.697 回答