Session timeout with Filter in Spring Boot - spring-boot

I'm trying to implement Session timeout logic in Spring Boot project with Filter.
The filter intercepts every request and checks whether the session is new
If the session is timed out, it should redirect to login page.
Since there is an iFrame, the redirection is happening inside iframe. how to redirect main page to login page?
Here is the code below:
<body>
<a
class="nav-link dropdown-toggle menu-submenu"
role="button"
data-toggle="collapse"
aria-haspopup="true"
aria-expanded="false"
target="basefrm"
th:attr="href='#'+${submenu.shortCode}, title=${submenu.title}">
<iframe
class="embed-responsive-item justify-content justify-content-center "
height="100%"
width="100%"
id="basefrm"
name="basefrm"
scrolling="overflow-x:no ;overflow-y:no"
frameborder="0"
allowTransparency="false"
style="height: 100%;"></iframe>
</body>
#WebFilter("/*")
#Order(1)
public class TransactionFilter extends HttpFilter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession(false);
if(session !=null && !session.isNew() && session.getAttribute("login") != null) {
if(req.getRequestURI().equalsIgnoreCase(req.getContextPath()+"/")) {
res.sendRedirect(req.getContextPath()+"/loginpage.html");
}
else {
chain.doFilter(request, response);
}
}
else {
logger.debug("session timed out!");
session = req.getSession(true);
session.setAttribute("login", true);
logger.debug("path:{}"+req.getContextPath());
res.sendRedirect(req.getContextPath()+"/loginpage.html");
}
}
}

Related

Spring OAuth2 SSO preflight request handling

