I'm trying to change the authentication approach in my seam application. I currently use a login form to authenticate. In the future, I'd like to delegate the authentication to another layer that will rewrite every request with a specific HTTP header containing the username of authenticated user.
I'm facing a weird problem: when using login page to authenticate, I'm able to extract the user through the entityManager. But when I query the entityManager using the information off the header, I'm unable to find the user. The entityManager behave like the user does not exist.
I already tried two approaches:
Creating a fake login page which triggers the authentication process
Creating a servlet which gets the request and starts the
authentication process
Both times, the entityManager fails to return me any user.
I read a lot about how seam manages the persistence context, but I didn't find a single explanation which make this issue clear. Do you have any ideas? suggestions? or even guesses?
the code which uses the entityManager is the following:
#Name("userService")
#AutoCreate
public class UserService {
#Logger
private Log logger;
#In
private EntityManager entityManager;
public User getUser(String email) {
try {
return entityManager
.createQuery("SELECT u FROM User u where u.email=:email",
User.class).setParameter("email", email.trim())
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
}
The configuration for persistence context is:
<persistence:managed-persistence-context startup="false" scope="stateless"
auto-create="true" name="entityManager" persistence-unit-jndi-name="java:/EntityManagerFactory" />
I created an empty fake login page which executes a page action (authentication) in which i get the request user header as the following:
#Name("applicationAuthenticator")
public class ApplicationAuthenticator {
#Logger
private Log log;
#In
private Identity identity;
#In
private Credentials credentials;
#In(required=true)
private UserService userService;
#Begin
public void login() throws LoginException {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String userName=request.getHeader("user");
identity.unAuthenticate();
credentials.setUsername(userName);
credentials.setPassword("fake");
identity.acceptExternallyAuthenticatedPrincipal(new SimplePrincipal(credentials.getUsername()));
User user=userService.getUserByEmail(credentials.getUsername());
identity.authenticate();
identity.quietLogin();
}
}
Thx in advance :-)
Thx #DaveB for your reply, the code which uses the entityManager is the following:
public User getUser(String email) {
try {
return entityManager
.createQuery("SELECT u FROM User u where u.email=:email",
User.class).setParameter("email", email.trim())
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
The configuration for persistence context is:
<persistence:managed-persistence-context startup="false" scope="stateless"
auto-create="true" name="entityManager" persistence-unit-jndi-name="java:/EntityManagerFactory" />
I created an empty fake login page which executes a page action (authentication) in which i get the request user header as the following:
HttpServletRequest request = (HttpServletRequest) FacesContext
.getCurrentInstance().getExternalContext().getRequest();
String userName = request.getHeader("user");
Related
I'm facing an issue for authenticating my users on my IS4 server with an application (Blazor Server/WASM).
My IDSRV4 is located on the same app that the API to protect, and each client has it owns database (for users and business models).
To choose the good database, I use an header and have set up a interceptor on EF Core to choose database according to header value.
Everything works great, except authentication. I was using resource_owner_password flow with a JavaScript app (security breach, I know) which allows me to pass custom headers for each calls (calling /connect/token with my custom header).
Now, with the authorization_code flow, I cannot manage to pass the header to choose good tenant, meaning good database, which causes authentication to failed because token is validated on another database that the user is currently register to.
I can pass the tenant to choose database during login process (when posting login info on IDSRV4 side), and I've set up event on client side to add header on when sending back code for having back id_token, but I lost the capability to do that after my idp redirect user after login on /connect/authorize (it seems there no extension on IDSRV4 side for adding custom headers).
So my user post login data (with tenant infos), that I lost when redirecting to /connect/authorize, and I have no way to customize this. Is there any chance to do that ?
Merging all users in the same database is not an option.
You can still add any custom headers to any HttpResponse from your MVC app hosting Identityserver.
// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseCustomHeaders();
//CustomHeadersMiddleware.cs
public static class CustomHeadersMiddlewareExtensions
{
public static IApplicationBuilder UseCustomHeaders(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomHeadersMiddleware>();
}
}
public class CustomHeadersMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<CustomHeadersMiddleware> _logger;
public CustomHeadersMiddleware(RequestDelegate next,
ILogger<CustomHeadersMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
//To add Headers AFTER everything you need to do this
context.Response.OnStarting(() =>
{
if (!context.Response.Headers.ContainsKey("X-Response-Time-Milliseconds"))
context.Response.Headers.Add("X-Response-Time-Milliseconds",
new[] {watch.ElapsedMilliseconds.ToString()});
return Task.FromResult(0);
});
await _next(context);
}
}
How to identify if authentication is not needed for current request?
We have a custom authentication handler, simplified here:
internal class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
static readonly string[] DontCheckAuth = new string[] { "/servers", "/user/profile/", "/highscore/list", "/highscore/metadata/" };
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
/*
if url contains any of DontCheckAuth
then return AuthenticateResult.NoResult()
else
do custom auth
*/
}
}
So everything works OK, but HandleAuthenticateAsync is run on every request, even on URLs I have marked as [AllowAnonymous]. Currently I just check the URL and compare it to hard-coded list of URLs that does not require authentication.
How can I automate this process so I don't have to manually update the list of URLs (which I forget to do for new APIs)?
Hello I have a problem creating simple login with Ldap. I have downloaded getting started project from spring.io website: Getting started LDAP.
It is working perfectly with ldif file but I want to replace it with running ldap server. I have tried it for days with no progress. I get best results with this piece of code (replaced in WebSecurityConfig of getting started project)
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(null, "ldap://ip:port/", "ou=GROUP,dc=domain,dc=com");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
If i try to login with good username and password in format "username" "password" console output: ActiveDirectoryLdapAuthenticationProvider : Active Directory authentication failed: Supplied password was invalid
If I use "username#domain.com" and good password, page just reloads with no output to console.
If I use random username and password console: Active Directory authentication failed: Supplied password was invalid
Can someone help?
As suggested in comment I have turned on logging and found out that the problem is same with "username#domain.com" too.
Problem was in aciveDirectoryLdapAuthenticationProvider() there were 3 problems in it.
I have removed OU group from rootDn and added domain so we can use only username to log in.
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://ip:port/", "dc=domain,dc=com");
changed searchfilter of provider
provider.setSearchFilter("(&(objectClass=user)(sAMAccountName={0}))");
and finally I had to change ActiveDirectoryLdapAuthProvider searchForUser method because it was matching "username#domain.com" with sAMAccountName istead of "username". This:
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context,
searchControls, searchRoot, searchFilter,
new Object[] { bindPrincipal });
replaced with this:
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context,
searchControls, searchRoot, searchFilter,
new Object[] { username });
Complete aciveDirectoryLdapAuthenticationProvider:
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://ip:port/", "dc=domain,dc=com");
provider.setSearchFilter("(&(objectClass=user)(sAMAccountName={0}))");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
Can someone provide better solution for the second/third problem? maybe better searchfilter? I dont have any field in ldap that matches "username#domain.com" format of bindPrincipal that is using ActiveDirectoryLdapAuthProvider.
i use spring data rest 2.1 and would like secure my rest api, what is the best way to secure the api by token. I would have an process like the following steps. Is it also possible to change the base url from / to /api , but my controllers like 'home' would also display the jsp side from /home but not from /api/..
User go to login site
User login and get an token from server
User go to dashboard site and js do ajax call and set HTTP-Header Field 'X_AUTH_TOKEN' with token
You could configure base URI for you API using RepositoryRestConfiguration. Something like this:
#Configuration
public static class RepositoryConfig extends
RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
try {
config.setBaseUri(new URI("/api"));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
You could add security using Spring-Security or if you like a simple custom solution use Spring Interceptor
I am using NancyFx to make a simple website were users can login and out using an ajax control.
I have read the documentation on Forms Authentication with Nancy and I think I have completed all the required steps.
Install the Nancy.Authentication.Forms package
Implement an IUserMapper
Implement routes to handle login and logout
Configure and enable Forms Authentication
I am having an issue where after calling login, requests do not have a current user set.
I can see a cookie set after the login is executed. However the user mapper is not getting called. I have tried requesting routes with and with out the this.RequiresAuthentication(); and still no user.
Here is my Bootstrapper Implementation for step 4
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
container.Register<ISessionFactory>((c, p) => SessionFactory.Factory);
}
protected override void ConfigureConventions(NancyConventions conventions)
{
base.ConfigureConventions(conventions);
conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("assets", #"content/assets"));
conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("application", #"content/application"));
}
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
base.ConfigureRequestContainer(container, context);
container.Register<IUserMapper, UserMapper>();
}
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(container, pipelines, context);
var formsAuthConfiguration =
new FormsAuthenticationConfiguration()
{
RedirectUrl = "~/",
UserMapper = container.Resolve<IUserMapper>()
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
}
}
Here is my login & logout logic for step 3.
Post["/login"] = x =>
{
//Verify here, hardcode for testing
string email = "test#example.com";
User user = ExecuteCommand(new FetchUser(email));
this.LoginWithoutRedirect(user.Session);
return new { email = user.Email, authorized = true, status = "okay" };
};
Post["/logout"] = x =>
{
return this.Logout("~/");
};
My IUsermapper simply looks up a user from a database with the given id.
I can see it gets constructed when the RequestStartup resolves the IUserMapper but then there are never any calls to the get GetUserFromIdentifier function.
Any help would be great.
The GetUserFromIdentifier method is not being called because you are using the LoginWithoutRedirect extension. It is not the login that calls GetUserFromIdentifier but rather any subsequent redirect.
A more usual way of doing things would be:
string email = "test#example.com";
User user = ExecuteCommand(new FetchUser(email));
this.LoginAndRedirect(user.Session);
It is not expected that the login route would be accessed directly. Instead the user would normally request a protected resource, be authenticated and then redirected to the requested resource.
A couple of other points:
When I tried your code I got an error returning an anonymous type. Instead I needed to return the type as json, like this:
this.LoginWithoutRedirect(user.Session);
return Response.AsJson(new
{
email = user.Email,
authorized = true,
status = "okay"
});
This works fine, it logs in and returns your anonymous type as a json object and since there is no redirect then, as expected, it does not call GetUserFromIdentifier.
Finally, your /logout route should be protected using this.RequiresAuthentication(). It makes sense because only authenticated users need to logout. It will also protect you when GetUserFromIdentifier returns null - perhaps because a cached user has been timed out. RequiresAuthentication detects this null user and redirects back to Get["/login"].