How can I redirect a user after successful login to different pages, based on their previous page? - spring

I'm redirecting a user to the homepage with the "Default target-url" being set to "/". However, I need to redirect a user if they login on either a product page (/p/) or a search page (/search). How could I go about doing this? I'm not all that knowledgeable about Spring Security and redirects yet.
I've tried intercepting the request within the onAuthenticationSuccess() method in my AuthenticationSuccessHandler and checking for the URL if it contains the product page or search page url.
Within the AuthenticationSuccessHandler:
if (!response.isCommitted()) {
super.onAuthenticationSuccess(request,response,authentication);
}
Within the spring-security-config.xml:
<bean id="authenticationSuccessHandler" class ="com.storefront.AuthenticationSuccessHandler" scope="tenant">
<property name="rememberMeCookieStrategy" ref="rememberMeCookieStrategy" />
<property name="customerFacade" ref="customerFacade" />
<property name="sCustomerFacade" ref="sCustomerFacade" />
<property name="sProductFacade" ref="sProductFacade" />
<property name="defaultTargetUrl" value="/" />
<property name="useReferer" value="true" />
<property name="requestCache" value="httpSessionRequestCache" />
The expected results will be:
When a user logs in on a product page, they will be returned to the product page they were on.
When a user logs in on a search page, they will be returned to the search page they were on.
If the user logs in while not on a product or search page, they are redirected to the homepage.