I am studying Spring OAuth and got some troubles with CORS and preflight requests, probably someone could help me.
As a base I took example project from "Cloud Native Java" book:
https://github.com/cloud-native-java/edge
For my question two parts are relevant: Gateway SSO service (greetings-client) and Authorization service (auth-service).
Here is SSO configuration:
#Configuration
#EnableOAuth2Sso
class SsoConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers( "/", "/app.js", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().logout().logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Edge service has simple UI part. When called directly it tries to make a request to /user endpoint, which is protected, to get Principal info.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Edge Service</title>
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width"/>
<base href="/"/>
<script type="text/javascript"
src="/webjars/jquery/jquery.min.js"></script>
<script type="text/javascript"
src="/webjars/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript"
src="/webjars/angularjs/angular.min.js"></script>
</head>
<body ng-app="app" ng-controller="home as home">
<div class="container" ng-show="!home.authenticated">
Login
</div>
<div class="container" ng-show="home.authenticated">
<!--1-->
Logged in as:
<b><span ng-bind="home.user"></span></b> <br/>
Token:
<b><span ng-bind="home.token"></span> </b><br/>
Greeting from Zuul Route: <b>
<span ng-bind="home.greetingFromZuulRoute"></span></b> <br/>
Greeting from Edge Service (Feign):
<b><span ng-bind="home.greetingFromEdgeService"></span></b><br/>
</div>
<!--2-->
<script type="text/javascript" src="app.js"></script>
</body>
</html>
And javascript:
var app = angular.module("app", []);
//<1>
app.factory('oauth', function () {
return {details: null, name: null, token: null};
});
app.run(['$http', '$rootScope', 'oauth', function ($http, $rootScope, oauth) {
$http.get("/user").success(function (data) {
oauth.details = data.userAuthentication.details;
oauth.name = oauth.details.name;
oauth.token = data.details.tokenValue;
// <2>
$http.defaults.headers.common['Authorization'] = 'bearer ' + oauth.token;
// <3>
$rootScope.$broadcast('auth-event', oauth.token);
});
}]);
app.controller("home", function ($http, $rootScope, oauth) {
var self = this;
self.authenticated = false;
// <4>
$rootScope.$on('auth-event', function (evt, ctx) {
self.user = oauth.details.name;
self.token = oauth.token;
self.authenticated = true;
var name = window.prompt('who would you like to greet?');
// <5>
$http.get('/greetings-service/greet/' + name)
.success(function (greetingData) {
self.greetingFromZuulRoute = greetingData.greeting;
})
.error(function (e) {
console.log('oops!' + JSON.stringify(e));
});
// <6>
$http.get('/lets/greet/' + name)
.success(function (greetingData) {
self.greetingFromEdgeService = greetingData.greeting;
})
.error(function (e) {
console.log('oops!' + JSON.stringify(e));
});
});
});
So it is expected, that login procedure is initiated and login form appears.
Actual result: browser gets redirected to authorization server and hits CORS error
Access to XMLHttpRequest at 'http://localhost:9191/uaa/oauth/authorize?client_id=html5&redirect_uri=http://localhost:8082/login&response_type=code&state=1zegi7' (redirected from 'http://localhost:8082/user') from origin 'http://localhost:8082' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Here: localhost:8082 - Gateway service, localhost:9191 - authorization server.
In Browser console I can see that was an OPTIONS request.
On the other hand, if I explicitly call /login endpoint (provided by spring) it works as expected - the login form appears and
after credentials validation I get redirected back to the home page.
Gateway service has simple servlet filter, where I explicitly set ACCESS_CONTROL_ALLOW_ORIGIN header.
#Component
class CorsFilter implements Filter {
private final Log log = LogFactory.getLog(getClass());
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = HttpServletResponse.class.cast(res);
HttpServletRequest request = HttpServletRequest.class.cast(req);
log.info(request.getMethod());
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
The questions:
How to properly handle preflight requests in such case?
Shouldn't servlet filter handle OPTIONS request? (I do not see it in logs)
What I have tried:
Use explicit servlet filter (shown above)
Use HttpSecurity.cors() method in conjuction with CorsConfigurationSource bean:
#Configuration
#EnableOAuth2Sso
class SsoConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().antMatcher("/**").authorizeRequests()
.antMatchers( "/", "/app.js", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().logout().logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedMethods(Arrays.asList("GET","POST","OPTIONS"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
UPDATE: Here are properties I used for SSO gateway:
spring.application.name=greetings-client
server.port=${PORT:8082}
security.oauth2.resource.userInfoUri=http://auth-service/uaa/user
spring.mvc.dispatch-options-request=true
zuul.routes.hi.path=/lets/**
zuul.routes.hi.serviceId=greetings-service
management.security.enabled=false
zuul.ignoredServices=*
eureka.instance.preferIpAddress=true
eureka.instance.leaseRenewalIntervalInSeconds=10
And here are properties for Auth service:
server.port=${PORT:9191}
spring.application.name=auth-service
server.context-path=/uaa
security.sessions=if_required
logging.level.org.springframework.security=DEBUG
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
eureka.instance.preferIpAddress=true
eureka.instance.leaseRenewalIntervalInSeconds=10
And Authorization server configuration:
#Configuration
#EnableAuthorizationServer
class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final ClientDetailsService clientDetailsService;
#Autowired
public AuthorizationServerConfiguration(
AuthenticationManager authenticationManager,
ClientDetailsService clientDetailsService) {
this.authenticationManager = authenticationManager;
this.clientDetailsService = clientDetailsService;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// <1>
clients.withClientDetails(this.clientDetailsService);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// <2>
endpoints.authenticationManager(this.authenticationManager);
}
}

using servlet filter with session variable

I have created a Java web application using JSF 2.
When a user login to my application, I store his identifier in the session, so:
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getSessionMap().put("userid", myBean.getUserId());
Then I created my filter:
public class PageFilter implements Filter {
private FilterConfig filterconfig;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterconfig = filterconfig;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httprequest =(HttpServletRequest) request;
HttpServletResponse httpresponse =(HttpServletResponse) response;
HttpSession session = ((HttpServletRequest) request).getSession();
String userid = (String) session.getAttribute("userid");
String pageRequested = httprequest.getRequestURI();
try {
if( userid != null && pageRequested.contains("index.xhtml") ) {
System.out.printf("User authenticated with " + httprequest.getRemoteUser() + " username conected.");
httprequest.getRequestDispatcher("/service/home.xhtml").forward(request, response);
} else {
chain.doFilter(request, response);
}
}catch(IOException | ServletException e){
//do something
}
}
#Override
public void destroy() {
System.out.print("Existing from loginFilter");
}
}
My scope is to manage the refresh button of the browser so if user is already logged then user is redirected to /service/home.xhtml. Also, the url in my web application is always:
localhost:8080/myapplication
So if user browses the site among all the pages, the url is always that (the action is hidden).
The problem is that if user clicks on the url in the browser, the request is for index.xhtml and my session is null (I cannot get user identifier by session.getAttribute("userid");).
Where is my fault?
The index.xhtml is defined as welcome-file-list in my web.xml:
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
Thanks.

Spring & Security: limit uploads to authenticated users

I'm facing a security problem regarding file uploads.
How do I limit file uploads to specific user roles?
I'm using #PreAuthorize("hasRole('USER')"), but it is uploading the file first and then checking the role. You can especially see this when file upload size is exceeded. User will get an upload size exceeded exception instead of redirecting to the login-form.
This is how my controller looks like:
#Controller
#PreAuthorize("hasRole('USER')")
#Secured("ROLE_USER") // added this just to see if it makes a difference, it doesn't
#RequestMapping(value = "/self/upload", produces = "application/json")
public class JsonUserSelfUpload {
...
#RequestMapping(value = "", method = RequestMethod.POST, consumes="multipart/form-data")
public ModelAndView fileUpload(
#RequestParam(value = "file", required = true) MultipartFile inputFile,
#RequestParam(value = "param1", defaultValue = "") String type,
HttpServletResponse response
) throws Exception {
...
}
}
Anyone know how to secure file uploads to specific roles?
Edit, to be more specific:
I want to reject uploads if user is not authenticated. By reject I mean, close connection before the upload actually finishes. Not sure if spring is capable in doing this or I'd need a filter to reject uploads (multipart).
Update:
Tried with a filter with no success either.
Seems like one has no way to close the connection.
This is what my filter looks like:
public class RestrictUploadFilter implements Filter{
#Override
public void init(FilterConfig arg0) throws ServletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String contentType = request.getContentType();
if (HttpMethods.POST.equals(request.getMethod()) && contentType != null && contentType.toLowerCase().indexOf("multipart/form-data") > -1) {
UserSession session = SpringHelper.getUserSession();
if (session != null && session.getRoles().contains(UserRole.USER)) {
// user is allowed to upload
chain.doFilter(req, res);
} else {
// access denied
response.setStatus(HttpStatus.FORBIDDEN_403);
response.setHeader("Connection", "close");
response.flushBuffer();
}
} else {
chain.doFilter(req, res);
}
}
}

Spring security - Restricting Authenticated User redirection to Login

After login, when login url is accessed with out logging out, login page is shown, but I do not want the login Page, instead remain on the same page even when login url is accessed from address bar.
Following is my security configuration:
<form-login login-page="/loginform.do" authentication-failure-url = "/loginform.do?error=1" default-target-url="/dashBoard.do" always-use-default- target="false" />
One solution I come across is to redirect page, if the role is not 'ROLE_ANONYMOUS'
<sec:authorize ifNotGranted="ROLE_ANONYMOUS">
<% response.sendRedirect("/mainpage.jsp"); %>
</sec:authorize>
But can a similar configuration be done in security configuration file ?
I solved this with an HandlerInterceptor because I dont know a build in solution.
import org.springframework.web.util.UrlPathHelper;
...
public class PreventLoginPageForLoggedInUserInterceptor extends HandlerInterceptorAdapter {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception {
if (urlPathHelper.getLookupPathForRequest(request).startsWith("/login"))
&& isAuthenticated()) {
sendRedirect(request, response);
return false;
} else {
return true;
}
}
private void sendRedirect(HttpServletRequest request,
HttpServletResponse response) {
response.setStatus(HttpStatus.TEMPORARY_REDIRECT.value());
response.setHeader("Location", response.encodeRedirectURL(request.getContextPath() + "/"));
}
private boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (authentication != null)
&& (!authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()
}
}

How to handle servlet response in JSP through AJAX call?

I want to display servlet response in my JSP page (as a hyperlink) through an ajax call. Can anyone please tell me how I could display the content in my jsp page? I am also not too sure if I am doing it the right way. There could be some errors in either my servlet class or Ajax.js. I'm still in learning phase. Here is my code snippet:
JSP page
<script type="text/javascript"> var AJAX_SERVLET="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/ajaxServlet";
</script>
<label for="push">Push to start</label>
<button dojoType="dijit.form.Button" style="width: 4em" type="button" name="submitButton" value="Submit" onclick="ajaxFunction()"></button>
Ajax.js
function ajaxFunction() {
if (xmlhttp) {
xmlhttp.open("GET", AJAX_SERVLET, true); //AJAX_SERVLET has the servlet path
xmlhttp.onreadystatechange = handleServerResponse;
xmlhttp.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded');
xmlhttp.send(null);
}
}
function handleServerResponse() {
if (xmlhttp.readyState == 4) {
//alert(xmlhttp.status);
if (xmlhttp.status == 200) {
var resultContent =httpRequest.getResponseHeader("Content-Type");
} else {
alert("Error during AJAX call. Please try again");
}
}
Getters/Setters
public class SearchResponse {
private String productNumber;
private String productType;
private String funcDesignation;}
Servlet Class
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
List result = new ArrayList();
result.add(new SearchResponse("001", "User Manual", "Operator"));
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new Gson().toJson(result));
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
doPost(request, response);
}

Resources