I'm using Spring Security's ability to auto login by unauthenticated request, when it looks at request url and determines whether user is using correct token or no, then do auto login on success and show message on failure:
public class LinkRememberMeService implements RememberMeServices, LogoutHandler
{
#Autowired
private UserAccountProvider userAccountProvider;
#Override
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response)
{
//if ok - return authentication object
//if not ok - retun null then spring redirects to default intercept-url
//but instead of retuning null i need to render view with error message for this case
}
.....
}
autoLogin can return authentication object or null, when object is returned (token is ok) user is automatically logged in, that's fine. However in case of bad token you have only one option to return null, and Spring redirects to default entry url (ex. '/login').
In case of bad token i want to either render thymeleaf template or redirect to failure url, but i can't find a way to make it work.
Related
I'm trying to implement JWT based authentication in TomEE 8 (based on Tomcat 9).
I use org.glassfish.soteria:jakarta.security.enterprise:1.0.1 as an implementation of Jakarta Security.
Following this tutorial https://github.com/payara/Payara-Examples/blob/master/javaee/security-jwt-example/src/main/java/fish/payara/examples/security/JWTAuthenticationMechanism.java java class looks like this:
#RequestScoped
public class JWTAuthenticationMechanism implements HttpAuthenticationMechanism
private static final Logger LOGGER = Logger.getLogger(JWTAuthenticationMechanism.class.getName());
/**
* Access to the
* IdentityStore(AuthenticationIdentityStore,AuthorizationIdentityStore) is
* abstracted by the IdentityStoreHandler to allow for multiple identity
* stores to logically act as a single IdentityStore
*/
#Inject
private IdentityStoreHandler identityStoreHandler;
#Inject
private TokenProvider tokenProvider;
#Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
LOGGER.log(Level.INFO, "validateRequest: {0}", request.getRequestURI());
// Get the (caller) name and password from the request
// NOTE: This is for the smallest possible example only. In practice
// putting the password in a request query parameter is highly insecure
String name = request.getParameter("name");
String password = request.getParameter("password");
String token = extractToken(context);
if (name != null && password != null) {
LOGGER.log(Level.INFO, "credentials : {0}, {1}", new String[]{name, password});
// validation of the credential using the identity store
CredentialValidationResult result = identityStoreHandler.validate(new UsernamePasswordCredential(name, password));
if (result.getStatus() == CredentialValidationResult.Status.VALID) {
// Communicate the details of the authenticated user to the container and return SUCCESS.
return createToken(result, context);
}
// if the authentication failed, we return the unauthorized status in the http response
return context.responseUnauthorized();
} else if (token != null) {
// validation of the jwt credential
return validateToken(token, context);
} else if (context.isProtected()) {
// A protected resource is a resource for which a constraint has been defined.
// if there are no credentials and the resource is protected, we response with unauthorized status
return context.responseUnauthorized();
}
// there are no credentials AND the resource is not protected,
// SO Instructs the container to "do nothing"
return context.doNothing();
}
...
User sends login request with username and password, identityStoreHandler validates it. Then we generate JWT token and send it back. Frontend attaches it to each next request.
This works.
validateRequest() is triggered for every request, protected or unprotected. As I understand it comes from the spec and is a desired behaviour.
Now, if token is expired and user sends request to not protected url - it will be rejected, because token is present and invalid.
I want first to check if url is protected or not and only if it's protected check for token presence and validity. But ((HttpMessageContext ) context.isProtected()) always returns false. In controller protected methods are annotated with #RolesAllowed and #PermitAll annotations. I tried this also with web.xml based constraints, but isProtected() is still false.
Why is it always false?
Update
I was under the wrong impression that annotation-based security and via descriptor (web.xml) are interchangeable.
If web.xml doesn't contain any security constraints - requests of unauthenticated user to resources with #RolesAllowed are rejected with 403 error, requests to resources with #PermitAll are fulfilled. This is strange behaviour, both require authenticated user and should be rejected.
If web.xml has auth-constraint tag then context.isProtected() returns true for that url-pattern.
But it still returns false for methods annotated with #RolesAllowed and #PermitAll, if path in those methods doesn't match url-pattern in web.xml.
According to this https://www.ibm.com/support/knowledgecenter/SSEQTP_8.5.5/com.ibm.websphere.base.doc/ae/twbs_jaxrs_impl_securejaxrs_annotations.html
Annotated constraints are additional to any configured security constraints. The JAX-RS runtime environment checks for annotated constraints after the web container runtime environment has checked for security constraints that are configured in the web.xml file.
Does it mean that container based security (Soteria) will not consider annotated methods as protected?
I have an implementation of AuthenticationSuccessHandler that catches the request after user is successfully logged in. So when processing is finished inside a handler, I want to redirect a user to the page that he first desired to go, before he was redirected to the login page, and not to some hardcoded page. Is this possible?
This is how my handler implementation looks like:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
Util.setUserdata(userBean, authentication, request);
proceed(request, response, authentication);
}
To explain the problem more clearly, a short example:
User wants to land to a page app/test/page.html
He gets redirected login page and logs in successfully
Handler method gets invoked and I do some logic after which I want to redirect him back to app/test/page.html, but I don't know how to access the URL he wanted to land on before redirection to login page.
Spring security's ExceptionTranslationFilter actually stores the request in session before redirecting to login.
ExceptionTranslationFilter.java
And then it has SavedRequestAwareAuthenticationSuccessHandler which is a AuthenticationSuccessHandler that retrieves and forwards to it.
SavedRequestAwareAuthenticationSuccessHandler.java
You don't need to use that SavedRequestAwareAuthenticationSuccessHandler directly if you have some customization. You can get the previous url directly from session like session.getAttribute(""SPRING_SECURITY_SAVED_REQUEST") as well and it has url and method etc so you can do the redirect yourself. You will have to cast it to SavedRequest before you can the url etc if you are going this route
TLDR: My method requires 2 redirects/forwards to work (1 for authentication and 1 to serve the jsp page). How can I resolve both redirects/forwards (or make it a non-requirement) so as to not run into the error, java.lang.IllegalStateException: Cannot forward after response has been committed.
For more context:
I have a java servlet with a method that looks something like the following:
#GET
#Path("/test")
#Authenticate
public Viewable test(#Context HttpServletRequest request, #Context HttpServletResponse response) {
Map<String, Object> model = createModel();
return new Viewable("/somePath/jspFile", model);
}
The #Authenticate annotation intercepts the call to do some Open ID Connect type authentication which results in the user being forwarded to a different server for all authentication needs. If the user is authenticated, they are redirected back to my application.
However, when hitting the url for this method, I am getting java.lang.IllegalStateException: Cannot forward after response has been committed. I don't know too much about using this Viewable class, but based on the fact that I don't run into that error when returning String/void/whatever else, I assume returning a new Viewable needs to do some forwarding that results in the user seeing the jsp page.
I've read the main SO post about this error, but I am unsure how to apply the fixes to my current problem. For example, I don't know how I would apply something like the following fix:
protected void doPost() {
if (someCondition) {
sendRedirect();
} else {
forward();
}
}
The fix assumes that I can I can either redirect OR forward, but my current method needs a redirect for authentication AND a forward/redirect to serve the jsp page. Maybe there's an obvious fix I'm missing that doesn't require a complete rehaul of the current code?
Edit: It would be nice if I could check if the user was authenticated first, but I assume using this annotation at all automatically entails an initial redirect
Edit: It looks like the user is redirected for the initial login authentication, but does not need to be redirected again after being authenticated once due to SSO
Ok based on some preliminary testing, it seems like the following solution has worked for me:
Check if the user has already been authenticated
Return a Response rather than a Viewable.
Since the user only needs to be redirected the first time for authentication, I can return an empty/meaningless response as a placeholder. And then once the user has been authenticated and is returned to my app, I can return a Viewable wrapped in a Response object.
So the code would look something like the following:
#GET
#Path("/test")
#Authenticate
public Response test(#Context HttpServletRequest request, #Context HttpServletResponse
response) {
Map<String, Object> model = createModel();
if (userIsAuthenticated()) {
return Response.status(401).build();
} else {
return Response.ok(new Viewable("/somePath/jspFile", model)).build();
}
}
I'm implementing an application that has a variety of ways to login, most importantly via a request header for pre-auth purposes and normal username/password. Currently everything works, but I've been requested to redirect the user to an error page if they attempted to use the pre-auth filter and it "failed".
From what I can tell, if pre-auth returns null it just assumes non-authed and continues the flow. I'm looking for a way to redirect or raise an exception, but I can't seem to hit my AuthenticationFailureHandler from the pre-auth filter. Is there a better way to do this? Do I need to hack something together that optionally treats the principal as an Exception. Should I be using a different authentication mechanism?
I'm by no means am Spring Security expert, so any help is welcome.
I cannot share the code due to contractual agreements, but here's an example:
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
Object principal = null;
// Try login stuff if token exists...
String token = request.getHeader("the-token-key");
if (token != null && !token.empty()) {
...
// If successful, correctly set principal
principal = ...;
// Else login failed, redirect or throw exception??
}
return principal;
}
I've been trying to implement OAuth2 password expiration filter and I'm unsure about what the proper way would be to do so. The idea is as follows:
User tries to login.
User gets response with a header containing token if the password is expired.
User get's redirected to password change page using that token (i.e. /password-change/{token}).
He submits his old and new passwords, it gets changed.
Some rest controller retrieves user id by that token and does the rest password changing logic.
User should be redirected back to the initial login page where he logins with his new password (if he would be logged in instantly after the password change, he could navigate through secured pages even if the password would not be changed in background due to some exception, etc.).
So... I set a custom flag in user details for password expiration because I can't use credentialsNonExpired as it gets validated in DaoAuthenticationProvider and thrown as an exception which gets processed as InvalidGrantException which doesn't give me much control. I've figured out that in order to access user details right after it's authentication my filter should be in the inner Spring Security filter chain placed after OAuth2AuthenticationProcessingFilter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
}
}
Why does my filter get placed after OAuth2AuthenticationProcessingFilter while there's no BasicAuthenticationFilter in the chain? I've digged through Spring Security and OAuth2 documentation and sources and couldn't find the right answer.
If that user's password is expired my filter generates some random string and it saves it to retrieve user details later during the password change request (at least it should be):
public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
private static final String TOKEN_HEADER = ...;
private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (details.isPasswordExpired) {
String uuid = UUID.randomUUID().toString();
repo.push(uuid, details.getId());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession(false).invalidate(); // don't create a new session
response.addHeader(TOKEN_HEADER, uuid);
response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
} else {
filterChain.doFilter(request, response);
}
}
}
Do I have to revoke the OAuth token as well? It gets reused in later requests and I keep getting the last userDetails object and therefore I keep getting the same response from my filter.
Is it even the right place to do all this validation? How should one validate the password for the concrete user and not the OAuth client?
Ok, I think I resolved this issue by revoking the access token via injected TokenStore in my filter (I used BearerTokenExtractor to get the token value) which seems pretty logical in this situtation. I still had no time to figure out, why my filter gets placed after OAuth2AuthenticationProcessingFilter, though.