Derive tenant id for signed in user in OAuth2 based multi-tenant Spring Boot application using Spring Security - spring

I am working on a Spring Boot B2B application, where I would like to onboard a considerable number of tenants. Each of the tenants has its own authentication providers. I have decided to support only OAuth2-based authentication.
As of now, my application has been single-tenant, that's why I did not have the need to derive tenant id for any user. But with multi-tenancy, I need to serve the resources based on the tenant, the user belongs, what role the user has for the given tenant, etc. In other words, each and every flow in my application is going to be dependent on the tenant id information of a user.
In order to achieve the same, I have added tenantId column to the existing User entity as follows:
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String id;
#NotNull
private String login;
#Column(name = "first_name", length = 50)
private String firstName;
#Column(name = "last_name", length = 50)
private String lastName;
#Email
#Column(length = 254, unique = true)
private String email;
...
#Column(length = 254, unique = true)
private String tenantId;
...
}
Now, I am deriving and capturing tenant id information for a given user at the time of signup to my application as follows:
User getUser(AbstractAuthenticationToken authToken) { <---THIS FUNCTION GETS CALLED WHILE USER SIGNS UP FOR THE APPLICATION
Map<String, Object> attributes;
User user = null;
if (authToken instanceof OAuth2AuthenticationToken) {
attributes = ((OAuth2AuthenticationToken) authToken).getPrincipal().getAttributes();
} else if (authToken instanceof JwtAuthenticationToken) {
attributes = ((JwtAuthenticationToken) authToken).getTokenAttributes();
} else {
throw new IllegalArgumentException("AuthenticationToken is not OAuth2 or JWT!");
}
if (user == null) {
user = getUser(attributes);
}
return Pair.of(user, attributes);
}
private static User getUser(Map<String, Object> details) {
User user = new User();
if (details.get("uid") != null) {
user.setId((String) details.get("uid"));
user.setLogin((String) details.get("sub"));
} else if (details.get("user_id") != null) {
user.setId((String) details.get("user_id"));
} else {
user.setId((String) details.get("sub"));
}
if (details.get("email") != null) {
user.setEmail(((String) details.get("email")).toLowerCase());
} else {
user.setEmail((String) details.get("sub"));
}
user.setTenantId(getTenantIdFromEmail(user.getEmail()));
...
}
private getTenantIdFromEmail(String email) {
String subDomain = getSubDomain(email);
return tenantRepository.findBySubDomainName(subDomain).getTenantId();
}
Now, whenever I need tenant id information for a signed-in user, I can do like below:
public String getTenantId() {
Optional<User> signedInUserOptional =
userRepository.findByLogin(SecurityUtils.getCurrentUserLogin();
return signedInUserOptional.isPresent() ? signedInUserOptional.get().getTenantId() : null;
}
Here, I have two questions as follows:
I am not sure if the code to derive tenant id would work correctly across different Ouath2 authentication providers of multiple tenants, if not, could anyone please help here? (by different authentication providers, I meant, each of the tenants may have their own internal authentication providers)
In almost each application flow (for example, doing any CRUD operation on a resource), I have to derive tenant id information as mentioned above. Is there any way to make this a little efficient i.e. compute it once for a signed-in user and then pass it across the entire application flow for the user session?

Consider using ThreadLocal variables and set this during successful authentication ex:OAuthFilter in your case.
public class TenantIdContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setCurrentTenant(String tenantId) {
contextHolder.set(tenantId);
}
public static String getCurrentTenant() {
return contextHolder.get();
}
public static void clear() {
contextHolder.remove();
}
}
Usage
private static User getUser(Map<String, Object> details) {
User user = new User();
if (details.get("uid") != null) {
user.setId((String) details.get("uid"));
user.setLogin((String) details.get("sub"));
} else if (details.get("user_id") != null) {
user.setId((String) details.get("user_id"));
} else {
user.setId((String) details.get("sub"));
}
if (details.get("email") != null) {
user.setEmail(((String) details.get("email")).toLowerCase());
} else {
user.setEmail((String) details.get("sub"));
}
TenantIdContextHolder.setCurrentTenant(getTenantIdFromEmail(user.getEmail()));
...
}
Getting current tenant anytime
String currentTenant = TenantIdContextHolder.getCurrentTenant();