Hybris OOTB (I'm referring to V6.7) has a functionality where you can list the URLs for which you want to redirect to Default Target Url. The idea here is to have another list(or replace the existing one) with the reverse logic which only allows the given URLs and redirects all other URLs to the default target URLs.
In OOTB you can see listRedirectUrlsForceDefaultTarget in the spring-security-config.xml, where can define the list of URLs which you want to redirect to the default target. Like below.
<alias name="defaultLoginAuthenticationSuccessHandler" alias="loginAuthenticationSuccessHandler"/>
<bean id="defaultLoginAuthenticationSuccessHandler" class="de.hybris.platform.acceleratorstorefrontcommons.security.StorefrontAuthenticationSuccessHandler" >
<property name="customerFacade" ref="customerFacade" />
<property name="defaultTargetUrl" value="#{'responsive' == '${commerceservices.default.desktop.ui.experience}' ? '/' : '/my-account'}"/>
<property name="useReferer" value="true"/>
<property name="requestCache" ref="httpSessionRequestCache" />
<property name="uiExperienceService" ref="uiExperienceService"/>
<property name="cartFacade" ref="cartFacade"/>
<property name="customerConsentDataStrategy" ref="customerConsentDataStrategy"/>
<property name="cartRestorationStrategy" ref="cartRestorationStrategy"/>
<property name="forceDefaultTargetForUiExperienceLevel">
<map key-type="de.hybris.platform.commerceservices.enums.UiExperienceLevel" value-type="java.lang.Boolean">
<entry key="DESKTOP" value="false"/>
<entry key="MOBILE" value="false"/>
</map>
</property>
<property name="bruteForceAttackCounter" ref="bruteForceAttackCounter" />
<property name="restrictedPages">
<list>
<value>/login</value>
</list>
</property>
<property name="listRedirectUrlsForceDefaultTarget">
<list>/example/redirect/todefault</list>
</property>
</bean>
StorefrontAuthenticationSuccessHandler
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
final Authentication authentication) throws IOException, ServletException
{
//...
//if redirected from some specific url, need to remove the cachedRequest to force use defaultTargetUrl
final RequestCache requestCache = new HttpSessionRequestCache();
final SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null)
{
for (final String redirectUrlForceDefaultTarget : getListRedirectUrlsForceDefaultTarget())
{
if (savedRequest.getRedirectUrl().contains(redirectUrlForceDefaultTarget))
{
requestCache.removeRequest(request, response);
break;
}
}
}
//...
}
Now reverse that logic by declaring new list (let's say listAllowedRedirectUrls ) or replacing listRedirectUrlsForceDefaultTarget with listAllowedRedirectUrls in the spring-security-config.xml and do the respective changes in the SuccessHandler. Like
<property name="listAllowedRedirectUrls">
<list>/p/</list>
<list>/search</list>
</property>
StorefrontAuthenticationSuccessHandler
if (savedRequest != null)
{
for (final String listAllowedRedirectUrl : getListAllowedRedirectUrls())
{
if ( ! savedRequest.getRedirectUrl().contains(listAllowedRedirectUrl))
{
requestCache.removeRequest(request, response);
break;
}
}
}
You have to do the same changes for /login/checkout handler declaration (defaultLoginCheckoutAuthenticationSuccessHandler) as well.

You can extend AbstractLoginPageController for running your own scenario. Save referrer URL in doLogin method to httpSessionRequestCache. Then check and filter and return referrer URL in getSuccessRedirect method.

Related

Spring MVC IE7 redirect

First, I execute save.do in edit.jsp
#RequestMapping(value = "/saveUser.do")
public String saveUser(User user) {
userService.save(user);
return "redirect:/listUser.do";
}
I then system redirect to list.do
#RequestMapping(value = "/listUser.do")
public String listUser(User user, HttpServletRequest request) throws Exception {
List<User> list = userService.getAll(user, getRowBounds(request));
request.setAttribute("list", list);
return "/framework/system/user/listUser";
}
When I use chrome, the page will view new data.
But if I use IE7, the page does not view new data, only views the old data.
But with IE11 seems to be working fine.
Tanks for every one.
I find the answer.
Add
<mvc:interceptors>
<bean id="webContentInterceptor"
class="org.springframework.web.servlet.mvc.WebContentInterceptor">
<property name="cacheSeconds" value="0"/>
<property name="useExpiresHeader" value="true"/>
<property name="useCacheControlHeader" value="true"/>
<property name="useCacheControlNoStore" value="true"/>
</bean>
</mvc:interceptors>
how to set header no cache in spring mvc 3 by annotation

Apache Shiro authentication against LDAP - any username/password combination gets through

I'm developing a web application using Spring, Vaadin and Apache Shiro for authentication and authorization. I have two realms, since some users log in through a database, others authenticate against LDAP. JDBC realm works perfectly but somehow LDAP realm lets everybody through - no matter what username/password combination is provided.
Here is my Spring configuration:
<!-- Apache Shiro -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<ref bean="jdbcRealm" />
<ref bean="ldapRealm" />
</list>
</property>
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
</property>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean id="ldapContextFactory" class="org.apache.shiro.realm.ldap.JndiLdapContextFactory">
<property name="url" value="ldap://localhost:389" />
</bean>
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="ldapRealm" class="org.apache.shiro.realm.ldap.JndiLdapRealm">
<property name="contextFactory" ref="ldapContextFactory" />
<property name="userDnTemplate" value="uid={0},ou=people,dc=maxcrc,dc=com" />
</bean>
Logging in is rather typical:
try {
// Obtain user reference
Subject currentUser = SecurityUtils.getSubject();
// Create token using provided username and password
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
// Remember user
if(rememberMe.getValue())
token.setRememberMe(true);
// Login
currentUser.login(token);
// If we are here, no exception was raised and the user was logged in, so redirect
UI.getCurrent().getNavigator().navigateTo("main" + "/" + "main-page");
// Fire CustomEvent
fireEvent(new CustomEvent(ErasmusLoginForm.this));
} catch ( UnknownAccountException e ) {
Notification.show("No such user...");
} catch ( IncorrectCredentialsException e ) {
Notification.show("Invalid creditentials...");
} catch ( LockedAccountException e ) {
Notification.show("Locked account...");
} catch ( AuthenticationException e ) {
e.printStackTrace();
Notification.show("Some other exception...");
} catch (Exception e) {
// Password encryption exception
}
I read almost everywhere with no luck.
This post (Shiro Authenticates Non-existent User in LDAP) also wasn't helpful to me - both the DN template and the URL are correct and the server (LDAP server) is running. Why does it let everybody through?
If I turn Ldap realm off, JDBC authentication works perfectly. But with both of them on, everybody gets through since I'm using FirstSuccessfulStrategy.
EDIT: Additional note: if I provide an empty password, AuthenticationException is raised. But any non-empty password works fine.
Any ideas?

Spring 3.2 with MVC, ContentNegotation, REST and PDF Generator

Let's say, I have a REST styled controller mapping
#RequestMapping(value="users", produces = {MediaType.APPLICATION_JSON_VALUE})
public List<User> listUsers(#ReqestParams Integer offset, #ReqestParams Integer limit, #ReqestParams String query) {
return service.loadUsers(query, offset, limit);
}
Serving JSON (or even XML) is not an issue, this is easy using ContentNegotation and MessageConverters
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true" />
<property name="favorParameter" value="false" />
<property name="ignoreAcceptHeader" value="false" />
<property name="mediaTypes" >
<value>
html=text/html
json=application/json
xml=application/xml
</value>
</property>
</bean>
Now, I need to add support for PDF. Naturally, I want to use (Spring) MVC + REST as much as possible. Most examples I have found implement this with an explicit definition not using REST style, e.g.
#RequestMapping(value="users", produces = {"application/pdf"})
public ModelAndView listUsersAsPdf(#ReqestParams Integer offset, #ReqestParams Integer limit, #ReqestParams String query) {
List<User> users = listUsers(offset, limit, query); // delegated
return new ModelAndView("pdfView", users);
}
That works, but is not very comfortable because for every alternate output (PDF, Excel, ...) I would add a request mapping.
I have already added application/pdf to the content negotation resolver; unfortunately any request with a suffix .pdf or the Accept-Header application/pdf were be responded with 406.
What is the ideal setup for a REST/MVC style pattern to integrate alternate output like PDF?
You can create a WEB-INF/spring/pdf-beans.xml like below.
<bean id="listofusers" class="YourPDFBasedView"/>
And your controller method will return view name as listofusers.
#RequestMapping(value="users")
public ModelAndView listUsersAsPdf(#ReqestParams Integer offset, #ReqestParams Integer limit, #ReqestParams String query) {
List<User> users = listUsers(offset, limit, query); // delegated
return new ModelAndView("listofusers", users);
}
And you can use contentNegotiationViewResolver in this way:
<bean class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="order" value="1"/>
<property name="location" value="WEB-INF/spring/pdf-views.xml"/>
</bean>
<!--
View resolver that delegates to other view resolvers based on the content type
-->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<!-- All configuration is now done by the manager - since Spring V3.2 -->
<property name="contentNegotiationManager" ref="cnManager"/>
</bean>
<!--
Setup a simple strategy:
1. Only path extension is taken into account, Accept headers are ignored.
2. Return HTML by default when not sure.
-->
<bean id="cnManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="ignoreAcceptHeader" value="true"/>
<property name="defaultContentType" value="text/html" />
</bean>
For JSON: Create a generic JSON view resolver like below and register it as bean in context file.
public class JsonViewResolver implements ViewResolver {
/**
* Get the view to use.
*
* #return Always returns an instance of {#link MappingJacksonJsonView}.
*/
#Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
MappingJacksonJsonView view = new MappingJacksonJsonView();
view.setPrettyPrint(true); // Lay the JSON out to be nicely readable
return view;
}
}
Same for XML:
public class MarshallingXmlViewResolver implements ViewResolver {
private Marshaller marshaller;
#Autowired
public MarshallingXmlViewResolver(Marshaller marshaller) {
this.marshaller = marshaller;
}
/**
* Get the view to use.
*
* #return Always returns an instance of {#link MappingJacksonJsonView}.
*/
#Override
public View resolveViewName(String viewName, Locale locale)
throws Exception {
MarshallingView view = new MarshallingView();
view.setMarshaller(marshaller);
return view;
}
}
and register above xml view resolver in context file like this:
<oxm:jaxb2-marshaller id="marshaller" >
<oxm:class-to-be-bound name="some.package.Account"/>
<oxm:class-to-be-bound name="some.package.Customer"/>
<oxm:class-to-be-bound name="some.package.Transaction"/>
</oxm:jaxb2-marshaller>
<!-- View resolver that returns an XML Marshalling view. -->
<bean class="some.package.MarshallingXmlViewResolver" >
<constructor-arg ref="marshaller"/>
</bean>
You can find more information at this link:
http://spring.io/blog/2013/06/03/content-negotiation-using-views/
Using all view resolver techniques, you can avoid writing duplicate methods in controller, such as one for xml/json, other for excel, other for pdf, another for doc, rss and all.
Knalli, if you replace #ResponseBody with ModelAndView(), you can achieve both the features.
Is there any reason you want to keep #ResponseBody ? I just want to know if I am missing anything, just want to learn.
Other option is to write HttpMessageConverters then:
Some samples are here.
Custom HttpMessageConverter with #ResponseBody to do Json things
http://www.javacodegeeks.com/2013/07/spring-mvc-requestbody-and-responsebody-demystified.html
This is working sample. I have configured contentnegotiationviewresolver for this, and give highest order. After that I have ResourceBundleViewResolver for JSTL and Tiles View, then XmlViewResolver for excelResolver, pdfResolver, rtfResolver. excelResolver, pdfResolver, rtfResolver. XmlViewResolver and ResourceBundleViewResolver works only with MAV only, but MappingJacksonJsonView and MarshallingView takes care for both MAV and #ResponseBody return value.
<bean id="contentNegotiatingResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order"
value="#{T(org.springframework.core.Ordered).HIGHEST_PRECEDENCE}" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
<entry key="pdf" value="application/pdf" />
<entry key="xlsx" value="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
<entry key="doc" value="application/msword" />
</map>
</property>
<property name="defaultViews">
<list>
<!-- JSON View -->
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
<!-- XML View -->
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>Employee</value>
<value>EmployeeList</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
</list>
</property>
<property name="ignoreAcceptHeader" value="true" />
</bean>
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver"
id="resourceBundleResolver">
<property name="order" value="#{contentNegotiatingResolver.order+1}" />
</bean>
<bean id="excelResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location">
<value>/WEB-INF/tiles/spring-excel-views.xml</value>
</property>
<property name="order" value="#{resourceBundleResolver.order+1}" />
</bean>
<bean id="pdfResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location">
<value>/WEB-INF/tiles/spring-pdf-views.xml</value>
</property>
<property name="order" value="#{excelResolver.order+1}" />
</bean>
<bean id="rtfResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location">
<value>/WEB-INF/tiles/spring-rtf-views.xml</value>
</property>
<property name="order" value="#{excelResolver.order+1}" />
</bean>
And our XMLViewResolver spring-pdf-views.xml looks like this.
<bean id="employees"
class="EmployeePDFView"/>
And EmployeePDFView will have code for generating pdf and writing pdf byte stream on Response object. This will resolve to rest url that will end with .pdf extension, and when you return MAV with "employees" id.

