I am developing an App in FrontEnd (ReactJS, AXIOS) and Backend (Spring).
When I did a post to do login with success with :
axios({
method: "post",
url: "/api/login",
data: bodyFormData,
headers: { "Content-Type": "multipart/form-data" },
})
.then(function (response) {
//handle success
console.log(response);
})
.catch(function (response) {
//handle error
console.log(response);
})
I got this answer in Postman
{
"timestamp": "2022-05-23T01:48:27.986+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/"
}
I have these filters
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll().and()
.authorizeRequests()
.antMatchers("/console/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/api/login").permitAll()
.and()
.logout().logoutUrl("/api/logout").permitAll();
http.csrf().disable();
http.headers().frameOptions().disable();
}
I want to receive just in /api/login the data of the user validated.
Can it achieve that with the filters only or do I need to create a controller?
I solved it with two things.
I created a endpoint with
This code give me the actual user :
#RequestMapping("/actualuser")
public Object actualUser(){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getDetails());
return auth.getPrincipal();
}
and I this filter in .formLogin() and logout .logout()
.successForwardUrl("/api/actualuser")
Related
This is how my websecurityconfig is described:
public void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(corsFilter(), SessionManagementFilter.class) //adds your custom CorsFilter
.authorizeRequests()
.antMatchers("/ping/get").permitAll()
.antMatchers("/user/updatePassword").permitAll()
.antMatchers("/user/resetPassword").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(successHandler())
.failureHandler(failureHandler())
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.logout()
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
})
.permitAll()
;
http.csrf().disable();
http.headers()
.addHeaderWriter(
new StaticHeadersWriter("Access-Control-Allow-Origin", "http://localhost:4200")
);
http.headers()
.addHeaderWriter(
new StaticHeadersWriter("Access-Control-Allow-Credentials", "true")
);
}
and the cors filter i've defined the header for samesite=none as follows
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) servletResponse;
HttpServletRequest req= (HttpServletRequest) servletRequest;
res.setHeader("Access-Control-Allow-Origin", req.getHeader("origin"));
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin,content-type");
res.addHeader("Access-Control-Expose-Headers", "xsrf-token");
res.setHeader("Set-Cookie", "HttpOnly;Secure;SameSite=None");
if ("OPTIONS".equals(req.getMethod())) {
res.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(req, res);
}
}
#Override
public void destroy() {
}
}
But whenever i'm calling the login endpoint, i'm only receiving httponly,secure but nnot samesite=none with my JSESSIONID cookkie. How do i make this work?
i've tried all different filter from answers of other questons but none of them work.
The issue is only on chrome. It would be helpful if there is any workaroud also. Recently chrome removed the samesite flag which could be disabled. need to solve this in order to create a webview for my site.
You can set the SameSite attribute when using Spring Session with a custom CookieSerializer.
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setSameSite("None");
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
Spring boot 2.4.3
If the user has no permission for action, it gives 403 response but without any response body. However the header WWW-Authenticate is set:
WWW-Authenticate: Bearer error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token."
I would like still to have some response body with message. How could I achieve it?
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.formLogin(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(authorize -> authorize
.mvcMatchers(HttpMethod.GET, "/app/**", "/admin/**").authenticated()
.mvcMatchers(HttpMethod.PUT, "/app/**", "/admin/**").authenticated()
.mvcMatchers(HttpMethod.POST, "/app/**", "/admin/**").authenticated()
)
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
}
I had the same problem and found the following solution:
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
e.getCause().printStackTrace();
response.setStatus(...);
response.setContentType(...);
response.getWriter().write("your custom error response");
}
}
Then in your security config:
http.
//...
.oauth2ResourceServer()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
I have a Spring-cloud based micro-services application. I also have an API gateway in front of all these services.
I need to support two types of clients.
One of them can call my application using an authorization token (by calling /authorize for example). The token is basically the SESSION ID. All the servers share the session by using Spring Session Redis.
The second client can only send me http basic authentication (user:pass as authorization header).
In the case of the second client, I need to check if the user is already authenticated and has an active session in redis. I added filter before BasicAuthenticationFilter in my security configuration to check that.
If the user has an active session, I'm putting the SESSIONID in the header, and removing the authorization header from the request (I'm using a custom HttpServletRequest wrapper for that). My purpose was that from that point on, Spring will manage the request in the downstream micro-services as if it was sent with a SESSIONID. The reason for that is to avoid a very long login time (more than 1 second).
Here’s my issue: when spring checks if the SESSIONID exists, it checks the original request which doesnt have any sessionId.
Security configuration:
#Resource
#Qualifier("sessions")
private Map<String, String> sessions;
#Autowired
#Qualifier("httpSessionStrategy")
HttpSessionStrategy sessionStrategy;
#Override
protected void configure(HttpSecurity http) throws Exception {
// // #formatter:off
http
.addFilterBefore(setSessionIdInHeader(), BasicAuthenticationFilter.class)
.sessionManagement()
.and()
.exceptionHandling()
.authenticationEntryPoint(restEntryPoint())
.and()
.headers().addHeaderWriter(new StaticHeadersWriter("Server",""))
.and()
.httpBasic()
.authenticationEntryPoint(restEntryPoint())
.and()
.logout().addLogoutHandler(clearTicketOnLogoutHandler())
.logoutSuccessHandler(logoutSuccessHandler())
.and()
.authorizeRequests()
.antMatchers("/index.html", "/login", "/").permitAll()
.antMatchers(HttpMethod.OPTIONS).denyAll()
.antMatchers(HttpMethod.HEAD).denyAll()
.anyRequest().authenticated()
.and()
.authenticationProvider(authenticationProvider)
.csrf()
.disable()
.addFilterAfter(ticketValidationFilter(), SessionManagementFilter.class)
.addFilterAfter(changePasswordFilter(), SessionManagementFilter.class)
.addFilterAfter(httpPolutionFilter(), SessionManagementFilter.class)
.addFilterAfter(saveSessionId(), SessionManagementFilter.class);
// #formatter:on
}
Filter to add header to request:
private Filter setSessionIdInHeader(){
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Jedis jedis = null;
String authorization = request.getHeader("authorization");
String sessionId = null;
if (authorization != null){
if (sessions.get(authorization) != null){ //user already authenticated
sessionId = sessions.get(authorization);
jedis = getJedisPool().getResource();
if (jedis.hgetAll("spring:session:sessions:"+sessionId) != null){ //session alive in redis
log.info("session :"+ sessionId +" exists in redis");
HeaderMapRequestWrapper wrapper = new HeaderMapRequestWrapper(request);
wrapper.addHeader("TOKEN", sessionId);
wrapper.addHeader("mock_authorization", authorization);
filterChain.doFilter(wrapper, response);
}
}
}
filterChain.doFilter(request, response);
}
};
}
Change header name of SESSIONID:
#Bean
public HeaderHttpSessionStrategy httpSessionStrategy(){
HeaderHttpSessionStrategy headerHttpSessionStrategy = new HeaderHttpSessionStrategy();
headerHttpSessionStrategy.setHeaderName("TOKEN");
return headerHttpSessionStrategy;
}
private Filter saveSessionId() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if(request.getHeader("authorization") != null){
sessions.put(request.getHeader("authorization"), request.getSession().getId());
}else{
sessions.put(request.getHeader("mock_authorization"), request.getSession().getId());
}
}
};
}
I've implemented some token based authentication in my spring-boot application. I have a filter and in that filter, I am doing the following:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader("X-TOKEN-AUTH");
String username = null;
if (securityEnabled) {
if (authToken != null) {
try {
username = userTokenService.validateToken(authToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), null, userDetails.getAuthorities());
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (AuthenticationException ae) {
//TODO log something about signature exception
log.warn(ae.getMessage());
}
}
}
chain.doFilter(request, response);
}
I also have a custom AuthFailureHandler:
#Component
public class AuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.write(exception.getMessage());
writer.flush();
}
}
My code username = userTokenService.validateToken(authToken); throws an AuthenticationException for various reasons. AuthenticationException is a custom exception that extends Exception. When I catch this exception, I still want to return a 401, but I want my message to appear in what is currently being sent back as JSON by Spring Security as default:
{
"timestamp": 1463408604943,
"status": 401,
"error": "Unauthorized",
"message": "An Authentication object was not found in the SecurityContext",
"path": "/api/brands/2"
}
I would want, for example...
{
"timestamp": 1463408604943,
"status": 401,
"error": "Unauthorized",
"message": "Invalid Token: Expired",
"path": "/api/brands/2"
}
I'm unsure how to override this behavior.
So...I finally figured this out. The problem was that Filters are higher up on the food chain so they really don't involve Spring all that much. Where I was throwing an exception in the Filter, Spring wasn't necessarily catching it. The filter would just throw a 500 and display the exception message. To fix it, I simply had to catch my exceptions in the filter and then call sendError with the appropriate http status.
try {
username = userTokenService.validateToken(authToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), null, userDetails.getAuthorities());
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(auth);
chain.doFilter(request, response);
return;
} catch (Exception ex) {
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());
return;
}
The return statement in the catch is so that my chain.doFilter(request, response); at the end of the doFilter method isn't called.
I am using java based spring security configuration in my spring boot application. When user clicks on logout link, user is redirected to the login page. Here, in this case, I need to pass a custom parameter in the logout success url.
e.g. when I logout, app is redirected to http://localhost:8080/app/login
But I want it to have a parameter like below
http://localhost:8080/app/login?idletimeout=true
I have created my custom LogoutSuccesshandle for this. I get the param value in the handler and then I construct the success url and then redirect to it. But then on logout that parameter goes missing.
Below is my handler code.
public class LogoutSuccessHandlerImpl extends SimpleUrlLogoutSuccessHandler {
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
request.getSession().invalidate();
SecurityContextHolder.clearContext();
request.setAttribute("isLoggedOut", "true");
String contextPath = request.getContextPath();
String redirectURL = "/login";
String isIdleTimeOut = request.getParameter("idleTimeout");
request.setAttribute("idleTimeout", isIdleTimeOut);
System.out.println(isIdleTimeOut + " isIdleTimeOut ");
if (isIdleTimeOut != null && isIdleTimeOut.equalsIgnoreCase("true")) {
System.out.println("in if ");
redirectURL += "?idleTimeout=" + isIdleTimeOut;
}
// setDefaultTargetUrl(redirectURL);
// response.sendRedirect(redirectURL);
// super.onLogoutSuccess(request, response, authentication);
redirectStrategy.sendRedirect(request, response, redirectURL);
}
Below is my java config code.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/checkLogin")
.defaultSuccessUrl("/home")
.failureUrl("/login?login_error=1")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandlerImpl())
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/error**").permitAll()
.antMatchers("/checkLogin**").permitAll()
.anyRequest()
.authenticated()
.accessDecisionManager(accessDecisionManager)
.and()
.exceptionHandling()
.accessDeniedPage("/accessDenied")
.and()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.invalidSessionUrl("/login")
.maximumSessions(1);
}
What you can do is to prepare your own logout method (a custom logout url) for your applicatoin:
1) Prepare your LogoutController:
#Controller
#RequestMapping(value = "/logout")
public class LogoutController {
#RequestMapping(value = {"", "/"})
public String logout(HttpServletRequest request) {
SecurityContextHolder.clearContext();
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return "redirect:" + <your-logout-success-url>;
}
}
Here, invalidating the session and clearing the context provides you a logout mechanism.
2) Update your logout urls inside jsp files:
<span>Log out</span>
3) In addition, for default "log out" scenarios, you can still continue to use the Spring Security log out:
<logout logout-success-url="/" invalidate-session="true" delete-cookies="JSESSIONID"
logout-url="/j_spring_security_logout"/>
So, in this logout method, you can do whatever you want with the request object when user demands the logout process. You do not need to pass parameters now.
It should be something like this
http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/login?idletimeout=true"));
In xml
<logout invalidate-session="true" logout-success-url="/login?idletimeout=true"/>