Related

Spring boot application not accepting ID of incoming POST request

I have an existing system that uses string based unique IDs for users and I want to transfer that System into a Spring boot application. I want to creat a user so I send a POST request with the following content:
As you can see, the id gets ignored.
This is my Spring code for the user class:
#PostMapping("/user")
ResponseEntity addUser(User receivedUser) {
Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
logger.info("Empfangener User: " + receivedUser.toString());
try {
User mailCheckUser = userService.getUserByMail(receivedUser.getEmail());
User nameCheckUser = userService.getUserByName(receivedUser.getUsername());
if (mailCheckUser != null){
return new ResponseEntity("Email already exists", HttpStatus.NOT_ACCEPTABLE);
}
if (nameCheckUser != null){
return new ResponseEntity("Username already exists", HttpStatus.NOT_ACCEPTABLE);
}
userService.addUser(receivedUser);
} catch (Exception userCreationError) {
return new ResponseEntity(receivedUser, HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity(receivedUser, HttpStatus.OK);
}
public void addUser(User user) {
userRepository.save(user);
}
And this is my user class:
#Entity
#Table
public class User {
#Id
#Column(unique =true)
private String id;
private #Column(unique =true)
String username;
private #Column(unique =true)
String email;
private #Column(unique =true)
String simpleAuthToken;
private
String password;
/*REDACTED*/
private
boolean isBlocked;
public User(String id, String name, String email, boolean isBlocked) {
this.id = id;
this.username = name;
this.email = email;
this.simpleAuthToken = simpleAuthToken;
this.isBlocked = false;
}
public User() {
}
/*GETTERS AND SETTERS ARE HERE, BUT I CUT THEM FOR SPACING REASONS*/
}
And this is the Spring Output:
My expected outcome would be that Spring would recognize the id and then create a user with the id I provided. Why is the id always null?
EDIT: If I put the ID in a Put or Get Mapping as Path variable, like so:
#PutMapping("/user/{id}")
ResponseEntity updateUser(#PathVariable String id, User receivedUser) {}
then it gets read and recognized, but it will still be null in the receivedUser
First add #RequestBody in the post request body. In the Post request (/test/user) your passing some params but in the method level not received.
If you want receive id from postman then add #RequestParam("id")String id in the method level.
How you generating unique Id by manually or some generators?
And double check user id at the database console level.

Contact Provider using View Model and Live Data

My android app is using contacts providers to display all the contacts to the user. I'm using Loaders to load the contacts by following the tutorial/documentation at https://developer.android.com/training/contacts-provider/retrieve-names
But from the link https://developer.android.com/guide/components/loaders, it is mentioned that loaders are deprecated as of Android P.
Loaders have been deprecated as of Android P (API 28). The recommended
option for dealing with loading data while handling the Activity and
Fragment lifecycles is to use a combination of ViewModels and
LiveData. ViewModels survive configuration changes like Loaders but
with less boilerplate. LiveData provides a lifecycle-aware way of
loading data that you can reuse in multiple ViewModels. You can also
combine LiveData using MediatorLiveData, and any observable queries,
such as those from a Room database, can be used to observe changes to
the data. ViewModels and LiveData are also available in situations
where you do not have access to the LoaderManager, such as in a
Service. Using the two in tandem provides an easy way to access the
data your app needs without having to deal with the UI lifecycle. To
learn more about LiveData see the LiveData guide and to learn more
about ViewModels see the ViewModel guide.
So my question is:
1. How can we fetch the contacts using android view Model and live data from contact providers?
2. Can we use Room database for contact providers?
Below you can find the link to the source code where I tried to use the Android View Model and Live data to fetch the contacts from ContactProviders.
https://github.com/deepak786/phonebook-contacts
3. What can be improved in the above source code so that fetching will be faster?
Thanks & Regards
Deepak
Below you can find a very simple solution for loading contacts using MVVM:
https://github.com/NaarGes/Android-Contact-List
Here comes a bit of code in case the link is no longer working.
First, let's create a simple POJO for contacts UserObject.java
public class UserObject {
private String email, name, phone;
public UserObject() {
// EMPTY CONSTRUCTOR FOR FIREBASE REALTIME DATABASE
}
public UserObject(String email, String name, String phone) {
this.email = email;
this.name = name;
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
Now, let's create our repository ContactRepository.java
public class ContactRepository {
private Context context;
private static final String TAG = "debinf ContRepo";
public ContactRepository(Context context) {
this.context = context;
}
public List<UserObject> fetchContacts() {
List<UserObject> contacts = new ArrayList<>();
Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
Log.i(TAG, "fetchContacts: cursor.getCount() is "+cursor.getCount());
if ((cursor != null ? cursor.getCount() : 0) > 0) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String phone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
UserObject contact = new UserObject("",name, phone);
Log.i(TAG, "fetchContacts: phone is "+phone);
contacts.add(contact);
}
}
if (cursor != null) {
cursor.close();
}
return contacts;
}
}
Next, we create our ContactViewModel.java
public class ContactViewModel extends ViewModel {
private ContactRepository repository;
private MutableLiveData<List<UserObject>> contacts;
public ContactViewModel(Context context) {
repository = new ContactRepository(context);
contacts = new MutableLiveData<>();
}
public MutableLiveData<List<UserObject>> getContacts() {
contacts.setValue(repository.fetchContacts());
return contacts;
}
}
Next, we create a factory for our ViewModel ContactViewModelFactory.java
public class ContactViewModelFactory implements ViewModelProvider.Factory {
private Context context;
public ContactViewModelFactory(Context context) {
this.context = context;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(ContactViewModel.class)) {
return (T) new ContactViewModel(context);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
Let's not forget to add permission in our AndroidManifest
<uses-permission android:name="android.permission.READ_CONTACTS" />
And finally, we ask for permission in our MainActivity.java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.WRITE_CONTACTS,Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST);
}
and bring our contacts to surface
ContactViewModelFactory factory = new ContactViewModelFactory(this);
viewModel = ViewModelProviders.of(this, factory).get(ContactViewModel.class);
viewModel.getContacts().observe(this, new Observer<List<UserObject>>() {
#Override
public void onChanged(#Nullable List<UserObject> userObjects) {
Log.i(TAG, "ViewModel: userObjects size is "+userObjects.size());
Log.i(TAG, "ViewModel: userObjects size is "+userObjects.get(1).getPhone());
}
});
class ContactsViewModel(private val contentResolver: ContentResolver) : ViewModel()
{
lateinit var contactsList: LiveData<PagedList<Contact>>
fun loadContacts() {
val config = PagedList.Config.Builder()
.setPageSize(20)
.setEnablePlaceholders(false)
.build()
contactsList = LivePagedListBuilder<Int, Contact>(
ContactsDataSourceFactory(contentResolver), config).build()
}
}
class ContactsDataSourceFactory(private val contentResolver: ContentResolver) :
DataSource.Factory<Int, Contact>() {
override fun create(): DataSource<Int, Contact> {
return ContactsDataSource(contentResolver)
}
}
class ContactsDataSource(private val contentResolver: ContentResolver) :
PositionalDataSource<Contact>() {
companion object {
private val PROJECTION = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
)
}
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Contact>) {
callback.onResult(getContacts(params.requestedLoadSize, params.requestedStartPosition), 0)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Contact>) {
callback.onResult(getContacts(params.loadSize, params.startPosition))
}
private fun getContacts(limit: Int, offset: Int): MutableList<Contact> {
val cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
PROJECTION,
null,
null,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY +
" ASC LIMIT " + limit + " OFFSET " + offset)
cursor.moveToFirst()
val contacts: MutableList<Contact> = mutableListOf()
while (!cursor.isAfterLast) {
val id = cursor.getLong(cursor.getColumnIndex(PROJECTION[0]))
val lookupKey = cursor.getString(cursor.getColumnIndex(PROJECTION[0]))
val name = cursor.getString(cursor.getColumnIndex(PROJECTION[2]))
contacts.add(Contact(id, lookupKey, name))
cursor.moveToNext()
}
cursor.close()
return contacts
}
}
Please find the full source code here.

