Spring security custom session timeout - spring

I am using spring security with my spring mvc webapp and I am trying to implement a custom session expiry. My requirement is when the session expired I need to retreive the user for which the session expired and then grab something from their security contexxt and write it to the redirect url.
The problem I am facing is that on session timeout the security context is not the user for which the session has timed out. Instead the security context comes out as anonymous user.
How can I get org.springframework.security.web.session.SessionManagementFilter to pass the user for which the session has timed out into onInvalidSessionDetected(request, response);
Here is the method in SessionManagementFilter where I need to somehow get the user for which the session timeout is happening and pass it to invalidSessionStrategy. Or alternatively can I just grab that inside invalidSessionStrategy.
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (!securityContextRepository.containsContext(request)) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {
// The user has been authenticated during the current request, so call the session strategy
try {
sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
} catch (SessionAuthenticationException e) {
// The session strategy can reject the authentication
logger.debug("SessionAuthenticationStrategy rejected the authentication object", e);
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
// Eagerly save the security context to make it available for any possible re-entrant
// requests which may occur before the current request completes. SEC-1396.
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
} else {
// No security context or authentication present. Check for a session timeout
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
logger.debug("Requested session ID" + request.getRequestedSessionId() + " is invalid.");
if (invalidSessionStrategy != null) {
invalidSessionStrategy.onInvalidSessionDetected(request, response);
return;
}
}
}
}
chain.doFilter(request, response);
}
When I do Authentication authentication = securityContext.getAuthentication(); inside onInvalidSessionDetected I get an anonymous user which is not what I want.
thanks

Related

Spring security 403 with disabled csrf

Using spring security, I've looked at similar questions but they say to try disable cors & csrf.
I am using it on the browser so I will need csrf. But just testing briefly doesn't change the outcome.
On login I get an access token and refresh token.
Using this token gives me a 403 forbidden response code.
My configuration is the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/login").permitAll();
http.authorizeRequests().antMatchers(GET, "/**").hasAnyAuthority("STUDENT");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
I think maybe its something to do with this filter but if I change forbidden.value to something else the result is still forbidden.value
public class CustomAuthorizationFilter extends OncePerRequestFilter { // INTERCEPTS EVERY REQUEST
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getServletPath().equals("/login")){ filterChain.doFilter(request,response); } // DO NOTHING IF LOGGING IN
else{
String authorizationHeader = request.getHeader(AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
try {
String token = authorizationHeader.substring("Bearer ".length()); // TAKES TOKEN STRING AND REMOVES BEARER
// THIS NEEDS MAKING SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); // <<<<<<<<<<<<<<<<<<<<<<<
JWTVerifier verifier = JWT.require(algorithm).build(); // USING AUTH0
DecodedJWT decodedJWT = verifier.verify(token);
String email = decodedJWT.getSubject(); // GETS EMAIL
String[] roles = decodedJWT.getClaim("roles").asArray(String.class); // GETS ROLES
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); }); // CONVERTS ALL USERS ROLE INTO AN AUTHORITY
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null); // PASSWORD IS NULL AT THIS POINT
SecurityContextHolder.getContext().setAuthentication(authToken); // INSERTS TOKEN INTO CONTEXT // THIS SHOWS AUTHENTICATED FALSE, DETIALS FALSE AND GRANTED AUTHORITIES EMPTY
filterChain.doFilter(request, response); // GETS TO THIS LINE HERE
}
catch (Exception e){
response.setHeader("error" , e.getMessage() );
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error); // THEN SKIPS RIGHT TO THIS LINE HERE EVEN IF BREAKPOINTING BEFORE
}
}
else{ filterChain.doFilter(request, response); }
}
}
}
debugging shows it hits filterChain.doFilter(request, response) then jumps straight to the exception catch objectMapper line
The user submitting is also of the Student role.
this line
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null);
is missing authorities:
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null, authorities);
Hope that my answer can help,
you can drop a breakpoint to the line change the response status, and then check who and why it returns 403, it can finally help you get the solution
Drop a breakpoint on the line set the 403 status, to see how this happen from the stackframes.
Guess that it returns 403 without much other information, but it must need to set the status to the response, right? So drop a breakpoint to the setStatus method, I don't know where it should locate, in tomcat lib, spring lib, or servlet lib. Check the HttpResponse, they're several implementation, set the breakpoints for those setStatus/setCode methods. (Next you can see it acutally happens at HttpResponseWrapper::setStatus)
Analyze the stackframes to see what's going on there
please check https://stackoverflow.com/a/73577697/4033979

Spring Security exclude URL in timeout

