Is there any way, with springSecurityService, to log someone else out?
(For example, if a regular user leaves for the day and forgets to logout, and the manager wants to log their account out.)
I have done in my application where , A admin User can logged-out forcefully any user from the list of all users currently logged-in into the system.
I get all users those are currently logged-in into the system and send to the jsp where list of all logged-in users are shown to the Admin user.
#PreAuthorize("hasRole('Currently_Logged-In_Users')")
#RequestMapping(value = "/getLoggedInUsers", method = RequestMethod.POST)
#ResponseBody
public Map<String, List<?>> getLoggedInUsers(Map<String, Object> map ,Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String userName = auth.getName();
List<Object> principals = sessionRegistry.getAllPrincipals();
List<UserInfo> usersInfoList = new ArrayList<UserInfo>();
for (Object principal: principals) {
if (principal instanceof UserInfo) {
if(!((UserInfo) principal).getUsername().equals(userName)){
for(SessionInformation sess :sessionRegistry.getAllSessions(principal, false)){
if(!sess.isExpired()){
usersInfoList.add((UserInfo) sess.getPrincipal());
}
}
}
}
}
Map<String, List<?>> loggedInUserMap = new HashMap<String, List<?>>();
loggedInUserMap.put("loggenInUsers",
usersInfoList);
return loggedInUserMap;
}
Now Admin user can select any user or multiple user by clicking on check box against the users. and call forced Logged-out action.
#PreAuthorize("hasRole('Currently_Logged-In_Users')")
#RequestMapping(value = "/logoutSelectedUsers", method = RequestMethod.POST)
#ResponseBody
public Map<String, String> forcedLoggedOut(#RequestParam("userList[]")ArrayList<String> userList ,Model model ,HttpServletRequest request ) {
Map<String,String> map= new HashMap<String,String>();
try{
String successMessage =null;
List<String> userCodeList = new ArrayList<String>();
for(String userCode :userList ){
userCodeList.add(userCode);
}
List<Object> principals = sessionRegistry.getAllPrincipals();
for (Object principal: principals) {
if (principal instanceof UserInfo) {
if(userCodeList.contains(((UserInfo) principal).getUsername())){
for(SessionInformation sess :sessionRegistry.getAllSessions(principal, false)){
sess.expireNow();
successMessage = "msg.loggedOutSuccessfully";
}
}
}
}
map.put("successmsg", successMessage);
}
catch(Exception e){
map.put("failmsg", "msg.notLoggedOut");
logger.error(e.toString(),e);
}
return map;
}
The springSecurityService itself does not have this capability.
However, nothing is stopping you from creating your own ServletFilter to track session ids and security principals and expose a controller and pages to invalidate the associated session with a login.
Here's how I do it.
Edit: The example below uses the webxml plugin. You can also edit web.xml directly. See this answer for setting the timeout.
// src/groovy/com/example/ExpiringSessionEventListener.groovy:
package com.example
import grails.util.Holders
import javax.servlet.http.HttpSessionListener
import javax.servlet.http.HttpSessionEvent
import org.springframework.security.core.context.SecurityContext
public class ExpiringSessionEventListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
// Do some logging
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
SecurityContext securityContext = event.session.getAttribute("SPRING_SECURITY_CONTEXT")
if (securityContext) {
UserService userService = Holders.applicationContext.getBean("userService")
String userName = securityContext.authentication.principal.username
userService.userLoggedOut(userName, event.session.id, Boolean.TRUE)
}
}
}
// grails-app/services/com/example/UserService.groovy:
package com.example
import grails.plugin.springsecurity.annotation.Secured
class UserService {
#Secured(["ROLE_USER"])
def userLoggedOut(String userName, String sessionId, Boolean expired) {
User user = User.findByUsername(userName)
if (expired) {
// Do user cleanup stuff after expired session
} else {
// Do user cleanup stuff after clicking the logout button
}
}
}
Edit:
// grails-app/conf/WebXmlConfig.groovy:
webxml {
sessionConfig.sessionTimeout = 10 // minutes
listener.add = true
listener.classNames = [
"com.example.ExpiringSessionEventListener"
]
}
Related
I'm writing Grails application with Spring Security plugin.
I have enabled queries generated by GORM into console and I have noticed that every request Security query the database about users, selecting them by it's username.
My intention is load user's by it's ID's, not usernames for increase performance.
I am aware that there is a possibility to overwrite UserDetailsService method loadUserByUsername(String username), but this method is used both for refreshing user's credential during the session and in login form, where in fact I want to authenticate user by it's username.
I have three questions:
How to load user by id? Should I inject user ID instead of username in GrailsUser (implementation of UserDetails) instead of regular username and use long selectById = Long.valueOf(String username)?
How to create different user provider for refreshing session to grab user by ID and different for user login (when I want to grab user by it's username/email)?
There is possibility to fetch user credentials not every request, but every X seconds?
Finally i managed to solve this problem. The queries are generated by:
springSecurityService.getCurrentUser()
Unfortunatelly, this method fetches User model class by username (from Principal object) and maps it into database field, at most configured by:
grails.plugin.springsecurity.userLookup.usernamePropertyName
as mentioned in documentation.
I have tried
grails.plugin.springsecurity.userLookup.usernamePropertyName = 'id'
but i received class-cast exception from String to Long.
Workaround is simple - create own Principle with username field typed as Long.
See PrincipalProxy in my solution:
package com.selly.util.security
import java.security.Principal;
import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
class AppMetadataAuthenticationToken implements Authentication, Principal {
private boolean authenticated
private GrailsUser userDetails
private Principal principal
public AppMetadataAuthenticationToken(GrailsUser userDetails) {
this.userDetails = userDetails
this.principal = new PrincipalProxy(userDetails)
}
public GrailsUser getUser() {
return userDetails
}
public String getUsername() {
return userDetails.getUsername()
}
#Override
public String getName() {
return userDetails.getUsername()
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return userDetails.getAuthorities()
}
#Override
public Object getCredentials() {
return userDetails.password
}
#Override
public Object getDetails() {
return getUser()
}
#Override
public Object getPrincipal() {
return principal
}
#Override
public boolean isAuthenticated() {
return authenticated
}
#Override
public void setAuthenticated(boolean authenticated) throws IllegalArgumentException {
this.authenticated = authenticated
}
static class PrincipalProxy implements Principal {
GrailsUser grailsUser
Long username
public PrincipalProxy(GrailsUser grailsUser) {
this.grailsUser = grailsUser
this.username = grailsUser.id
}
#Override
public String getName() {
return grailsUser.id
}
}
}
To return this Token, just register your own AuthenticationProvider:
package com.selly.util.security;
import grails.plugin.springsecurity.SpringSecurityService
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
public class AppUsernamePasswordAuthenticationProvider extends DaoAuthenticationProvider implements AuthenticationProvider {
SpringSecurityService springSecurityService
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
def token = (UsernamePasswordAuthenticationToken) authentication
def user = userDetailsService.loadUserByUsername(authentication.principal)
if(!user)
throw new UsernameNotFoundException("Cannot find user", authentication.principal)
if(!passwordEncoder.isPasswordValid(user.password, authentication.credentials, null))
throw new BadCredentialsException("Invalid password")
return new AppMetadataAuthenticationToken(user)
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
package com.selly.util.security;
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
public class AppMetadataAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// TODO Auto-generated method stub
return authentication;
}
#Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return AppMetadataAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Register it in resources.groovy
appUsernamePasswordAuthenticationProvider(AppUsernamePasswordAuthenticationProvider) {
userDetailsService = ref('userDetailsService')
passwordEncoder = ref('passwordEncoder')
userCache = ref('userCache')
saltSource = ref('saltSource')
preAuthenticationChecks = ref('preAuthenticationChecks')
postAuthenticationChecks = ref('postAuthenticationChecks')
springSecurityService = ref('springSecurityService')
}
And in Config.groovy:
grails.plugin.springsecurity.providerNames = [
'appMetadataAuthenticationProvider',
'appUsernamePasswordAuthenticationProvider',
// 'daoAuthenticationProvider',
// 'anonymousAuthenticationProvider',
// 'rememberMeAuthenticationProvider'
]
Now all works perfectly:
Hibernate: select this_.id as id13_0_, this_.account_expired as account2_13_0_, this_.account_locked as account3_13_0_, this_.enabled as enabled13_0_, this_."password" as password5_13_0_, this_.password_expired as password6_13_0_, this_.username as username13_0_, this_.workspace as workspace13_0_ from users this_ where (**this_.id=?**) limit ?
Instead of using getCurrentUser() you can also getPrincipal() and cast to your previously populated object with more data than Principal interface offers.
I'm using spring-security and wish to retrieve all users and all groups to be stored in a reference table so I can quickly look up users without having to consult the LDAP directory. I have created a LdapAuthoritiesPopulator implementation mirroring DefaultLdapAuthoritiesPopulator with the following additional method:
public final Collection<GrantedAuthority> getAllAuthorities() {
if (groupSearchBase == null) {
return new HashSet<>();
}
Set<GrantedAuthority> authorities = new HashSet<>();
Set<String> roles = ldapTemplate.searchForSingleAttributeValues(
groupSearchBase,
allAuthorityFilter,
new String[0],
groupRoleAttribute);
for (String role : roles) {
if (convertToUpperCase) {
role = role.toUpperCase();
}
authorities.add(new SimpleGrantedAuthority(rolePrefix + role));
}
return authorities;
}
This now allows me to retrieve all groups, allAuthorityFilter is a property defaulting to (&(objectClass=group)(objectCategory=group)).
I am now trying to achieve the same thing with the users by creating a custom LdapUserSearch based of of FilterBasedLdapUserSearch with the following additional method:
public List<String> findAllUsers() {
SpringSecurityLdapTemplate template
= new SpringSecurityLdapTemplate(contextSource);
template.setSearchControls(searchControls);
List<String> r = template.search(searchBase,
allUsersFilter,
new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes atrbts)
throws NamingException {
return (String) atrbts.get(userNameAttribute).get();
}
});
return r;
}
There are two problems I have with this:
If the user-list is large I get javax.naming.SizeLimitExceededException which I do not know how to resolve.
I want this method to return something like DirContextOperations similar to how searchForUser(String) works so that my LdapUserDetailsMapper implementation can be reused to return all user properties.
I'm finding the documentation for LdapTemplate a little hairy and having trouble finding the answers I'm after, any assistance would be greatly appreciated.
UPDATE: I have solved point (2) above by
public List<UserDetails> getAllUserDetails(boolean includeAuthorities) {
List<UserDetails> r = new ArrayList<>();
for (DirContextOperations ctx : userSearch.findAllUserOperations()) {
try {
Attribute att = ctx.getAttributes().get(usernameAttribute);
String username = (String) att.get();
r.add(userMapper.mapUserFromContext(
ctx,
username,
includeAuthorities
? authPop.getGrantedAuthorities(ctx, username)
: Collections.<GrantedAuthority>emptySet()));
} catch (NamingException ex) {
LOG.warn("Username attribute " + usernameAttribute + " not found!");
}
}
return r;
}
In my UserSearch implementation I have:
public List<DirContextOperations> findAllUserOperations() {
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
template.setSearchControls(searchControls);
return template.search(searchBase,
allUsersFilter, new ContextMapper() {
#Override
public Object mapFromContext(Object o) {
return (DirContextOperations) o;
}
});
}
However I have not solved point #1. If I need to batch this somehow then that is fine as long as there is a way to tell LdapTemplate where to resume on subsequent calls.
I have a class :
#SessionScoped
public class LoggedUser {
private User user;
...
}
that I use to keep track if a user is logged in my application.
In my Struts2 application I have a Interceptor to check if the user is logged, if not he's forwarded to the login page.
public class LoggedUserInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 2822434409188961460L;
#Inject
private LoggedUser loggedUser;
#Override
public String intercept(ActionInvocation invocation) throws Exception {
if(loggedUser==null || !loggedUser.isLogged()){
return "login";
}
return invocation.invoke();
}
}
The problem occur when the session timeout. The object LoggdeUser is never null or deleted. I have always the last instance.
I added A session listener.
public class SessionListener implements HttpSessionListener {
private static Logger logger = Logger.getLogger(SessionListener.class.getName());
#Override
public void sessionCreated(HttpSessionEvent event) {
logger.info("sessionCreated = " + event.getSession().getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
logger.info("sessionDestroyed = " + event.getSession().getId());
}
}
I see that sessionDestroyed is called, but when I enter again in my Interceptor.. LoggedUser is never recreated for a new session.
why ?
my Struts2 Action for the login is this
public class LoginUserAction extends ActionSupport implements ModelDriven<LoggedUser>, ServletRequestAware {
...
#Inject
private LoggedUser loggedUser;
public String execute() throws Exception {
...
loggerUser.setLoggedTime(now);
...
return SUCCESS;
}
I add that too in web.xml
session-config
session-timeout 2 /session-timeout
/session-config
I don't know anything about Struts2, but my guess is that the interceptor's scope is wider than session scope. In other words, the interceptor instance is kept around longer than the session. Guice can't and won't set an injected field to null when the session ends, nor will it ever re-inject an object automatically.
What you need to do if you use an object with a shorter lifecycle inside an object with a longer lifecycle (such as a RequestScoped object inside a SessionScoped object or a SessionScoped object inside a singleton) is inject a Provider for the shorter lived object.
In your case, I think this is probably what you need:
public class LoggedUserInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 2822434409188961460L;
#Inject
private Provider<LoggedUser> loggedUserProvider;
#Override
public String intercept(ActionInvocation invocation) throws Exception {
LoggedUser loggedUser = loggedUserProvider.get();
if(loggedUser==null || !loggedUser.isLogged()){
return "login";
}
return invocation.invoke();
}
}
I don't know guice but the session is available to the interceptor and can be made readily available to the action via SessionAware :
ActionInvocation provides access to the session. The following is part of a "Authentication" interceptor. If there is not a "User" object then Action.LOGIN is returned.
public String intercept(ActionInvocation invocation) throws Exception {
Map session = invocation.getInvocationContext().getSession();
appLayer.User user = (appLayer.User) session.get("User");
if (user == null){
return Action.LOGIN;
}
return invocation.invoke();
}
I only place the user object on the session when the user logs in.
In the login action I place the user object on the session:
public class Login extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String userName;
private String password;
public String execute() {
//use DB authentication once this works
//if ("ken".equalsIgnoreCase(userName) && "ken".equalsIgnoreCase(password)){
try {
User user = new User(userName, password);
session.put("User", user);
return ActionSupport.SUCCESS;
} catch (Exception e) {
System.out.println("There was an exception" + e.getMessage());
return ActionSupport.LOGIN;
}
}
public void validate() {
String user = this.getUserName();
String pass = this.getPassword();
//This block is a bit redundant but I couldn't figure out how
//to get just the hibernate part to report an exception on Username/pass
if (!StringUtils.isBlank(this.getUserName()) && !StringUtils.isBlank(this.getPassword())) {
try {
Class.forName("com.ibm.as400.access.AS400JDBCDriver").newInstance();
String url = "jdbc:as400://192.168.1.14";
DriverManager.getConnection(url, user, pass).close();
} catch (Exception e) {
addFieldError("login.error.authenticate", "Bad Username / Password");
}
}
if (StringUtils.isBlank(getUserName())) {
addFieldError("login.error.name", "Missing User Name");
}
if (StringUtils.isBlank(getPassword())) {
addFieldError("login.error.password", "Missing Password");
}
//else both are blank don't report an error at this time
}
... getters/setters....
}//end class
If I remember correctly this comes from at least in part from "Struts2 in Action". Anyways I'm a big believer in DI but since the Session is pretty accessible from the interceptors and the action I don't bother.
the easiest way to do that is finally to put the token in the session at the login and check it with an Interceptor
#Override
public String intercept(ActionInvocation invocation) throws Exception {
loggedUser = (LoggedUser) invocation.getInvocationContext().getSession().get(LoggedUser.SESSIONTOKEN);
if(loggedUser==null || !loggedUser.isLogged()){
logger.info("loggedUser not present in session");
return "login";
}
return invocation.invoke();
}
in the Action
request.getSession().setAttribute(LoggedUser.SESSIONTOKEN, loggedUser);
I have a MVC app where I have a User class and the user can also impersonate another user(Admin users only).
So I have this code below that authenticates the request and instantiates my version of a User class.
It then tries to get the impersonated user from the Session object but Session is not available in this method in the global.asax.
Hope this makes sense.
How else could I do this?
My question I guess is at what point in the global.asax methods do you get access to Session object for each request?
protected void Application_OnAuthenticateRequest(object sender, EventArgs e)
{
IMylesterService service = ObjectFactory.GetInstance<IMylesterService>();
if (Context.User != null)
{
if (Context.User.Identity.IsAuthenticated)
{
User user = service.GetUser(Context.User.Identity.Name);
if (user == null)
throw new ApplicationException("Context.user.Identity.name is not a recognized user");
User impersonatedUser = (User)this.Session["ImpersonatedUser"];
if (impersonatedUser == null)
user.ImpersonatedUser = user;
else
user.ImpersonatedUser = impersonatedUser;
System.Threading.Thread.CurrentPrincipal = Context.User = user;
return;
}
}
User guest = service.GetGuestUser();
guest.ImpersonatedUser = guest;
System.Threading.Thread.CurrentPrincipal = Context.User = guest;
}
Try creating an authorization filter:
public class CustomAuthorizationFilter : AuthorizeAttribute
{
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
// perform your authorization here
// full access to HttpContext and session
}
}
You can then apply this attribute to your controllers. Ideally you'd have a base controller that all other controllers inherit from and you could apply the attribute at the class level on that controller. Then all of your requests would be authorized and apply the impersonation as you have coded above.
Session will not be available during AuthenticateRequest: What you will need to do is tag the required information to the Identity.userData; so for example if you where using forms authentication do the following:
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Context.User != null)
{
if (Context.User.Identity.IsAuthenticated)
{
// retrieve the value
var id = (FormsIdentity)Context.User.Identity;
var myvalue = id.Ticket.UserData; // "Here you are"
}
}
}
For sign in using forms you will need to write a custom cookie:
MVC -> class FormsAuthenticationService : IFormsAuthenticationService
public static void SetAuthenticationCookie(HttpContextBase context, FormsAuthenticationTicket ticket)
{
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName)
{
Value = FormsAuthentication.Encrypt(ticket),
Secure = FormsAuthentication.RequireSSL,
Domain = FormsAuthentication.CookieDomain,
HttpOnly = true,
Expires = DateTime.Now.AddMinutes(15)
};
if (!context.Request.IsSecureConnection && FormsAuthentication.RequireSSL)
{
throw new HttpException("Ticket requires SSL.");
}
context.Response.Cookies.Add(cookie);
}
public static FormsAuthenticationTicket CreateTicket(HttpContextBase context, string emailAddress, string userData, bool persist)
{
return new FormsAuthenticationTicket(1, emailAddress, DateTime.Now, DateTime.Now.AddMinutes(15), persist, userData, FormsAuthentication.FormsCookiePath);
}
Finally in SignIn you would now create the required ticket by calling CreateTicket(...), and then you would write it out by SetAuthenticationCookie(...).
public void SignIn(string userName, string password)
{
if(CheckUserValid(userName,password, out string email))
{
var ticket = CreateTicket(email, "Here you are", true);
SetAuthenticationCookie(HttpContext.Current.Base(), ticket);
}
}
I have never used it, but assume that session state is assigned during the AcquireRequestState:
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}
I had this same issue of needing to access session in global.asax and finally solved it by moving my code into the AcquireRequestState handler, which happens after the authentication is passed.
protected void Application_AcquireRequestState(Object sender, EventArgs e)
{
if (Request.IsAuthenticated && Context.Session != null)
{
// access Context.Session
}
}
This fires a lot and the current context does not always have a valid session object, hence the check.
EDIT: Had to add the check for IsAuthenticated too -- was getting a null error when logged out. Works great now.
i give users special URL with access key in it. users accessing the public page via this special url should be able to see some additional data as compared to simple anonymous user.
i want to give some additional role to anonymous user based on parameters provided in request so i can do something like this in my template:
<#sec.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER, ROLE_INVITED_VISITOR">
...some additional stuff for invited user to see
</#sec.authorize>
currently i'm implementing Spring's OncePerRequestfilter:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if (null != request.getParameter("accessKey")) {
if(isValid(request.getParameter("accessKey"))) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//how do i add additional roles to authenticated (potentially anonymous) user?
}
}
}
Why not just create a wrapper class that delegates to the original, but adds on a couple of extra GrantedAuthorities:
public class AuthenticationWrapper implements Authentication
{
private Authentication original;
private GrantedAuthority[] extraRoles;
public AuthenticationWrapper( Authentication original, GrantedAuthority[] extraRoles )
{
this.original = original;
this.extraRoles = extraRoles;
}
public GrantedAuthority[] getAuthorities()
{
GrantedAuthority[] originalRoles = original.getAuthorities();
GrantedAuthority[] roles = new GrantedAuthority[originalRoles.length + extraRoles.length];
System.arraycopy( originalRoles, 0, roles, 0, originalRoles.length );
System.arraycopy( extraRoles, 0, roles, originalRoles.length, extraRoles.length );
return roles;
}
public String getName() { return original.getName(); }
public Object getCredentials() { return original.getCredentials(); }
public Object getDetails() { return original.getDetails(); }
public Object getPrincipal() { return original.getPrincipal(); }
public boolean isAuthenticated() { return original.isAuthenticated(); }
public void setAuthenticated( boolean isAuthenticated ) throws IllegalArgumentException
{
original.setAuthenticated( isAuthenticated );
}
}
and then do this in your filter:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
GrantedAuthority extraRoles = new GrantedAuthority[2];
extraRoles[0] = new GrantedAuthorityImpl( "Role X" );
extraRoles[1] = new GrantedAuthorityImpl( "Role Y" );
AuthenticationWrapper wrapper = new AuthenticationWrapper( auth, extraRoles );
SecurityContextHolder.getContext().setAuthentication( wrapper );
The Authentication is now replaced by your version with the extra roles. NB You may have to handle the case where the Authentication has not yet been authenticated and so its getAuthorities() returns null. (The wrapper implementation currently assumes that it will always get a non-null array from its wrapped Authentication)