keycloak backchannel logout spring session that use redis as session store - spring-security

I use keycloak as a Central Authentication Service for (single sign on/out) feature.
I have app1, app2, app3. app1 and app2 is monothetic application. app3 use spring session (use redis as session store),
All feature work fine. But I use the back channel to logout for SSO(single sign out) feature, that's works for app1 and app2. But it not work for this app3.
I wonder how to back channel logout application that use spring session

The keycloak admin url invoke when client user send a logout request to it.I find that KeycloakAutoConfiguration#getKeycloakContainerCustomizer() inject WebServerFactoryCustomizer for add KeycloakAuthenticatorValve, and that Valve
use CatalinaUserSessionManagement, but it have not any info about redis as its session store. So I add a customizer for enhence the Valve.
first i set the order of the autoconfig, because extra customizer must be callback after it.
#Slf4j
#Component
public class BeanFactoryOrderWrapper implements DestructionAwareBeanPostProcessor {
#Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
}
#Override
public boolean requiresDestruction(Object bean) {
return true;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("getKeycloakContainerCustomizer")) {
Object wrapRes = this.wrapOrder(bean);
return wrapRes;
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private Object wrapOrder(Object bean) {
log.info("rewrite keycloak auto config customizer Order for next custom");
final WebServerFactoryCustomizer origin = (WebServerFactoryCustomizer) bean;
return new KeycloakContainerCustomizerWithOrder(origin);
}
}
class KeycloakContainerCustomizerWithOrder implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final WebServerFactoryCustomizer origin;
public KeycloakContainerCustomizerWithOrder(WebServerFactoryCustomizer origin) {
this.origin = origin;
}
#Override
public void customize(ConfigurableServletWebServerFactory factory) {
origin.customize(factory);
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
}
I extra RedisIndexedSessionRepository, and set it to proxy object
#Slf4j
#Configuration
#RequiredArgsConstructor
class ContainerConfig {
private final RedisIndexedSessionRepository sessionRepository;
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> getKeycloakContainerCustomizerGai() {
return configurableServletWebServerFactory -> {
if (configurableServletWebServerFactory instanceof TomcatServletWebServerFactory) {
TomcatServletWebServerFactory container = (TomcatServletWebServerFactory) configurableServletWebServerFactory;
container.getContextValves().stream().filter(ele -> ele.getClass() == KeycloakAuthenticatorValve.class).findFirst().map(ele -> (AbstractKeycloakAuthenticatorValve) ele).ifPresent(valve -> {
try {
final Field field = AbstractKeycloakAuthenticatorValve.class.getDeclaredField("userSessionManagement");
field.setAccessible(true);
final CatalinaUserSessionManagement origin = (CatalinaUserSessionManagement) field.get(valve);
field.set(valve, new CatalinaUserSessionManagementGai(origin, sessionRepository));
} catch (Exception e) {
log.error("enhence valve fail");
}
});
}
};
}
}
#Slf4j
class CatalinaUserSessionManagementGai extends CatalinaUserSessionManagement {
private final CatalinaUserSessionManagement origin;
private final RedisIndexedSessionRepository sessionRepository;
public CatalinaUserSessionManagementGai(CatalinaUserSessionManagement origin, RedisIndexedSessionRepository sessionRepository) {
this.origin = origin;
this.sessionRepository = sessionRepository;
}
public void login(Session session) {
origin.login(session);
}
public void logoutAll(Manager sessionManager) {
origin.logoutAll(sessionManager);
}
public void logoutHttpSessions(Manager sessionManager, List<String> sessionIds) {
for (String sessionId : sessionIds) {
logoutSession(sessionManager, sessionId);
}
}
protected void logoutSession(Manager manager, String httpSessionId) {
try {
final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Manager.class, String.class);
method.setAccessible(true);
method.invoke(origin,manager,httpSessionId);
} catch (Exception e) {
log.error("session manager proxy invoke error");
}
// enhence part
sessionRepository.deleteById(httpSessionId);
}
protected void logoutSession(Session session) {
try {
final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Session.class);
method.setAccessible(true);
method.invoke(origin,session);
} catch (Exception e) {
log.error("session manager proxy invoke error");
}
}
public void sessionEvent(SessionEvent event) {
origin.sessionEvent(event);
}
}
that work for me

