Currently, I am using the default HttpSession object in both controllers and gsp pages:
In controllers:
...
session.mykey = anObject; // adding an object to session
...
if (session.otherkey) { // performing some checking
In GSPs:
...
<g:if test="${session.mykey}">
...
I'd like to have a "remember me" functionality. Shiro has already it built in. However, as far as I understood, in order to do it I have to use the shiro native session mode (in Config.groovy: security.shiro.session.mode="native"). By default, it persists the session state, so objects will remain in the session as far as the cookie expires or the user logs off.
Is my understanding right?
Then i will have to change my controllers to this:
def shiroSession = SecurityUtils.subject.session
shiroSession.setAttribute("mykey",anObject)
....
if (shiroSession.getAttribute("otherkey") ){
And my views to this:
<g:if test="${SecurityUtils.subject.session.getAttribute('mykey')}">
So, my questions are:
Is that right?
Can't I just use the previous way to access the session?
Do I have to turn off the default http session in some configuration?
I gave up keeping objects in the session persistently (until cookie expires). Here is what i did:
In the login method in the controller:
if (! session.currentProfile){
Subject currentUser = SecurityUtils.getSubject()
if (currentUser.isRemembered()){
boolean success = configureSession(session, currentUser.getPrincipal())
if (success){
...
}
}
....
}
The first "if" checks whether the session has the object i need.
The configureSession method puts in the session all information I need.
Related
I'm really trying to understand how Spring Security works, but I'm a bit lost at the moment. Here's the simple scenario:
User visits the website home page but doesn't log in
SecurityContextPersistenceFilter logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter populates SecurityContextHolder with an anonymous token
A session is created with ID = C2A35ED5A41E29865FF53162B0024D52
User lets the page sit idle until the session times out
User clicks on the About page (or home page again)
SecurityContextPersistenceFilter again logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter again populates SecurityContextHolder with an anonymous token
SessionManagementFilter logs that requested session ID C2A35ED5A41E29865FF53162B0024D52 is invalid
SessionManagementFilter logs that it is starting a new session and redirecting to /invalidsession
These pages are configured to .authorizeRequests().antMatchers("/","/home","/about").permitAll(). I have the invalid session option turned on to handle authenticated users: .sessionManagement().invalidSessionUrl("/errors/invalidSession"). If I comment out that option, then everything described above is exactly the same EXCEPT for step #10 - SessionManagementFilter sees that the requested session ID is invalid (#9) but does NOT start a new session and perform the redirect (#10).
WHY? What can I do to keep the invalid session option but correctly handle anonymous users, i.e., not be redirected? Or is that just not possible and I'll have to handle authenticated users separately? I'd be very grateful if anyone can help me understand what's happening here and point me in a direction to solve this. Let me know if you need to see my full http configuration.
EDIT
I ran a series of tests with anonymous and registered (authenticated) users. If .sessionManagement().invalidSessionUrl("/errors/invalidSession") is enabled then both types of users will eventually arrive at the error page. Authenticated users with RememberMe unchecked are the same as anon users. If RememberMe is checked, then the error page appears once RememberMe times out.
If I disable the invalid session option, no users ever get the error page (which makes sense). Both types of users can browse public pages as long as they want and authenticated users will be asked to log in after the session or RememberMe expires.
If you're interested the code involved here is in SessionManagementFilter
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
If .sessionManagement().invalidSessionUrl is enabled the default method SimpleRedirectInvalidSessionStrategy is called, which executes this piece of code:
if (createNewSession) {
request.getSession();
}
redirectStrategy.sendRedirect(request, response, destinationUrl);
The createNewSession boolean can be set through setCreateNewSession(boolean createNewSession), which is described as:
Determines whether a new session should be created before redirecting (to avoid possible looping issues where the same session ID is sent with the redirected request). Alternatively, ensure that the configured URL does not pass through the SessionManagementFilter.
So, it looks to me like .sessionManagement().invalidSessionUrl works best for sites where all pages are authenticated. The options I'm looking at are a custom filter placed before the SessionManagementFilter that checks the page access and turns 'createNewSession' on/off as needed or turning off the invalid session option and handling it elsewhere for authenticated pages (?). I also stumbled across <%# page session=“false” %> in this SO question - Why set a JSP page session = “false” directive? - which I'm going to look into further. Being so new to Spring Security I don't have a good sense of the best practice for handling this situation correctly. Any help would be appreciated.
OK, so I've spent the last couple of weeks digging around in Spring Security trying to understand how it all fits together. I'm still learning, but for this particular situation I found two approaches that work.
The obvious one is to just bypass security for public pages like this:
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/resources/**")
;
}
I still don't know enough about web security in general to know if this is an acceptable approach or not, but it allows anonymous users to browse the site w/o ever getting an invalid session error.
The harder solution (for a Java and Spring noob like me) is based upon these SO questions:
Spring security invalid session redirect
How to set a custom invalid session strategy in Spring Security
The default SimpleRedirectInvalidSessionStrategy class is final which meant I had to create basically a copy of that class (not sure how good an idea that is). You can't use a session attribute because the session has been destroyed by the time it gets to this strategy so I created a helper class for a session cookie called authUser (I can post the class if anyone wants to see it). The cookie is created or updated in the LoginSuccessHandler or RememberMeSuccessHandler and it indicates if the user is anonymous or authenticated:
authCookie.setCookie(request, response, "anonymousUser");
or
authCookie.setCookie(request, response, authentication.getName());
I'm currently using the actual login only for testing purposes - it will ultimately be just a simple yes/no indicator of some sort. CustomLogoutSuccessHandler resets it to anonymousUser
The invalid session method looks like this:
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String url = destinationUrl;
//reset context default value
redirectStrategy.setContextRelative(false);
if (authCookie.isCurrentCookieAnonymous()) {
//pass the URL originally requested by the anonymous user
url = request.getRequestURI();
//the URL needs to have the context removed
redirectStrategy.setContextRelative(true);
}
//always revert to anonymous user
authCookie.setCookie(request, response, "anonymousUser");
logger.debug("Starting new session (if required) and redirecting to '" + url + "'");
if (createNewSession)
request.getSession();
redirectStrategy.sendRedirect(request, response, url);
}
Again, I can post the full class if requested.
The SecurityConfig class includes the following:
#Bean
public SessionManagementBeanPostProcessor sessionManagementBeanPostProcessor() {
return new SessionManagementBeanPostProcessor();
}
protected static class SessionManagementBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof SessionManagementFilter) {
SessionManagementFilter filter = (SessionManagementFilter) bean;
filter.setInvalidSessionStrategy(new RedirectInvalidSession("/errors/invalidSession"));
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
My testing so far has been successful for both anonymous and authenticated users, but this approach has not been production tested.
In my application I need to programmatically logout a user, not the current one. I was able to expire its session but this does not force a logout for that specific user. So my question is, how can I force a logout on the user's next request? This is my code now:
sessionRegistry.getAllPrincipals().each {
principal = it
sessionRegistry.getAllSessions(it, false).each {
if (principal.username == userSec.username) {
it.expireNow()
}
}
}
I have this in my resources.groovy:
sessionRegistry(SessionRegistryImpl)
sessionAuthenticationStrategy(ConcurrentSessionControlStrategy, sessionRegistry) {
maximumSessions = -1
}
concurrentSessionFilter(ConcurrentSessionFilter){
sessionRegistry = sessionRegistry
expiredUrl = '/login/concurrentSession'
}
Using the session mechanismus it is not possible. You have to use a storage medium where you can keep the users. This medium can be either an in-RAM singleton, like ConcurrentHashMap instance, or a DB, depending on your clustering architecture.
Then upon each call to springSecurityService.currentUser (e.g. in Spring Security core plugin), you have to check if the sessionRegistry contains that user, and if not, invalidate the session or the user
I had a Posting on a blog about Sessions AND Cookies. Here are details
Sessions
Sessions are More Secure
Sessions are on the server
Cookies
Cookies are On client side
Less Secure
Once it is disable on browser the difficult to use.
On the basis of above argument i used sessions in Login system to keep UserId,UserName & roleName
Now on the the basis of roleName i will decide either this is Admin to enter to administrator section or not.
I have used this Code in Model in MVC
public bool LoginMe()
{
Int64 Error;
//create db
Database db = DatabaseFactory.CreateDatabase("DBContext");
DbCommand dbCommand = db.GetStoredProcCommand("ValidateUser");
db.AddInParameter(dbCommand, "#Username", DbType.String, this.UserName);
db.AddInParameter(dbCommand, "#Password", DbType.String, EncryptPassword(this.Password));
db.AddOutParameter(dbCommand, "#Error", DbType.Int64, 10);
DataSet dsResult = db.ExecuteDataSet(dbCommand);
Error = Convert.ToInt64(db.GetParameterValue(dbCommand, "#Error"));
if (Error == 1100)
{
try
{
var query = (from o in dsResult.Tables[0].AsEnumerable()
select new AllUser
{
UserId = o.Field<int>("UserId"),
UserName = o.Field<string>("UserName"),
roleName = o.Field<string>("roleName"),
}).Single(); // this will raise an exception if there isn't just one record returned
Session["UserId"] = query.UserId;
Session["UserName"] = query.UserName;
Session["roleName"] = query.roleName;
return true;
}
catch {
// do nothing and let method return false as something has gone wrong.
// add logging here if you are using it to show there has been a problem
}
}
return false;
}
I used it in View like #Session["UserId"]
Now an expert comment on this like
If you aren't using https and securing the session cookie then this might make it easy to hack your site, although that's the same for any session based site (nearly all of them)
It might be nice to add some check so that if you remove a user's rights, the session variables are deleted the next time that user requests something from the server,
otherwise they could carry on using the site even though their account it banned.You'd have to decide if this is likely and then how you want to do this (using an authorization filter maybe.)
Above comments confused me.Can any body make it clear?What is the best way to keep these information?
Session state uses client tickets to identify the server-side session, it may be susceptible to session ID spoofing and injection attacks.
So, to hack session values one would require hacking the remote-server.
And yes, for highly secure application(such as online banking) use https.
http://msdn.microsoft.com/en-us/magazine/cc163730.aspx#S9
Secure sockets layer (SSL) should be used to prevent network-level sniffing of session IDs, authentication tickets, application cookies, and other request/response information.
Can session value be hacked?
Use HTTPS if you application handles sensitive information(credit-card number,account num,passwords).
Store the User object (model with userId,username,role) in the session than separate attributes
Set setHttpOnly attribute for SESSION_ID.
It might be costly to refresh the User object stored in session before invoking every operation to reflect the current rights stored in database.
As a application administrator I would like to be able to log off any user, for example, after setting the flag "enabled = false" to the selected user. Is it possible in spring-security?
I should add that my application allows the use of "remember Me" for users.
I'm using:
grails 2.2.1
plugin spring-security-core 1.2.7.3
Settings spring-security-core (config.groovy):
grails.plugins.springsecurity.useHttpSessionEventP ublisher = true
grails.plugins.springsecurity.useSessionFixationPr evention = true
grails.plugins.springsecurity.userLookup.userDomai nClassName = 'com.app.User'
grails.plugins.springsecurity.userLookup.authority JoinClassName = 'com.app.UserRole'
grails.plugins.springsecurity.authority.className = 'com.app.Role'
grails.plugins.springsecurity.userLookup.usernameP ropertyName = 'email'
grails.plugins.springsecurity.securityConfigType = "Requestmap"
grails.plugins.springsecurity.rejectIfNoRule = true
grails.plugins.springsecurity.requestMap.className ='com.app.Requestmap'
grails.plugins.springsecurity.requestMap.urlField= 'url'
grails.plugins.springsecurity.requestMap.configAtt ributeField='configAttribute'
grails.plugins.springsecurity.rememberMe.cookieNam e = 'remember_me'
grails.plugins.springsecurity.cacheUsers = false
grails.plugins.springsecurity.rememberMe.tokenVali ditySeconds=604800
Has anyone had a similar problem may be?
thank you in advance :)
Setting the UserDetails.enabled flag to false should cause a DisabledException to be thrown on the user's next secure request. This can either send the user to the default authfail handler, or you can configure an exception handler in your UrlMappings to send the user to a custom controller or action.
If you're not using the enabled flag anywhere else in your application, you can direct the DisabledException to an action which clears the authentication session (and rememberMe), then resets the enabled flag.
Another possible way would be to create a custom filter and inject it into the spring security filter chain in your Bootstrap.
Both the url exception mapping and the filter configuration are described in the Spring Security Plugin Documentation
Thanks Codelark, I'm reading about filters and try to use them :)
Unfortunately, when I set enabled = false for the selected user application does not redirect it to "/login /authfail".
Best of all, the changes (enabled = false) are visible to the user profile but the lack of the desired effect.
I would add, I tried to set "expireNow" a user session:
def expireSession(User user) {
def orginalUser = springSecurityService?.principal.username
springSecurityService?.reauthenticate(user?.email) //modified user np: test#app.com
springSecurityService?.reauthenticate(orginalUser) //admin np: admin#app.com
sessionRegistry.getAllPrincipals()?.each { princ ->
sessionRegistry.getAllSessions(princ, false);
if(princ?.username?.equals(user?.email)) { //killing sessions only for user (test#app.com)
sessionRegistry.getAllSessions(princ, false)?.each { sess ->
sess.expireNow()
}
}//if
}//each
}//expireSession
Namely sessionRegistry really gets active sessions for each user, but by calling:
sess.expireNow()
The result is that calling expireSession (user) for the same user again, the session is no longer visible. Which is understandable because it has expired.
But in spite of expired user session. He may continue to work in the application. The application does not log you off
In my grails app I have customized the post authorization workflow by writing a custom auth success handler (in resources.groovy) as shown below.
authenticationSuccessHandler (MyAuthSuccessHandler) {
def conf = SpringSecurityUtils.securityConfig
requestCache = ref('requestCache')
defaultTargetUrl = conf.successHandler.defaultTargetUrl
alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
targetUrlParameter = conf.successHandler.targetUrlParameter
useReferer = conf.successHandler.useReferer
redirectStrategy = ref('redirectStrategy')
superAdminUrl = "/admin/processSuperAdminLogin"
adminUrl = "/admin/processAdminLogin"
userUrl = "/admin/processUserLogin"
}
As you can from the last three lines in the closure above, depending on the Role granted to the logging in User I am redirecting her to separate actions within the AdminController where a custom UserSessionBean is created and stored in the session.
It works fine for a regular login case which in my app is like so:
User comes to the app via either http://localhost:8080/my-app/ OR http://localhost:8080/my-app/login/auth
She enters her valid login id and password and proceeds.
The app internally accesses MyAuthSuccessHandler which redirects to AdminController considering the Role granted to this User.
The UserSessionBean is created and stored it in the session
User is taken to the app home page
I have also written a custom MyUserDetailsService by extending GormUserDetailsService which is correctly accessed in the above flow.
PROBLEM SCENARIO:
Consider a user directly accessing a protected resource (in this case the controller is secured with #Secured annotation) within the app.
User clicks http://localhost:8080/my-app/inbox/index
App redirects her to http://localhost:8080/my-app/login/auth
User enters her valid login id and password
User is taken to http://localhost:8080/my-app/inbox/index
The MyAuthSuccessHandler is skipped entirely in this process and hence my UserSessionBean is not created leading to errors upon further use in places where the UserSessionBean is accessed.
QUESTIONS:
In the problem scenario, does the app skip the MyAuthSuccessHandler because there is a target URL for it to redirect to upon login?
Can we force the process to always pass through MyAuthSuccessHandler even with the target URL present?
If the answer to 2 is no, is there an alternative as to how and where the UserSessionBean can still be created?
You can implement a customized eventListener to handle the post-login process, without disrupting the original user requested url.
In config.groovy, insert a config item:
grails.plugins.springsecurity.useSecurityEventListener = true
In you resources.groovy, add a bean like this:
import com.yourapp.auth.LoginEventListener
beans = {
loginEventListener(LoginEventListener)
}
And create a eventListener in src/groovy like this:
package com.yourapp.auth
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
import org.springframework.web.context.request.RequestContextHolder as RCH
class LoginEventListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
//deal with successful login
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
User.withTransaction {
def user = User.findByUsername(event.authentication.principal.username)
def adminRole = Role.findByAuthority('ROLE_ADMIN')
def userRole = Role.findByAuthority('ROLE_USER')
def session = RCH.currentRequestAttributes().session //get httpSession
session.user = user
if(user.authorities.contains(adminRole)){
processAdminLogin()
}
else if(user.authorities.contains(userRole)){
processUserLogin()
}
}
}
private void processAdminLogin(){ //move admin/processAdminLogin here
.....
}
private void processUserLogin(){ //move admin/processUserLogin here
.....
}
}
Done.
1) Yes, because it is an "on-demand" log in.
2) Yes, you can set it to always use default. The spring security plugin has a setting for it "successHandler.alwaysUseDefault" change that to true it defaults to false.
Also if you need more details check out the spring docs look for the Setting a Default Post-Login Destination section.
3) If you want to still create the user session bean and then redirect to the original URL you have two options create the bean in an earlier filter or expose the needed data via a custom UserDetailsService. Personally I would go the route of a custom details service.