I'm using Spring 4.1.7.RELEASE with Spring Security 4.0.2.RELEASE.
...
<global-method-security pre-post-annotations="enabled" />
<http auto-config="true" use-expressions="true">
<form-login login-page="/user/login" />
<logout invalidate-session="true" logout-success-url="/user/login?logout"/>
<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" />
<intercept-url pattern="/user/login**" access="permitAll" />
<intercept-url pattern="/login**" access="permitAll" />
<intercept-url pattern="/user/create" access="hasAuthority('user_access_full')"/>
<csrf />
</http>
<authentication-manager>
<authentication-provider user-service-ref="myUserDetailsService" >
<password-encoder hash="bcrypt" />
</authentication-provider>
</authentication-manager>
<beans:bean id="myUserDetailsService" class="my.service.MyUserDetailsService" autowire="byType"/>
...
I want to use @PreFilter
to filter the user service input for assigning roles to the new user.
@Entity
public class JRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@NotEmpty
@Column(nullable = false, unique = true)
private String name;
private String displayName;
public JRole(){
}
public JRole(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
...
@Transactional
@PreFilter(value = "hasRole('admin') or (filterObject.name!='admin' and filterObject.name!='office_admin')")
public void create(JUser user, Set<JRole> roles) {
JUser u2 = userDao.findByUsername(user.getUsername());
if (u2 != null) {
throw new MessageException("username duplicate!");
}
if (roles == null || roles.isEmpty()) {
throw new MessageException("empty roles");
}
user.setRoles(roles);
doCreate(user);
}
When I call the above method if my controller:
@RequestMapping(value = "/create", method = RequestMethod.POST)
public String create2(Model model, @Valid @ModelAttribute("user") JUser user, BindingResult result) {
addAttributes(model, CREATE);
if (!result.hasErrors()) {
if (user.getOfficeType() == null) {
user.setOfficeType(office.getType());
}
if (user.getEnabled() == null) {
user.setEnabled(true);
}
System.out.println(user.getRoles().size());
try {
userService.create(user, user.getRoles());// method arguments for PreFilter
model.addAttribute("createSuccess", "کاربر به نام " + user.getFullName() + " ساخته شد.<br/>" + " نام کاربری:" + user.getUsername());
} catch (MessageException ex) {
String mess = NUtil.getDeepMessage(ex);
result.rejectValue(null, "hey", mess + "salam");
}
System.out.println(user.getFullName());
}
System.out.println(result);
return "user/manage";
}
I face this exception:
java.lang.NullPointerException
at org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice.findFilterTarget(ExpressionBasedPreInvocationAdvice.java:71)
at org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice.before(ExpressionBasedPreInvocationAdvice.java:35)
at org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter.vote(PreInvocationAuthorizationAdviceVoter.java:57)
at org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter.vote(PreInvocationAuthorizationAdviceVoter.java:25)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:62)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at ir.iais.nezarat.service.UserService$$EnhancerBySpringCGLIB$$3a7efc2e.create(<generated>)
at ir.iais.nezarat.controller.UserController.create2(UserController.java:105)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(Applicatio ...
...
...
NullPointerException occurs at this line:
//user is not null, user.getRoles() is not null
userService.create(user, user.getRoles());
Please help!
Although Spring Security reference says:
You can also filter before the method call, using
@PreFilter
, though this is a less common requirement. The syntax is just the same, but if there is more than one argument which is a collection type then you have to select one by name using thefilterTarget
property of this annotation.
source code checks only number of arguments not the types of the arguments:
private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation mi) {
Object filterTarget = null;
if (filterTargetName.length() > 0) {
filterTarget = ctx.lookupVariable(filterTargetName);
if (filterTarget == null) {
throw new IllegalArgumentException(
"Filter target was null, or no argument with name "
+ filterTargetName + " found in method");
}
}
else if (mi.getArguments().length == 1) {
Object arg = mi.getArguments()[0];
if (arg.getClass().isArray() || arg instanceof Collection<?>) {
filterTarget = arg;
}
if (filterTarget == null) {
throw new IllegalArgumentException(
"A PreFilter expression was set but the method argument type"
+ arg.getClass() + " is not filterable");
}
}
if (filterTarget.getClass().isArray()) {
throw new IllegalArgumentException(
"Pre-filtering on array types is not supported. "
+ "Using a Collection will solve this problem");
}
return filterTarget;
}
and Spring Security API says:
For methods which have a single argument which is a collection type, this argument will be used as the filter target.
[...]
the name of the parameter which should be filtered (must be a non-null collection instance) If the method contains a single collection argument, then this attribute can be omitted.
You have not defined a filterTarget
but two arguments, so you get a NullPointerException
.
You have to add filterTarget
parameter:
@Transactional
@PreFilter(filterTarget="roles", value = "hasRole('admin') or (filterObject.name!='admin' and filterObject.name!='office_admin')")
public void create(JUser user, Set<JRole> roles) {
JUser u2 = userDao.findByUsername(user.getUsername());
if (u2 != null) {
throw new MessageException("username duplicate!");
}
if (roles == null || roles.isEmpty()) {
throw new MessageException("empty roles");
}
user.setRoles(roles);
doCreate(user);
}