Related

Vaadin 23 Spring Security with Keycloak - redirect user after login to the correct page

I configured Vaadin 23 application with Spring Security and Keyclock. Everything works fine except the users are not redirect to the page where they initiated the login process. The user is always redirected to the home page.
This is a SecurityConfiguration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends VaadinWebSecurity {
private final ClientRegistrationRepository clientRegistrationRepository;
private final GrantedAuthoritiesMapper authoritiesMapper;
private final ProfileService profileService;
private String jwtAuthSecret;
SecurityConfiguration(#Value("${spring.security.jwt.auth.secret}") String jwtAuthSecret, ClientRegistrationRepository clientRegistrationRepository,
GrantedAuthoritiesMapper authoritiesMapper, ProfileService profileService) {
this.jwtAuthSecret = jwtAuthSecret;
this.clientRegistrationRepository = clientRegistrationRepository;
this.authoritiesMapper = authoritiesMapper;
this.profileService = profileService;
SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategy.class.getName());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
// Enable OAuth2 login
.oauth2Login(oauth2Login ->
oauth2Login
.clientRegistrationRepository(clientRegistrationRepository)
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint
.userAuthoritiesMapper(authoritiesMapper)
)
.loginPage("/login")
.successHandler(new KeycloakVaadinAuthenticationSuccessHandler(profileService))
)
// Configure logout
.logout(logout ->
logout
.logoutSuccessHandler(logoutSuccessHandler())
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
).sessionManagement(sessionManagement -> {
sessionManagement.sessionConcurrency(concurrency -> {
concurrency.maximumSessions(-1);
concurrency.sessionRegistry(sessionRegistry());
final var expiredStrategy = new UidlExpiredSessionStrategy();
concurrency.expiredSessionStrategy(expiredStrategy);
});
});
setStatelessAuthentication(http, new SecretKeySpec(Base64.getDecoder().decode(jwtAuthSecret), JwsAlgorithms.HS256), "com.example");
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
#Primary
public SpringViewAccessChecker springViewAccessChecker(AccessAnnotationChecker accessAnnotationChecker) {
return new KeycloakSpringViewAccessChecker(accessAnnotationChecker, "/oauth2/authorization/keycloak");
}
private OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler() {
var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
return logoutSuccessHandler;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.ignoring().antMatchers("/session-expired", "/images/*", "/login", "/favicon.ico");
}
#Bean
public PolicyFactory htmlSanitizer() {
return Sanitizers.FORMATTING.and(Sanitizers.BLOCKS).and(Sanitizers.STYLES).and(Sanitizers.LINKS);
}
}
How to properly redirect user to the original page?
UPDATED
public class KeycloakVaadinAuthenticationSuccessHandler extends VaadinSavedRequestAwareAuthenticationSuccessHandler {
private static final Logger logger = LoggerFactory.getLogger(KeycloakVaadinAuthenticationSuccessHandler.class);
private final ServiceFacade serviceFacade;
public KeycloakVaadinAuthenticationSuccessHandler(ServiceFacade serviceFacade) {
this.serviceFacade = serviceFacade;
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (authentication == null || !(authentication instanceof OAuth2AuthenticationToken)) {
String message = String.format("Authentication is null or not an instance of OAuth2AuthenticationToken: %s", authentication);
logger.error(message);
throw new IllegalStateException(message);
}
Collection<VaadinSession> vaadinSessions = VaadinSession.getAllSessions(request.getSession());
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
String keycloakSessionId = (String) token.getPrincipal().getAttributes().get("sid");
serviceFacade.getProfileService().createUserWithProfileIfNotExists((OAuth2AuthenticationToken) authentication, (user, profile, principalUserUuid) -> {
try {
if (CollectionUtils.isNotEmpty(vaadinSessions)) {
for (VaadinSession vaadinSession : vaadinSessions) {
if (vaadinSession.getService() != null) {
vaadinSession.access(() -> {
vaadinSession.setAttribute(UserInfo.SUB_PROPERTY, user.getUuid());
vaadinSession.setAttribute(UserInfo.KEYCLOAK_SESSION_ID, keycloakSessionId);
});
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new IOException(e);
}
});
super.onAuthenticationSuccess(request, response, authentication);
}
The success handler is the one taking care of redirecting to the original view. You are overriding that with your own version so it will not work out of the box. There is nowadays a setOAuth2LoginPage helper in VaadinWebSecurity that will set up the correct success handler.

Why isn't an custom implemented VaadinServiceInitListener is listening in vaadin 13.0.2?

I would like to validate user is signed in or not to achieve it i found something called VaadinServiceInitListener in vaadin 13.0.2 This class is used to listen to BeforeEnter event of all UIs in order to check whether a user is signed in or not before allowing entering any page.
I have created an vaadin 13.0.2 project with app-layout-addon by appreciated implemented login functionality and VaadinServiceInitListener to check whether a user is signed in or not.
public class AAACATInitListener implements VaadinServiceInitListener {
private static final long serialVersionUID = 1L;
private static InAppSessionContextImpl appContextImpl;
#Override
public void serviceInit(ServiceInitEvent event) {
System.out.println("in service init event");
event.getSource().addUIInitListener(new UIInitListener() {
private static final long serialVersionUID = 1L;
#Override
public void uiInit(UIInitEvent event) {
event.getUI().addBeforeEnterListener(new BeforeEnterListener() {
private static final long serialVersionUID = 1L;
#Override
public void beforeEnter(BeforeEnterEvent event) {
appContextImpl = (InAppSessionContextImpl)VaadinSession.getCurrent().getAttribute("context");
if (appContextImpl == null) {
WebBrowser webBrowser = UI.getCurrent().getSession().getBrowser();
String address = webBrowser.getAddress();
if(RememberAuthService.isAuthenticated(address) != null && !RememberAuthService.isAuthenticated(address).isEmpty()) {
//System.out.println("Found Remembered User....");
IBLSessionContext iblSessionContext = null;
try {
iblSessionContext = new UserBLManager().doRememberedStaffUserLogin(RememberAuthService.isAuthenticated(address), "");
if(iblSessionContext != null) {
InAppSessionContextImpl localAppContextImpl = new InAppSessionContextImpl();
localAppContextImpl.setBLSessionContext(iblSessionContext);
localAppContextImpl.setModuleGroupList(iblSessionContext.getSessionAccessControl().getPermittedModuleGroups());
appContextImpl = localAppContextImpl;
event.rerouteTo(ApplicationMainView.class);
}else {
Notification.show("Your access has been expired, Please contact your administrator", 5000, Position.BOTTOM_CENTER);
}
} catch (AuthenticationFailedException e) {
Notification.show("Authentication Failed, Please Reset Cookies And Try Again", 5000, Position.BOTTOM_CENTER);
} catch (Exception e){
e.printStackTrace();
Notification.show("Unexpected Error Occurred, Please Reset Cookies And Try Again", 5000, Position.BOTTOM_CENTER);
}
}else {
System.out.println("Session context is null, creating new context");
appContextImpl = new InAppSessionContextImpl();
VaadinSession.getCurrent().setAttribute("context", appContextImpl);
event.rerouteTo(LoginView.class);
}
} else {
System.out.println("Session context is not null");
InAppSessionContextImpl localAppContextImpl = new InAppSessionContextImpl();
localAppContextImpl.setBLSessionContext(appContextImpl.getBLSessionContext());
localAppContextImpl.setModuleGroupList(appContextImpl.getModuleGroupList());
appContextImpl = localAppContextImpl;
event.rerouteTo(ApplicationMainView.class);
}
}
});
}
});
}
public static void setBLSessionContext(IBLSessionContext iblSessionContext) {
appContextImpl.setBLSessionContext(iblSessionContext);
}
public static void setModuleGroupList(List<ModuleGroupVO> moduleGroupList) {
appContextImpl.setModuleGroupList(moduleGroupList);
}
private class InAppSessionContextImpl implements InAppSessionContext {
private static final long serialVersionUID = 1L;
private List<ModuleGroupVO> moduleGroupList;
private IBLSessionContext iblSessionContext;
private Map<String, Object> attributeMap;
public InAppSessionContextImpl() {
this.attributeMap = new HashMap<String, Object>();
}
#Override
public List<ModuleGroupVO> getModuleGroupList() {
return moduleGroupList;
}
public void setModuleGroupList(List<ModuleGroupVO> moduleGroupList) {
this.moduleGroupList = moduleGroupList;
}
#Override
public IBLSessionContext getBLSessionContext() {
return iblSessionContext;
}
public void setBLSessionContext(IBLSessionContext iblSessionContext) {
this.iblSessionContext = iblSessionContext;
}
#Override
public IBLSession getBLSession() {
if(iblSessionContext != null)
return iblSessionContext.getBLSession();
return null;
}
#Override
public boolean isPermittedAction(String actionAlias) {
if (getBLSessionContext() != null) {
if (getBLSessionContext().getSessionAccessControl() != null) {
return getBLSessionContext().getSessionAccessControl().isPermittedAction(actionAlias);
}
}
return false;
}
#Override
public void setAttribute(String key, Object attribute) {
attributeMap.put(key, attribute);
}
#Override
public Object getAttribute(String key) {
return attributeMap.get(key);
}
}
}
Expected results redirect to login page if user not signed in or else to main application page but AAACATInitListener is not listening.
If you are using Spring, simply add a #Component annotation to the class and it should work. If youre not using Spring, follow #codinghaus' answer.
To make Vaadin recognize the VaadinServiceInitListener you have to create a file called com.vaadin.flow.server.VaadinServiceInitListener and put it under src/main/resources/META-INF/services. Its content should be the full path to the class that implements the VaadinServiceInitListener interface. Did you do that?
You can also find a description on that in the tutorial.
The correct pattern to use beforeEnter(..) is not do it via VaadinServiceInitListener , instead you should implement BeforeEnterObserver interface in the view where you need use it and override beforeEnter(..) method with your implementation.
public class MainView extends VerticalLayout implements RouterLayout, BeforeEnterObserver {
...
#Override
public void beforeEnter(BeforeEnterEvent event) {
...
}
}

Commit EntityManager Transaction using #Transactional - Guice

I'm using Guice to Inject EntityManager.
When I commit the trasaction of the injected entityManager there is nothing happend in the BD side : no transaction passed !!! can you help me to figure out what is going on ?
Here is my code :
Web.xml
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>ca.products.services.InjectorListener</listener-class>
</listener>
The InjectorListener class :
public class InjectorListener extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(
new PersistenceModule(),
new GuiceModule(),
new RestModule());
}
}
The persistenceModule class :
public class PersistenceModule implements Module {
#Override
public void configure(Binder binder) {
binder
.install(new JpaPersistModule("manager1")
.properties(getPersistenceProperties()));
binder.bind(PersistenceInitializer.class).asEagerSingleton();
}
private static Properties getPersistenceProperties() {
Properties properties = new Properties();
properties.put("hibernate.connection.driver_class", "org.postgresql.Driver");
properties.put("hibernate.connection.url", "jdbc:postgresql://localhost:5432/postgres");
properties.put("hibernate.connection.username", "postgres");
properties.put("hibernate.connection.password", "postgres");
properties.put("hibernate.connection.pool_size", "1");
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.put("hibernate.hbm2ddl.auto", "create");
return properties;
}
}
The GuiceModule class :
public class GuiceModule extends AbstractModule {
#Override
protected void configure() {
bind(MemberRepository.class).to(MemberRepositoryImp.class);
bind(ProductRepository.class).to(ProductRepositoryImpl.class);
bind(ShoppingBagRepository.class).to(ShoppingBagRepositoryImpl.class);
}
}
The RestModule class :
public class RestModule extends JerseyServletModule {
#Override
protected void configureServlets() {
HashMap<String, String> params = new HashMap<>();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "ca.products.services");
params.put(JSONConfiguration.FEATURE_POJO_MAPPING, "true");
params.put(ResourceConfig.FEATURE_DISABLE_WADL, "true");
serve("/*").with(GuiceContainer.class, params);
}
}
and Finally the webservice (jeresy) call:
#Inject
private Provider<EntityManager> em;
#GET
#Transactional
#Path("/reset")
public void resetData() {
logger.info("Processing reset");
try {
em.get().getTransaction().begin();
for (int i = 0; i < 10; i++) {
em.get().persist(new Product("Product_" + i, "Desc_" + i));
}
em.get().flush();
em.get().getTransaction().commit();
} catch (Exception e) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
}
You probably need to add the a persist filter. This will also keep you from having to manage transactions manually. If you do not use the filter you can still inject the UnitOfWork to create transactions. If you are using jpa persist you should not be managing userTransactions.
This is a custom filter that also adds a Lifecycle which it automatically started on startup with some custom code and a map binder builder. It is only there for thoroughness. It is not part of the guice api but more similar to spring's Lifecycle listener. I don't have any spring dependencies at all.
#Singleton
public final class JpaPersistFilter implements Filter {
private final UnitOfWork unitOfWork;
private final PersistServiceLifecycle persistService;
#Inject
public JpaPersistFilter(UnitOfWork unitOfWork, PersistServiceLifecycle persistService) {
this.unitOfWork = unitOfWork;
this.persistService = persistService;
}
public void init(FilterConfig filterConfig) throws ServletException {
// persistService.start();
}
public void destroy() {
persistService.stop();
}
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
unitOfWork.begin();
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
unitOfWork.end();
}
}
/**
* Extra lifecycle handler for starting and stopping the service. This
* allows us to register a {#link Lifecycle} with the
* {#link LifecycleListener} and not have to worry about the service being
* started twice.
*
* #author chinshaw
*
*/
#Singleton
public static class PersistServiceLifecycle implements Lifecycle {
private final PersistService persistService;
private volatile boolean isStarted = false;
#Inject
public PersistServiceLifecycle(PersistService persistSerivce) {
this.persistService = persistSerivce;
}
#Override
public boolean isRunning() {
return isStarted;
}
#Override
public void start() {
if (!isStarted) {
persistService.start();
isStarted = true;
}
}
#Override
public void stop() {
persistService.stop();
isStarted = false;
}
}
}
Example of adding filter to module.
#Override
protected void configureServlets() {
filter("/api/*").through(JpaPersistFilter.class);
}
Example of using unit of work to manage the transaction.
#Inject
UnitOfWork unitOfWork;
public void doSomething() {
unitOfWork.begin();
try {
dao.saveState(someobject);
} finally {
unitOfWork.end();
}
}

