为可信空间定制Spring Security

Ser*_*gii 2 java configuration spring-mvc spring-security spring-boot

服务在可信空间中的网关之后工作(gateWay验证OAuth令牌并仅向服务提供唯一的用户ID,其他情况下它重定向以验证服务).

我想在服务中使用spring security来验证userId的权限.

所以我补充道 CustomUserDetailsService

@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
    @Autowired(required = false)
    private ContextSsoActiveProfileIdProvider contextSsoActiveProfileIdProvider;
    @Autowired
    private GrantedAuthorityService grantedAuthorityService;

    @Override
    public User loadUserByUsername(final String username) throws UsernameNotFoundException {
        // verify it with authentication service, but there is not token, userId only, so trust to gateway service.
        return new User(
                String.valueOf(contextSsoActiveProfileIdProvider.getSsoActiveProfileId()),
                "authenticatedWithGateWay",
                grantedAuthorityService.getGrantedAuthoritiesForCurrentUser()
        );
    }
}

其中contextSsoActiveProfileIdProvider.getSsoActiveProfileId()返回uniqueUserId和grantedAuthorityService.getGrantedAuthoritiesForCurrentUser()返回权限.

该服务在受信任区域中启动,因此我已在下一步配置安全性:

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll();
    }

    @Override
    protected UserDetailsService userDetailsService() {
        return userDetailsService;
    }
}

我需要为所有用户提供免费接入(不触发登录报价)所有的URI( http.authorizeRequests().antMatchers("/**").permitAll();),但它似乎抑制触发处理程序下一个注解@PreAuthorize,@PreFilter,@PostAuthorize@PostFilter.

我想我在这里http.authorizeRequests().antMatchers("/**").permitAll();或与其他配置部分有误.

更多问题症状:

  • CustomUserDetailsService.loadUserByUsername(..) 永远不会被称为;
  • 在REST API部分@AuthenticationPrincipal User activeUser为null
  • 在REST API部分Principal principal也是null

Ser*_*gii 5

可信空间问题与匿名用户识别有类似的解决方案(我在处理它时已经做了这个结论.)

简短的回答

受信任的空间并不需要授权,但没有一个UserDetailsS​​ervice会被调用,因为只使用的,AnonymousAuthenticationProvider并且AnonymousAuthenticationFilter在默认情况下.基于AnonymousAuthenticationFilter覆盖createAuthenticationAnonymousAuthenticationFilter使用自定义one(CustomAnonymousAuthenticationFilter)替换default()是足够好的实现自定义过滤器:

    @Configuration
    public static class NoAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
        @Autowired
        private UserDetailsService userDetailsService;
        @Autowired
        private IdentifiableAnonymousAuthenticationFilter identifiableAnonymousAuthenticationFilter;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.anonymous().authenticationFilter(identifiableAnonymousAuthenticationFilter);
            http.antMatcher("/**").authorizeRequests()
                    .anyRequest().permitAll();
        }
    }

完整答案

我发现如果用户未经授权,将永远不会调用CustomUserDetailsS​​ervice.持续研究关注AnonymousAuthenticationFilter,它负责创建匿名用户信息.因此,目的是用我的IdentifiableAnonymousAuthenticationFilter替换 AnonymousAuthenticationFilter,其中应该覆盖一些方法:

@Component
public class IdentifiableAnonymousAuthenticationFilter extends AnonymousAuthenticationFilter {
    public static final String KEY_IDENTIFIABLE_ANONYMOUS_AUTHENTICATION_FILTER
            = "Key.IdentifiableAnonymousAuthenticationFilter";
    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private GrantedAuthorityService grantedAuthorityService;
    private AuthenticationDetailsSource authenticationDetailsSource
            = new WebAuthenticationDetailsSource();

    public IdentifiableAnonymousAuthenticationFilter() {
        this(KEY_IDENTIFIABLE_ANONYMOUS_AUTHENTICATION_FILTER);
    }

    public IdentifiableAnonymousAuthenticationFilter(String key) {
        super(key);
    }

    @Override
    protected Authentication createAuthentication(HttpServletRequest request) {
        AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(
                KEY_IDENTIFIABLE_ANONYMOUS_AUTHENTICATION_FILTER,
                userDetailsService.loadCurrentUser(request),
                grantedAuthorityService.getGrantedAuthoritiesForCurrentUser());
        auth.setDetails(authenticationDetailsSource.buildDetails(request));
        return auth;
    }
}

将其注入配置中

@Configuration
public class IdentifyAnonymousConfigurationAdapter extends WebSecurityConfigurerAdapter {
    @Autowired
    private IdentifiableAnonymousAuthenticationFilter identifiableAnonymousAuthenticationFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.anonymous().authenticationFilter(identifiableAnonymousAuthenticationFilter);
    // ... some other configurations
    }
}
Run Code Online (Sandbox Code Playgroud)

现在看起来好多了,因为在AnonymousConfigurer中注入了identifiableAnonymousAuthenticationFilter.请注意基于的配置.如果你有几个,其中一个不会设置customAnonymousAuthenticationFilter但配置早于自定义..你将获得AnonymousAuthenticationFilter的默认实例(默认情况下配置):WebSecurityConfigurerAdapterWebSecurityConfigurerAdapter

  protected final HttpSecurity getHttp() throws Exception {
      //...
      http
        .csrf().and()
        .addFilter(new WebAsyncManagerIntegrationFilter())
        .exceptionHandling().and()
        .headers().and()
        .sessionManagement().and()
        .securityContext().and()
        .requestCache().and()
        .anonymous().and()
      // ...

如果应用程序已修复,我会关心它,但AnonymousAuthenticationFilter早于IdentifiableAnonymousAuthenticationFilter调用.而doFilter的付诸SecurityContextHolder中 incorrect的认证.

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    if(SecurityContextHolder.getContext().getAuthentication() == null) {
        SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req));
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
        }
    } else if(this.logger.isDebugEnabled()) {
        this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
    }

    chain.doFilter(req, res);
}
Run Code Online (Sandbox Code Playgroud)

因此,当下次为IdentifiableAnonymousAuthenticationFilter调用doFilter时,它不会替换因为条件(请参阅之前的方法).Authenticationif(SecurityContextHolder.getContext().getAuthentication() == null)

因此,提供配置,WebSecurityConfigurerAdapter使用魔术注释@Order来管理配置加载顺序,可以提供配置.

警告

或者有人会想 - doFilter在没有条件的情况下添加覆盖IdentifiableAnonymousAuthenticationFilter(它是黑客):

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req));
        if (logger.isDebugEnabled()) {
            logger.debug("Populated SecurityContextHolder with anonymous token: '"
                    + SecurityContextHolder.getContext().getAuthentication() + "'");
        }
        chain.doFilter(req, res);
    }

如果你需要处理授权/认证用户的弹簧安全性是不可接受的,但在某些情况下它就足够了.

PS

解决方案的某些部分可以改进,但我希望这个想法一般都很明确.