PostConstruct method will not be called after login - session

In our application we have a dashboard, which will be loaded after login from an init() method with PostConstruct annotation.
Everything works fine, until the session is expired. In this moment, if I click on a button in dashboard, this init method will be called again, although I am not logged in. I will be redirected to the login page and now if I login, the PostConstruct method will not be called anymore and the content of dashboard can not be loaded.
I know that the PostConstruct will be called only once after a session is created (The Controller class is #SessionScoped). I guess the problem is that the server creates a new session after timeout, because the init method get called after timeout and before login, and that leads to not being called after login again. What would be the best solution to solve this problem?
One workaround would be to load the content of dashboard in a method which is not annotated with PostConstruct. But I wonder if the problem can be solved differently.

You might implement a Servlet filter to monitor the status of your user sessions. This way, you will detect the expired session out of the Managed Bean and the init method won't be called.
This filter might look like:
public class UriFilter implements javax.servlet.Filter {
#Inject SessionController session;
private static final String SIGNON_PAGE_URI = "/myappname/engine/index.xhtml";
private static final String SUBDOMAIN_URI = "/myappname/engine/";
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse)response;
HttpServletRequest req = (HttpServletRequest )request;
String uri = ((HttpServletRequest) request).getRequestURI();
if( !this.authorize(req ) && !(uri.endsWith("/") || uri.endsWith("index.xhtml") || !uri.endsWith(".xhtml")) ){
if(request.getParameter("fileName") != null)
res.sendRedirect(SIGNON_PAGE_URI+"?uri="+uri.substring(SUBDOMAIN_URI.length(), uri.length())+"?fileName="+request.getParameter("fileName"));
else {
res.setHeader("Cache-Control","no-cache, no-store, must-revalidate");
res.setHeader("Pragma","no-cache");
res.setDateHeader("Expires",0);
res.sendRedirect(SIGNON_PAGE_URI+"?faces-redirect=true");
}
} else{
//Desativa o cache do browser
res.setHeader("Cache-Control","no-cache, no-store, must-revalidate");
res.setHeader("Pragma","no-cache");
res.setDateHeader("Expires",0);
//Processa request e response
chain.doFilter( req, res );
}
}
private boolean authorize( HttpServletRequest req ){
boolean authorized = false;
HttpSession session = req.getSession(false);
if(session != null){
if(this.session != null) {
if(this.session.getLogged() != null) {
authorized = true;
}
}
}
return authorized;
}
#Override
public void destroy() {
}
}
The SessionController is the #SessionScoped that track the logged user.
In your web.xml file you have to specify the filter:
<filter>
<filter-name>URI Filter</filter-name>
<filter-class>com.myappname.filter.UriFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>URI Filter</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/engine/*</url-pattern>
</filter-mapping>

Related

SpringBoot 2 - OncePerRequestFilter - modify response Headers after processing of Controller

Hello I want to modify some of my API's response Headers after I have completed processing (executed logic) and have concluded with an HTTP status code.
For example if the response is 404, then include specific for example Cache-Control Headers example dont cache, or something like that.
I have already 2 OncePerRequestFilter registered, which work fine - but obviously I can not do logic - once the processing is complete. The CacheControlFilter already has logic that adds by default some Cache-Control headers - e.g cache for 15 sec etc. It seems though that this happens (the addition of headers on the response) on a very early stage of the dispatch and when it reaches to the phase of executing the actual Controller/Endpoint and there is an exception or Error that obviously is going to be handled by an advice etc, I can not mutate these already existing headers- that were already added by the filter.
#Bean
public FilterRegistrationBean filterOne() {
Filter filter = new FilterOne();
return createFilter(filter, "FilterOne",List.of("/*"));
}
#Bean
public FilterRegistrationBean cacheControlFilter() {
Filter filter = new CacheControlFilter();
return createFilter(filter, "CacheControlFilter", List.of("/*"));
}
private FilterRegistrationBean createFilter(Filter aFilter, String filterName,
List<String> urlPatterns) {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean(aFilter);
filterRegBean.addUrlPatterns(urlPatterns.toArray(new String[0]));
filterRegBean.setName(filterName);
filterRegBean.setEnabled(true);
filterRegBean.setAsyncSupported(true);
return filterRegBean;
}
I have already tried, to add an HttpServletResponseWrapper as indicated on these post here and here on the CacheControlFilter but it does not seem to work. I have also seen a similar S.O thread here.
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(response) {
#Override
public void setStatus(int sc) {
super.setStatus(sc);
handleStatus(sc);
}
#Override
#SuppressWarnings("deprecation")
public void setStatus(int sc, String sm) {
super.setStatus(sc, sm);
handleStatus(sc);
}
#Override
public void sendError(int sc, String msg) throws IOException {
super.sendError(sc, msg);
handleStatus(sc);
}
#Override
public void sendError(int sc) throws IOException {
super.sendError(sc);
handleStatus(sc);
}
private void handleStatus(int code) {
if(code == 404)
addHeader("Cache-Control, "xxx");
}
};
But the code is not executed at all! So I want to manipulate the Cache-Control headers on the second filter only after though the processing is complete and I am ready to return a response.
I am not sure if the fact that I also have, doing some clean up and setting responses upon errors - mixes things up!
#ControllerAdvice
#Slf4j
public class GlobalErrorHandler
Update: As a note, when my Controller is throwing an Exception or Error, the above GlobalErrorHandler is invoked and there I execute a special handling, returning an error response. What I see though is that magically the response has already the default headers populated by the Filter (CacheControlFilter). So it ends up being a bit weird, I add extra logic,to change the control header and I end up with a response that has the same header 2 times (1 with the value set by the CacheControlFilter and then any special value I am trying to override on the ControllerAdvice
Any tips or help appreciated thanks! I am using Spring Boot 2.1.2 with Undertow as my underlying servlet container.
The link you mentioned says that cannot get the status code or modify the headers in ResponseBodyAdvice is not true . If you cast ServerHttpResponse to ServletServerHttpResponse , you can do both of them. So simply implement a ResponseBodyAdvice :
#ControllerAdvice
public class CacheControlBodyAdvice implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(response instanceof ServletServerHttpResponse) {
ServletServerHttpResponse res= (ServletServerHttpResponse)(response);
if(res.getServletResponse().getStatus() == 400){
res.getServletResponse().setHeader("Cache-Control", "XXXXX");
}
}
return body;
}
}
One more thing need to pay attention is that if your controller method throws an exception before complete normally , depending on how to handle the exceptions , the ResponseBodyAdvice may not be trigger. So , I suggest to implement the same logic in the GlobalErrorHandler for safety guard :
#ControllerAdvice
public class GlobalErrorHandler{
#ExceptionHandler(value = Exception.class)
public void handle(HttpServletRequest request, HttpServletResponse response) {
if(response.getStatus() == 400){
response.setHeader("Cache-Control", "XXXXX");
}
}
}
I supposed that you are using spring-mvc (As you mentioned in your tags); If so you can bind to HttpServletResponse to add your headers. You can do it in your method handler like so:
#RestController
class HelloWordController{
#GetMapping("/hello")
public String test(HttpServletResponse response){
response.addHeader("test", "123");
return "hola";
}
}
Another solution (fashion) would be to return a ResponseEntity instead :
#RestController
class HelloWorkController{
#GetMapping("/hello")
public ResponseEntity<String> test(HttpServletResponse response){
return ResponseEntity.status(HttpStatus.OK)
.header("test", "4567")
.body("hello world");
}
}
There are a dozen of ways of changing a HttpServletResponse before return to client in Spring and injecting the response into the handler method or leveraging ControllerAdvice are valid solutions. However, I don't understand the underlying premise of your question that filters can't do the job:
I have already 2 OncePerRequestFilter registered, which work fine -
but obviously I can not do logic - once the processing is complete.
As far as modifying HttpServletResponse is concerned, Filters work totally fine for me and are at least as suitable as any other tool for that job:
#Bean
public FilterRegistrationBean createFilter() {
Filter filter = new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
super.doFilter(request, response, filterChain);
response.setHeader("Cache-Control", "xxx");
}
};
return new FilterRegistrationBean(filter);
}

Keycloak spring boot microservices

i have a few java micro services deployed on open shift . all of them are protected by a api-gateway application which uses keycloak for authentication & Authorization.
Down stream services need to log which user perform certain actions.
in my api-gateway application properties i have already set zuul.sensitiveHeaders to empty
zuul.sensitiveHeaders:
i can see bearer token in the downstream applications .
but how do i get the principal/user from token as downstream applications don't have keycloak dependency in gradle. ( if i add the dependency , i need to reconfigure realm and other properties ) .. is this the right way to do ?
i also tried adding a filter in api-gateway to separately set the user_name in header
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
System.out.println(" Filter doFilter "+req.getUserPrincipal());
if(req.getUserPrincipal() != null ){
res.setHeader("MYUSER",req.getUserPrincipal()==null?"NULL":req.getUserPrincipal().getName());
}
chain.doFilter(request, response);
}
But when i try to get the header in downstream microservices is null.
I wouldn't recommend doing this, or assuming that your non-web facing apps are completely secure. Realistically you should be re-validating the bearer token.
What you need is a zuul filter to add a header to the request. This is mostly from memory and you could update the filter to check if it should filter or not, that the request doesn't already contain an expected header etc.
#Component
public class AddUserHeader extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(AddUserHeader.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter{
return true;
}
#Override
public Object run() {
RequestContext.getCurrentContext().addZuulRequestHeader("MYUSER", SecurityContextHolder.getAuthentication().getPrincipal().getName());
return null;
}

Spring Boot Redirect to requested URL after login

I have a Spring Boot UI application. I am trying to redirect users to the originally requested URL after login.
When a user requests http://www.example.com/myapp/user/22, the application aptly redirects to http://www.example.com/myapp/login. Once the user logs in, the application redirects to http://www.example.com/myapp/dashboard. I would like the application to redirect to http://www.example.com/myapp/user/22.
I have gone through several links and feel I have a proper configuration, yet, redirection is not working as expected.
My Security Config is
public class SecurityConfig extends WebSecurityConfigurerAdapter {
.....
....
#Autowired
private MyAuthenticationSuccessHandler authenticationSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/user/**").authenticated()
.and().csrf().disable().formLogin()
.successHandler(authenticationSuccessHandler)
......
and My Success Handler is
#Component
public class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
...
public MyAuthenticationSuccessHandler() {
super();
this.setDefaultTargetUrl("/myapp/dashboard");
this.setUseReferer(true);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//Do something ..........
........
.........
super.onAuthenticationSuccess(request, response, authentication);
}
I tried using SavedRequestAwareAuthenticationSuccessHandler too.
I notice that my success handler is invoked, but the target URL is always /user/login and my login controller is invoked..
#RequestMapping("/login")
public ModelAndView login(#ModelAttribute() {
if(!userIdentified) {
//go to login page
} else {
new ModelAndView("redirect:/myapp/dashboard");
}
}
and the user is redirected to "dashboard".
What else am I missing?
Use "Referer" from session attribute to get the latest request URL. On my app, i use this one
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public static final String REDIRECT_URL_SESSION_ATTRIBUTE_NAME = "REDIRECT_URL";
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
Object redirectURLObject = request.getSession().getAttribute(REDIRECT_URL_SESSION_ATTRIBUTE_NAME);
if(redirectURLObject != null)
setDefaultTargetUrl(redirectURLObject.toString());
else{
setDefaultTargetUrl("/");
}
request.getSession().removeAttribute(REDIRECT_URL_SESSION_ATTRIBUTE_NAME);
super.onAuthenticationSuccess(request, response, authentication);
}
}
Edit :
Sorry i forgot to show the login controller
#RequestMapping(method = RequestMethod.GET, value = {"/login"})
String login(Model model, Principal principal, HttpServletRequest request) throws Exception{
String referer = request.getHeader("Referer"); //Get previous URL before call '/login'
//save referer URL to session, for later use on CustomAuthenticationSuccesshandler
request.getSession().setAttribute(CustomAuthenticationSuccessHandler.REDIRECT_URL_SESSION_ATTRIBUTE_NAME, referer);
return principal == null ? "login" : "redirect:/";
}
Although Singgih S answer works, BUT there is a better way as below :
Ref:
https://www.baeldung.com/spring-security-redirect-login
There is no magic in these easy to use features in Spring Security.
When a secured resource is being requested, the request will be
filtered by a chain of various filters. Authentication principals and
permissions will be checked. If the request session is not
authenticated yet, AuthenticationException will be thrown.
The AuthenticationException will be caught in the
ExceptionTranslationFilter, in which an authentication process will be
commenced, resulting in a redirection to the login page.
Therefore :
1. When redirection to the "/login" page occurs, your secured request url is saved in the session as DefaultSavedRequest object.
2. Also we know when a successful form based login occurs, one of the implementations of AuthenticationSuccessHandler is called.
so we can create a custom class and get DefaultSavedRequest in it as below :
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if(defaultSavedRequest != null){
getRedirectStrategy().sendRedirect(request, response, defaultSavedRequest.getRedirectUrl());
}else{
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
3. We have to introduce this class in WebSecurityConfigurerAdapter :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.(...).anyRequest().authenticated().and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.successHandler(new CustomAuthenticationSuccessHandler());
So you can implement your logic in the above onAuthenticationSuccess method.
Best wishes
The Spring route, ala extending SavedRequestAwareAuthenticationSuccessHandler or SimpleUrlAuthenticationSuccessHandler can be a bit clunky to implement. In the controller (ex. a POST method that processes logins), you can do the header request yourself; ex:
HttpServletRequest request =null;
String priorUrl = request.getHeader("Referer");
You will notice that you will have the URL prior to either a manual (initiated by user) logout or a session timeout (as handled by Spring session): you'll get an https://iAmPriorUrl.com/.... Then you can do whatever you want with it.

SpringMVC Session Timeout - Redirect to a Special JSP

I've looked everywhere but haven't found a simple solution.
We have a special JSP, timeout.jsp, that needs to be shown whenever a SpringMVC module intercepts an invalid session action. The timeout is already configured in web.xml and works correctly.
Previously in Struts, it was a matter of defining a forward and intercepting dispatchMethod,
<forward name="sessionTimeout" path="/WEB-INF/timeout.jsp" redirect="false" />
#Override
protected ActionForward dispatchMethod(final ActionMapping mapping, final ActionForm form,
final HttpServletRequest request, final HttpServletResponse response, final String name)
throws Exception {
//...
if (!isSessionValid())
return mapping.findForward("sessionTimeout");
}
But how would you implement a catch-all solution in SpringMVC modules?
All my SpringMVC URLs come to this servlet mapping, *.mvc:
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>*.mvc</url-pattern>
</servlet-mapping>
Anything that sends a URL with this pattern should be cross-checked for session validity and if invalid, redirected to timeout.jsp.
NOTE
The solution given here (https://stackoverflow.com/a/5642344/1005607) did not work:
<web-app>
<error-page>
<exception-type>org.springframework.web.HttpSessionRequiredException</exception-type>
<location>/index.jsp</location>
</error-page>
</web-app>
There's a NullPointerException in my SpringMVC Form Code even before any kind of SessionRequiredException, as soon as I try to access the session. I need to globally protect against these NullPointerExceptions.
My final solution: an old-fashioned Filter. It works for me, no other simple solution available.
web.xml
<filter>
<filter-name>spring_mvc_controller_filter</filter-name>
<filter-class>myapp.mypackage.SpringMVCControllerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>spring_mvc_controller_filter</filter-name>
<url-pattern>*.mvc</url-pattern>
</filter-mapping>
SpringMVCControllerFilter
public class SpringMVCControllerFilter implements Filter
{
#Override
public void destroy() {
// TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpSession session = request.getSession(false);
if (session.isValid() && !session.isNew())
{
chain.doFilter(request, response);
}
else
{
request.getRequestDispatcher("/WEB-INF/jsp/sessionTimeout.jsp").forward(request, response);
}
}
#Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}

How to handle exceptions properly in custom Spring security 3.0 authentication?

I'm developing a REST service based in tokens. When an user goes to ../rest/authenticate with the user and password via curl, gets a valid token in order to use the whole API.
My problem appears when the user forgets to insert the username, the password or the token in the other methods because i've not managed to handle the Authentication exceptions as I want.
I cand handle the exceptions but tomcat gets the response and inserts some html that I don't expect.
This is the typical response of tomcat.
Is it possible to receive a response like 200 OK which don't have this html code?
At the momment, this is my config:
AuthenticationProcessingFilter
Decides if the url is secured or not. If has to be secured, calls the authentication manager in order to validate it. If receives an authentication exceptions calls the AuthenticationEntryPoint
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private final Collection<String> nonTokenAuthUrls = Lists.newArrayList("/rest","/rest/authenticate");
TokenAuthenticationManager tokenAuthenticationManager;
RestAuthenticationEntryPoint restAuthenticationEntryPoint;
public AuthenticationTokenProcessingFilter(TokenAuthenticationManager tokenAuthenticationManager, RestAuthenticationEntryPoint restAuthenticationEntryPoint) {
this.tokenAuthenticationManager = tokenAuthenticationManager;
this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
try{
if(!nonTokenAuthUrls.contains(httpRequest.getRequestURI())){ //Auth by token
String hash = httpRequest.getHeader("token");
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(hash, null);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
SecurityContextHolder.getContext().setAuthentication(tokenAuthenticationManager.authenticate(authentication));
}
response.reset();
chain.doFilter(request, response);
}catch(AuthenticationException authenticationException){
SecurityContextHolder.clearContext();
restAuthenticationEntryPoint.commence(httpRequest, httpResponse, authenticationException);
}
}
AuthenticationManager
public class TokenAuthenticationManager implements AuthenticationManager{
#Autowired
UserService userService;
#Autowired
TokenService tokenService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Object hash = authentication.getPrincipal();
if(hash == null)
throw new BadCredentialsException("Token is required");
User user = tokenService.getUserFromTokenHash((String)hash);
if(user == null)
throw new BadCredentialsException("Non-existent token");
if(!tokenService.validate((String)hash))
throw new BadCredentialsException("Expired Token");
org.springframework.security.core.userdetails.User userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getUserGrantedAuthorities(user.getRoles()));
return new UsernamePasswordAuthenticationToken(userDetails, user.getPassword(), getUserGrantedAuthorities(user.getRoles()));
}
AuthenticationEntryPoint
This class works OK. The code received is 401 unauthorized but the message is in the tomcat html
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
response.setContentType("application/json");
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage() );
response.getOutputStream().println("{ \"error\": \"" + authenticationException.getMessage() + "\" }");
}
}
The RestAccessDeniedHanler is not called either. It's difficult becasue there are a lot of classes that have to be implemented.
I reviewed some post in stackoverflow and other websites and my approach consist on catching the exceptions in the AuthenticationProcessingFilter and call the AuthenticationEntryPoint manually. I decided to do that becasue I've tried to configure this in the applicationContext-security.xml with no success.
appliacionContext-security.xml
<b:bean id="restAuthenticationEntryPoint" class="...web.security.RestAuthenticationEntryPoint" />
<b:bean id="tokenAuthenticationManager" class="...dp.web.security.TokenAuthenticationManager"/>
<b:bean id="AuthenticationTokenProcessingFilter" class="...web.security.AuthenticationTokenProcessingFilter">
<b:constructor-arg type="...dp.web.security.TokenAuthenticationManager" ref="tokenAuthenticationManager"></b:constructor-arg>
<b:constructor-arg type="...dp.web.security.RestAuthenticationEntryPoint" ref="restAuthenticationEntryPoint"></b:constructor-arg>
</b:bean>
<b:bean id="accessDeniedHandler" class="...dp.web.security.RestAccessDeniedHandler">
</b:bean>
<http realm="Protected REST API" pattern="/rest/**" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="restAuthenticationEntryPoint">
<custom-filter ref="AuthenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
<access-denied-handler ref="accessDeniedHandler"/>
</http>
how can I send a clean response with the error code and a message?
You can use error-pages in your web.xml to intercept Tomcat's error page. For example,
<error-page>
<error-code>404</error-code>
<location>/404</location>
</error-page>
Now you use RequestMapping to map /404 to a page that returns your JSON response without any HTML:
#RequestMapping(value = "/404", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
#ResponseBody
public ResponseEntity<ResponseStatus> handle404() {
HttpStatus status = null;
ResponseStatus responseStatus = new ResponseStatus("404", "Wrong path to resource.");
status = HttpStatus.NOT_FOUND;
ResponseEntity<ResponseStatus> response = new ResponseEntity<ResponseStatus>(responseStatus, status);
return response;
}
Which will simply return a JSON object called Response Status that contains an error code and error message as fields.

Resources