Fisrt, I need to say that I'm using session scoped bean. So before session is closed the preDestroy() method is invoked
#Component
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "session")
public class MySessionBean {
#PreDestroy
public void preDestroy() {
//Do Smth with using Security principal
}
}
When I logout by using Spring Security utils everything goes fine, the preDestroy() method is called.
The main problems come when I use
server.session-timeout = 60 or = 1 in application.properties
preDestroy() is called approximately in 2.5 minutes after session has opened.
And much more interesting is that SecurityContextHolder.getContext().getAuthentication().getPrincipal(); is null. BUT I've successfully loged out.
Also I've tried a
#Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() {
return (ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) ->
configurableEmbeddedServletContainer.setSessionTimeout(1, TimeUnit.MINUTES);
}
I have the same result.
Also the problem exists while using Provided Tomcat
UPDATE:
The weird thing is that if I manually after 1 minute check the session
existence the method preDestroy() is called immediately. But
Security Principal is already null
Thanks in Advance!
When a session does timeout the SecurityContextHolder.getContext().getAuthentication().getPrincipal() will always return null. The SecurityContext is only filled when a request comes in, one of the filters does that. When a session times out the filters will of course not be invoked and as such the SecurityContext not filled.
Instead create a bean that implements ApplicationListener<HttpSessionDestroyedEvent>. The HttpSessionDestroyedEvent has a method getSecurityContexts that returns the SecurityContexts as originally in the HttpSession.
public class YourListener implements ApplicationListener<HttpSessionDestroyedEvent> {
public void onApplicationEvent(HttpSessionDestroyedEvent evt) {
for (SecurityContext ctx : evt.getSecurityContexts() ) {
Authentication auth = ctx.getAuthentication();
Object principal = auth.getPrincipal();
// Do your thing with the principal.
}
}
}
As M. Deinum said:
There is a thread which check about every x seconds if sessions are
invalid. So when your set your timeout to 1 minute it is 1 minute +
a bit more before your sessions is actually cleared. When you check
the session yourself, the invalid session is already cleaned as then
it it is forcefully checked.
So delay of preDestroy() invocation has been explained.
The next problem was how to get Security Principal after SESSION-TIMEOUT
NOTE that by implementing
ApplicationListener<HttpSessionDestroyedEvent>
HttpSessionListener
Session scope bean
the
SecurityContextHolder.getContext().getAuthentication() == null when appropriate destroy method is called
To get principal visit relatedStackPost
After you'll do that implement HttpSessionListener
#Component
public class MySessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
...
}
#Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
HttpSession httpSession = httpSessionEvent.getSession();
SecurityContext securityContext = (SecurityContextImpl) httpSession.getAttribute("SPRING_SECURITY_CONTEXT");
}
}
Related
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!
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();
}
I'm trying to implement login mechanizm using CDI #javax.enterprise.context.SessionScoped
Code:
#Named
#SessionScoped
public class Auth implements Serializable {
private User user;
#Inject
private UserStore userStore;
#Produces #CurrentUser
public User getUser() {
if (user == null) {
Principal principal= FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
if (principal != null) {
Map parameters = new HashMap();
parameters.put("email", principal.getName());
user = (User) userStore.findWithNamedQuery(User.GET_BY_EMAIL, parameters).get(0);
}
}
return user;
}
public void logout() throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
ec.redirect(ec.getRequestContextPath() + "/");
}
}
The class that always receives the same User instance:
#Stateful
public class NewsService implements Serializable {
#Inject #CurrentUser
private User currentUser;
#Inject
private NewsStore newsStore;
public List<News> getNewsForLoggedUser(Integer start, Integer limit) {
Map<String, Object> params = new HashMap<>();
params.put("user", currentUser);
return (List<News>) newsStore.findWithNamedQuery(News.getAllForUser, params, start, limit);
}
}
There problem is with getUser() method. Even though the session was invalidated it still returns value from the first login. How can I destroy CDI bean or change it so it outputs always actual value? I tried annotation logout() method with #PreDestroy, but it produces such error:
13:20:50,329 ERROR [org.jboss.weld.Bean] (default task-22) WELD-000019 Error destroying an instance Managed Bean [class com.intenso.presentation.Auth] with qualifiers [#Default #Any #Named] of com.intenso.presentation.Auth#59821e
I'm running on WildFly 8.0.0.Alpha4
The problem probably comes from the fact that your producer doesn't have an explicit scope, so it has the #Dependentpseudo-scope. That means the producer code is called once at your EJB build-time and that the Injected value stays the same for all your EJB life.
You didn't mention how your #Stateful EJB is used, but I assume that it's injected in another piece of code so your code keep it longer than a request.
To correct your bug your should give a normal scope to your producer (#RequestScopedseems the best choice). So your producer code will become
#Produces #CurrentUser #RequestScoped
public User getUser() { ... }
Remember a producers doesn't inherit the scope from the bean that contains it.
With this normal scope, the user bean won't be directly injected, but CDI will inject a proxy of it. Thus the currentUser will stay the same only during the life of the current request.
Don't hesitate to read the dependent scope chapter of the CDI 1.1 spec, it's quite helpful
i have had similar issues before, i feel ur session pages are cached and you to clear them by
doing this
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Cache-Control", "no-cache,no-store,must-revalidate"); // HTTP 1.1
httpResponse.setHeader("Pragma", "no-cache"); // HTTP 1.0
httpResponse.setDateHeader("Expires", 0); // Proxies.
chain.doFilter(request, response);
in your FilterServlet.
I need to access an application-scoped managed bean to modify certain properties from within an HttpSessionListener.
I already used something like the following:
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
ApplicationScopedBean appBean = (ApplicationScopedBean) externalContext.getApplicationMap().get("appBean");
appBean.getConnectedUsers().remove(user);
}
externalContext = FacesContext.getCurrentInstance().getExternalContext() causes a null pointer exception here already, and even if it didn't I'm not sure appBean could be accessible the above way.
Any ideas?
The FacesContext is only available in the thread serving the HTTP request initiated by the webbrowser which has invoked the FacesServlet. During a session destroy there's not necessarily means of a HTTP request. Sessions are usually destroyed by a background thread managed by the container. This does not invoke a HTTP request through the FacesServlet. So you should not expect the FacesContext to be always there during the session destroy. Only when you call session.invalidate() inside a JSF managed bean, then the FacesContext is available.
If your application scoped managed bean is managed by JSF #ManagedBean, then it's good to know that JSF stores it under the covers as an attribute of the ServletContext. The ServletContext in turn is available in the session listener by HttpSession#getServletContext().
So, this should do:
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
ApplicationScopedBean appBean = (ApplicationScopedBean) session.getServletContext().getAttribute("appBean");
appBean.getConnectedUsers().remove(user);
}
If you're running a Servlet 3.0 capable container, an alternative is to just let your application scoped bean implement HttpSessionListener and register itself as such upon construction. This way you have direct reference to the connectedUsers property.
#ManagedBean
#ApplicationScoped
public class AppBean implements HttpSessionListener {
public AppBean() {
ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
context.addListener(this);
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
connectedUsers.remove(user);
}
// ...
}
Again another alternative is to keep the User in the session scope as a session scoped managed bean. You can then use the #PreDestroy annotation to mark a method which should be invoked when the session is destroyed.
#ManagedBean
#SessionScoped
public class User {
#ManagedProperty("#{appBean}")
private AppBean appBean;
#PreDestroy
public void destroy() {
appBean.getConnectedUsers().remove(this);
}
// ...
}
This has the additional benefit that the User is in EL context available as #{user}.
I have a SessionScoped bean say UserSession which holds a String property token which acts as a authenticated token for the logged in user. This token is injected into other SessionScoped and ViewScoped beans so that they can consume this token and perform stuff. I have no problems injecting the property.
However, there's a use case wherein the token in the UserSession itself is replaced by a different String. And once I do this, the other beans still refer to the old value of the token which results in invalid access.
How can I notify the other beans of this value change? or do I retrieve bean instance through
FacesContext context = FacesContext.getCurrentInstance();
Application app = context.getApplication();
UserSession session = (UserSession) app.evaluateExpressionGet(context, "#{userSession}", UserSession.class);
And then retrieve token as session.getToken()?
I'm using Mojarra 2.0.4
I don't see why that is a problem. This more sounds like as if you're copying the property like as:
#ManagedBean
#SessionScoped
private OtherBean {
#ManagedProperty(value="#{userSession}")
private UserSession userSession;
private String token;
#PostConstruct
public void init() {
this.token = userSession.getToken();
}
public void someAction() {
doSomethingWith(token);
}
public void otherAction() {
doSomethingElseWith(token);
}
// ...
}
while you should rather be accessing it directly:
// ...
public void someAction() {
doSomethingWith(userSession.getToken());
}
public void otherAction() {
doSomethingElseWith(userSession.getToken());
}
// ...
Fix your beans accordingly to get rid of the private token property which contains the copy and just let all methods get it from the injected bean directly.