Application scope in spring - java

Default scope for a bean in spring is singleton. However when I have next service defined:
#Service("usersLoggedInService")
public class UsersLoggedInServiceImpl implements UsersLoggedInService {
private Map<Long, String> OnlineUsers = new LinkedHashMap<Long, String>();
#Override
public Map<Long, String> getOnlineUsers() {
return OnlineUsers;
}
#Override
public void setOnlineUsers(Long id, String username) {
OnlineUsers.put(id, username);
}
#Override
public void removeLoggedOutUser(Long id){
if(!OnlineUsers.isEmpty() && OnlineUsers.size()>0)
OnlineUsers.remove(id);
}
}
and using it for login auditing so whenever new user logged in I am adding it to OnlineUsers LinkedHashMap in next way:
usersLoggedInService.setOnlineUsers(user.getId(), user.getUsername());
in one of my service classes. This works fine and I can see the users added in map.
But, when on log out I want to remove the user added in LinkedhashMap and when I check usersLoggedInService.getOnlineUsers() I could see that its empty. I don't understand why.
Logout handler definition:
<logout invalidate-session="true"
logout-url="/logout.htm"
success-handler-ref="myLogoutHandler"/>
And its implementation:
#Component
public class MyLogoutHandler extends SimpleUrlLogoutSuccessHandler {
#Resource(name = "usersLoggedInService")
UsersLoggedInService usersLoggedInService;
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
if (authentication != null) {
Object principal = authentication.getPrincipal();
if(principal instanceof User){
User user = (User) principal;
usersLoggedInService.removeLoggedOutUser(user.getId());
}
}
setDefaultTargetUrl("/login");
super.onLogoutSuccess(request, response, authentication);
}
}
Please let me know where the problem is. I don't expect this map to be empty.
-----Updated ------
When new users logged in then I can see all the users already added in LinkedHashmap. This method is inside one of the user service class:
#Service("userService")
public class UserServiceImpl implements UserService {
#Autowired
UsersLoggedInService usersLoggedInService;
#Override
public User getUserDetail() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
Object principal = auth.getPrincipal();
if(principal instanceof User){
User user = (User) principal;
usersLoggedInService.setOnlineUsers(user.getId(), user.getUsername());
return user;
}
return null;
}
}
when users logged in suppose two users logged in I get Map as {1=user1, 9=user2} but if any of the users logged out then inside onLogoutSuccess() method I get map value as {}. Now if one more user logged in then I get map {1=user1, 9=user2, 3=user3}. So,Map is getting empty inside onLogoutSuccess() method only and it showing populated values everywhere else.

