I have made a servlet filter to do custom authentication (based on a header set by the apache fronting my tomcat). If there is already an authentication object in the security context, I am using this.
However in some cases that authentication object belongs to another user, not the one making the request.
What am I doing wrong?
Is there a way to reliably get the authenticated user, or should I always do the authentication in the filter?
If I have to do the authentication every time, is it okay to create a new Authentication object (which is a wrapper around my user entity), or it would lead to memory leak, so I should cache those objects?
Here are the relevant parts of the code:
#Service
public class RemoteAuthenticationFilter extends GenericFilterBean
implements Filter {
#Override
public void doFilter(
final ServletRequest req, final ServletResponse res,
final FilterChain filterChain
) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) req;
final SecurityContext context = SecurityContextHolder.getContext();
if (
context.getAuthentication() == null ||
!context.getAuthentication().isAuthenticated()
) {
//creating an Authentication in auth
SecurityContextHolder.getContext().setAuthentication(auth);
} else {
// in this branch context.getAuthentication() sometimes returns another user
}
filterChain.doFilter(req, res);
}
}
This is because you never clear SecurityContext and since it uses ThreadLocal to store authentication, if the same thread is used to process the next request, it still retains the previous authentication.
I would suggest you to add SecurityContextHolder.clearContext() after chain.doFilter(req, resp), get rid of if-else statement and just create a new authentication for each request.
Related
I have a spring-boot REST API application. The REST endpoints are protected by spring-security.
This is the configuration of spring-security:
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.exceptionHandling().authenticationEntryPoint(new CustomForbiddenErrorHandler())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/**").authenticated() // HTTP 403 if the request is not authenticated
.antMatchers("/**").permitAll();
}
It works fine. If I make a rest call without having an auth-token on the HTTP header I get back the proper HTTP error code.
Can I force spring somehow to add a self auth-token if it is not presented in order so I can make REST calls without having my own access management system? It will be installed later.
I am not asking how to write JUnit test. What I am asking is how to generate a mock auth-token on the fly and add it to the request if it does not exist.
You can override the existing authentication filter, or create a new custom filter, to check if a request contains a bearer token or not. Based on the result, you can either process the request as it is, or augment the request with your custom authentication object.
Check out OAuth2AuthenticationProcessingFilter, this extracts an OAuth2 token from the incoming request and uses it to populate the Spring Security context. You can either override its behavior or create a new filter that populates the security context with your mock authentication object.
Here is a sample code to get you started:
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
boolean debug = logger.isDebugEnabled();
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
Authentication authentication = this.tokenExtractor.extract(request);
if (Objects.isNull(authentication)) {
final UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("username", "password");
authenticationToken.setDetails(Collections.singletonMap("user_uuid", userUuid.toString()));
final OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(null, authenticationToken);
// You can either ask your authenticatoin manager to authenticate these credentials or directly publish auth success event with your mock auth object.
this.eventPublisher.publishAuthenticationSuccess(oAuth2Authentication);
SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication);
} else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
Authentication authResult = this.authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
this.eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
In my Spring Boot application i'm handling JWTs with the following Filter:
(irrelevant code is omitted for brevity)
#Component
public class JwtFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// processing the JWT from the request
Authentication auth =
new UsernamePasswordAuthenticationToken(username, password, roles);
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
}
}
This works perfectly, but i would like to migrate this functionality to an interceptor instead, so i simply added the same logic to a HandlerInterceptor:
#Component
public class JwtInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// processing the JWT from the request
Authentication auth =
new UsernamePasswordAuthenticationToken(username, password, roles);
SecurityContextHolder.getContext().setAuthentication(auth);
return true;
}
}
But in this case, i always receive 403 Forbidden response for my requests.
I've debugged the JwtInterceptor and it seems to be working properly.
preHandle() is fully executed, setAuthentication() is called with the correct auth parameter and the function returns true.
So i guess the Authentication gets "lost" after the execution of this interceptor, but i cannot figure out why and at this point i don't really know how to debug further.
I'd really appreciate any advice on how to solve this (or even just how to figure out what the problem might be).
I have a simple implementation of login filter.
public class LoginFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
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);
if (session == null || session.getAttribute("loggedInUser") == null) {
response.sendRedirect(request.getContextPath() + "/login.jsp");
} else {
chain.doFilter(request, response);
}
}
#Override
public void destroy() {}
}
When I go to any registered page(i.e. /account?id=1) without session attribute loggedInUser, filter works fine. It redirects me to login page.
But if I go to non-exists page (i.e. /blablabla.html), filter redirects me to login page again. Is there any method to get 404 error on entering non-exists pages and redirect to /login on exists?
The bug is in the requirement: you filter all requests to deny access to guests but still want the request to be processed if it's a 404. This would be conceptually wrong: a 404 is still an applicative response in the sense that it gives the user a view of the internals of the system - so the user must be authorized before knowing that something is or is not there.
Another option is splitting your app in a public and a private zone:
/public/style.css
/public/app.js
...
/private/customer/123
/private/oder/8932
...
and just filter requests in the private zone.
Note: if you are concerned about the beauty of the URL consider that the /private/ prefix is not a requirement. The filter can be attached in such a way that any prefix can be omitted
Remember the filters are there to filter any incoming request or outcoming response, so actually the flow is something like this.
client -----> request ---- > filter ----> servlet dispather ----> resources
So now, unfortunately the request will be intercepted by the filter no matter is the resource exist or not, and this happens before the servlet dispather can get the request and get realize that the resource doesn't exist.
I hope, this explanation can answer your question.
Thanks.
I have implemented my server with Spring 4 and Spring Security 3.2.
I am working with two possible scenarios to authentification, it depends from the user client type, web applications, which authentificate throught html form, and mobile client like Android or iOS.
User mobile application can leave to work with the app losing his authentication in the server when the session expire, in this scenario I am trying to authenticate client through Authenticator header param and one custom EntryPoint which can see like this.
public class AuthEntryPoint implements AuthenticationEntryPoint {
#Autowired
private RestProvider restProvider;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authenticationException)
throws IOException, ServletException {
Device device = (Device) request.getAttribute("device");
if (device.isNormal()) { // WEB
response.sendRedirect(response.encodeRedirectURL(request.getContextPath()));
} else { // MOBILE
DeviceAuth deviceAuth = new DeviceAuth(request.getHeader("Authorization"));
UserAuthToken userAuthToken = (UserAuthToken) this.restProvider.authenticate(
new IncomingToken(
deviceAuth.getEmail(),
null,
"user",
deviceAuth.getNode(),
deviceAuth.getAuthToken())
);
if (userAuthToken != null) {
SecurityContextHolder.getContext().setAuthentication(userAuthToken);
}
if (request.getRequestURI() != null) {
response.sendRedirect(response.encodeRedirectURL(request.getContextPath() +
request.getRequestURI()));
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Not page");
}
}
}
}
Device class is loaded previusly in the request from one custom Filter which analyze the request to determine the device type.
RestProvider is one of the two application Providers which return UserAuthToken, one custom implementation of UsernamePasswordAuthenticationToken.
All this work fine, except redirect question, I want to achieve transparent authentication process, namely, when user leave to work with the application, and return to use it after his server session (and authentication) has been destroyed, server identify bad credentials, get authentication header to authenticate and continue with the request transparently to the app.
How I can do?
Answer my question by myself.
Instead of implement AuthenticationEntryPoint, I had implemented BasicAuthenticationFilter with the same logic that can see in my previous post, removing the normal device control, as normal internet explorers not work with BasicAuthentication.
public class BasicAuthFilter extends BasicAuthenticationFilter {
...
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain filterChain) throws IOException, ServletException {
...
}
}
HERE you can read more about BasicAuthentication.
I've implemented a simple filter that simply adds two Principles to the current session (see doFilter below). My problem is that this is firing when i request a resource but then I'm never able to see the resource becasue the FORM based login screen pops up. I'm attempting to get around the form based login with this particular filter (eventually using a quick-to-expire token) though nothing seems to seem to allow me to do this.
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httprequest = (HttpServletRequest)request;
HttpServletResponse httpresponse = (HttpServletResponse)response;
HttpSession session = httprequest.getSession(true);
Subject subject = (Subject)session.getAttribute("javax.security.auth.subject");
if (subject == null){
subject = new Subject();
PlainUserPrincipal user = new PlainUserPrincipal("admin");
PlainRolePrincipal role = new PlainRolePrincipal("admin");
subject.getPrincipals().add(user);
subject.getPrincipals().add(role);
}
chain.doFilter(httprequest, httpresponse);
}
Due to security reasons you can't map servlets/filters on an URL pattern of /j_security_check when running Tomcat. The symptoms indicate that you're doing this. I say specifically Tomcat, because I've seen cases that it works on other (specific) container makes/versions. But you don't want to be dependent on that.
Rather filter on /*, or at least the same URL pattern as your security constraint, and intercept on the presence of the user principal and the absence of the session object.
if (request.getUserPrincipal() != null && session.getAttribute("subject") == null) {
Subject subject = new Subject();
// ...
session.setAttribute("subject", subject);
}
chain.doFilter(request, response);