Spring Rest Api Design For Following a User in Twitter Clone Appliction?

I Want to Design a Demo Twitter Clone Application where user can follow any other user . however i am doubting my rest api design . please suggest me am i right .
Can I pass followerId in url rather than passing it as requestbody as we already know followerId in Advance and server does not create followerId here ?
and if better option could be there like put/patch or any rest api design ?
Please suggest me better design if possible
Here JwtUser is Authenticated User
public class FollowerDto {
private Long followerId;
private boolean following;
public FollowerDto() {
}
public FollowerDto(Long followerId, boolean following) {
this.followerId = followerId;
this.following = following;
}
public boolean getFollowing() {
return following;
}
public void setFollowing(boolean following) {
this.following = following;
}
public Long getFollowerId() {
return followerId;
}
public void setFollowerId(Long followerId) {
this.followerId = followerId;
}
}
#PostMapping("/follower")
#ResponseStatus(HttpStatus.CREATED)
public StatusDto addFollower(#RequestBody #Valid final FollowerDto
followerDto, #CurrentUser final JwtUser user, final
HttpServletResponse response) {
RestPreconditions.checkRequestElementNotNull(followerDto);
RestPreconditions.checkArgumentCondition(followerDto.getFollowing());
return userService.addFollower(user, followerDto.getFollowerId(),
response);
}
// Service Layer
#Override
public StatusDto addFollower(final JwtUser jwtUser, final Long followerId, final HttpServletResponse response) {
final User follower = userRepository.findById(followerId).orElse(null);
ServicePreconditions.checkEntityExists(follower, "Follower does not exist with id " + followerId);
final User currentUser = userRepository.findByEmail(jwtUser.getEmail());
if (currentUser != null) {
ServicePreconditions.checkOKArgument(!currentUser.equals(follower));
final Set<User> existingFollowers = currentUser.getFollowers();
if (existingFollowers != null) {
existingFollowers.add(follower);
} else {
currentUser.setFollowers(Sets.<User>newHashSet(follower));
}
userRepository.save(currentUser);
final URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/{idOfNewResource}").buildAndExpand(follower.getId()).toUri();
response.setHeader(HttpHeaders.LOCATION, uri.toASCIIString());
return new StatusDto("Follower Added Successfully to user having email " + jwtUser.getEmail());
}
return new StatusDto("Follower is not Added to user with email " + jwtUser.getEmail());
}