From what you've described it looks like that new instance of service is created for handler.
It might be that default scope of your configuration is not singleton(it's should be easy to check).
Also, could you please try to use #Autowired annotation? There is subtle difference between #Autowire and #Resource from documentation it looks like it shouldn't cause such issue but worth to try anyway:
#Component
public class MyLogoutHandler extends SimpleUrlLogoutSuccessHandler {
#Autowired
private UsersLoggedInsService usersLoggedInService;
// ...
}
-----Update #1 ------
Yeap, try #Autowired with #Qualifier(when I tried that example without qualifier spring created two instances):
#Autowired
#Qualifier("usersLoggedInService")
UsersLoggedInService usersLoggedInService;
----Update #2 ------
Well, I simply copied all your code in sample project and it works on my machine.
Is it possible for you to share your codebase on service like GitHub?

Please show the class that is calling setOnlineUsers. And how and at what place did you check that map is not empty?
Try putting a logger in method onLogoutSuccess and check if you are getting a new or same instance of UsersLoggedInService.
And I am assuming you have a typo in the bean that you are wiring UsersLoggedIn*s*Service. There is an extra 's'

Related

How to get current user in every request in Spring Boot?

I would like to get the username of the user in every request to add them to log file.
This is my solution:
First, I created a LoggedUser with a static property:
public class LoggedUser {
private static final ThreadLocal<String> userHolder =
new ThreadLocal<>();
public static void logIn(String user) {
userHolder.set(user);
}
public static void logOut() {
userHolder.remove();
}
public static String get() {
return userHolder.get();
}
}
Then I created a support class to get username:
public interface AuthenticationFacade {
Authentication getAuthentication();
}
#Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
#Override
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Finally, I used them in my Controllers:
#RestController
public class ResourceController {
Logger logger = LoggerFactory.getLogger(ResourceController.class);
#Autowired
private GenericService userService;
#Autowired
private AuthenticationFacade authenticationFacade;
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
loggedUser.logIn(authenticationFacade.getAuthentication().getName());
logger.info(LoggedUser.get()); //Log username
return userService.findAllRandomCities();
}
}
The problem is I don't want to have AuthenticationFacade in every #Controller, If I have 10000 controllers, for example, it will be a lot of works.
Do you have any better solution for it?
The solution is called Fish Tagging. Every decent logging framework has this functionality. Some frameworks call it MDC(Mapped Diagnostic Context). You can read about it here and here.
The basic idea is to use ThreadLocal or InheritableThreadLocal to hold a few key-value pairs in a thread to track a request. Using logging configuration, you can configure how to print it in the log entries.
Basically, you can write a filter, where you would retrieve the username from the security context and put it into the MDC and just forget about it. In your controller you log only the business logic related stuff. The username will be printed in the log entries along with timestamp, log level etc. (as per your log configuration).
With Jhovanni's suggestion, I created an AOP annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LogUsername {
}
In the same package, I added new #Aop #Component class with AuthenticationFacade injection:
#Aspect
#Component
public class LogUsernameAop {
Logger logger = LoggerFactory.getLogger(LogUsernameAop.class);
#Autowired
private AuthenticationFacade authenticationFacade;
#Before("#annotation(LogUsername)")
public void logUsername() throws Throwable {
logger.info(authenticationFacade.getAuthentication().getName());
LoggedUser.logIn(authenticationFacade.getAuthentication().getName());
}
}
Then, in every #GetMapping method, If I need to log the username, I can add an annotation before the method:
#PostMapping
#LogUsername
public Course createCourse(#RequestBody Course course){
return courseService.saveCourse(course);
}
Finally, this is the result:
2018-10-21 08:29:07.206 INFO 8708 --- [nio-8080-exec-2] com.khoa.aop.LogUsername : john.doe
Well, you are already accesing authentication object directly from SecurityContextHolder, you can do it in your controller.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null){
//log user name
logger.info(authentication.get());
}
return userService.findAllRandomCities();
}
If you do not want to put all this in every endpoint, an utility method can be created to extract authentication and return its name if found.
public class UserUtil {
public static String userName(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication == null ? null : authentication.getName();
}
}
and call it in your endpoint like
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//log user name
logger.info(UserUtil.username());
return userService.findAllRandomCities();
}
However, you are still adding lines of code in every endpoint, and after a few of them it starts to feel wrong being forced to do it. Something I suggest you to do is try aspect oriented programming for this kind of stuff. It will require you to invest some time in learning how it works, create annotations or executions required. But you should have it in a day or two.
With aspect oriented your endpoint could end like this
#RequestMapping(value ="/cities")
#LogUserName
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//LogUserName annotation will inform this request should log user name if found
return userService.findAllRandomCities();
}
of course, you are able to remove #LogUserName custom annotation and configure the new aspect with being triggered by methods inside a package, or classes extending #Controller, etc.
Definitely it is worth the time, because you can use aspect for more than just logging user name.
You can obtain the username via request or parameter in your controller method. If you add Principal principal as a parameter, Spring Ioc Container will inject the information regarding the user or it will be null for anonymous users.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Principal principal){
if(principal == null){
// anonymous user
}
}
There are various ways in Spring Security to fetch the user details from the security context. But according to your requirement, you are only interested in username, so you can try this:
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Authentication authentication){
logger.info(authentication.getName()); //Log username
return userService.findAllRandomCities();
}
Hope this helps!

Custom method in Spring Web Security