In Spring Security, how to exclude one particular URL from resetting the session timeout? Overall application session timeout(server.servlet.session.timeout) is 15 minutes. We have a ajax call from the web page that will get called every 1 minute. This call needs to be secured, but should not impact the session time.
We have tried adding a filter extending ConcurrentSessionFilter. Also, a filter extending SessionManagementFilter. Adding ignoring() skips authentication too. Nothing helped. Can this requirement be achieved in Spring Security? Any suggestions?
This is how i handled it. Just sharing, it may be of help to someone. Please share any better ways.
Spring Security filter is added as last in the chain.
http.addFilterAfter(new SessionInvalidationFilter(timeOutInMinutes), SwitchUserFilter.class);
It keeps track of a lastUpdatedTime, which gets updated for all calls except for those URLs that needs to be ignored. In case, the differential time is greater than the configured timeout, session gets invalidated.
public class SessionInvalidationFilter extends GenericFilterBean {
private static final String LASTUPDATEDDATETIME = "LASTUPDATEDDATETIME";
private static final List<String> ignoredURLs = Arrays.asList("/Notifications/number"); // this is the AJAX URL
private int timeOutInMinutes = 15;
public SessionInvalidationFilter(int timeOutInMinutes) {
this.timeOutInMinutes = timeOutInMinutes;
}
#Override
/**
* LASTUPDATEDDATETIME is updated for all calls except the ignoredURLs.
* Session invalidation happens only during the ignoredURLs calls.
*/
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
try {
if (session != null && request.getRequestURI() != null) {
if (ignoredURLs.contains(request.getRequestURI())) {
Object lastUpdatedDateTimeObject = session.getAttribute(LASTUPDATEDDATETIME);
if (lastUpdatedDateTimeObject != null) {
LocalDateTime lastUpdatedDateTime = (LocalDateTime) lastUpdatedDateTimeObject;
long timeInMinutes = ChronoUnit.MINUTES.between(lastUpdatedDateTime, LocalDateTime.now());
if (timeInMinutes >= timeOutInMinutes) {
log.info("Timing out sessionID:{}", session.getId());
session.invalidate();
SecurityContextHolder.clearContext();
}
}
} else {
session.setAttribute(LASTUPDATEDDATETIME, LocalDateTime.now());
}
}
} catch (Exception e) {
log.error("Exception in SessionInvalidationFilter", e);
}
chain.doFilter(request, response);
}
}

How can I refresh tokens in Spring security

This line:
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
Throws an error like this when my jwt token expires:
JWT expired at 2020-05-13T07:50:39Z. Current time:
2020-05-16T21:29:41Z.
More specifically, it is this function that throws the "ExpiredJwtException" exception :
How do I go about handling these exceptions? Should I catch them and send back to the client an error message and force them to re-login?
How can I implement a refresh tokens feature? I'm using Spring and mysql in the backend and vuejs in the front end.
I generate the initial token like this:
#Override
public JSONObject login(AuthenticationRequest authreq) {
JSONObject json = new JSONObject();
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authreq.getUsername(), authreq.getPassword()));
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
.collect(Collectors.toList());
if (userDetails != null) {
final String jwt = jwtTokenUtil.generateToken(userDetails);
JwtResponse jwtres = new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(),
userDetails.getEmail(), roles, jwtTokenUtil.extractExpiration(jwt).toString());
return json.put("jwtresponse", jwtres);
}
} catch (BadCredentialsException ex) {
json.put("status", "badcredentials");
} catch (LockedException ex) {
json.put("status", "LockedException");
} catch (DisabledException ex) {
json.put("status", "DisabledException");
}
return json;
}
And then in the JwtUtil class:
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRESIN))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
For more info, here is my doFilterInternal function that filters every request:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException, ExpiredJwtException, MalformedJwtException {
try {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
boolean correct = jwtUtil.validateToken(jwt, userDetails);
if (correct) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
} catch (ExpiredJwtException ex) {
resolver.resolveException(request, response, null, ex);
}
}
There are 2 main approaches to deal with such situations:
Manage access and refresh tokens
In this case, the flow is the following one:
User logins into the application (including username and password)
Your backend application returns any required credentials information and:
2.1 Access JWT token with an expired time usually "low" (15, 30 minutes, etc).
2.2 Refresh JWT token with an expired time greater than access one.
From now, your frontend application will use access token in the Authorization header for every request.
When backend returns 401, the frontend application will try to use refresh token (using an specific endpoint) to get new credentials, without forcing the user to login again.
Refresh token flow
(This is only an example, usually only the refresh token is sent)
If there is no problem, then the user will be able to continue using the application. If backend returns a new 401 => frontend should redirect to login page.
Manage only one Jwt token
In this case, the flow is similar to the previous one and you can create your own endpoint to deal with such situations: /auth/token/extend (for example), including the expired Jwt as parameter of the request.
Now it's up to you manage:
How much time an expired Jwt token will be "valid" to extend it?
The new endpoint will have a similar behaviour of refresh one in the previous section, I mean, will return a new Jwt token or 401 so, from the point of view of frontend the flow will be the same.
One important thing, independently of the approach you want to follow, the "new endpoint" should be excluded from the required Spring authenticated endpoints, because you will manage the security by yourself:
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
..
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
..
.authorizeRequests()
// List of services do not require authentication
.antMatchers(Rest Operator, "MyEndpointToRefreshOrExtendToken").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
..
}
}
You can call the API for getting the refresh token as below
POST https://yourdomain.com/oauth/token
Header
"Authorization": "Basic [base64encode(clientId:clientSecret)]"
Parameters
"grant_type": "refresh_token"
"refresh_token": "[yourRefreshToken]"
Please be noticed that, the
base64encode is the method to encrypt the client authorization. You can use online at https://www.base64encode.org/
the refresh_token is the String value of the grant_type
yourRefreshToken is the refresh token received with JWT access token
The result can be seen as
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
"expires_in":20,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}
Good luck.

