为什么 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
。
我们正在按如下方式实现 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
。