web-dev-qa-db-ja.com

Spring Securityではアクセスは常に拒否されます-DenyAllPermissionEvaluator

Spring BootアプリケーションでACLを構成しました。 ACL構成は次のとおりです。

_@Configuration
@ComponentScan(basePackages = "com.company")
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ACLConfigration extends GlobalMethodSecurityConfiguration {

    @Autowired
    DataSource dataSource;

    @Bean
    public EhCacheBasedAclCache aclCache() {
        return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
    }

    @Bean
    public EhCacheFactoryBean aclEhCacheFactoryBean() {
        EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
        ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
        ehCacheFactoryBean.setCacheName("aclCache");
        return ehCacheFactoryBean;
    }

    @Bean
    public EhCacheManagerFactoryBean aclCacheManager() {
        return new EhCacheManagerFactoryBean();
    }

    @Bean
    public DefaultPermissionGrantingStrategy permissionGrantingStrategy() {
        ConsoleAuditLogger consoleAuditLogger = new ConsoleAuditLogger();
        return new DefaultPermissionGrantingStrategy(consoleAuditLogger);
    }

    @Bean
    public AclAuthorizationStrategy aclAuthorizationStrategy() {
        return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ACL_ADMIN"));
    }

    @Bean
    public LookupStrategy lookupStrategy() {
        return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
    }

    @Bean
    public JdbcMutableAclService aclService() {
        return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
    }

    @Bean
    public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
        return new DefaultMethodSecurityExpressionHandler();
    }

    @Override
    public MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler = defaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
        expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
        return expressionHandler;
    }
}
_

参照:

セキュリティ構成は次のとおりです。