apache shiro: how to set the authenticationStrategy using spring applicationcontext?

I've been struggling with authenticationStrategy settings with shiro 1.2.1 in a spring based web application. I have 2 realms. One authenticates against database and one against ldap. both realms are working fine just that i wanted a FirstSuccessfulStrategy but it seems both realms are still being called. here is my security-application-context:
<bean id="passwordService" class="org.apache.shiro.authc.credential.DefaultPasswordService">
<property name="hashService" ref="hashService" />
</bean>
<bean id="hashService" class="org.apache.shiro.crypto.hash.DefaultHashService">
<property name="hashAlgorithmName" value="SHA-512" />
<property name="hashIterations" value="500000" />
</bean>
<bean id="SaltedSha512JPARealm" class="bla.bla.webapp.security.SaltedSha512JPARealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.PasswordMatcher">
<property name="passwordService" ref="passwordService"/>
</bean>
</property>
</bean>
<bean id="ldapContextFactory" class="org.apache.shiro.realm.ldap.JndiLdapContextFactory">
<property name="url" value="${user.ldap.connection.url}"/>
<property name="authenticationMechanism" value="${user.ldap.connection.auth_mecanism}"/>
</bean>
<bean id="ldapRealm" class="bla.bla.webapp.security.LDAPRealm">
<property name="userDnTemplate" value="${user.ldap.connection.userDnTemplate}"/>
<property name="contextFactory" ref="ldapContextFactory" />
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="roleRepository,roleRightRepository,rightRepository,userRepository">
<property name="realms">
<list>
<ref local="ldapRealm"/>
<ref local="SaltedSha512JPARealm"/>
</list>
</property>
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
</property>
</bean>
is there anything there that i am not doing well?
FirstSuccessfulStrategy means that your authenticator will try all your realms to authenticate user until the first successful. Your realms was configured in order: ldapRealm, SaltedSha512JPARealm. So if lapRealm will fail authenticator will try second one. To solve this you can try to configure the most successful or the quickest realm to be first, e.g. you can change your realms order to be SaltedSha512JPARealm, ldapRealm:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="roleRepository,roleRightRepository,rightRepository,userRepository">
<property name="realms">
<list>
<ref local="SaltedSha512JPARealm"/>
<ref local="ldapRealm"/>
</list>
</property>
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
</property>
</bean>
But you should understand that for this configuration if SaltedSha512JPARealm will fail, authenticator will try ldapRealm.
Or you can try to use different token classes for this realms. But it will work only if you have different authentication entry points for each of them.
UPD
It seems that ModularRealmAuthenticator is designed so that it will always try to authenticate user by all realms. FirstSuccessfulStrategy can affect only on authentication result. It will return first successful AuthenticationInfo. To achieve your goal you need to override ModularRealmAuthenticator#doMultiRealmAuthentication method. It can look like this:
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, t);
}
}
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
// dirty dirty hack
if (aggregate != null && !CollectionUtils.isEmpty(aggregate.getPrincipals())) {
return aggregate;
}
// end dirty dirty hack
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
<property name="authenticator.authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
</property>
the above definition is wrong. Define it as follows
<property name="authenticator.authenticationStrategy" ref="authcStrategy"/>
And define the below bean definition separately
<bean id="authcStrategy" class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"/>
Then it will work as expected