HttpServletRequest getting new session

I have an application that does authentication via oauth.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) request;
HttpServletResponse httpResp = (HttpServletResponse) response;
// Check if already logged in
if (getUser(httpReq) != null) {
chain.doFilter(request, response);
return;
}
// Try to parse auth response
if (procAuthResponse(httpReq)) {
chain.doFilter(request, response);
return;
}
// Go to auth server
sendAuthRequest(httpReq, httpResp);
}
This works fine.
In the method procAuthResponse I am paring the response from the server and to this.
HttpSession session = request.getSession();
session.setAttribute(USER_PRINCIPLE_ATR, userInfo);
It works also well, but there is a session scoped class with the method getCurrent user, that is used by servlets.
public UserInfo getCurrentUser() {
HttpSession session = getHttpSession();
if (session == null) {
LOG.warn("Method getCurrentUser: unable to find a session");
return null;
}
Object user = session.getAttribute(OAuthLoginFilter.USER_PRINCIPLE_ATR);
if (!(user instanceof UserInfo)) {
LOG.warn(String.format("Method getCurrentUser, wrong type for attribute %s", OAuthLoginFilter.USER_PRINCIPLE_ATR));
return null;
}
currentUser = (UserInfo) user;
return currentUser;
}
This method gets called multiple times and it turnes out that on the first call everything works as expected and after that the getHttpSession() returns a different session that does not contain any information that is set in the filter class. It is not a new session every time, the session without the needed information is always the same.
Code of getHttpSession()
private HttpSession getHttpSession() {
Object request = FacesContext.getCurrentInstance().getExternalContext().getRequest();
if (!(request instanceof HttpServletRequest)) {
LOG.warn("not a valid http request");
return null;
}
HttpServletRequest hreq = (HttpServletRequest) request;
return hreq.getSession(false);
}
Do you have any idea why this happens?
Thx for your help
There was still an old filter class, not configured in the web.xml, but annotated with #WebFilter("/*").
I deleted this file and now everything works as expected.

Spring Security: How to get the initial target url

I am using the spring security to restricted urls. I am trying to provide signup and login page, on the same page.
On login spring security transfers to the restricted page. However i am trying to pass the target url to the signup process, so that after signup we can redirect to the restricted page.
How to get the actual URL that user was redirected from.
Any Ideas?
This is how i got the URL from the Spring Security.
SavedRequest savedRequest = (SavedRequest)session.getAttribute(
AbstractProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY);
String requestUrl = savedRequest.getFullRequestUrl();
They moved things around a bit in spring security 3.0, so the above code snippet doesn't work anymore. This does the trick, though:
protected String getRedirectUrl(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if(session != null) {
SavedRequest savedRequest = (SavedRequest) session.getAttribute(WebAttributes.SAVED_REQUEST);
if(savedRequest != null) {
return savedRequest.getRedirectUrl();
}
}
/* return a sane default in case data isn't there */
return request.getContextPath() + "/";
}
with spring security 4.1.4:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
if (savedRequest != null) {
response.sendRedirect(savedRequest.getRedirectUrl());
}
else{
response.sendRedirect("some/path");
}
}
DefaultSavedRequest savedRequest = (DefaultSavedRequest)session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
String requestURL = savedRequest.getRequestURL(); // URL <br>
String requestURI = savedRequest.getRequestURI(); // URI

Resources