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.
Related
I want to be able to handle 401 and show a specific page in angular 8 but currently only showing index.html file
Things to mind
Angular is the view for the spring boot so its not a separate application
I am not using spring security. Im just using filters in spring to determine if to be authorize
This is my filter.
#Component
public class CustomSessionFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) httpServletRequest;
if (!req.getRequestURI().startsWith("/sample/path")){
HttpServletResponse httpResponse = (HttpServletResponse) httpServletResponse;
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
}
Maybe its relevant that i have a Controller that extend ErrorController
#CrossOrigin
#RestController
public class IndexController implements ErrorController {
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public ModelAndView saveLeadQuery() {
return new ModelAndView("forward:/");
}
#Override
public String getErrorPath() {
return PATH;
}
}
EDIT: I didnt use spring security because i dont need to login i just have to go through specific path and do some authentication.. and there is no user for the application
I just wanted to place my 0.02$ here.
In order to secure a route in your application, you can create a Security Config that extends WebSecurityConfigurerAdapter, where you can protect certain routes through either antMatchers or mvcMathchers (second is recommended). Furthermore, there you can declare a set of roles or conditions (via Spring Expression Language) that will automatically throw a 401 Error in case a user that does not have an access is trying to access the route.
More about that you can find here https://www.baeldung.com/security-spring
As of ErrorController, I believe that controller advice would be more suitable for this use case. It pretty much intercepts all errors that you declare and can return more informative and generic response :)
More about that https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
As
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.
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).
What I am trying to do is the following:
Modify the logback to write out the users id and request id on the log lines.
e.g.
2017-11-24 [userid:abcd123 - requestId:12345679] [ClassA:MethodA1] message...
2017-11-24 [userid:abcd123 - requestId:12345679] [ClassA:MethodA2] message...
2017-11-24 [userid:abcd123 - requestId:12345679] [ClassB:MethodB1] message...
Notice that the requestId remains the same as it is all part of one request made to the system by the end user.
I have created a Filter based off of several examples where it shows how to set values into the MDC. e.g.(https://logback.qos.ch/manual/mdc.html1)
...
#Component
public class RequestFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
String mdcData = String.format("[userId:%s | requestId:%s] ", user(), requestId());
MDC.put("mdcData", mdcData); //Referenced from logging configuration.
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
private String requestId() {
return UUID.randomUUID().toString();
}
private String user() {
return "tux";
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
...
If I make a request to a rest service it executes one time without the system making any additional requests to itself for information. This is what I had expected and a I can see the log entries where they all contain the same requestId.
If I make a browser page request to one of our Swagger pages then the web page makes multiple internal requests for additional information that will appear on the page. The logging captures about 20 requests made by the loading of the web page request due to all of the additional requests for information that the page needs to render. When this occurs then I end up with X number of log entries where each of the internal requests are generating a unique request and requestId gets logged for each of them. This was not my intention.
HOW do I determine with a request to the system the initiating portion over the internal request calls that are created?
I need to not set the MDC values for the requestId over and over. I only need it set once per call based on the first request that gets made from the external user.
Not even sure what you would call this other than lifecycle of a request but I'm not finding the answer.
Appreciate any guidance. Thanks.
EDIT: Link to another question I have out there that is only dealing with identifying the original user request.
One way to address this is to map you RequestFilter to the URL pattern of your services that you want to log and not to "/*".
EDIT: Another idea would be to map the filter to "/*" but in your doFilter method, do not MDC any requests that contain "swagger" in the URL. You might have to experiment and possibly include all the URL's that get generated from the Swagger page, as some may not contain the word "swagger" in them.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (isRequestSwaggerUrl(request)) {
chain.doFilter(request, response);
return;
}
try {
String mdcData = String.format("[userId:%s | requestId:%s] ", user(), requestId());
MDC.put("mdcData", mdcData); //Referenced from logging configuration.
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
I am using Spring 4 to create an API and I need to have authentication for the API requests.
Currently, I have created a HandlerInterceptorAdapter to pick out authentication related headers and perform some validation on those values.
If everything is OK, I set the SecurityContext to a custom implementation of Authentication then in the postHandle I set the authentication to null.
Everything works great, except I keep getting warnings in Tomcat7 about ThreadLocal variables not being removed when the application shuts down.
SEVERE: The web application [] created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal#6c3e4fdb]) and a value of type [org.springframework.security.core.context.SecurityContextImpl] (value [org.springframework.security.core.context.SecurityContextImpl#ffffffff: Null authentication]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
I get that I may be doing this totally wrong, if so I would love some direction. :D
Here is my interceptor:
/**
* Intercepts Requests to set the Authentication in the SecurityContext.
* Sets the response to 401 - Unauthorized, if the header is missing
*/
#Component
public class AuthenticationHandlerInterceptor extends HandlerInterceptorAdapter {
private HandlerMediator mediator;
Logger log = LoggerFactory.getLogger(AuthenticationHandlerInterceptor.class);
#Autowired
public AuthenticationHandlerInterceptor(HandlerMediator mediator) {
this.mediator = mediator;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String username = request.getHeader("authentication-username");
String token = request.getHeader("authentication-token");
// if the remote host is local, then override the authentication
if (request.getRemoteHost().equals("0:0:0:0:0:0:0:1") || request.getRemoteHost().equals("127.0.0.1")) {
log.info("On localhost, overriding authentication with localhost");
username = "TEST";
token = "localhost:localhost";
}
if (username == null || username.trim().length() == 0) {
failAuthentication(response, "Missing Authentication Username Header");
return false;
}
if (token == null || token.trim().length() == 0 || !token.contains(":")) {
failAuthentication(response, "Missing Authentication Token Header");
return false;
}
String[] keys = token.split(":");
String appName = keys[0];
String apikey = keys[1];
if (!appName.equals("localhost")) {
// we are not under localhost so we have to authenticate the application calling us
if (mediator.executeCommand(new AuthenticateApplicationCommand(appName, apikey)) == false) {
failAuthentication(response, "Application Token failed authentication");
return false;
}
}
SecurityContextHolder.getContext().setAuthentication(new ApiAuthentication(username, appName));
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
SecurityContextHolder.getContext().setAuthentication(null);
}
private void failAuthentication(HttpServletResponse response, String message) throws Exception {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("text/html;charset=UTF-8");
ServletOutputStream out = response.getOutputStream();
out.println(message);
out.close();
}
}
How do I get rid of these warnings?
Thanks,
Joe
To do authentication based on the content of HTTP headers, the framework foresees a defining custom authentication filter, plugged in the spring security chain via configuration similar to this (see also this answer):
<security:http>
...
<security:custom-filter ref="customAuthenticationFilter" after="SECURITY_CONTEXT_FILTER" />
</security:http>
There in this filter it's possible to add code similar to this (see also BasicAuthenticationFilter):
try {
....
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
}
Have a look at class ThreadLocalSecurityContextHolderStrategy,it's there that the SecurityContextImpl instance is getting stored in a ThreadLocal and cleared.
You could try to call clearContext() in AuthenticationHandlerInterceptor, but it's probably better to use the hook the framework foresees as that should lead to less surprises similar to the one you reported.