Filtering JSON response based on authentication

I want to filter object properties based on authentication or even roles.
So, for example full user profile will be returned for authenticated user and filterd for non authenticated.
How can I achieve it with MappingJacksonHttpMessageConverter? I have already declared custom beans for Jaskon:
<bean id="objectMapper" class="com.example.CustomObjectMapper"/>
<bean id="MappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="order" value="1" />
<!-- <property name="customArgumentResolver" ref="sessionParamResolver"/> -->
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<!-- <property name="conversionService" ref="conversionService" /> -->
<!-- <property name="validator" ref="validator" /> -->
</bean>
</property>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter" />
<bean class="org.springframework.http.converter.FormHttpMessageConverter" />
<ref bean="MappingJacksonHttpMessageConverter"/>
</list>
</property>
</bean>
Note: In controllers I am writing results as:
public void writeJson (Object jsonBean, HttpServletResponse response) {
MediaType jsonMimeType = MediaType.APPLICATION_JSON;
if (jsonConverter.canWrite(jsonBean.getClass(), jsonMimeType)) {
try {
jsonConverter.write(jsonBean, jsonMimeType, new ServletServerHttpResponse(response));
} catch (IOException m_Ioe) {
} catch (HttpMessageNotWritableException p_Nwe) {
} catch (Exception e) {
e.printStackTrace();
}
} else {
log.info("json Converter cant write class " +jsonBean.getClass() );
}
}
If you're wanting to return two separate types of JSON objects (e.g. fullProfile and partialProfile), then you would be best-off making two different services with two different urls. Then you could control access to those urls in the normal manner with Spring Security's intercept-url tags.
I did most of that here https://stackoverflow.com/a/39168090/6761668
All you need to do is pencil in your own security rules, perhaps injecting the current user and deciding what to include or not based on their role. I used an annotation on the entity column:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;
#Retention(RetentionPolicy.RUNTIME)
public #interface MyRestricted {
String[] permittedRoles() default {};
}
The column looked like this:
#Column(name = "DISCOUNT_RATE", columnDefinition = "decimal", precision = 7, scale = 2)
#MyRestricted(permittedRoles = { "accountsAdmin", "accountsSuperUser" })
private BigDecimal discountRate;
The rules looked like this:
final MyRestricted roleRestrictedProperty = pWriter.findAnnotation(MyRestricted.class);
if (roleRestrictedProperty == null) {
// public item
super.serializeAsField(pPojo, pJgen, pProvider, pWriter);
return;
}
// restricted - are we in role?
if (permittedRoles.contains(myRole)) {
super.serializeAsField(pPojo, pJgen, pProvider, pWriter);
return;
}
// Its a restricted item for ME
pWriter.serializeAsOmittedField(pPojo, pJgen, pProvider);

Resources