I am using Spring 3.1 version.
I have implemented spring security for login to my web portal. It works fine except for one issue. I have set session timeout to 2 min.
Once timeout happens and then user click any URL, It gets redirected to logout page. But when user re authenticates, user directly lands on the home page which is default target URL instead of last access page.
Like if user is accessed /home/editproduct then after timeout & when he again reautenticate he should be accessed to the home/editproduct instead of only /home page.
i am using spring with JSON & AJAX call.
<bean id="myNePublicUserNamePasswordAuthFilter"
class="com.ne.mynelson.authentication.publicuser.MyNePublicUserPasswordAuthFilter">
<property name="filterProcessesUrl" value="/service/json_authentication_check"></property>
<property name="authenticationManager" ref="myNePublicUserAuthenticationManager" />
<property name="authenticationFailureHandler" ref="failureHandler" />
<property name="authenticationSuccessHandler" ref="successHandler" />
<property name="authenticationInputProcessor" ref="myNePublicUserAuthInputProcessor"></property>
</bean>
<bean id="successHandler"
class="com.ne.mynelson.authentication.publicuser.MyNePublicUserAuthSuccessHandler">
<property name="authHandlerView" ref="authHandlerView"></property>
<property name="sessionRegistry" ref="sessionRegistry"></property>
<property name="publicLoginManager" ref="publicLoginManager"></property>
</bean>
EDIT: For SessionManagementFilter
You need to implement the InvalidSessionStrategy, override the onInvalidSessionDetected method, just like SimpleRedirectInvalidSessionStrategy, but before redirect, you need to create a new session, and save the request to session.
HttpSession session = request.getsession(false);
if (session != null) {
// for creating a new session
session.invalidate();
}
DefaultSavedRequest savedRequest = new DefaultSavedRequest(request,
new PortResolverImpl());
request.getSession(true).setAttribute("SPRING_SECURITY_SAVED_REQUEST", savedRequest);
redirectStrategy.sendRedirect(request, response, destinationUrl);
and then inject this bean to SessionManagementFilter.
EDIT: For ConcurrentSessionFilter
If you use the concurrentSessionFilter, you can implement SessionInformationExpiredStrategy, just like SimpleRedirectSessionInformationExpiredStrategy, and in the method onExpiredSessionDetected, still do the same thing like I post above, before redirect, create new session, and put the save request to new session, you can get the requestby event.getRequest(), then inject this sessionInfomationExpiredStrategy to concurrentSessionFilter.
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
logger.debug("Redirecting to '" + destinationUrl + "'");
DefaultSavedRequest savedRequest = new DefaultSavedRequest(event.getRequest(),
new PortResolverImpl());
request.getSession(true).setAttribute("SPRING_SECURITY_SAVED_REQUEST", savedRequest);
redirectStrategy.sendRedirect(event.getRequest(), event.getResponse(), destinationUrl);
}
Finally , Using SavedRequestAwareAuthenticationSuccessHandler instead of SimpleUrlAuthenticationSuccessHandler. It will try to get the request target url and then redirect to the saved URL.
Related
I am using spring 4.2.1 with spring security 4.0.2
On login, I need to return a json object tree to the client, containing the cached data it requires for the session.
So I've added a the following method:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody ServerResponse<?> login(#RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
Authentication result = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(result);
Object data = null; // Do stuff here
return new ServerResponse<>(data);
}
My spring security config:
<ss:http auto-config="false" use-expressions="true" entry-point-ref="authenticationEntryPoint">
<ss:anonymous enabled="false" />
<!-- this is enabled by default in spring 4 -->
<ss:csrf disabled="true" />
<ss:custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<ss:session-management session-authentication-strategy-ref="sas" />
<ss:port-mappings>
<ss:port-mapping http="8080" https="8443" />
</ss:port-mappings>
<ss:intercept-url pattern="/app/logi**" access="permitAll()" />
<ss:intercept-url pattern="/app/logou**" access="permitAll()" />
<ss:intercept-url pattern="/app/**" access="hasAuthority('user')" />
<ss:intercept-url pattern="/www/**" access="hasAuthority('user')" />
</ss:http>
All the pages I find regarding a programmatic login confirm that what I am doing is fine.
However, when I try and call another web service method later, I get 403 as the client is not logged in.
I read some vague references to having to use a spring filter, but I am not sure how I would get the filter to return the json tree to the client after successful login.
Any suggestions or links to an example on how to do this would be much appreciated.
Thanks
Sooo it turns out the problem was that I was doing Cross Origin Resource Sharing and the browser was not sending the cookie across with the next request.
Basically I was calling the server from html on the file system (with origin file://)
I was handling options calls, but I was not sending back
Access-Control-Allow-Credentials true
headers in the responses and I had to configure angular to send the cookie by passing the flag
withCredentials: true
in the config object to $http.post
I managed to get my application working with Active Directory (basically LDAP) using spring-security, like this:
<authentication-manager>
<authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</authentication-manager>
<beans:bean id="grantedAuthoritiesMapper" class="mypackage.ActiveDirectoryGrantedAuthoritiesMapper"/>
<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="xxxx.xxx.xxxx" />
<beans:constructor-arg value="ldap://xxx.xxx.xxx.xxx:389" />
<beans:property name="authoritiesMapper" ref="grantedAuthoritiesMapper" />
<beans:property name="useAuthenticationRequestCredentials" value="true" />
<beans:property name="convertSubErrorCodesToExceptions" value="true" />
</beans:bean>
But by doing only this I need to have the users registered both in my application and in the Active Directory before-hand. I would like to be able to before the user logs in (but after the Active Directory validation) to see if he exists in my database and if he doesn't create a new user in my application and then proceed as normal.
I believe I need to create a preAuthentication provider, but I don't know exactly where I can insert my own class to code the checking and registration of the user.
Optimally I would also like to check for a specific authority before creating the user.
Can anyone give me a hand?
In the end I had to change my authentication handler onAuthenticationSuccess. In spring there are authentication and login in the lifecycle of the login process. During authentication the user is, well, authenticated, but not yet logged into the application (ie only its credentials were deemed valid, but the rest of the application is not aware of the user yet.)
I changed my security.xml to:
<beans:bean id="authHandler" class="mypackage.activedirectory.ActiveDirectoryAuthenticationHandler"
...
<form-login login-page="/login" authentication-failure-handler-ref="authHandler"
authentication-success-handler-ref="authHandler" />
And here is the class:
public class ActiveDirectoryAuthenticationHandler extends MyAuthenticationHandler {
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth)
throws IOException, ServletException {
String username= auth.getName();
User user= userService.findByUsername(username);
if (user == null) {
user= new User();
//set user properties
}
try {
userService.save(user);
} catch (EntityException e) {
System.out.println(e.getMessage());
}
super.onAuthenticationSuccess(req, resp, auth);
}
}
I still haven't figured out how to handle AD specific errors (like user is blocked, or password expired), right now it shows my application default login error message.
I have a slight issue with my configuration of spring security and the InvalidSessionStrategy implementation I use.
Say a user is connected to the app and is viewing page: /userArea/thePage and their session times out, the user will first be redirected to the /signin page. Then, upon successful signin, they will be redirected to the home page of they personal area (/userArea) whereas I want them to come back to where they were located when the session timed out i.e. /userArea/thePage.
Is this possible?
If so how do I need to alter my config/app?
Here is my current config:
<beans:bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
<beans:constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
<beans:property name="invalidSessionStrategy" ref="simpleRedirectInvalidSessionStrategy" />
</beans:bean>
<beans:bean id="simpleRedirectInvalidSessionStrategy" class="org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/signin" />
<beans:property name="createNewSession" value="true" />
</beans:bean>
<http auto-config="true" use-expressions="true">
<custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
<form-login login-processing-url="/resources/j_spring_security_check" login-page="/signin" authentication-failure-url="/signin?login_error=t" default-target-url="/userArea" />
<logout logout-url="/resources/j_spring_security_logout" logout-success-url="/signin" />
...
edit 1: Let me better specify my requirements:
When a user session times out, I want the user to be redirected to the saved request (the url they requested before being redirected to the signin page).
However, when they initially signin with the app, I want them to be redirected to the home page of the personal area.
Are my requirements possible to implement using solution suggested by Carsten (see below)?
You could set the always-use-default-target="true" in the form-login tag. This redirects the user to the url they where trying to access before being intercepted to login.
But this will be the standard behaviour and not only in the case of a session timeout. Depending on the application this might not be what you want.
Edit:
To do what you want you need to find a way to save the information on which page the user was when the session timedout. I don't know of any out of the box solution for this problem, since there is no state that indicates whether or not the user timed out or logged out manually.
What needs to be done ist to:
set a flag or save the page-url on session timeout
check in a custom AuthenticationSuccesHandler and redirect accordingling
If I would implement somehing like that I would most likely store the page-url. Also there are a few tricky things with this from an UX perspective. What happens if the saved page relies on a state achieved earlier? (I assume thats the reason you want the User to go to the default-url on normal login?) What happens if the user just does not log out shuts down sleeps for the night and logs in navigating to the login page (does the flag/page-url time out?)? etc.
In general I think it would be better use the always-use-default-target="true" since this adds the comfort of bookmarking any page and not having to navigate there at each login.
Looks like it's common issue for any Spring project.
Spring developers thought that this is undocumented behavior https://github.com/spring-projects/spring-security/issues/1981, my business users are thinking that it's a bug.
So, as a result we need to do some custom implementation)))
Personally for me it's a bug and after making a custom implementation I don't understand why it's not fixed at Spring Framework.
As in a lot of other cases we have no choice and just copy-paste SimpleRedirectInvalidSessionStrategy and add our custom code.
You can even more simplify this code(I just make a customization which can be used OOTB in Spring):
public class CustomInvalidSessionStrategy implements InvalidSessionStrategy {
private final Log logger = LogFactory.getLog(this.getClass());
private String destinationUrl = null;
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private boolean createNewSession = true;
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException {
this.logger.debug("Starting new session (if required) and redirecting to '" + this.destinationUrl + "'");
if (this.createNewSession) {
request.getSession();
}
if (destinationUrl == null) {
this.redirectStrategy.sendRedirect(request, response, request.getRequestURI());
} else {
this.redirectStrategy.sendRedirect(request, response, this.destinationUrl);
}
}
public void setCreateNewSession(final boolean createNewSession) {
this.createNewSession = createNewSession;
}
public void setInvalidSessionUrl(final String invalidSessionUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(invalidSessionUrl), "url must start with '/' or with 'http(s)'");
this.destinationUrl = invalidSessionUrl;
}
}
And some extra configuration for Spring security:
<security:http ...>
...
<security:session-management invalid-session-strategy-ref="customInvalidSessionStrategy" />
...
</security:http>
<bean id="customInvalidSessionStrategy" class="com.custom.web.security.CustomInvalidSessionStrategy"/>
i want to change remember me request parameter to override default parameter '_spring_security_remember_me'
and custom my remember me service to replace <remember-me /> namespace config.
so i config my remember me service:
<bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="key" value="MY_REMEMBER_ME_KEY" />
<property name="cookieName" value="MY_REMEMBER_ME_COOKIE" />
<property name="parameter" value="remember" />
<property name="tokenValiditySeconds" value="1209600" />
<property name="useSecureCookie" value="true" />
<property name="userDetailsService" ref="userDetailsService" />
<property name="alwaysRemember" value="false" />
</bean>
namespace config:
<intercept-url pattern="/secure/index" access="ROLE_ADMIN" />
<remember-me services-ref="rememberMeServices"/>
when i run application and login. i find cookie is created then i close my ie and reopen.
entry the path '/secure/index', tomcat show me access is denied .
but i revert to Spring Security default config , all is ok.
i debug code find
RememberMeAuthenticationFilter#doFilter
...
Authentication rememberMeAuth = rememberMeServices.autoLogin(request, response);
...
//autoLogin(request, response) method code.
String rememberMeCookie = extractRememberMeCookie(request);
...
protected String extractRememberMeCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if ((cookies == null) || (cookies.length == 0)) {
return null;
}
for (int i = 0; i < cookies.length; i++) {
if (cookieName.equals(cookies[i].getName())) {
return cookies[i].getValue();
}
}
return null;
}
in method extractRememberMeCookie(request), code request.getCookies() always return null when i use my custom remember me service, but i revert Spring Security default namespace <remember-me/> and do the same(clean Cookies - login - close ie - reopen - entry path '/secure/index'), i also find cookie is create .
and i debug the code i find request.getCookies() return the cookie name 'SPRING_SECURITY_REMEMBER_ME_COOKIE' and authentication successfully.
need other config to remember me authentication ?
but i don't know , would someone help me.
Your <remember-me /> still need key
this should be
<remember-me key="MY_REMEMBER_ME_KEY" services-ref="rememberMeServices"/>
As per the documentation of TokenBasedRememberMeServices,
An org.springframework.security.core.userdetails.UserDetailsService is
required by this implementation, so that it can construct a valid
Authentication from the returned
org.springframework.security.core.userdetails.UserDetails. This is
also necessary so that the user's password is available and can be
checked as part of the encoded cookie.
Perhaps your configuration is incorrect/incomplete.
This is actually an old post. But I just had the issue request.getCookies() null w/ Spring 4.
I've removed useSecureCookie = true to fix it.
I'm trying to authenticate and then query our corporate LDAP using Spring LDAP and Spring security. I managed to make authentication work but when I attempt to run search I always get the following exception
In order to perform this operation a successful bind must be completed on the connection
After much research I have a theory that after I authenticate and before I can query I need to bind to connection. I just don't know what and how?
Just to mention - I can successfully browse and search our LDAP using JXplorer so my parameters are correct.
Here's section of my securityContext.xml
<security:http auto-config='true'>
<security:intercept-url pattern="/reports/goodbye.html"
access="ROLE_LOGOUT" />
<security:intercept-url pattern="/reports/**" access="ROLE_USER" />
<security:http-basic />
<security:logout logout-url="/reports/logout"
logout-success-url="/reports/goodbye.html" />
</security:http>
<security:ldap-server url="ldap://s140.foo.com:1389/dc=td,dc=foo,dc=com" />
<security:authentication-manager>
<security:authentication-provider ref="ldapAuthProvider">
</security:authentication-provider>
</security:authentication-manager>
<!-- Security beans -->
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://s140.foo.com:1389/dc=td,dc=foo,dc=com" />
</bean>
<bean id="ldapAuthProvider"
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg>
<bean class="foo.bar.reporting.server.security.ldap.LdapAuthenticatorImpl">
<property name="contextFactory" ref="contextSource" />
<property name="principalPrefix" value="TD\" />
<property name="employee" ref="employee"></property>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="foo.bar.reporting.server.security.ldap.LdapAuthoritiesPopulator" />
</constructor-arg>
</bean>
<!-- DAOs -->
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource" />
Here's code snippet from LdapAuthenticatorImpl that performs authentication. No problem here:
#Override
public DirContextOperations authenticate(final Authentication authentication) {
// Grab the username and password out of the authentication object.
final String name = authentication.getName();
final String principal = this.principalPrefix + name;
String password = "";
if (authentication.getCredentials() != null) {
password = authentication.getCredentials().toString();
}
if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
final InitialLdapContext ldapContext = (InitialLdapContext)
this.contextFactory.getContext(principal, password);
// We need to pass the context back out, so that the auth provider
// can add it to the Authentication object.
final DirContextOperations authAdapter = new DirContextAdapter();
authAdapter.addAttributeValue("ldapContext", ldapContext);
this.employee.setqId(name);
return authAdapter;
} else {
throw new BadCredentialsException("Blank username and/or password!");
}
}
And here's another code snippet from EmployeeDao with my futile attempt to query:
public List<Employee> queryEmployeesByName(String query)
throws BARServerException {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person"));
filter.and(new WhitespaceWildcardsFilter("cn", query));
try {
// the following line throws bind exception
List result = ldapTemplate.search(BASE, filter.encode(),
new AttributesMapper() {
#Override
public Employee mapFromAttributes(Attributes attrs)
throws NamingException {
Employee emp = new Employee((String) attrs.get("cn").get(),
(String) attrs.get("cn").get(),
(String) attrs.get("cn").get());
return emp;
}
});
return result;
} catch (Exception e) {
throw new BarServerException("Failed to query LDAP", e);
}
}
And lastly - the exception I'm getting
org.springframework.ldap.UncategorizedLdapException:
Uncategorized exception occured during LDAP processing; nested exception is
javax.naming.NamingException: [LDAP: error code 1 - 00000000: LdapErr:
DSID-0C090627, comment: In order to perform this operation a successful bind
must be completed on the connection., data 0, vece]; remaining name
'DC=TD,DC=FOO,DC=COM'
It looks like your LDAP is configured to not allow a search without binding to it (no anonymous bind). Also you have implemented PasswordComparisonAuthenticator and not BindAuthenticator to authenticate to LDAP.
You could try modifying your queryEmployeesByName() method to bind and then search, looking at some examples in the doc.
I'm going to accept #Raghuram answer mainly because it got me thinking in the right direction.
Why my code was failing? Turned out - the way I wired it I was trying to perform anonymous search which is prohibited by the system - hence the error.
How to rewire example above to work? First thing (and ugly thing at that) you need to provide user name and password of user that will be used to access the system. Very counterintuitive even when you login and authenticated, even if you are using BindAuthenticator system will not attempt to reuse your credentials. Bummer. So you need to stick 2 parameters into contextSource definition like so:
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://foo.com:389/dc=td,dc=foo,dc=com" />
<!-- TODO - need to hide this or encrypt a password -->
<property name="userDn" value="CN=admin,OU=Application,DC=TD,DC=FOO,DC=COM" />
<property name="password" value="blah" />
</bean>
Doing that allowed me to replace custom implementation of authenticator with generic BindAuthenticator and then my Java search started working
I got the same error, couldn't find a solution.
Finally I changed the application pool identity to network service and everything worked like a charm.
(I have windows authentication and anonymous enabled on my site)