Update User's first name and last name in principal

I am updating user's information like first name and last name and I am getting first name and last name in all the pages for welcome message.
I have two controllers one for ajax request mapping and the other for normal request mapping.
Normal request mapping controller have this method. In this controller all page navigation is present and some request mapping which are not ajax calls
private String getPrincipalDisplay() {
GreenBusUser user = null;
String userName = "";
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
user = (GreenBusUser) principal;
userName = user.getFirstName() + " " + user.getLastName();
} else {
userName = "";
}
return userName;
}
This is how I am getting the username on every page by return string of this function I am adding it in ModelMap object.
When I update user's information I am doing in ajax request mapping.
#RequestMapping(value = "/restify/updateUserData", method = RequestMethod.PUT, headers = "Accept=application/json")
public ServiceResponse forgotPassword(#RequestBody Object user)
{
//logger.debug("getting response");
return setDataPut("http://localhost:7020/forgotPassword",user);
}
user is an Object type which has json data. Now how do I retrieve data from object and update my first name and last name in principal.
This is my GreenBusUser class
public class GreenBusUser implements UserDetails
{
private static final long serialVersionUID = 1L;
private String username;
private String password;
private Collection<? extends GrantedAuthority> grantedAuthorities;
private String firstName;
private String lastName;
public GreenBusUser(String username,String password,Collection<? extends GrantedAuthority> authorities,String firstName, String lastName)
{
this.username = username;
this.password = password;
this.grantedAuthorities = authorities;
this.firstName=firstName;
this.lastName=lastName;
this.grantedAuthorities.stream().forEach(System.out::println);
}
public Collection<? extends GrantedAuthority> getAuthorities()
{
return grantedAuthorities;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getPassword()
{
return password;
}
public String getUsername()
{
return username;
}
public boolean isAccountNonExpired()
{
return true;
}
public boolean isAccountNonLocked()
{
return true;
}
public boolean isCredentialsNonExpired()
{
return true;
}
public boolean isEnabled()
{
return true;
}
}
UPDATE:::::
I have updated your code and applied some part of your answer into mine but still I ran into a problem
#RequestMapping(value="/updateUser",method=RequestMethod.GET)
public String updateUser(ModelMap model) {
UserInfo user = getUserObject();
GreenBusUser newGreenBususer = null;
List<User> list = new ArrayList<User>();
list = FetchDataService.fetchDataUser("http://localhost:8060/GetuserbyUserName?username=" + getPrincipal(), user.getUsername(), user.getPassword());
logger.debug("new user list ----->>>"+list.size());
User newuser=(User)list.get(0);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
SecurityContextHolder.getContext().getAuthentication().getPrincipal(), SecurityContextHolder.getContext().getAuthentication().getCredentials());
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
newGreenBususer=(GreenBusUser)principal;
logger.debug("newGreenBususerDetails---->>>"+newGreenBususer.toString());
newGreenBususer.setFirstName(newuser.getFirstName());
newGreenBususer.setLastName(newuser.getLastName());
if(newGreenBususer.getFirstName()!=null) {
logger.debug("got my first name");
}
if(newGreenBususer.getLastName()!=null) {
logger.debug("got my last name");
}
auth.setDetails(newGreenBususer);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
model.addAttribute("user", getPrincipalDisplay());
model.addAttribute("userData", list);
model.addAttribute("check", true);
return "GreenBus_updateProfile_User";
}
At first it sets the firstname and lastname to GreenBusUser and then there is setDetails method when I reload the page it says No user found when I am calling getUserObject() method at the top of this method.
private X2CUser getUserObject() {
X2CUser userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
userName = ((X2CUser) principal);
} else {
logger.info("No user found");
}
return userName;
}
If you are updating the password, then it will be good to logout the user and tell him to relogin.
Try this code .. It might help you.
UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(user, pass);
Authentication auth = authManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
securityContext.setAuthentication(auth);
I have finally resolved my problem though I have later added some code in my question part in UPDATE section.
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
newGreenBususer=(GreenBusUser)principal;
newGreenBususer.setFirstName(newuser.getFirstName());
newGreenBususer.setLastName(newuser.getLastName());
Yes that's all need to be done.
This part--->>
auth.setDetails(newGreenBususer);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
set new context making security pointing to null when I reload still not clear because I am setting the details before reload so its like I get new context but I have set the new user details.
Though I have finally resolved my problem but if anyone could shed some light why it was happening then I will accept his/her answer.
Thanks alot for your support. Keep Learning!

