I'm trying to use ServiceStack authentication plugins out of the box along with RavenDB and the RavenUserAuthRepository package.
AppHost
var store = new DocumentStore()
{
ConnectionStringName = "ServiceStackAuthSample"
}
.Initialize();
IndexCreation.CreateIndexes(typeof(RavenUserAuthRepository).Assembly, store);
container.Register(store);
var session = container.Resolve<IDocumentStore>().OpenSession();
container.Register(p => session).ReusedWithin(ReuseScope.Request);
container.Register<IUserAuthRepository>(p => new RavenUserAuthRepository(p.Resolve<IDocumentStore>(), p.Resolve<IDocumentSession>()));
The first authentication attempt via Facebook, GoogleOAuth, Twitter--works as expected. However, if I attempt to re-authenticate, RavenDB doesn't seem to like it and I get the following:
error CodeNonUniqueObjectExceptionmessageAttempted to associate a different object with id 'UserAuths/1'.stack Trace[Auth: 10/21/2013 6:51:04 PM]: [REQUEST: {provider:facebook}] Raven.Client.Exceptions.NonUniqueObjectException: Attempted to associate a different object with id 'UserAuths/1'. at Raven.Client.Document.InMemoryDocumentSessionOperations.AssertNoNonUniqueInstance(Object entity, String id) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:line 778 at Raven.Client.Document.InMemoryDocumentSessionOperations.StoreInternal(Object entity, Etag etag, String id, Boolean forceConcurrencyCheck) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:line 670 at Raven.Client.Document.InMemoryDocumentSessionOperations.Store(Object entity) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:line 608 at ServiceStack.Authentication.RavenDb.RavenUserAuthRepository.CreateOrMergeAuthSession(IAuthSession authSession, IOAuthTokens tokens) at ServiceStack.ServiceInterface.Auth.AuthProvider.OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary2 authInfo) at ServiceStack.ServiceInterface.Auth.FacebookAuthProvider.Authenticate(IServiceBase authService, IAuthSession session, Auth request) at ServiceStack.ServiceInterface.Auth.AuthService.Authenticate(Auth request, String provider, IAuthSession session, IAuthProvider oAuthConfig) at ServiceStack.ServiceInterface.Auth.AuthService.Post(Auth request) at lambda_method(Closure , Object , Object ) at ServiceStack.ServiceHost.ServiceRunner1.Execute(IRequestContext requestContext, Object instance, TRequest request)
RavenDB searches tell me it may have something to do with my session(s), but I'm not sure what I'm doing wrong here. It could be my understanding of how to manage RavenDB sessions in ServiceStack.
UPDATE
It turns out there was a bug in the v3 branch of ServiceStack.AuthenticationRavenDb.RavenUserAuthRepository::GetUserAuth method. While all other methods were using a private IDocumentSession member, injected by constructor, GetUserAuth was creating a brand new instance of IDocumentSession.
private readonly IDocumentStore _documentStore;
public RavenUserAuthRepository(IDocumentStore documentStore,IDocumentSession session)
{
_documentStore = documentStore;
_session = session;
}
Old
public UserAuth GetUserAuth(string userAuthId)
{
using (var session = documentStore.OpenSession())
{
int intAuthId;
return int.TryParse(userAuthId, out intAuthId)
? session.Load<UserAuth>(intAuthId)
: session.Load<UserAuth>(userAuthId);
}
}
Fixed
public UserAuth GetUserAuth(string userAuthId)
{
using (_session)
{
int intAuthId;
return int.TryParse(userAuthId, out intAuthId)
? _session.Load<UserAuth>(intAuthId)
: _session.Load<UserAuth>(userAuthId);
}
}
This lead to CreateOrMergeAuthSession getting the UserAuth with one session instance (via GetUserAuth), then using the private session member to save the UserAuth, causing the conflict.
Thanks for the tip, David!
I'm not familiar with the ServiceStack DI API you've shown here, but it looks to me like you're creating and registering one DocumentStore (which is good) but then also creating one IDocumentSession and registering just that instance. There should be one unique instance of IDocumentSession for every request, so the container needs to be configured that every request will call the DocumentStore's OpenSession method.
Related
I have a stateless service in Azure Service Fabric, and I'm using Microsoft.Extensions.DependencyInjection, although the same issue exists for any other DI frameworks. In my Program.cs, I create a ServiceCollection, add all (but one) of my registrations, create the service provider, and pass it to my service's constructor. Any service method with external entry will create a new service scope and call the main business logic class. The issue is that one of the classes I want to have scoped lifetime needs a value that is an input parameter on the request itself. Here's a code snippet of what I would like to achieve.
internal sealed class MyService : StatelessService, IMyService
{
private IServiceProvider _serviceProvider;
private IServiceScopeFactory _scopeFactory;
public MyService(StatelessServiceContext context, IServiceProvider serviceProvider)
: base(context)
{
_serviceProvider = serviceProvider;
_scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
}
public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken)
{
using (var scope = _scopeFactory.CreateScope())
{
var requestContext = new RequestContext(correlationId);
//IServiceCollection serviceCollection = ??;
//serviceCollection.AddScoped<RequestContext>(di => requestContext);
var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>();
return await businessLogic.ProcessAsync(request, cancellationToken);
}
}
}
The cancellation token is already passed around everywhere, including to classes that don't use it directly, just so it can be passed to dependencies that do use it, and I want to avoid doing the same with the request context.
The same issue exists in my MVC APIs. I can create middle-ware which will extract the correlation id from the HTTP headers, so the API controller doesn't need to deal with it like my service fabric service does. One way I can make it work is by giving RequestContext a default constructor, and have a mutable correlation id. However, it's absolutely critical that the correlation id doesn't get changed during a request, so I'd really like the safety of having get-only property on the context class.
My best idea at the moment is to have a scoped RequestContextFactory which has a SetCorrelationId method, and the RequestContext registration simply calls the factory to get an instance. The factory can throw an exception if a new instance is requested before the id is set, to ensure no id-less contexts are created, but it doesn't feel like a good solution.
How can I cleanly register read-only objects with a dependency injection framework, where the value depends on the incoming request?
I only had the idea for a RequestContextFactory as I was writing the original question, and I finally made time to test the idea out. It actually was less code than I expected, and worked well, so this will be my go-to solution now. But, the name factory is wrong. I'm not sure what to call it though.
First, define the context and factory classes. I even added some validation checks into the factory to ensure it worked the way I expect:
public class RequestContext
{
public RequestContext(string correlationId)
{
CorrelationId = correlationId;
}
public string CorrelationId { get; }
}
public class RequestContextFactory
{
private RequestContext _requestContext;
private bool _used = false;
public void SetContext(RequestContext requestContext)
{
if (_requestContext != null || requestContext == null)
{
throw new InvalidOperationException();
}
_requestContext = requestContext;
}
public RequestContext GetContext()
{
if (_used || _requestContext == null)
{
throw new InvalidOperationException();
}
_used = true;
return _requestContext;
}
}
Then, add registrations to your DI container:
services.AddScoped<RequestContextFactory>();
services.AddScoped<RequestContext>(di => di.GetRequiredService<RequestContextFactory>().GetContext());
Finally, the Service Fabric service method looks something like this
public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken)
{
using (var scope = _scopeFactory.CreateScope())
{
var requestContext = new RequestContext(correlationId);
var requestContextFactory = scope.ServiceProvider.GetRequiredService<RequestContextFactory>();
requestContextFactory.SetContext(requestContext);
var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>();
return await businessLogic.ProcessAsync(request, cancellationToken);
}
}
Kestrel middleware could look something like this
public async Task Invoke(HttpContext httpContext)
{
RequestContext requestContext = new RequestContext(Guid.NewGuid().ToString());
var factory = httpContext.RequestServices.GetRequiredService<RequestContextFactory>();
factory.SetContext(requestContext);
httpContext.Response.Headers["X-CorrelationId"] = requestContext.CorrelationId;
await _next(httpContext);
}
Then just do the normal thing and add a RequestContext parameter to the constructor of any class that needs to get the correlation id (or any other info you put in the request context)
I think I have come across a bug in spring-session but I just want to ask here if it really is a bug. Before I forget
https://github.com/paranoiabla/spring-session-issue.git
here's a github repository that reproduces the problem. Basically I have a 2 controllers and 2 jsps, so the flow goes like this:
User opens http://localhost:8080/ and the flow goes through HomepageController, which puts 1 attribute in the spring-session and returns the homepage.jsp which renders the session id and the number of attributes (1)
The homepage.jsp has this line inside it:
${pageContext.include("/include")}
which calls the IncludeController to be invoked.
The IncludeController finds the session from the session repository and LOGs the number of attributes (now absolutely weird they are logged as 0) and returns the include.jsp which renders both the session id and the number of session attributes (0).
The session id in both jsps is the same, but somehow after the pageContext.include call the attributes were reset to an empty map!!!
Can someone please confirm if this is a bug.
Thank you.
Problem
The problem is that when using MapSessionRepository the SessionRepositoryFilter will automatically sync the HttpSession to the Spring Session which overrides explicit use of the APIs. Specifically the following is happening:
SessionRepositoryFilter is obtaining the current Spring Session. It caches it in the HttpServletRequest to ensure that every invocation of HttpServletRequest.getSession() does not make a database call. This cached version of the Spring Session has no attributes associated with it.
The HomepageController obtains its own copy of Spring Session, modifies it, and then saves it.
The JSP flushes the response which commits the HttpServletResponse. This means we must write out the session cookie just prior to the flush being set. We also need to ensure that the session is persisted at this point because immediately afterwards the client may have access to the session id and be able to make another request. This means that the Spring Session from #1 is saved with no attributes which overrides the session saved in #2.
The IncludeController obtains the Spring Session that was saved from #3 (which has no attributes)
Solution
There are two options I see to solving this.
Use HttpSession APIs
So how would I solve this. The easiest approach is to stop using the Spring Session APIs directly. This is preferred anyways since we do not want to tie ourselves to the Spring Session APIs if possible. For example, instead of using the following:
#Controller
public class HomepageController {
#Resource(name = "sessionRepository")
private SessionRepository<ExpiringSession> sessionRepository;
#Resource(name = "sessionStrategy")
private HttpSessionStrategy sessionStrategy;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(final Model model) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final String sessionIds = sessionStrategy.getRequestedSessionId(request);
if (sessionIds != null) {
final ExpiringSession session = sessionRepository.getSession(sessionIds);
if (session != null) {
session.setAttribute("attr", "value");
sessionRepository.save(session);
model.addAttribute("session", session);
}
}
return "homepage";
}
}
#Controller
public class IncludeController {
private final static Logger LOG = LogManager.getLogger(IncludeController.class);
#Resource(name = "sessionRepository")
private SessionRepository<ExpiringSession> sessionRepository;
#Resource(name = "sessionStrategy")
private HttpSessionStrategy sessionStrategy;
#RequestMapping(value = "/include", method = RequestMethod.GET)
public String home(final Model model) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final String sessionIds = sessionStrategy.getRequestedSessionId(request);
if (sessionIds != null) {
final ExpiringSession session = sessionRepository.getSession(sessionIds);
if (session != null) {
LOG.error(session.getAttributeNames().size());
model.addAttribute("session", session);
}
}
return "include";
}
}
You can simplify it using the following:
#Controller
public class HomepageController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpServletRequest request, Model model) {
String sessionIds = request.getRequestedSessionId();
if (sessionIds != null) {
final HttpSession session = request.getSession(false);
if (session != null) {
session.setAttribute("attr", "value");
model.addAttribute("session", session);
}
}
return "homepage";
}
}
#Controller
public class IncludeController {
#RequestMapping(value = "/include", method = RequestMethod.GET)
public String home(HttpServletRequest request, final Model model) {
final String sessionIds = request.getRequestedSessionId();
if (sessionIds != null) {
final HttpSession session = request.getSession(false);
if (session != null) {
model.addAttribute("session", session);
}
}
return "include";
}
}
Use RedisOperationsSessionRepository
Of course this may be problematic in the event that we cannot use the HttpSession API directly. To handle this, you need to use a different implementation of SessionRepository. For example, another fix is to use the RedisOperationsSessionRepository. This works because it is smart enough to only update attributes that have been changed.
This means in step #3 from above, the Redis implementation will only update the last accessed time since no other attributes were updated. When the IncludeController requests the Spring Session it will still see the attribute saved in HomepageController.
So why doesn't MapSessionRepository do this? Because MapSessionRepository is based on a Map which is an all or nothing thing. When the value is placed in the map it is a single put (we cannot break that up into multiple operations).
so I'm using the spring-session project and I want to know if it is possible to autowire the HttpSessionManager bean? I can see in the users example you are getting it from the request together with the SessionRepository:
HttpSessionManager sessionManager =
(HttpSessionManager) req.getAttribute(HttpSessionManager.class.getName());
SessionRepository<Session> repo =
(SessionRepository<Session>) req.getAttribute(SessionRepository.class.getName());
However, I want to access it from a service near the db layer and because I don't think it is a good design practice to pass the request down to the service I tried to autowire it but it doesn't find a bean of this type. The SessionRepository can be autowired fine because I have defined the bean in my configuration. I also tried to get it using the RequestContextHolder but then the getSessionIds methods always returns empty map, so I end up creating a new session all the time. Here's my whole method:
#Override
public Session getCurrentSession() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpSessionManager sessionManager =
(HttpSessionManager) request.getAttribute(HttpSessionManager.class.getName());
final Map<String, String> sessionIds = sessionManager.getSessionIds(request);
if (sessionIds != null) {
for (Map.Entry<String, String> e : sessionIds.entrySet()) {
final Session session = sessionRepository.getSession(e.getValue());
if (session != null) {
return session;
}
}
}
Session session = sessionRepository.createSession();
sessionRepository.save(session);
return session;
}
My guess is that the RequestContextHolder is capturing the HttpServletRequest before the SessionRepositoryFilter is invoked. That means that the request will not yet be wrapped.
By default the EnableRedisHttpSession configuration does not expose CookieHttpSessionStrategy as a Bean. This is necessary in order to allow users to override the SessionStrategy and supporting older versions of Spring (newer versions of Spring support #Conditional). If you wish to expose CookieHttpSessionStrategy as a Bean, then you can add the following to your configuration:
#Bean
public CookieHttpSessionStrategy sessionStragegy() {
return new CookieHttpSessionStrategy();
}
After thinking about it some I may be able to expose it in future versions. I have created gh-spring-session-75 to address it.
I have an MVC application and a custom class called AuthorisationFilter which has a .NET interface of IAuthorizationFilter, this has an OnAuthorization method which gets called when I click around my site, at that point I go about validating the security access of the user (which works), but I don't want to do this all the time as it is time consuming.
In this I'm trying to use the Session to store a temporary piece of login information (this is an internal application by the way), but I can't get it working as I'd expect. I can't just use an HttpContext so end up constantly creating a new instance of HttpContextBase, which I assume is then clearing out the Session. My code is as follows:
internal void SetSecurityLevel(int token)
{
HttpContextBase _cBase = new HttpContextWrapper(HttpContext.Current);
_cBase.Session["SecurityRights"] = token;
}
internal int GetSecurityLevel()
{
HttpContextBase _cBase = new HttpContextWrapper(HttpContext.Current);
if (_cBase.Session["SecurityRights"] == null)
{
SetSecurityLevel(-1);
}
return (int)_cBase.Session["SecurityRights"];
}
Please note this is only part of the code, SetSecurityLevel is set to the correct value by a separate method call which is not shown
Anyway what I'm really wanting to do is have the session set in this class and have it persisted. I tried a few different ways, including setting the context when the class is initialised, but I end up with a NullReference on the .Session object in GetSecurityLevel
private HttpContextBase _cBase = new HttpContextWrapper(HttpContext.Current);
public AuthorisationFilter()
{
_cBase = new HttpContextWrapper(HttpContext.Current);
}
Is there a way I can do this within the class?
You are right that you can't set in the constructor but you can in OnActionExecuting when the context is available
public override void OnActionExecuting(ActionExecutedContext filterContext)
{
_session = = filterContext.HttpContext.Session;
I would wonder a little why you're setting this security level in the filter and not just doing it directly from where it's needed.
I am implementing COMET in my MVC web application by using the PokiIn library for pushing notifications to clients.
Whenever a client connects, the ClientId is available in the OnClientConnected event of the CometWorker class:
public static Dictionary<int, string> clientsList
= new Dictionary<int, string>();
public static string clientId = "";
static void OnClientConnected(string clientId,
ref Dictionary<string, object> list)
{
BaseController.clientId = clientId;
}
I assign the the clientId received in the handler to the static ClientId of controller. And then when the Handler action is called, I map this ClientId to the Identity of the logged in user:-
public ActionResult Handler()
{
if (User.Identity.IsAuthenticated)
{
if (clientsList.Keys.Contains(currentUser.UserId))
clientsList[currentUser.UserId] = clientId;
else
clientsList.Add(currentUser.UserId, clientId);
}
return View();
}
Because multiple requests will be served by different threads on the server, each will access the static ClientId in both the methods.
How can I synchronize its access, so that untill one request is done with it in both the methods (OnClientConnected and Handler), the other request waits for it ?
Please tell me if my question is not clear. I will try to improve it further.
Store the clientid in the user's session not in a static variable on the controller. It needs to be in data associated with the user not the entire application. Or better yet, resolve the name/id lookup when the client connects.
I think you should use lock(clientsList){} whenever you want to update your dictionary