_@Configuration
@EnableWebSecurity
public class CustomSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public AuthenticationEntryPoint entryPoint() {
        return new LoginUrlAuthenticationEntryPoint("/authenticate");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/authenticate/**").permitAll()
                .anyRequest().fullyAuthenticated()
                .and().requestCache().requestCache(new NullRequestCache())
                .and().addFilterBefore(authenticationFilter(), CustomUsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }

    @Bean
    public CustomUsernamePasswordAuthenticationFilter authenticationFilter()
            throws Exception {
        CustomUsernamePasswordAuthenticationFilter authenticationFilter = new CustomUsernamePasswordAuthenticationFilter();
        authenticationFilter.setUsernameParameter("username");
        authenticationFilter.setPasswordParameter("password");
        authenticationFilter.setFilterProcessesUrl("/authenticate");
        authenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
        authenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
        authenticationFilter.setAuthenticationManager(authenticationManagerBean());
        return authenticationFilter;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
_

私のCustomAuthenticationProviderクラス:

_@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UsersService usersService;

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        User user = usersService.findOne(username);

        if(user != null && usersService.comparePassword(user, password)){

            return new UsernamePasswordAuthenticationToken(
                    user.getUsername(),
                    user.getPassword(),
                    AuthorityUtils.commaSeparatedStringToAuthorityList(
                            user.getUserRoles().stream().collect(Collectors.joining(","))));
        } else {
            return null;
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
_

これが私のCustomUsernamePasswordAuthenticationTokenです。

_public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        if(!request.getMethod().equals("POST"))
            throw new AuthenticationServiceException(String.format("Authentication method not supported: %s", request.getMethod()));

        try {

            CustomUsernamePasswordAuthenticationForm form = new ObjectMapper().readValue(request.getReader(), CustomUsernamePasswordAuthenticationForm.class);

            String username = form.getUsername();
            String password = form.getPassword();

            if(username == null)
                username = "";

            if(password == null)
                password = "";

            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);

            setDetails(request, token);

            return getAuthenticationManager().authenticate(token);

        } catch (IOException exception) {
            throw new CustomAuthenticationException(exception);
        }
    }

    private class CustomAuthenticationException extends RuntimeException {
        private CustomAuthenticationException(Throwable throwable) {
            super(throwable);
        }
    }
}
_

上記とは別に、CustomAuthenticationFailureHandlerCustomAuthenticationSuccessHandlerCustomNoRedirectStrategyおよびCustomUsernamePasswordAuthenticationFormがありますが、この質問の長さのためにスキップしました。

そして、私は here で見つかるMySQLスキーマを使用しています。

次のように、ACL関連のテーブルにエントリを追加しています。

_INSERT INTO acl_class VALUES (1, com.company.project.domain.users.User)
INSERT INTO acl_sid VALUES (1, 1, "demo")
_

(ユーザー名demoのユーザーがいます)

_INSERT INTO acl_object_identity VALUES (1, 1, 1, NULL, 1, 0)
INSERT INTO acl_entry VALUES (1, 1, 1, 1, 1, 1, 1, 1)
_

しかし、私が得ているすべては:

_Denying user demo permission 'READ' on object com.company.project.domain.users.User@4a49e9b4
_

私の中で

_@PostFilter("hasPermission(filterObject, 'READ')")
_

私はここでいくつかの問題を疑っています:

  1. hasPermission式:「READ」と「1」で置き換えましたが、範囲はありません。
  2. データベースのエントリが正しくありません
  3. カスタム権限エバリュエーターを実装していません。これは必須ですか、それともexpressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));で十分ですか?

更新

_@PostFilter_が使用されるサンプルメソッド:

_@RequestMapping(method = RequestMethod.GET)
    @PostFilter("hasPermission(filterObject, 'READ')")
    List<User> find(@Min(0) @RequestParam(value = "limit", required = false, defaultValue = "10") Integer limit,
                    @Min(0) @RequestParam(value = "page", required = false, defaultValue = "0") Integer page,
                    @RequestParam(value = "email", required = false) String email,
                    @RequestParam(value = "firstName", required = false) String firstName,
                    @RequestParam(value = "lastName", required = false) String lastName,
                    @RequestParam(value = "userRole", required = false) String userRole) {

        return usersService.find(
                limit,
                page,
                email,
                firstName,
                lastName,
                userRole);
    }
_

更新#2:

この質問は、認証/承認/ ACLに関して設定されたすべてを反映しています。

更新#3:

私は今問題を解決するために非常に近いです、残っている唯一のものはこれを解決することです:

https://stackoverflow.com/questions/42996579/custom-permissionevaluator-not-called-although-set-as-permissionevaluator-deny

誰かがその質問で私を助けることができれば、私はこれを解決するために私が経験したことを最終的に書くことができます。

20
Hasan Can Saral

ここに待望の答えがあります:

documentation は以下を明確に説明しています:

HasPermission()式を使用するには、アプリケーションコンテキストでPermissionEvaluatorを明示的に構成する必要があります。これは次のようになります。

基本的に私はAclConfigurationを拡張するGlobalMethodSecurityConfigurationで実行していました:

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
        expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
        return expressionHandler;
    }

これはSpringによって処理されていませんでした!

AclConfigGlobalMethodSecurityConfigurationを分離する必要がありました。ある場合@Beansは後者で定義されており、上記のメソッドは処理されていません。バグである可能性があります(そうでない場合は、件名の説明は大歓迎です)。

3
Hasan Can Saral

Spring Security 4.2.1.RELEASEを使用するようにアプリケーションをアップグレードした後、アップグレード前に正常に機能していたすべての_@PreAuthorize_アノテーション付きメソッドで予期しないアクセスが拒否されるようになりました。私は春のセキュリティコードをデバッグしましたが、以下のコードに示すように、デフォルトのプレフィックスを空に設定したという事実に関係なく、チェックされるすべてのロールにデフォルトの文字列「ROLE_」がプレフィックスされていることが問題であることに気付きました。

_auth.ldapAuthentication()
        .groupSearchBase(ldapProperties.getProperty("groupSearchBase"))
        .groupRoleAttribute(ldapProperties.getProperty("groupRoleAttribute"))
        .groupSearchFilter(ldapProperties.getProperty("groupSearchFilter"))

        //this call used to be plenty to override the default prefix
        .rolePrefix("")

        .userSearchBase(ldapProperties.getProperty("userSearchBase"))
        .userSearchFilter(ldapProperties.getProperty("userSearchFilter"))
        .contextSource(this.ldapContextSource);
_

すべてのコントローラーメソッドには@PreAuthorize("hasRole('my_ldap_group_name')")の注釈が付けられていますが、フレームワークは空のロールプレフィックス設定を考慮に入れていなかったため、代わりにROLE_my_ldap_group_nameを使用して実際のロールをチェックしていました。

フレームワークのコードを深く掘り下げた後、クラス_org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler_のデフォルトのロールプレフィックスが_"ROLE_"_に設定されていることに気付きました。その値のソースを追跡したところ、最初に_org.springframework.security.config.core.GrantedAuthorityDefaults_の最初の初期化中に、クラス_org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer_の宣言されたBeanをチェックしてデフォルトのプレフィックスを探すことがわかりました。この初期化子Beanは宣言されたものを見つけることができませんでした。前述のデフォルトの接頭辞を使用しました.

これは予期された動作ではないと思います:Spring SecurityはldapAuthenticationの同じrolePrefixを検討する必要がありましたが、この問題を解決するには、アプリケーションコンテキストにBean _org.springframework.security.config.core.GrantedAuthorityDefaults_を追加する必要がありました(アノテーションベースを使用しています)構成)、次のように:

_@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CesSecurityConfiguration extends WebSecurityConfigurerAdapter {

    private static final String ROLE_PREFIX = "";
    //... ommited code ...
    @Bean
    public GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults(ROLE_PREFIX);
    }

}
_

たぶん同じ問題が発生しているでしょう-DefaultMethodSecurityExpressionHandlerを使用していて、GrantedAuthorityDefaults Beanも使用しているので、私と同じSpring Securityバージョンを使用している場合-4.2.1.RELEASE同じ問題。

6
s_bighead

DB内のデータと構成は適切に見えます。私はいつも@PostFilter("hasPermission(filterObject, 'READ')")を使っています。

UserDetailsを拡張するユーザークラスが、dbにあるgetUsername()を介して同じユーザー名を返していることを確認します。セキュリティとアプリが同じコンテキストにあることを確認することと一緒に。

hasPermission メソッドは Authentication オブジェクトを1番目のパラメーターとして取得します。

boolean hasPermission(Authentication authentication,
                      Object targetDomainObject,
                      Object permission)

Authenticationオブジェクトは、通常 sernamePasswordAuthenticationToken の実装クラスです。そのため、getPrincipal()メソッドは、DBと同じものを返すgetUserName()メソッドを持つオブジェクトを返す必要があります。

PrincipalSid を見てください。

public PrincipalSid(Authentication authentication) {
    Assert.notNull(authentication, "Authentication required");
    Assert.notNull(authentication.getPrincipal(), "Principal required");

    if (authentication.getPrincipal() instanceof UserDetails) {
        this.principal = ((UserDetails) authentication.getPrincipal()).getUsername();
    }
    else {
        this.principal = authentication.getPrincipal().toString();
    }
}
0
denov