I want to allow only a specific user to access their modification page.
For example, I want user 3 to be the only one to able access the url : /user/3/edit
For this, I have put in my SecurityConfiguration.java :
.authorizeRequests()
.antMatchers("/user/{id}/edit").access("#MyClass.checkId(#id)");
MyClass.java is the following:
#Component
public class MyClass{
public boolean checkId(Long id) {
if(id == SecurityUtils.getCurrentUserId()){ //I have this configured and working
return true;
}
return false;
}
}
Yet when go to the following url: user/4/edit logged in as user 3 (these are examples), I cannot seem to enter the checkId method, and nothing happens, and my page loads with everything in it.
Do you have any idea? Is antMatchers.access() the way to go?
Thank you for your time!
You'll need to subclass two classes.
First, set a new method expression handler
<global-method-security>
<expression-handler ref="myMethodSecurityExpressionHandler"/>
</global-method-security>
myMethodSecurityExpressionHandler will be a subclass of DefaultMethodSecurityExpressionHandler which overrides createEvaluationContext(), setting a subclass of MethodSecurityExpressionRoot on the MethodSecurityEvaluationContext.
For example:
#Override
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth, mi, parameterNameDiscoverer);
MethodSecurityExpressionRoot root = new MyMethodSecurityExpressionRoot(auth);
root.setTrustResolver(trustResolver);
root.setPermissionEvaluator(permissionEvaluator);
root.setRoleHierarchy(roleHierarchy);
ctx.setRootObject(root);
return ctx;
}
There is another solution and it can be accomplish in a very elegant way using Expression-Based Access Control, for this case you can use the #PreAuthorize annotation and inside it validate the principal user id. For example:
#PreAuthorize("#id == principal.userNumber")
#RequestMapping(method = RequestMethod.POST, value = "/user/{id}/edit")
public void userUpdate(Long id){ .. }
Please just make sure that the implementation of the UserDetails interface has the userNumber property.
You can see more information about Expression-Based Access Control
Another approach is to inject the Principal object into the Request handler method like this:
#RequestMapping(method = RequestMethod.POST, value = "/user/{id}/edit")
public void userUpdate(Long id, Principal myPrincipal){
MyUserDetails user = (MyUserDetails) myPrincipal;
if (user.getUserNumber == id) { ... }
....
}

Spring Security : bypass #Secured for specific service

