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

时间:2015-03-27 17:16:09

标签: spring spring-security

我们正在实施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和谷歌,但没有发现很多信息。

Serializable targetId在哪里持久存在?为什么在某些情况下它会为空而在其他情况下为空?

认为可能是我实现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() + "]";
    }

}

和Impl:

@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>

这是Spring 3.2.4.RELEASE。

更新 - 显示对象已保留:

我通过标准CRUD DAO impl持续公司对象:

@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的数据库中持续存在。

1 个答案:

答案 0 :(得分:0)

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

例如,在debuglevel中包含“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类似。