Overriding user injected by #CreatedBy Annotation in AuditorAware Implementation

Following is my implementation of AuditAware.
#Component
public class AuditorAwareImpl implements AuditorAware<String> {
#Log
private Logger logger;
#Override
public String getCurrentAuditor() {
String user = "SYSTEM";
try {
final Subject subject = SecurityUtils.getSubject();
if (subject != null) {
final Session session = subject.getSession(false);
if (session != null) {
final User userObj = (User) session.getAttribute("user");
if (userObj != null) {
user = userObj.getUsername();
}
}
}
} catch (final Exception e) {
this.logger.error("getCurrentAuditor", "No logged in user information found.", e);
}
return user;
}
}
As we know, The value of user returned by AuditorAwareImpl injected by Spring in attribute marked by #CreatedBy annotation as below.
#CreatedBy
#Column(name = "CREATED_BY", length = 150, nullable = false, updatable = false)
private String createdBy;
There are two problems I want to solve.
I am triggering a separate thread which saves
thousands of objects in database. If session times out in between, session object becomes null and AuditAwareImpl throws error and hence background process errors out.
I also want to run few quartz Schedular related jobs in which in which session object is itself not available. But I still want to inject some hard coded user in that case. But how do I do it?
One thing that I have tried at individual object level, using following code is,
#Transient
private String overRiddenUser;
#PrePersist
public void updateCreatedBy(){
if(overRiddenUser!=null){
this.createdBy=overRiddenUser;
}
}
I explicitly set overRiddenUser="Admin" whenever I want to override user injected by spring in createdBy and overwrite it using PrePersist method. But even this does not seem to work since before even I overwrite this value, spring tries to populate created by using value returned by AuditAware. If session is already expired by then the process errors outs! How do I solve this?
I found the solution to above problems as below.
Step 1
I defined a bean as below that contains a HashMap. This map will store current thread name as key and mapped username as value.
public class OverRiddenUser {
Map<String,String> userThreadMap= new HashMap<>();
//add getters/setters
}
Step 2
Initialised this bean on startup in class annoted by #Configuration.
#Bean("overRiddenUser")
public OverRiddenUser overRideUser(){
OverRiddenUser obj = new OverRiddenUser();
return obj;
// Singleton implementation can be done her if required
}
Step 3
Auto wired this bean in the impl class where I want to override session user.
#Autowired
OverRiddenUser overRiddenUser;
Assigned a random name to currently running thread.
RandomString randomThreadName = new RandomString(9);
Thread.currentThread().setName(randomThreadName.toString());
And then put required user name for current thread in hashmap as below.
try {
if(overRiddenUser!=null){
overRiddenUser.getUserThreadMap().put(Thread.currentThread().getName(),"My Over ridden username");
}
// Entire Database related code here
} catch (Exception e){
// handle the exceptions thrown by code
} finally {
//Perform clean up here
if(overRiddenUser!=null){
overRiddenUser.getUserThreadMap().remove(Thread.currentThread().getName());
}
}
}
Step 4
Then I modified AuditAwareImpl as below.
#Component
public class AuditorAwareImpl implements AuditorAware<String> {
#Log
private Logger logger;
#Autowired
OverRiddenUser overRiddenUser;
#Override
public String getCurrentAuditor() {
String user = "SYSTEM";
try {
if(overRiddenUser!=null && overRiddenUser.getUserThreadMap()!=null && overRiddenUser.getUserThreadMap().get(Thread.currentThread().getName())!=null){
user=overRiddenUser.getUserThreadMap().get(Thread.currentThread().getName());
}else{
final Subject subject = SecurityUtils.getSubject();
if (subject != null) {
final Session session = subject.getSession(false);
if (session != null) {
final User userObj = (User) session.getAttribute("user");
if (userObj != null) {
user = userObj.getUsername();
}
}
}
}
} catch (final Exception e) {
this.logger.error("getCurrentAuditor", "No logged in user information found.", e);
}
return user;
}
}
So if the currently running thread accesses AuditAwareImpl then it gets custom user name which was set for that thread. If this is null then it pulls username from session as it was doing earlier. Hope that helps.

Resources