Basic Authentication service called By Zuul

I'm Zuul as edge server. so all request pass by this edge server.
I have a micro-service A. all web services of A are protected by Basic Authentication.
How can we call the services of A b passing by Zuul proxy?
Should I add header for messages?
This is my Zuul filter:
public class BasicAuthorizationHeaderFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.getRequest().getRequestURL();
ctx.addZuulRequestHeader("Authorization", "Basic " + Utils.getBase64Credentials("user", "Token"));
return null;
}
}
Ideally the requester would have the token in the request.
If you want to have Zuul add the authentication token then you can create a ZuulFilter and use:
context.addZuulRequestHeader("Authorization", "base64encodedTokenHere");
Doing this would give open access to the services - which may not be wise.
#Component
public class PreFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(PreFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
ctx.addZuulRequestHeader("Authorization", request.getHeader("Authorization"));
LOG.info("Parametres : {}", request.getParameterMap()
.entrySet()
.stream()
.map(e -> e.getKey() + "=" + Stream.of(e.getValue()).collect(Collectors.toList()))
.collect(Collectors.toList()));
LOG.info("Headers : {}", "Authorization" + "=" + request.getHeader("Authorization"));
LOG.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
return null;
}
}
You can call (through Zuul) your service A like this :
https://login:password#zuulurl.com/serviceA
but firslty allow AUTHORIZATION header through Zuul for this specific service (route) with the property sensitiveHeaders in your properties file :
zuul.routes.serviceA.sensitiveHeaders=Cookie,Set-Cookie
or let it empty if you want to pass the Cookie headers too.
Here more informations about headers through Zuul
Use zuul's sensitive header property with the blank value,
zuul.sensitiveHeaders=
Above property will do the trick but if you want to have filters for Cookie headers
you can use that property with values,
zuul.sensitiveHeaders=Cookie,Set-Cookie
This change is little tricky.
#Override
public int filterOrder() {
return 1; // change the return value to more than 5 the above code will work.
}
try with the final code below:
#Component
public class PreFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(PreFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
ctx.addZuulRequestHeader("Authorization", request.getHeader("Authorization"));
return null;
}
}