I am currently implementing Spring Security in my application. I did manage to put #Secured annotation on my service that getAllUsers() from the database, and it is working fine as long as the user is identified (depending on his rights, he can get or not the list of users).
But I have a #Scheduled method in charge of indexing all users, and when it is launched it call the same protected getAllUsers() method, and obviously crashes as it is not logged in : I get the following exception :
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
I'm currently thinking of one possible solution, which would be to mark the internal methods with a custom annotation, which would be retrieved by a custom AccessDecisionVoter allowing the caller to call the protected method.
I'm looking for best practice for this kind of usecase
Because method is #Secured and spring expect security authentication object in context. Here is working example of AccessDecisionVoter Spring-security - AccessDecisionVoter-impl wont be invoked
or if u will have filters or smth which will depends on user context values this one should be ok
#Scheduled
public void method() {
try {
ScheduledAuthenticationUtil.configureAuthentication();
// do work
}
catch(Exception e) {
e.printStackTrace();
}
finally {
ScheduledAuthenticationUtil.cleanAuthentication();
}
}
private static class ScheduledAuthenticationUtil {
public static void configureAuthentication() {
// inject auth obj into SecurityContextHolder
}
public static void cleanAuthentication() {
// SecurityContextHolder clean authentication
}
}
I assume your service class looks like :
public class MyServiceImpl implements MyService {
...
#Secured
public Xxx getAllUsers() {
...
// call DAO
...
return xxx;
}
...
}
And you call myService.getAllUsers() from the #Scheduledclass.
The simplest way is to split getAllUsers and make the service class inherit from 2 interfaces, one containing the secured method, and one that would contain a publically accessible version :
public class MyServiceImpl implements MyService, MyScheduledService {
...
#Secured
public Xxx getAllUsers() {
return restrictedGetAllUsers;
}
public Xxx restrictedGetAllUsers() {
...
// call DAO
...
return xxx;
}
...
}
public interface MyService {
Xxx getAllUsers();
}
public interface MyScheduledService extends MyService {
Xxx restrictedGetAllUsers();
}
Then in your controller class :
#Autowired MyService myService => will call only getAllUsers()
and in your #Scheduled class :
#Autowired MyScheduledService myService => will call restrictedGetAllUsers()
All that may seem overcomplicated, but as your scheduled class and you controller have no reason to call the service methods the same way, it make sense to present them two different interfaces with different security requirements.
I went with kxyz answer, improved with a service that run a piece of code by setting the wanted Authorities before running the code, and putting back the previous authorities when the code is done :
public void runAs(Runnable runnable, GrantedAuthority... authorities) {
Authentication previousAuthentication = SecurityContextHolder.getContext().getAuthentication();
configureAuthentication(authorities);
try {
runnable.run();
} finally {
configureAuthentication(previousAuthentication);
}
}
protected void configureAuthentication(GrantedAuthority... authorities) {
Authentication authentication = new UsernamePasswordAuthenticationToken("system", null, Arrays.asList(authorities));
configureAuthentication(authentication);
}
protected void configureAuthentication(Authentication authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
Reffers to PhilippeAuriach answer - there is a better way to run new thread with authorites - with spring security extended runnable method, context from main thread is copied into delegated runnable
public void authorizedExecute(Runnable runnable) {
new Thread(new DelegatingSecurityContextRunnable(runnable)).start();
}

How do I decide which Persistent Token to delete in Spring Security PersistentTokenRepository.removeUserTokens

I've implemented Persistent token based Remember Me with Spring Security 3.2.3.RELEASE.
During development and testing I realized the database is filled with tokens for the same username.
When removeUserTokens is called I don't know which of the tokens I need to delete. I guess that the user have multiple tokens, one for each device he's using (Computer, Android, etc...), and if he logs out of one device, I want to delete the token for that device so he stays logged in another device.
Any ideas?
1) create custom PersistentTokenRepository with one additional method:
public class MDJdbcTokenRepository extends JdbcTokenRepositoryImpl {
public void removeTokenBySeries(String series) {
getJdbcTemplate().update("delete from persistent_logins where series = ?", series);
}
}
2) create custom RememeberMeServices
public class MDRememberMeServices extends PersistentTokenBasedRememberMeServices {
private MDJdbcTokenRepository tokenRepository;
public MDRememberMeServices(String key,
UserDetailsService userDetailsService,
MDJdbcTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository);
setParameter("remember-me");// parameter name in login form
this.tokenRepository = tokenRepository;
}
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
cancelCookie(request, response);
if (authentication != null) {
String rememberMeCookie = extractRememberMeCookie(request);
if(rememberMeCookie != null && rememberMeCookie.length() != 0) {
String[] cookieTokens = decodeCookie(rememberMeCookie);
if (cookieTokens.length == 2) {
String series = cookieTokens[0];
//remove by series
tokenRepository.removeTokenBySeries(series);
}
}
}
}
}
3) add beans
private static String key = "your string here";
#Bean
public MDJdbcTokenRepository persistentTokenRepository() {
MDJdbcTokenRepository db = new MDJdbcTokenRepository();
db.setDataSource(dataSource());
return db;
}
#Bean
public RememberMeServices rememberMeServices() throws Exception {
return new MDRememberMeServices(key, userDetailsService, persistentTokenRepository());
}
4) change your security spring config:
and().rememberMe().key(key).rememberMeServices(rememberMeServices())
The easiest solution is to change the Authentication object you use in such a way that calls to the getName() return "deviceID"+"username" instead of "username".
You could for example hash HTTP header UserAgent and use it as a device ID. The call to PersistentTokenRepository.removeUserTokens will then always only remove tokens related to the particular device.
The value returned from the getName() call can be customized by changing logic in your UserDetailsService implementation, as the Authentication.getName() typically delegates to UserDetails.getUsername() of the object returned from the UserDetailsService.
Other possible solutions will most likely require custom implementation of the org.springframework.security.web.authentication.RememberMeServices interface.
I am also a newbie in this world of Spring but I hope I can give you a tip...
Taking a look at PersistentTokenRepository you can notice that the only way to get the remember-me token is by using the seriesId stored in the cookie of your local browser. That way, when you log out on that device you only remove the "token) (actually the entire entry) of the corresponding seriesId.
So my try is to recommend you to just log out your user in order to delete its associated token.
I really hope that helps!

Custom Implicit Type Variable in Spring

I want to implement a public/global variable so that I can access from any layer(controller/service/dao) of a spring project. For example
class Abc{
public User user;
public String subdomain;
}
Now I want to get user, subdomain values from any layer. But remember that, my project has user management. So I need to specific value for each user session.
Note:
The life cycle of this values is session
This is not singletone forall users
This is specific per session
Thanks
A possible solution is to have a service which has the ability to lookup the currently logged on user, and provides the context information you require. As #siledh mentioned, once you have this information, you can then pass it into layers where you do not want to have any concept of context (e.g. in your DOAs)
#Service
public class ContextService {
public User getLoggedOnUser() {
// Get user id/username from Spring Security principal
// Use id/username to lookup the User
// Return the logged on user
}
}
#Controller
public class SomeController {
#Autowired
private ContextService context;
#Autowired
private SomeReposity someReposity;
#RequestMapping("/home")
public String homePage() {
User loggedOn = contextService.getLoggedOnUser();
String userInformationNeededForHomePage =
someReposity.findSomethingForUser(loggedOn);
...
}
}
#Repository
public class SomeReposity {
public String findSomethingForUser(User user) {
// find something
}
}

Categories

Resources