为什么 PermissionEvaluator 中的 Serializable targetId 在某些情况下会为 null 而在其他情况下不会?

Why would the Serializable targetId in PermissionEvaluator be null in some cases and not others?

我们正在按如下方式实现 PermissionEvaluator,它会抛出一个 NullPointerException(注释为 "NPE HERE"):

public class MethodsPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Object targetDomainObject,
                                 Object permission) {
        Object principle = authentication.getPrincipal();
        if (!(principle instanceof UserDetailsAdapter)) {
            return false;
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Serializable targetId,
                                 String targetType,
                                 Object permission) {

        Object principle = authentication.getPrincipal();
        if (!(principle instanceof UserDetailsAdapter)) {
            return false;
        }
        Account account = ((UserDetailsAdapter)principle).getAccount();
       // dashboard_get permission: ensure user is manager and their company ID is the same.
       if ("dashboard_get".equals(permission)) {
           if (account instanceof Manager) {
               Company company = ((Manager)account).getCompany();
               if (company != null && company.getId().intValue() == ((Integer)targetId).intValue()) { //<-- NPE HERE
                  return true;
               }
            }
       }
      //...redacted other checks, logging
}

生成的堆栈跟踪显示第 54 行:

Mar 26, 2015 8:04:37 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [Spring MVC Dispatcher Servlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException
at com.company.web.security.MethodsPermissionEvaluator.hasPermission(MethodsPermissionEvaluator.java:54)
at org.springframework.security.access.expression.SecurityExpressionRoot.hasPermission(SecurityExpressionRoot.java:140)

看了之后,targetId 似乎是空的。但是,此代码的原作者能够毫无问题地 运行 此代码。我尝试对我们的回购协议进行比较,但没有发现任何有意义的东西。我还搜索了 SO 和 google,但没有找到太多信息。

Serializable targetId 保存在哪里?为什么它在某些情况下为 null 而在其他情况下不是?

考虑到这可能是我实现 UserDetails 的方式,这里是相关部分。它们基于 Wheeler 和 White 在 Spring in Practice 中的实施。

我正在按如下方式实现 UserDetails:

public class UserDetailsAdapter implements UserDetails {

    public UserDetailsAdapter(Account account) {
       this.account = account;
    }

    public Account getAccount() {
       return account;
    }

    public Integer getId() {
       return account.getId();
    }

    public String getFirstName() {
       return account.getFirstName();
    }

    public String getLastName() {
       return account.getLastName();
    }

    public String getEmail() {
       return account.getEmail();
    }

    @Override
    public String getUsername() {
       return account.getEmail();
    }

    @Override
    public String getPassword() {
       return account.getPassword();
    }

    @Override
    public boolean isAccountNonExpired() {
       return true;
    }

    @Override
    public boolean isAccountNonLocked() {
       return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
       return true;
    }

    @Override
    public boolean isEnabled() {
       return account.isEnabled();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
       Set<GrantedAuthority> authorities = new HashSet<>();
       for (Role role : account.getRoles()) {
         authorities.add(new SimpleGrantedAuthority(role.getName()));
       }
       return authorities;
    }

    private Account account;

    private static final long serialVersionUID = 2251948358105443813L;

    @Override
    public String toString() {
        return "UserDetailsAdapter [account=" + account + ", firstName=" +
        account.getFirstName() + ", lastName=" + account.getLastName() + "]";
    }

}

和实现:

@Service("myUserDetailsService") // need a specific instance for Remember Me functionality
@Transactional(readOnly = false)
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String email)
           throws UsernameNotFoundException {

        List<Account> accounts = accountDao.findByEmail(email); 
        Account account = null;
        for (Account possibleAccount : accounts) {
            if (possibleAccount.isEnabled()) {
                 account = possibleAccount;
            }
        }
        if (account == null) {
            Integer accountId = otherService.copyToAccount(email);
            account = accountDao.find(accountId);

            if (account == null) {
                throw new UsernameNotFoundException(email+ " is not registered.");
            }
        }

        Collection<Role> roles = account.getRoles();

        if (roles.isEmpty()) {
            throw new UsernameNotFoundException("User " + email+ " has no authorities");
        }
        return new UserDetailsAdapter(account);
   }
  //...redacted autowiring and logging
}

以及布线:

<authentication-manager >
   <authentication-provider user-service-ref="myUserDetailsService">
    <password-encoder ref="passwordEncoder" />
   </authentication-provider>
</authentication-manager>

<beans:bean id="myUserDetailsService"
         class="com.company.service.UserDetailsServiceImpl" />

<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<global-method-security secured-annotations="enabled" pre-post-annotations="enabled">
    <expression-handler ref="expressionHandler"/>
</global-method-security>

<beans:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <beans:property name="permissionEvaluator">
        <beans:bean id="permissionEvaluator" class="com.company.web.security.MethodsPermissionEvaluator"/>
    </beans:property>
</beans:bean>

这是Spring3.2.4.RELEASE.

更新 - 显示对象已持久化:

我正在通过标准的 CRUD DAO 实现持久化公司对象:

@Override
public Company create() {
   Company company = new Company();
   company.setCreationDate(new Date());
   save(company);
   return company;
}

在@Controller中:

@RequestMapping(value = {"/company"}, method = RequestMethod.GET)
public String createForm(Model model) {
    model.addAttribute("company", companyService.createCompany());
    return "company";
}

在@Service 中:

public Company createCompany() {
     return companyDao.create();
}

我看到它保存在数据库中并分配了一个 ID。

我们的开发人员能够通过在编译时将调试信息添加到我们的构建(在我们的例子中是一只蚂蚁 build.xml)来纠正这个问题。

例如在调试级别中包含 "vars":

  <target name="compile" depends="init"
          description="compile the source">
    <javac srcdir="${src}" destdir="${build}" source="1.7" target="1.7"
       nowarn="true" debug="true" debuglevel="lines,source,vars"
       includeantruntime="true" deprecation="${deprecation}">
      <compilerarg value="-Xbootclasspath/p:${toString:pathing.classpath}"/>
    </javac>
    <javac srcdir="${project_dir}/src/test" destdir="${build}" source="1.7" target="1.7"
       nowarn="true" debug="true" debuglevel="lines,source,vars"
       includeantruntime="true" deprecation="${deprecation}">
      <compilerarg value="-Xbootclasspath/p:${toString:pathing.classpath}"/>
    </javac>
  </target>

认为 Maven 的解决方案类似 pom.xml