How Can i show urls clicked by user in Primefaces+JSF2

I am using JSF2 with Prime faces. I will want to show all the previous links or Urls clicked by user in every page.How can i do this?
Here's a good link for you: How can I get the request URL from a Java Filter?
What I would do is use something like that example and do a set of three URLs.
//Using linked example
public class MyFilter implements Filter {
public void init(FilterConfig config) throws ServletException { }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
chain.doFilter(request, response);
String url = ((HttpServletRequest) request).getPathTranslated();
UrlBean urlBean = (UrlBean) FacesContext.getCurrentInstance()getApplication().getValueBinding("#{urlBean}");
urlBean.storeUrl(url);
}
public void destroy() { }
}
This is totally untested, but the idea should work. You would just need to implement some logic (probably a stack) for your component so it stores what you need. (Obviously you might end up at the same URL multiple times). I should note the UrlBean is just an abstract idea, you would have to implement it
Hi I have created this Singleton class to put the Url Browsed by a User.
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class UrlHistory {
#SuppressWarnings("rawtypes")
private static Map<String, List> store = new HashMap<String, List>();
private static UrlHistoryBean instance = null;
public static UrlHistoryBean getInstance() {
if (instance == null) {
instance = new UrlHistoryBean();
}
return instance;
}
LinkedList<UrlData> urlList = new LinkedList<UrlHistoryBean.UrlData>();
public void addUrl(String sessionId, String urlString, int urlId) {
UrlData data = new UrlData();
data.setUrlName(urlString);
data.setUrlId(companyId);
if (urlList.isEmpty()) {
urlList.add(data);
} else {
boolean isEqual = false;
for (UrlData urlDataObj : urlList) {
if (urlDataObj.equals(data))
isEqual = true;
}
if(!isEqual)
urlList.addFirst(data);
}
store.put(sessionId, urlList);
}
#SuppressWarnings("rawtypes")
public static Map<String, List> getStore() {
return store;
}
#SuppressWarnings("rawtypes")
public static void setStore(Map<String, List> store) {
UrlHistoryBean.store = store;
}
public class UrlData {
String urlName;
int urlId;
public String getUrlName() {
return UrlName;
}
public void setUrlName(String UrlName) {
this.UrlName = UrlName;
}
public int getUrlId() {
return urlId;
}
public void setUrlId(int urlId) {
this.urlId = urlId;
}
public boolean equals(UrlData rData) {
boolean bEqual = false;
if (this.getUrlId() > 0 && rData.getUrlId() > 0 && this.getUrlId() == rData.getUrlId()) {
bEqual = true;
}
return bEqual;
}
}
}

Resources