We've created an application that mimics the Spring Security - API Gate Pattern tutorial (https://spring.io/guides/tutorials/spring-security-and-angular-js/#_the_api_gateway_pattern_angular_js_and_spring_security_part_iv). The only variation is that we're using a MySQL database rather than Redis.
Using localhost:8080 as the root, we have localhost:8080/login (login page), localhost:8080/ui (jQuery client), and localhost:8080/api (restful web services, business logic, etc.)
We're finding session handling and forwarding to the various entities works as expected. Meaning the session gets created as expected, forwarding is happening as expected, etc. There is one exception. If I log in, then log out, then go directly to localhost:8080/ui it'll forward me to the login page. You login, and it forwards you back to the localhost:8080/ui, but will display "ACCESS DENIED"!
After tracking the sessions in the database and client I've found that there are two sessions that exist in the database. One with permissions and one without. The client retains the one without!
Has anyone else run into this problem? Is there a way to circumvent this?
Here's a list steps I went through, the database session tracking, and client verification.
session_id principal_name Client
------------------------------------------------------------
1) go to localhost:8080
9229045c-27e0-410a-8711-45c56576d647 - X
2) login
2275db1c-fca4-4a2f-be73-e440599499d6 root X
3) logout
cc917e68-b1c0-46a4-bbe3-6705ccf7a5fa - X
4) go to localhost:8080/ui --> forwards to localhost:8080/login
cc917e68-b1c0-46a4-bbe3-6705ccf7a5fa - X
5) login -> forwards to localhost:8080/ui -> Access Denied
90d7931d-b265-42e2-a225-286bcf7d159c - X
d2fae0ac-9cf9-4287-8e38-51f64b0ab28d root
Alright, after many hours we found a solution to what seemed to be inconsistent behavior. Meaning sometimes you'd log in and it'd retain the proper session and you could go the the localhost:8080/ui page and not get the Whitelabel Error page... sometimes you'd still get it.
On the Gateway server...
1) Added RequestMethod.POST
#Controller
public class HomeController {
#RequestMapping(method = { RequestMethod.GET, RequestMethod.POST }, path = "/")
public String home() {
return "redirect:" + RequestMappings.UI;
}
}
2) Changed configure file, specifically
a) added .successForwardUrl(“/”)
b) added .loginProcessingUrl(“/login”)
c) added .logoutSuccessUrl("/login?logout")
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.frameOptions().sameOrigin()
.and().formLogin()
.loginPage(RequestMappings.LOGIN)
.failureHandler(failureHandler())
.successForwardUrl("/")
.permitAll()
.loginProcessingUrl("/login")
.and().logout()
.logoutSuccessUrl("/login?logout")
.and().authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers(RequestMappings.CHANGE_PASSWORD).permitAll()
.anyRequest().authenticated()
.and().csrf()
.csrfTokenRepository(csrfTokenRepository())
.and().addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
}
Now, there still is a way to get the whitepage error. If, before ever logging in, you go directly to localhost:8080/ui…. It’ll forward you to the localhost:8080/login page. You log in. You’ll be at localhost:8080/ui/ looking at everything as expected. If you remove the last forward slash then you’ll get the whitepage error. Then from there things can get mucked up in the cache. But if you go back to the root, you can login as normal and everything will work as normal.
I think what is going on is that the pre-login localhost:8080/ui call is being cached and because the index.html page was never loaded once you log back in and go back you pass the authorization check, but it tries to load… well, nothing, then throws an error. At least that’s my best guess.
Anyways, cheers! Thanks for the help, which started us off on the right track!
Most likely, when you go directly back to your UI application, it is still using the old session ID and when you log in, it is redirecting you to the UI again with the old session ID.
It could also be that it is still an old issue on Tomcat, check this thread to see how to properly clear your cookies: https://straypixels.net/clearing-cookies-in-spring-tomcat/
Bascially, extend the SimpleUrlLogoutSuccessHandler like this:
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
//This search may or may not actually be necessary, you might be able to just
// create a new cookie with the right name
for(Cookie cookie : request.getCookies()) {
if(cookie.getName() == "JSESSIONID") {
//Clear one cookie
cookie.setName("");
cookie.setMaxAge(0);
cookie.setPath(request.getContextPath());
response.addCookie(cookie);
//Clear the other cookie
Cookie cookieWithSlash = cookie.clone();
cookieWithSlash.setPath(request.getContextPath() + "/");
response.addCookie(cookieWithSlash);
}
}
//This is actually a filter; continue along the chain
super.onLogoutSuccess(request, response, authentication);
}
Related
I have created a set of spring boot microservices having followed along roughly with the tutorial I found here. I've had success getting my project up and going without any attempt to secure the rest endpoint or the user interface. But eventually I need to secure everything using LDAP, and I will need to conditionally allow access to user interface sections based upon user roles. For the time being, without involving LDAP, I have tried roughly a score of tutorials on how to use in-memory authentication in conjunction with OAuth2 and I cannot understand what it is exactly that I need to do to make this go.
I have created a github repository that contains two branches. The first branch is called no-authentication which demonstrates a simplified version of my project before any attempt to add authentication is begun. The README describes the project makeup, but it is essentially a configuration server, a service registry, one rest resource with two endpoints that are not authenticated, and a user interface module that access the two endpoints.
The idea is that the first endpoint will eventually require one group membership ("USER"), and the other endpoint another group membership ("ADMIN"). But I haven't gotten so far yet.
The second branch is called oauth-server. This branch adds another spring boot module called oauth2-server, and that module contains the code that my user interface is forwarded to in order authenticate.
I am presented with a login page as I would hope, but when I enter valid credentials (user/user or admin/admin) I see an error on the page:
OAuth Error
error="invalid_grant", error_description="Invalid redirect: http://localhost:8084/authtest/login does not match one of the registered values."
I am totally new to this area, and I have been really just playing whack-a-mole with my various attempts to get this working. You can view the code directly, and you will need to be well versed in order to understand the setup. Here is roughly how the authentication is set up to work.
There ui that needs to be authenticated is called authentication-test-ui. Its configuration can be found in its resources/bootstrap.yml, and the rest of its configuration comes from the control-center/config-server module's classpath, the source located at resources/config-repo/authentication-test-ui.yml. This is the security config:
gateway-server: 'http://localhost:8901/authserver'
security:
oauth2:
client:
client-id: ui
client-secret: uisecret
scope: ui
access-token-uri: ${gateway-server}/oauth/token
user-authorization-uri: ${gateway-server}/oauth/authorize
pre-established-redirect-uri: http://localhost:8084/authtest
resource:
user-info-uri: ${gateway-server}/userInfo
The UI's application class is annotated with #EnableOAuth2Sso.
The authorization server is in the module services/oauth2-server. This is annotated with #EnableAuthorizationServer and follows a similar configuration pattern as the ui (has a bootstrap.yml in its own module and a yml on the config server). The code performs in-memory authentication like this:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("ui")
.secret(passwordEncoder.encode("uisecret"))
.authorizedGrantTypes("authorization_code", "implicit", "password", "client_credentials", "refresh_token")
.scopes("ui")
.redirectUris(
"http://localhost:8084/authtest"
);
}
And the security configuration looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("admin").password(passwordEncoder().encode("admin")).roles("USER", "ADMIN")
.and()
.withUser("user").password(passwordEncoder().encode("user")).roles("USER");
}
I most likely have not provided the right information to be able to understand the issue simply by looking at the snippets I've pasted, so looking at the project is probably the only way to fully get it.
If anyone can help me with this I would greatly appreciate it. I've been on this for nearly a week and feel like I can't be too far off the mark. I'm really looking for the simplest way to get authenticated.
You configured redirect uri as http://localhost:8084/authtest but while calling you specified redirect uri as http://localhost:8084/authtest/login so it is failing.
My question is simple - how to implement login-logout in servlet jsp?
Following is the use case...
I have a users table in DB with email,username and password
I have a mapped bean object - User in java having email,username,password properties
Simply I want to login by validating email and password BUT
Once I login and then logout, when I click on back button, it should not retain the session.
It should not give any warning BUT simply should ask for login
If I copy-paste restricted resource's link, it should ask for login
What all solutions I've gone through...
Some say to implement tomcat security using roles and bla bla... BUt I think I should not set username, passwords in some tomcat config file. Bcz the details are in DB table
Some ask to implement no-cache, pragma bla bla... but never work
Back button disable is foolish thing
**
What Help I am expecting from you guys ...?
**
Is there any third-party API available to do this?
How things are implemented in production ready applications ?
Should I use JAAS, or any other security process for exactly above mentioned scenario OR WHAT
Please give me some hint or solution how I should proceed implementing production ready login-logout in servlet-jsp
I've searched on internet but end up with simple Login examples or tomcat security roles etc. No one gives the actual solution. ANd please don't say that this question is NOT RELATED TO this FORUM.
Thanks
This happens because browser caches the web pages that are being loaded,you can prevent it by using filters and telling browser not to cache the web pages like below.
doFilter method of Filter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
HttpSession session = request.getSession(false);//don't create if session doesn't exist.
if (session==null || session.getAttribute("username") == null) {
RequestDispatcher rd=request.getRequestDispatcher("login");//dispatch it to your desired page i.e login page
rd.forward(request, response);
} else {
chain.doFilter(req, res);
}
}
You should configure this filter inside web.xml or using Annotations for which url-patterns you want to filter.refer documentation for more details.
If you're using Tomcat then a good place to start is Tomcat Standard Realm Implementations.
It's important to remember that normal Java EE security authenticates users and authorises them using roles - even if you only have the one.
Once you have done that you can implement Logout by invoking a servlet which calls HttpServletRequest.logout() and then invalidates the HttpSession:
request.logout();
request.getSession().invalidate();
and then:
response.sendRedirect("some protected page");
which should resolve your back button problem and land back on the login page.
I'm having the weirdest problem I have ever seen before.
The application I am working on uses spring security 3.1.3 to provide authentication support. There is a custom login form for which I have implemented a custom authenticationmanager / successhandler and failurehandler.
For some reason on internet explorer I always get the error message "Please fill in all mandatory fields". This is caused by appending /login?error=1 to the end of my url which can only be accessed through the following code (the redirectAndAddError method):
public class TideUserNamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public TideUserNamePasswordAuthenticationFilter() {
super();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
String password = request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
redirectAndAddError(response);
return null;
}
return super.attemptAuthentication(request, response);
}
private void redirectAndAddError(HttpServletResponse response) {
try {
response.sendRedirect("/tide/login?error=1");
} catch (IOException e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
}
So what I tried was using Fiddler2, a web debugging proxy to view if one of the two parameters are actually empty. The strange thing is that when this program is running the error does not occur anymore and I can log on successfully.
Had anyone had a similar problem before? I think it's not related to my code as running the tool suddenly "solves" the problem.
This problem only occurs in internet explorer which makes it even more strange.
Edit
I have used another tool to watch the requests and this is what happens in IE:
First a POST request is sent to the uri /authenticate, I have set this myself like this:
<beans:property name="filterProcessesUrl" value="/authenticate"/>
The response of that request has http status code 302, moved temporarily and returns that the new location is at /login?error=1 (my form with the mandatory fields required error).
After that a GET request occurs to /login?error=1 with status code 401: Unauthorized. The intercept-url is set up like this:
<intercept-url pattern="/login**" access="permitAll"/>
The next request is a GET request to /login?error=1 again, this time the status code is showing: ERROR_INTERNET_CONNECTION_RESET, which looks like it could be a problem.
In Google Chrome the following request is made:
POST to /authenticate, result is a 302: moved temporarily to the dashboard page (which I display after logging on)
Someone on my team finally figured out what the problem was after finding this issue in the chromium bugtracker:
https://code.google.com/p/chromium/issues/detail?id=62687
The problem has been resolved by adding this in our login controller
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String doLogin() throws ServletException, IOException {
return "forward:/authenticate";
}
and changing the url that the form posts to to this one instead of the authentication url that spring security provides (we are redirecting to it manually now)
I have Spring app that uses JSP. And I need to redirect one page, but only once. It's due the spring security - once the user comes to this page, I need to call redirection to log him out, but to stay on the same page. Is there any way, how to do this only via java, html or js? Or maybe I've asked wrong - there must be some way, how to do this, but I'm not very into frontend technologies, so I'd be glad, if anybody could post some code to show me, how to make this work.. Thanks :)
So the usecase: user types mywebsite.com/login -> I need to call redirect to log him out in the case he's already logged -> the same site shall appear but now without redirection.
PS: I can't redirect the user to another site - it must be the same site with same source code.
If I understand your use case, I would rather log out a user as a part of the login action (i.e. factor out the logging out process, and call it from both the logout action, as well as from the login action).
If that is not feasible (and I can't of a reason why it wouldn't be), I would focus on "redirect if he's logged in" part rather than "redirect only once" part - the former is very easy to check without almost any modification to your code, assuming you're using some kind of sessions to track the logged-in status.
Disclaimer: I know next to nothing about Sprint, these are all very general comments that should work on any classic webapp.
You can use filter .
#Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
HttpServletRequest test1= (HttpServletRequest) arg0;
test1.getRequestURL()); it gives http://localhost:8081/applicationName/menu/index.action
test1.getRequestURI()); it gives applicationName/menu/index.action
String pathname = test1.getServletPath()); it gives //menu/index.action
if(pathname.equals("//menu/index.action")){
arg2.doFilter(arg0, arg1); // call to urs servlet or frameowrk managed controller method
// in resposne
HttpServletResponse httpResp = (HttpServletResponse) arg1;
RequestDispatcher rd = arg0.getRequestDispatcher("another.jsp"); redirect to another page at response time
rd.forward(arg0, arg1);
}
donot forget to put <dispatcher>FORWARD</dispatcher> in filter mapping in web.xml
If you do not want to use filter you can redirect to another jsp from urs controller method.
Somewhere in our chain of servlet filters there is a filter which forwards the request to the sign-in page when a 401 error is sent, as a usability tweak.
I'm trying to convert this to a Jetty handler because someone wants all web applications to be authenticated by the same logic instead of every webapp having to implement their own authentication.
(The main reason we're using a filter approach in the first place is that nobody was able to get Jetty's container-level authentication to work at all - we have the ability to choose Windows auth or built-in auth and want to be able to switch between these at runtime and were never able to figure out how to make that work with Jetty.)
Out in the Jetty handler, there is some logic like this:
private void handleErrorBetter(HttpServletRequest servletRequest,
HttpServletResponse servletResponse)
throws ServletException, IOException {
if (isPageRequest(servletRequest)) {
ServletContext rootContext = servletRequest.getServletContext().getContext("/");
RequestDispatcher dispatcher = rootContext.getRequestDispatcher("/sign_in");
dispatcher.forward(servletRequest, servletResponse);
} else {
// ...
}
}
servletRequest.getServletContext() appears to correctly return the context for /. Interestingly it appears to do this even if I make a request for a different webapp, but according to the Javadoc I have to use getContext("/") to be sure that I get the root context, so I'm doing that.
Getting the dispatcher succeeds too.
Then I call forward() and this always returns a 404 response to the client.
If I go to /sign_in directly from a web browser, the form loads.
There are only two contexts on the server: the root context /, and a /sample/ context which I'm using to test the second webapp. So I know that /sign_in will be in the root context, but why does forward() give a 404 when forwarding to it?
It turned out to be a bug.
https://bugs.eclipse.org/bugs/show_bug.cgi?id=386359