In a Spring MVC application I am trying to implement a custom logout success handler. This handler should access a session attribute and make some queries and logging bases on its value.
Relevant parts of the implementation:
Security configuration:
<http ...>
<logout success-handler-ref="logoutSuccessHandler"/>
</http>
<beans:bean id="logoutSuccessHandler" class="some.package.LogoutSuccessHandler">
<beans:constructor-arg value="/login" />
</beans:bean>
The handler itself:
public class LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
#Autowired
private SomeService someService;
public LogoutSuccessHandler(String defaultTargetUrl) {
this.setDefaultTargetUrl(defaultTargetUrl);
}
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String sessionAttribute = request.getSession().getAttribute("someAttribute").toString();
someService.doSomething(sessionAttribute);
super.onLogoutSuccess(request, response, authentication);
}
}
I am adding some attributes to the session when a user logs in. They are visible during different controller requests. The problem is, when I try to access session attributes during logout, they are all gone. Does that mean that by that time the logout has already taken place and session information is wiped out? I can see that SessionId is the same as before though.
So, my question is: what happens to the session attributes and is there a way to access them in LogoutSuccessHandler?
<logout success-handler-ref="logoutSuccessHandler" invalidate-session="false"/>
the default value of invalidate-session is true, so you will get a new session in your handler.
When you set this value to false, then you can get the old session, and don't forget to invalidate session after you finished your business.
Related
I am working on a spring MVC application And we are using Spring Security.
Now I have a requirement that as soon I enter into the application, a pop up should be displayed. So the application might enter directly into home page or to his profile page or to any other flow in the application. But I need to show the pop up irrespective of where the user enters.
How do I achieve this using Spring security or any other alternative please?
I have tried:
config.xml:
<bean id="popUpFilter" class="myPackage.security.popUpClass" />
<security:http entry-point-ref="myAppAuthEntryPoint" use-expressions="true">
.............
<security:custom-filter after="LOGIN_FILTER" ref="popUpFilter"/>
</security:http>
And My Code:
public class popUpClass implements AuthenticationSuccessHandler {
private static final Logger log = LoggerFactory.getLogger(popUpClass.class);
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//do some logic here if you want something to be done whenever
//the user successfully logs in.
log.debug("Entered into customfilter");
HttpSession session = httpServletRequest.getSession();
User user = SecurityClass.getUserDetails();
session.setAttribute("id", user.ID());
session.setAttribute("state", user.State());
//set our response to OK status
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
//since we have created our custom success handler, its up to us to where
//we will redirect the user after successfully login
httpServletResponse.sendRedirect("home");
}
}
NOTE: Popup should appear only once after the user log in.
I'm using Spring Boot and Spring Security. I have 2 apps. They works in couple. And when I want to logout from one, it should also logout from another. I use default logout flow. On application A I click logout link:
<logout invalidate-session="true" delete-cookies="JSESSIONID" logout-success-url="/app-b-logout" />
#RequestMapping("/app-b-logout")
public RedirectView appB_Logout() {
return new RedirectView("http://appB/logout");
}
On application B I have following settings:
.logout().permitAll().logoutUrl("/logout")
And logout success handler:
public class MyLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
implements LogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println(authentication == null); // MAGIC HERE
}
}
And in line System.out.println(authentication == null); I have different results. If I redirect from application A, authentication is null, when I click LOGOUT button on app B (using post form), I have authentication object.
But in both cases user is logged out in result. But I need to do some stuff with authentication object. How to make redirect on app A to make a POST request or just how to get this authentication when I redirect from app A?
I am pretty new in Spring Security and I have some doubt related the configuration found into a tutorial.
This is the spring-security.xml file used to the Spring Security configuration into the project:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<security:http>
<security:intercept-url pattern="/springLogin" access="permitAll"/>
<security:intercept-url pattern="/doSpringLogin" access="permitAll"/>
<security:intercept-url pattern="/myprofile" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/springHome" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/products" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/springLogout" access="permitAll"/>
<security:intercept-url pattern="/springLogin?error=true" access="permitAll"/>
<security:form-login login-page="/springLogin" login-processing-url="/doSpringLogin"
default-target-url="/springHome" authentication-failure-url="/springLogin?error=true"
username-parameter="username" password-parameter="password"
/>
<security:csrf disabled="true"/>
<security:logout logout-url="/springLogout" logout-success-url="/springLogin"/>
</security:http>
<bean id="userDetailsServiceImpl" class="com.demo.security.UserDetailsServiceImpl"></bean>
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsServiceImpl"></property>
</bean>
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<constructor-arg name="providers">
<list>
<ref bean="authenticationProvider"/>
</list>
</constructor-arg>
</bean>
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsServiceImpl">
<security:password-encoder hash="plaintext"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
</beans>
I it divided into some section. The first one is the tag content.
It contains something as:
<security:intercept-url pattern="/springLogin" access="permitAll"/>
that I think means that the page related to the /springLogin resource is accessible to everyone while
<security:intercept-url pattern="/myprofile" access="hasRole('ROLE_USER')"/>
means that the resource related to the /myprofile resource is accessible only to the logged user (the principal) having a ROLE_USER role setted.
Is it this reasoning correct?
Then in the previous configuration file there is:
1) The declaration of the authenticationManager bean:
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<constructor-arg name="providers">
<list>
<ref bean="authenticationProvider"/>
</list>
</constructor-arg>
</bean>
that I think it is used by Spring to populate the SecurityContext with the Principal objects (for example all the user of a web application) and with the Authorities (what a specific Principal can do).
Is this reasoning correct?
This object take as constructor arg a list of autentication provider bean that have to provide the Principal informations (so for example the role associated to a specific Principal)
In this case is provided an implementation of the DaoAuthenticationProvider class that take a bean having name="userDetailsService" as property, this one:
<bean id="userDetailsServiceImpl" class="com.demo.security.UserDetailsServiceImpl"></bean>
that is an instance of the UserDetailsServiceImpl class, this one:
public class UserDetailsServiceImpl implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
System.out.println(username);
User user = RegisteryDAO.getUserDAO().getUserByUsername(username);
if(user == null){
return null;
}
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(user.getRole()));
UserDetails userDetails = new org.springframework.security.core.userdetails.
User(user.getUsername(), user.getPassword(), true, true, true, true, authorities);
return userDetails;
}
}
So what exactly happen?
Using the debugger it seems to me that when te user try to access to a specific page this loadUserByUsername() return the UserDetails object related to the logged user that contain the List representing the roles associated to the specific logged user (for example the previous ROLE_USER)
Then I think that Spring automatically use the
<security:intercept-url pattern="/myprofile" access="hasRole('ROLE_USER')"/>
to check if the user have setted the propper role into the previous List list.
If it have so forward the request to the controller method that handle this Http Request otherwise avoid that this HttpRequest come to this controller method and show a page that say that the user can't access to this resource.
Here is an explanation of some of the concepts and questions you are asking about.
AuthenticationManager
AuthenticationManager is the component responsible for processing the Authentication request. The Authentication request might be instance of UsernamePasswordAuthenticationToken for username/password logins.
For other implementations look at Authentication JavaDoc.
AuthenticationManager also has collection of AuthenticationProvider implementations. These components are capable of processing specific Authentication types and AuthenticationManager iterates through them attempting to find one capable of handling the Authentication passed to it. If it finds one, it calls it with Authentication object presented and returns fully populated Authentication object if successful (otherwise AuthenticationException is thrown).
AuthenticationProvider
As mentioned above, AuthenticationProvider processes certain type of Authentication request. For instance the DaoAuthenticationProvider will perform following steps when called by AuthenticationManager:
Take the UsernamePasswordAuthenticationToken passed to it
Use the UserDetailsService service implementation provided to it (in your case it is UserDetailServiceImpl) to look up user by username
Check the password provided in the authentication token against the user using PasswordEncoder and SaltSource, if specified.
If the authentication succeeds, returns the populated Authentication object (UsernamePasswordAuthenticationToken in this case), which contains principal, credentials and is marked as authenticated
In case the authentication fails, AuthenticationException will be thrown
DaoAuthenticationProvider which you are using which is capable of processing UsernamePasswordAuthenticationToken requests. So typically form logins, and so on. You can see which types of authentications provider support by looking at its supports() method implementation, which in case of DaoAuthenticationProvider looks like this:
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
Spring Security Filter Chain
Now let's look at the Security filter chain, as defined by Spring Security documentation:
The order that filters are defined in the chain is very important.
Irrespective of which filters you are actually using, the order should
be as follows:
ChannelProcessingFilter, because it might need to redirect to a different protocol
SecurityContextPersistenceFilter, so a SecurityContext can be set up in the SecurityContextHolder at the beginning of a web request, and
any changes to the SecurityContext can be copied to the HttpSession
when the web request ends (ready for use with the next web request)
ConcurrentSessionFilter, because it uses the SecurityContextHolder functionality but needs to update the SessionRegistry to reflect
ongoing requests from the principal
Authentication processing mechanisms - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter,
BasicAuthenticationFilter etc - so that the SecurityContextHolder can
be modified to contain a valid Authentication request token
The SecurityContextHolderAwareRequestFilter, if you are using it to install a Spring Security aware HttpServletRequestWrapper into your
servlet container
RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder,
and the request presents a cookie that enables remember-me services to
take place, a suitable remembered Authentication object will be put
there
AnonymousAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder,
an anonymous Authentication object will be put there
ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error response can be returned or an
appropriate AuthenticationEntryPoint can be launched
FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied
When the user submits login form, AuthenticationManager is called at step 4 in the filter chain. In the case of form login it would be handled by UsernamePasswordAuthenticationFilter which calls the AuthenticationManager to process the authentication:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// ...
return this.getAuthenticationManager().authenticate(authRequest);
}
Using the debugger it seems to me that when te user try to access to a specific page this loadUserByUsername() return the UserDetails
Actually the loadUserByUsername() is called when user authenticates, for instance after submitting login form. If the user is already authenticated this is not called.
I think that means that the page related to the /springLogin resource is accessible to everyone:
<security:intercept-url pattern="/springLogin" access="permitAll" />
Then I think that Spring will automatically use following to check if the user has proper role:
<security:intercept-url pattern="/myprofile" access="hasRole('ROLE_USER')" />
Correct. This process is handled by FilterSecurityInterceptor, which extends AbstractSecurityInterceptor - core Spring Security component dealing with authorization. If the user is not authenticated or doesn't have the required role an Exception is thrown and handled by the ExceptionTranslationFilter. This filter handles Security exceptions. For instance in case of authentication failure it will redirect user to authentication entry point, e.g. the login page.
Internal architecture of Spring Security is pretty nicely described in the reference documentation. I recommend to take a look at it.
I have a web project with Spring Security and I have tried to save a cookie in the method that process the authentication success. However, when I look to the browser's cookies only appears the JSESSIONID one, and the same happens when I look to request.getCookies() at the servlet that Spring redirects to.
I have tried to save the cookie in one of the application's servlets and the cookie is saved correctly, so maybe Spring Security cleans the response. Do you have any idea?
One workaround would be to save it in Session, and then get it and save the cookie on the servlet to which the login redirects. Another one would be saving the cookie with javascript like this. But I don't like these solutions. Thanks in advance
Here is the relevant code:
public class RoleBasedAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler implements
AuthenticationSuccessHandler {
...
// save a cookie with the selected language
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap.containsKey("language")) {
saveCookie("language", parameterMap.get("language")[0], response);
}
}
public static void saveCookie(String cookieName, String value, HttpServletResponse response) {
Cookie cookie = new Cookie(cookieName, value);
//maxAge is one month: 30*24*60*60
cookie.setMaxAge(2592000);
cookie.setDomain("projectName");
cookie.setPath("/");
response.addCookie(cookie);
}
}
<security:http auto-config="false" ...>
<security:form-login login-page="/login.do" authentication-success-handler-ref="redirectRoleStrategy" .../>
...
</security:http>
<bean id="redirectRoleStrategy" class="com.companyName.security.RoleBasedAuthenticationSuccessHandler">
<beans:property name="roleUrlMap">
<beans:map>
<beans:entry key="ROLE_ADMIN" value="/privat/application.do"/>
...
</beans:map>
</beans:property>
</bean>
Are you setting the cookie before or after calling super in the RoleBasedAuthenticationSuccessHandler?
super.onAuthenticationSuccess(request, response, authentication);
You must set the cookie before your call to the super, as the logic in the superclass will send a redirect and therefore prevent you from updating content of the HttpServletResponse.
Try to call some harcoded value outside the if clause, just to see if it works:
saveCookie("language", "en", response);
Also as a test try to not set cookie domain and path initially:
Cookie cookie = new Cookie(cookieName, value);
//maxAge is one month: 30*24*60*60
cookie.setMaxAge(2592000);
//cookie.setDomain("projectName");
//cookie.setPath("/");
response.addCookie(cookie);
It should be possible to set a cookie from the authentication successful handler, this should normally work.
I fail to retrieve the Spring remember me cookie after the login request, but it works fine in the next request to a protected page. Could anyone please tell me how I can get hold of it right away?
I am setting the remember me cookie in the login request, but fail to retrive it after Spring redirects back to the original (protected) url.
Step by step:
Browser goes to example.com/protected
Spring redirects to login form page
Upon successful login, the SPRING_SECURITY_REMEMBER_ME_COOKIE is set in a very thin custom sub class of org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
It looks like Spring redirects back to example.com/protected, whithout a roundtrip to the browser, and both login "servlet" and protected page is handled by the same thread in Tomcat 6.
Our subclass of org.springframework.security.web.access.expression.WebSecurityExpressionRoot has methods that is invoked from a <intercept-url pattern="..." access="method()" />
In our method() request.getCookies() does not give the remember me cookie on the first request, but on all requests after that.
Our app has some problems because the cookie is missing...
My theory so far is that I don't understand SavedRequest properly.
Condensed config here:
<http auto-config="false" use-expressions="true" authentication-manager-ref="myAuthenticationManager" path-type="regex">
<form-login authentication-success-handler-ref="myAuthenticationSuccessHandler" login-page="..." login-processing-url="..." authentication-failure-url="..." username-parameter="username" password-parameter="password" />
<custom-filter ref="logoutFilter" position="LOGOUT_FILTER"/>
<expression-handler ref="myWebSecurityExpressionHandler" />
<custom-filter ref="myCustomeFilter1" before="FORM_LOGIN_FILTER"/>
<custom-filter ref="myCustomeFilter2" position="BASIC_AUTH_FILTER"/>
<custom-filter ref="mySecurityClientTokenAuthenticationFilter" after="LOGOUT_FILTER" />
<access-denied-handler ref="myAccessDeniedHandler"/>
<intercept-url pattern="xxx"
access="method()"/>
<intercept-url pattern="yyy"
access="method()"/>
<remember-me services-ref="rememberMeServices" key="my_remember"/>
</http>
I tried adding the following, with the only result that the user does not get redirected to the original page.
<http ...
<request-cache ref="nullRequestCache"/>
</http>
<bean:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
When using request.getCookie() in autoLogin() method of RememberMeService, the request passed in is SavedRequestAwareWrapper which encapsulates original request and saved request and overrides the getCookies method.
#Override
public Cookie[] getCookies() {
List<Cookie> cookies = savedRequest.getCookies();
return cookies.toArray(new Cookie[cookies.size()]);
}
Therefore, when you want to get cookie from request, you actually get cookie from the savedRequest. However, the cookie may exist in the original request.
You probably should get the original request for getting cookies you want. For example:
public class ApplicationRememberMeServiceImpl implements RememberMeService, LogoutHandler {
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
HttpServletRequestWrapper savedRequestWrapper = (HttpServletRequestWrapper) ((HttpServletRequestWrapper) request).getRequest();
HttpServletRequest httpServletRequest = (HttpServletRequest) savedRequestWrapper.getRequest();
Cookie cookie = WebUtils.getCookie(httpServletRequest, cookieName);
// logic continues...
}
}
Update 03/05/2015
Because spring security will wrap the original HttpServletRequest multiple times, it is more safe to extracting the original request in the way below:
public class ApplicationRememberMeServiceImpl implements RememberMeService, LogoutHandler {
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
HttpServletRequest httpServletRequest = request;
// Get the original request from multiple wrapped HttpServletRequest
if(httpServletRequest instanceof HttpServletRequestWrapper) {
HttpServletRequestWrapper httpServletRequestWrapper = (HttpServletRequestWrapper) httpServletRequest;
while(httpServletRequestWrapper.getRequest() instanceof HttpServletRequestWrapper) {
httpServletRequestWrapper = (HttpServletRequestWrapper) httpServletRequestWrapper.getRequest();
}
httpServletRequest = (HttpServletRequest) httpServletRequestWrapper.getRequest();
}
Cookie cookie = WebUtils.getCookie(httpServletRequest, cookieName);
// logic continues...
}
}