How to handle runtime exceptions thrown by Spring Security Authentication Providers ? I'm using Spring Boot 1.4.2 but I feel like this applies also to classic Spring applications.
Let's say I have an ActiveDirectoryLdapAuthenticationProvider configured to authenticate users against my corporate AD. It all works nice with Spring Security when the authentication fails due to bad credentials (an AuthenticationException is thrown which is properly handled by the Spring Security mechanism = the app goes back to the login screen and the authentication error can be shown).
However it so happens that sometimes, the AD is down temporarily and a org.springframework.ldap.CommunicationException is thrown instead. This exception is a runtime exception and therefore isn't trapped by Spring's security mechanism because it doesn't extend AuthenticationException.
What happens in this case, is that the app is redirected to the default error page (which is /error). What I want to do, is to still show the login screen with a custom message.
I've found that I can do that, if I create something like
public class ActiveDirectoryLdapExtendedAuthenticationProvider implements AuthenticationProvider {
private final ActiveDirectoryLdapAuthenticationProvider adAuthenticationProvider;
public ActiveDirectoryLdapExtendedAuthenticationProvider(ActiveDirectoryLdapAuthenticationProvider adAuthenticationProvider) {
this.adAuthenticationProvider = adAuthenticationProvider;
}
#Override
public Authentication authenticate(Authentication a) throws AuthenticationException {
Authentication auth = null;
try {
auth = adAuthenticationProvider.authenticate(a);
}
catch(CommunicationException communicationException) {
throw new AuthenticationServiceException("Could not reach User Directory. Please try again in a few minutes");
}
return auth;
}
This works but I feel that there must be a better way.
I've tried creating a ControllerAdvice annotated class but it doesn't get called by the POST to login by Spring Security. I would imagine that it's because POST is handled by Spring Security filters, which being Servlet filters, sit above the main Spring MVC dispatcher servlet.
I've also tried to create a SimpleMappingExceptionResolver to handle a CommunicationException and redirect to the login page, but that doesn't work either as my SimpleMappingExceptionResolver doesn't get called either.
The other workaround that I've come up with is to tackle the exception at the error page itself, something like (using Thymeleaf)
<div class="container error" th:switch="${exception}">
<span th:case="'org.springframework.ldap.CommunicationException'">Error communicating with User Directory. Please try again in a few minutes</span>
<span th:case="*">An unexpected error has occurred</span>
</div>
I still feel like there should be a better way. How can I configure the DispatcherServlet to ensure that a CommunicationException is to be redirected to the /login controller, and not the error page ? Or more generically... how can I configure that any exception at the login stage, is shown on the login screen ?
Experienced same problem and solved it creating custom Spring AuthenticationFailureHandler. It caught error by throwing BadCredentialsException in custom AuthenticationProvider class.
Related
I have a small test Spring Boot application that includes a controller that contains a simple service. The service is just a test service that causes the browser to display some HTML, as shown below:
#RestController
public class TestController
{
#GetMapping("/testserv")
public String getUserInformation()
{
return("<p>User authenticated!</p>");
}
}
I also have a configuration class designed to require a user to authenticate in order to access the service. The intent is to make all other services in the application accessible without logging in, while accessing /testserv requires login:
#Configuration
#EnableWebSecurity
public class TestSecure extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity httpSecure) throws Exception
{
httpSecure.authorizeRequests()
.antMatchers("/testserv").authenticated()
.anyRequest().permitAll();
}
}
If I run my application without TestSecure class, I can access the /testserv service without problems. However, when I put in the TestSecure class, I get 404 Not Found failures trying to access the service.
I did some more testing. I found that what seems to cause the Not Found failures is the line:
.antMatchers("/testserv").authenticated()
If I comment out that line I can access /testserv. With that line in there, the application suddenly cannot find the /testserv service and generates the 404 Not Found failures.
I believe I have done everything required in order for this to work, but perhaps I am missing something? Can anyone tell me why using antMatchers() would cause a service to be not found? How can I get proper access to this service?
The issue is that you are missing #RequestMapping for the controller. I think just adding that annotation should get your code working.
Updated based on OP comment
You should also use .formLogin() in order to show the login form
I'm using spring security to implement a programmatic, manual user login. I have a scenario where I have positively established the user's identity, and wish to log them in. I don't know their password, and so can't use the regular login code path where you submit a form to a url, which spring intercepts via a servlet Filter, doing all of it's auth+session magic.
I've searched, and it seems most people create their own Authentication object, and then tell spring about via:
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(user, "", user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
Indeed, this works. Spring even puts it into the session for me, making subsequent http requests maintain their auth status.
However, I feel like this is a dirty hack. I'll present some details that I hope will give concrete examples of the problems associated with using setAuthentication() inside a controller to achieve a manual login:
To give an idea, my config is:
httpSecurity
.authorizeRequests()
.antMatchers("/test/**").permitAll()
.antMatchers("/admin/**", "/api/admin/**").hasRole("USER_SUPER_ADMIN")
.and()
.formLogin()
.loginPage("/sign-in?sp")
.loginProcessingUrl("/api/auth/sign-in")
.successHandler(createLoginSuccessHandler())
.failureHandler(createLoginFailureHandler())
.permitAll()
.and()
.logout()
.logoutUrl("/api/auth/sign-out")
.logoutSuccessHandler(createLogoutSuccessHandler())
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry)
;
Key points in the above config:
I use custom success and failure handlers for the form login
I want to config behavior for max concurrent sessions per user
I want to maintain spring's default session fixation protection (changing session id upon login).
I want to use a session registry
... more of these session/login functionalities, had I chosen to config it.
I stepped through the code to see how spring processes a form login. As expected, Spring does all the session/login functionalities that my HttpSecurity config told it to do when I use the form login. But, when I do my own custom/manual login via SecurityContextHolder.getContext().setAuthentication(), it does NONE of those functionalities. This is because spring does all of it's session/login functionalities stuff inside of a servlet Filter, and my programmatic code can't really call a Filter. Now, I can attempt to add the missing functionalities myself, duplicating their code: I see that the spring Filter uses: ConcurrentSessionControlAuthenticationStrategy, ChangeSessionIdAuthenticationStrategy, and RegisterSessionAuthenticationStrategy. I can create these objects myself, configure them, and call them after my custom login. But, that's really lame to duplicate all that spring code. Furthermore, there's still other behaviors I'm missing - I noticed that when using the form login code path, that spring triggers some login events which don't get triggered when I do my custom login. And there's probably other stuff that I'm missing or don't understand. The whole process is pretty complicated, and I feel like there's a high chance of introducing bugs if this isn't done right, not to mention that library updates would be a pain if I started duplicating spring code.
So, I feel like I'm approaching this from the wrong way. Should I be using a different strategy, so that I'm not bypassing so much of the stuff that spring does for me? Maybe I should try to make my own AuthenticationProvider to accomplish this custom login?
*To clarify, my code more or less works. But, I feel like I accomplished it using a poor strategy because I had to write code duplicating a lot of stuff that spring does for me. Further, my code doesn't perfectly replicate what spring does, making me wonder what negative implications might result. There must be a better way to programatically achieve login.
I wanted to elaborate on how I implemented the advice of dur. In my scenario, I only used a custom AuthenticationProvider.
Instead of creating a custom servlet Filter, such as extending AbstractAuthenticationProcessingFilter, which seemed like a lot of work, I choose to instead use the following strategy:
At the point in my code where I was confident that I had identified the user, and wanted them to be "logged in", I stuck a flag in the user's session, marking that they should be logged in on the next request, along with any other identity/bookkeeping info I needed, such as their username.
Then, I told the browser client to make an http post to the loginProcessingUrl (the same one I configured spring security to use for form-based login), telling them to send the standard username and password form params, although they don't need to send real values - dummy values like foo are fine.
When the user makes that post request (eg to /login), spring will invoke my custom AuthenticationProvider, which will look in the user's session to check for the flag, and to gather the username. Then it will create and return an Authentication object, such as PreAuthenticatedAuthenticationToken, which identifies the user.
Spring will handle the rest. The user is now logged in.
By doing it this way, you stay within the "normal" way of doing logins, and so spring will still automatically:
Call any custom success and failure handlers you configured for the form login, which is nice if you use that place to do certain things on login, like query or update a db.
It will respect any max concurrent sessions per user settings that you may be using.
You get to keep spring's default session fixation attack protection (changing session id upon login).
If you set a custom session timeout, eg via server.session.timeout in a properties file, spring will use it. There's probably other session config attributes that are done at this time too.
If you enabled spring's "remember me" functionality, it will work.
It will fire a login event, which is used for other spring components, such as storing the user's session in a SessionRegistry. I think the events are also used by other parts of spring, such as the actuator, and for auditing.
When I first tried just doing the typically recommended SecurityContextHolder.getContext().setAuthentication(authentication) to login my user, instead of the custom AuthenticationProvider, none of the above bullets were done for me, which can utterly break your app... or cause subtle security bugs - neither are good.
Here's some code to help solidify what I said:
Custom AuthenticationProvider
#Component
public class AccountVerificationAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AppAuthenticatedUserService appAuthenticatedUserService;
#Autowired
private AuthService authService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// This will look in the user's session to get their username, and to make sure the flag is set to allow login without password on this request.
UserAccount userAccount = authService.getUserAccountFromRecentAccountVerificationProcess();
if (userAccount == null) {
// Tell spring we can't process this AuthenticationProvider obj.
// Spring will continue, and try another AuthenticationProvider, if it can.
return null;
}
// A service to create a custom UserDetails object for this user.
UserDetails appAuthenticatedUser = appAuthenticatedUserService.create(userAccount.getEmail(), "", true);
PreAuthenticatedAuthenticationToken authenticationToken = new PreAuthenticatedAuthenticationToken(appAuthenticatedUser, "", appAuthenticatedUser.getAuthorities());
authenticationToken.setAuthenticated(true);
return authenticationToken;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Config spring security to use the provider
// In your WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class AppLoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccountVerificationAuthenticationProvider accountVerificationAuthenticationProvider;
#Autowired
private ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// Spring will try these auth providers in the order we register them.
// We do the accountVerificationAuthenticationProvider provider first, since it doesn't need to do any slow IO to check,
// so it's very fast. Only if this AuthenticationProvider rejects (which means this http request is not for programmatic login), will spring then try the next AuthenticationProvider in the list.
authenticationManagerBuilder
.authenticationProvider(accountVerificationAuthenticationProvider)
// I'm using ActiveDirectory / LDAP for when a user logs in via entering a user + password via the html form, but whatever you want to use here should work.
.authenticationProvider(activeDirectoryLdapAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
...
}
}
For custom web authentication you should implement combination of a custom authentication filter (for example AbstractAuthenticationProcessingFilter or just GenericFilterBean), a custom authentication provider (AuthenticationProvider) or/and custom authentication token (AbstractAuthenticationToken).
For example, see source of Spring Security Kerberos.
See also:
The AuthenticationManager, ProviderManager and AuthenticationProvider
I'd like to keep track of certain things in a Spring Boot/OAuth2/Java Config setting and report those somewhere (think AWS CloudWatch, or Google Analytics, or any other similar services).
To be more precise, I have an Authorization+Resource Server with Spring OAuth2.
Those things I'd like to track would be (though not limited to):
Login failed attempts (for clients and users)
Login successful attempts (for clients and users)
Usage of REST controllers (like "/say_hello_world")
Exceptions (aside from the authentication ones)
I was planning on adding javax.servlet.Filters, but then it gets hairy when logging failed attempts with my Authorization Server (using #EnableAuthorizationServer and extending AuthorizationServerConfigurerAdapter). I think I'd need to either resort to using my custom Exception Translator or figure out how to set/wrap the AuthenticationManager in ClientCredentialsTokenEndpointFilter.
Is there a better way than wrapping a tons of things to collect the info I mentioned above?
Update:
As I mentioned in the comments, I'm not looking for a "log dump". I need to be able to get for example the user id that tried to login but failed, or the invalid access token that was used, etc.
I looked into coding my own ApplicationListener<ApplicationEvent>, detect AuthenticationFailureBadCredentialsEvent and the like and go that route.
For example, I can detect a BadCredentialsException, but then need to figure out if it's an InvalidTokenException or other (which is in the cause of that BadCredentialsException).
Next problem is that I can't extract the access token that was used and failed. Feels awkward and more hacking than it should.
I don't mind going through hoops like those, just wondered if there was a better way.
Update2:
There is one thing that helps "listening" to what's going on in a Spring application and it's the ApplicationListener.
Any events published can be caught by providing an implementation of that interface.
InteractiveAuthenticationSuccessEvent is published when a client successfully authenticates (i.e. clientId exists and secret key is valid)
AuthenticationSuccessEvent is published when a user successfully authenticates (i.e. username exists and password matches)
AuthenticationFailureBadCredentialsEvent is published when a user failed authenticating.
There was a problem with #2 and #3 in that in a ProviderManager a NullEventPublisher is setup by default so I had to change my (Java) config like so to get those authentication events:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
...
#Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationEventPublisher(authenticationEventPublisher)
...
}
...
}
Now there remains the problem of receiving the event when the client failed authentication.
The InteractiveAuthenticationSuccessEvent is triggered by AbstractAuthenticationProcessingFilter (which ClientCredentialsTokenEndpointFilter extends) upon successful authentication. But when authentication fails, it does not publish any event (since at least version 3.0.0 to 4.0.1).
Another way would be to configure the AuthenticationManager the same way I did above by setting up a non-NullEventPublisher but as far as I can tell there's no way to setup ClientCredentialsTokenEndpointFilter in Spring Boot without manually setting up the universe???
You can use AOP in spring. Create advice #Before, #After, #Arround execution of method, or even AfterThrowing to detect any exception occured.
Using this approach, you can separate your auditing process from your business logic.
I'm using the Spring Security plugin in Grails. I have a controller which uses annotations for some of the secure actions but not for non-secure content. And sure enough, the sec:isLoggedIn and other sec:loggedInUserInfo tags work for the secured actions, but they always show up as non logged in even when the user is logged in for the non-secure views. Here's what my controller looks like:
class ContentController {
def anonymousContent() {
getContent(params, 'pages')
}
#Secured(['ROLE_USER', 'ROLE_ADMIN'])
def secureContent() {
getContent(params, 'secure')
}
private getContent(params, path) {
def viewPath = "${path}/${params.view}"
render(view: viewPath, model: params)
}
}
I should mention that I am using some custom authentication as part of a SSO solution which basically has me overriding a couple classes like AuthenticationProvider, AbstractAuthenticationToken, LoginUrlAuthenticationEntryPoint, AbstractAuthenticationProcessingFilter, but I wouldn't think it should be causing this issue.
Any ideas would be appreciated. Thanks
For anyone else running in to this problem, I managed to find the issue was related to mod_proxy. I was using it to hide my context path which was in turn causing the servlet to have trouble reading the session cookie for my app. For unauthenticated pages this would mean it created a whole new session each time. For authenticated pages, it would create a new session as well, but thanks to our SSO authentication mechanism it would actually re-authenticate with each request. Probably good we caught it as that's an expensive operation. The answer was really to set the cookie path (setCookiePath) to root ('/') in our tomcat configuration. Hope that helps someone else :)
We are currently trying to implement a web application which is using the Java EE authentication mechanism with a FORM-based login, inside the Websphere 6.1 web container. If the authentication is successful, we have everything working; the LDAP membership groups are being retrieved, the group to role mapping is being performed, and the roles are being returned to and correctly interpreted by the web application.
If the authentication is not successful, the form-login-error page is being returned. However, this is a static page that simply says something like "there was an error". How do we trap the specific error that prevented the successful login (username/password incorrect, LDAP repository unavailable, account locked, password expired, etc.)? It seems like there should be some easy way to do this, as you would want to treat some "security" exceptions differently than others.
I use Struts so it will do forwarding for you. If you don't have a framework (why not?) you'll will have to do it manually.
The Java EE spec covers the j_security_check servlet.
The login page POSTs j_username and j_password to the j_security_check servlet. Your app will be configured to error to an unauthorized page (see web.xml) but will (initially) call a servlet. 401 or 403 will go to a forbidden page (again web.xml)
Inside that servlet (which extends the HttpServlet) - you will check for all that good stuff.
public final void doGet(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException
{
// initialize the app
AppInit initializer = new AppInit();
// get the logger
log = new Log4jWrapper(this.getClass());
// initialize the application session
HttpSession sess = request.getSession(true);
sess.setAttribute(CommonConstants.SESSION_CURR_USER_ID, request.getRemoteUser());
// initialize the JSP to forward to based on the user role
String fwdJSP = "SetupMainPage.jsp";
if (request.isUserInRole(CommonConstants.ROLE_MANAGER)) {
log.debug("User is a Manager");
}
//else other role checks - (these are users in groups in the LDAP)
// initialize the application session and set a variable to indicate that
// we are coming from a first time login (not a timeout login)
sess.setAttribute(CommonConstants.SESSION_COMING_FROM_INITIAL_LOGIN,"TRUE");
disp = getServletContext().getRequestDispatcher("SetupMainPage.jsp");
disp.forward(request, response);
}
//else failure
Unknown user
[11/22/08 8:54:47:993 EST] 7f6ac69c FormLoginServ E SECJ0118E: Authentication error during authentication for user s
right user - wrong password, but the request.getRemoteUser() will have a value
[11/22/08 8:56:45:082 EST] 7f51469c FormLoginServ E SECJ0118E: Authentication error during authentication for user jbsymolo
Unfortunately - i don't have any examples of someone locked out but I going to assume that the main security directory (LDAP) you will have an entry for the user for that.
This is from someone else (so I can't take credit)
I think this page describes how to do what you want to do.
Specifically how to retrieve the authentication exception from an arbitrary underlying authentication source (looks like Websphere calls them user registries).
Throwable t = com.ibm.websphere.security.auth.WSSubject.getRootLoginException();
if (t != null)
t = determineCause(t);
Where determineCause() is defined on the same page. This way, even if your server is configured to authenticate against a John Deer tractor, you will have access to the "OutOfGasLoginException" if there is one. The above code can go into the Servlet, Servlet Filter, or JSP that is redirect to by the container (as described above by jsymolon). It simply examines the exceptions and then places a corresponding friendly error message on the resulting page.
This is ancient knowledge - I believe to have done such a thing with tomcat. There was, as far as I can remember, no standard way, as the implementation was completely decoupled from the request and frontend web stuff, so that it was difficult to establish any means of communication between the authenticating component and the frontend (e.g. error page).
We ended up with a tomcat specific way, relying heavily on the current implementation. I'm no longer with that company, so I can't tell about the current state of the code or the solution we chose back then. I believe you'll also have to have some Websphere specific solution - be it the use of thread local variables, keying messages with the username that attempted to log in, somehow getting hold of the session identifier or similar.
Check this article Securing J2EE Applications with a Servlet Filter. I believe it covers your requirement to be able to pass the reason for the authentication error.
The JavaEE specification does not provide a standard mean to get an authentication feedback like error codes.
According to the following IBM Redpaper about z/OS security integration in gray note on page 57: IBM specific extension is available so that the error page JSP can report a specific message (like password expired) based on an error status code.
According to the WebSphere InfoCenter the FormLoginWeb sample from the TechSamp package in your WebSphere installation (samples/src/TechSamp/FormLoginWeb) is supposed to demonstrate such IBM specific extension but... The only thing interesting is the LoginFilter that intercepts calls on /j_security_check and is able to do pre-login validation and post-login action as explained in details in that paper.
With such a mechanism it is possible to get login exception from JAAS Subject and set an login error code in HttpSession so that the error page can generate a specific message.