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.
Related
I have a Spring Boot app using CAS WebSecurity to make sure that all incoming non authenticated requests are redirected to a common login page.
#Configuration
#EnableWebSecurity
public class CASWebSecurityConfig extends WebSecurityConfigurerAdapter {
I want to expose health endpoints through actuator, and added the relevant dependency. I want to bypass the CAS check for these /health URL which are going to be used by monitoring tools, so in the configure method, I have added :
http.authorizeRequests().antMatchers("/health/**").permitAll();
This works, but now I want to tweak it further :
detailed health status (ie "full content" as per the docs) should be accessible only to some specific monitoring user, for which credentials are provided in property file.
if no authentication is provided, then "status only" should be returned.
Following http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-monitoring.html#production-ready-health-access-restrictions, I've configured the properties as below, so that it should work :
management.security.enabled: true
endpoints.health.sensitive: false
But I have a problem with how I configure the credentials... following http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-monitoring.html#production-ready-sensitive-endpoints , I added in my config file :
security.user.name: admin
security.user.password: secret
But it's not working - and when I don't put the properties, I don't see the password generated in logs.
So I'm trying to put some custom properties like
healthcheck.username: healthCheckMonitoring
healthcheck.password: healthPassword
and inject these into my Security config so that configureGlobal method becomes :
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
CasAuthenticationProvider authenticationProvider) throws Exception {
auth.inMemoryAuthentication().withUser(healthcheckUsername).password(healthcheckPassword).roles("ADMIN");
auth.authenticationProvider(authenticationProvider);
}
and in the configure method, I change the config for the URL pattern to :
http.authorizeRequests()
.antMatchers("/health/**").hasAnyRole("ADMIN")
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
With that config, I get full content when authenticated, but logically, I don't get any status (UP or DOWN) when I'm not authenticated, because the request doesn't even reach the endpoint : it is intercepted and rejected by the security config.
How can I tweak my Spring Security config so that this works properly ? I have the feeling I should somehow chain the configs, with the CAS config first allowing the request to go through purely based on the URL, so that the request then hits a second config that will do basic http authentication if credentials are provided, or let the request hit the endpoint unauthenticated otherwise, so that I get the "status only" result.. But at the same time, I'm thinking Spring Boot can manage this correctly if I configure it properly..
Thanks !
Solution is not great, but so far, that's what works for me :
in my config (only the relevant code):
#Configuration
#EnableWebSecurity
public class CASWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//disable HTTP Session management
http
.securityContext()
.securityContextRepository(new NullSecurityContextRepository())
.and()
.sessionManagement().disable();
http.requestCache().requestCache(new NullRequestCache());
//no security checks for health checks
http.authorizeRequests().antMatchers("/health/**").permitAll();
http.csrf().disable();
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
http // login configuration
.addFilter(authenticationFilter())
.authorizeRequests().anyRequest().authenticated();
}
}
Then I added a specific filter :
#Component
public class HealthcheckSimpleStatusFilter extends GenericFilterBean {
private final String AUTHORIZATION_HEADER_NAME="Authorization";
private final String URL_PATH = "/health";
#Value("${healthcheck.username}")
private String username;
#Value("${healthcheck.password}")
private String password;
private String healthcheckRole="ADMIN";
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = this.getAsHttpRequest(request);
//doing it only for /health endpoint.
if(URL_PATH.equals(httpRequest.getServletPath())) {
String authHeader = httpRequest.getHeader(AUTHORIZATION_HEADER_NAME);
if (authHeader != null && authHeader.startsWith("Basic ")) {
String[] tokens = extractAndDecodeHeader(authHeader);
if (tokens != null && tokens.length == 2 && username.equals(tokens[0]) && password.equals(tokens[1])) {
createUserContext(username, password, healthcheckRole, httpRequest);
} else {
throw new BadCredentialsException("Invalid credentials");
}
}
}
chain.doFilter(request, response);
}
/**
* setting the authenticated user in Spring context so that {#link HealthMvcEndpoint} knows later on that this is an authorized user
* #param username
* #param password
* #param role
* #param httpRequest
*/
private void createUserContext(String username, String password, String role,HttpServletRequest httpRequest) {
List<GrantedAuthority> authoritiesForAnonymous = new ArrayList<>();
authoritiesForAnonymous.add(new SimpleGrantedAuthority("ROLE_" + role));
UserDetails userDetails = new User(username, password, authoritiesForAnonymous);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
private HttpServletRequest getAsHttpRequest(ServletRequest request) throws ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("Expecting an HTTP request");
}
return (HttpServletRequest) request;
}
private String[] extractAndDecodeHeader(String header) throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
} catch (IllegalArgumentException var7) {
throw new BadCredentialsException("Failed to decode basic authentication token",var7);
}
String token = new String(decoded, "UTF-8");
int delim = token.indexOf(":");
if(delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
} else {
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
}
}
I'm using Spring #RolesAllowed to secure my APIs (methods), but I'd like to change what happens when a method is called from an unauthorized user. The current behavior is that Spring throws an HTTP 403 error. This is great, but I would just like to add an additional error code in the body of the 403 response to be able to distinguish between access denied errors in different scenarios.
I'm having a hard time figuring out where the implementation of the #RolesAllowed annotation is located. Has anyone come across it? Or attempted to modify its behavior?
The methods in my controller currently look like the following:
#RolesAllowed({"ROLE_DEFENDANT", "ROLE_ADMIN"})
#RequestMapping(method = RequestMethod.POST, value = "/{caseId}/owner")
public ResponseEntity<?> assignOwner(#PathVariable String caseId) {
// method implementation
}
Another way to do this is with an exception handler class and the #ExceptionHandler annotation.
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<?> handleAccessDenied(HttpServletRequest request, AccessDeniedException ex) {
// exception handling logic
if (request.getUserPrincipal() == null) {
// user is not logged in
} else {
// user is logged in but doesn't have permission to the requested resource
}
// return whatever response you'd like
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
What you are trying to do, can be done without having to modify the annotation.
In your Spring config, you can specify an AccessDeniedHandler bean which will be called when Spring Security determines that your user is not allowed to perform the action that they've tried to perform.
The access denied handler is really simple:
public class CustomDefaultAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
An example of an AuthenticationProvider that gives you a bit more information about what failed would be:
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserService userService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
String username = String.valueOf(auth.getPrincipal().toString());
String password = String.valueOf(auth.getCredentials());
if(username.isEmpty() || password.isEmpty()){
throw new UsernameNotFoundException("You pudding, there is no username or password");
} else {
SystemUser user = userService.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("No user exists, stop hacking");
}
//Do more stuff here to actually apply roles to the AuthToken etc
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
}
}
I wrote a RESTful API in java in which the client has to create an account (POST to /user) and then login (POST to /login).
Now I am creating a frontend for the web-browser using AngularJS. The problem is, when I create a user, try to login and enter some login-secured-url's, I get
401 Unauthorized
It seems like every request should be authenticated separately, am I right ?
Steps I made:
POST to /user which creates a user
POST to /login which logs in the user
GET to /account/1/something which is login-secured path in my REST service, it returns 401
When I was writing the REST service, I was testing everything under RESTful WebService test-client in IntelliJ IDEA and it was and still is all right there.
What is the way to solve this problem ?
#Neo
Here you have RestAuthenticationSuccessHandler
#Component("successHandler")
public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private RequestCache requestCache = new HttpSessionRequestCache();
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication)
throws ServletException, IOException {
final SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
clearAuthenticationAttributes(request);
return;
}
final String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
requestCache.removeRequest(request, response);
clearAuthenticationAttributes(request);
return;
}
clearAuthenticationAttributes(request);
}
public void setRequestCache(final RequestCache requestCache) {
this.requestCache = requestCache;
}
}
I have also another classes, but the post would become too wordy if I added it all, so please let me know if you (suggesting a classname) need code of any of these below:
I have a java that calls a Servlet:
public class UserServlet extends HttpServlet {
#Autowired
private UserService userService;
#Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
userService.checkUser();
userService.doSomethingRestricted();
}
#Override
public void init(final ServletConfig config) throws ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext());
super.init(config);
}
}
And my autowired service :
#Component(value = "userService")
public class UserService {
public boolean checkUser() {
if (SecurityContextHolder.getContext().getAuthentication() != null) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getPrincipal() != null && auth.getPrincipal() instanceof User) {
User springUser = (User) auth.getPrincipal();
if (springUser != null) {
LOG.debug("USER CONNECTED :: {}", springUser.getUsername());
}
}
} else {
LOG.debug("NO CONNECTED USER, CREATING ONE");
Collection<GrantedAuthority> authorities = getGrantedAuthorities();
org.springframework.security.core.userdetails.User springUser = new org.springframework.security.core.userdetails.User("user","password", true, true, true, true, authorities);
Authentication auth = new UsernamePasswordAuthenticationToken(springUser, "", authorities);
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(auth);
SecurityContextHolder.setContext(sc);
}
return true;
}
#Secured({ "CONNECTED" })
public void doSomethingRestricted() {
LOG.debug("SOMETHING RESTRICTED HAS BEEN DONE!!");
}
}
When I test my application the first time, the Java client sends a POST to the server, the server would check the user and would not find a context: a new context would be created.
When I run the java client the subsequent times, I find an existing Context (the one created in the first call).
Obviously there's something missing because If the first user logs in successfully it does not mean any user can connect.
What am I missing ? At first I thought about using sessions for each Java client's instance (I dont have web browser clients so I need to set the session ids manually), but when is Spring supposed to get or set the session id in the http request ?
TL;DR :
What does SecurityContextHolder.getContext().getAuthentication() do in my example ?
It gets you the authentication details of the current login user , have you added
<bean id="httpSessionFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>
to introduce login for web application , spring security is designed to work with POJO as well , you would need to add this filter in your mapping if you are doing it old way. If you are using http tags in applicationContext then it should work as it is.
</security:filter-chain-map>
Its been quite long since I have used spring security without the new http tags in applicatin Context . The spring security context comes with different filters , SecurityContextPersistenceFilter determines how the context is persisted.
"org.springframework.security.web.context.SecurityContextPersistenceFilter" is for persisting security context per session .
Spring security derived from its integration with acegi security which used to have "net.sf.acegisecurity.
ui.webapp.HttpSessionIntegrationFilter" filter for the same task
It is a filter , so spring can identify session based on sessionid.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
JSF HTTP Session Login
I am using Primefaces to implement my web application. In my implementation the user can log in to the system, then they can load the redirected pages again by copying that URL without login again. How can I prevent this?
Here is my login logic:
public String doLogin() {
if(username != null &&
username.equals("admin") &&
password != null &&
password.equals("admin")) {
msg = "table?faces-redirect=true";
} else
if(user_name.contains(username) &&
pass_word.contains(password) &&
!user_name.contains("admin")) {
msg = "table1?faces-redirect=true";
}
}
return msg;
}
If the user session hasn't expired, then this is normal behavior for web applications. If the session has expired, then you must make sure there is a logged user and that is has the privileges to access to the page he/she's using in the URL. You can achieve this using a Filter.
I'm assuming your web app is on a Java EE 6 container like Tomcat 7 or GlassFish 3.x:
#WebFilter(filterName = "MyFilter", urlPatterns = {"/*.xhtml"})
public class MyFilter implements Filter {
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//get the request page
String requestPath = httpServletRequest.getRequestURI();
if (!requestPath.contains("home.xhtml")) {
boolean validate = false;
//getting the session object
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpSession session = (HttpSession)httpServletRequest.getSession();
//check if there is a user logged in your session
//I'm assuming you save the user object in the session (not the managed bean).
User user = (User)session.get("LoggedUser");
if (user != null) {
//check if the user has rights to access the current page
//you can omit this part if you only need to check if there is a valid user logged in
ControlAccess controlAccess = new ControlAccess();
if (controlAccess.checkUserRights(user, requestPath)) {
validate = true;
//you can add more logic here, like log the access or similar
}
}
if (!validate) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.sendRedirect(
httpServletRequest.getContextPath() + "/home.xhtml");
}
}
chain.doFilter(request, response);
}
}
Some implementation for your ControlAccess class:
public class ControlAccess {
public ControlAccess() {
}
public boolean checkUserRights(User user, String path) {
UserService userService = new UserService();
//assuming there is a method to get the right access for the logged users.
List<String> urlAccess = userService.getURLAccess(user);
for(String url : urlAccess) {
if (path.contains(url)) {
return true;
}
}
return false;
}
}
While looking for a nice way to explain this, I found a better answer from BalusC (JSF expert). This is JSF 2 based:
JSF HTTP Session Login
You can do form based authentication to protect your inner pages from being accessed by unauthenticated users.
You can also let the container handle the authentication for you using JDBC realm authentication as in this example