How to storage and get token when custom ClientDetailsService? - spring-cloud

This is my code.
The clientService implements ClientDetailsService , but not found the token storage.
How to storage and get token when custom(e.g. database) ClientDetailsService?

When we have a custom implementation of ClientDetailsService, we essentially override its loadClientByClientId(..) method. This method takes clientId in parameter i.e. the username of the client. In that custom implementation class, all we need to do is check if the given client name exists in database or not. If it does exist, then load all its data and return the object. This class needs be injected with DAO or Repository's dependency to talk to database.
#Override
public ClientDetails loadClientByClientId(final String clientId) throws ClientRegistrationException {
Objects.requireNonNull(clientId, "Client ID must not be null");
final com.ex.auth.domain.ClientDetails clientDetails = clientDetailsRepository.findOne(clientId);
if (clientDetails == null) {
throw new NoSuchClientException(String.format("Client %s does not exist.", clientId));
}
return convertToDmo(clientDetails);
}

Related

EF Core - multi tenant application with 2 DB Context

Background:
.NET 6 application (front-end Angular SPA) Will be deployed as single application with 1 database per tenant. There is a shared database (called GlobalContext) which holds Tenant and TenantUser information.
In my Program.cs I don't have a connection string to the tenant database, as that information information is only available after a user has logged in.
builder.Services.AddDbContext<GlobalContext>(
options => options.UseSqlServer(config.GetValue<string>("ConnectionStringGlobal"))
);
No connection string specified here
builder.Services.AddDbContext<FinanceAppContext>();
In the OnConfiguring method in the FinanceappContext I obtain the connection string using a service;
var tenant = await _tenantService.GetConnectionString();
optionsBuilder.UseSqlServer(_config.GetValue<string>(tenant));
base.OnConfiguring(optionsBuilder);
The TenantService is a transient service which obtains the logged in users tenant from the GlobalContext
public async Task<string> GetConnectionString() {
try
{
var userId = _contextAccessor.HttpContext.User.FindFirst(ClaimTypes.Email).Value;
var user = await _globalContext.TenantUser
.Include(tenantuser => tenantuser.Tenant)
.FirstOrDefaultAsync(tenantuser => tenantuser.EmailAddress == userId);
return user.Tenant.Name;
}
catch (System.Exception)
{
return "";
}
}
But when I try to access any data via the FinanceAppContext I get the following error;
A relational store has been configured without specifying either the DbConnection or connection string to use
It seems like the fact I don't specify a connection string in Program.cs and but do specify one in OnConfiguring seems to be an issue?
So the issue was that my OnConfiguring method was marked as async and I was making an await call to my TenantService to obtain the connection string. I remove the async/await and the retrieval of the user from the GlobalContext now works.

ASP MVC EF6 Code first Multi tenant get tenant id

we keep fighting with out multi tenant application.
This is an ASP MVC EF6 Code First web application.
We initialize a list of tenants in the Application_Start, getting a pair of values:
Host
TenantId
So we can associate any host with one TenantId, and store that list in cache.
We have configured a custom filter to get the current tenant.
public class TenantActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items.Add("TenantId", GetCurrentTenant(filterContext.HttpContext.Request.Url.Host));
base.OnActionExecuting(filterContext);
}
}
The GetCurrentTenant function just access the list in cache and get the current one based on the host passed.
Is it correct to store the current tenant in an item in the context?
After that, we have created an Interceptor to get any query and add a filter to filter by TenantId. This is done and working good, we just need to add the tenantId from the context:
The problem we have is where we get the TenantId for each request.
if (HttpContext.Current.CurrentHandler == null) return;
var clientId = Convert.ToInt32(HttpContext.Current.Items["ClientId"]);
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = clientId;
}
We don't know if this is the correct approach since there is a lot of informationon the net.
Thanks.
In my experience, the persistence of the tenant Id in the HTTP context is not right, as in some cases, the HTTP context becomes null.
You can try to get the tenant Id from the claims of the current principal. Creating a static class with a tenant identifier property that reads from the claims and gives is more reliable. Assuming you are using the owin pipeline, this should be easy to do. You can take a look at the reference sample application from github here
It looks like the below block,
public static class UserContext
{
public static string TenantId
{
get
{
return Threading.Thread.CurrentPrincipal.FindFirst("tenantid");
}
}
}

custom Shiro realm - who does the actual authentication

when sub classing shiro's AuthorizingRealm (or only AuthenticationRealm) by overriding
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
}
Is it my job to check that the credentials provided in the AuthenticationToken actually match?
Or am I supposed to return the AuthenticationInfo with the principals resolved from the AuthenticationToken and the correct password for the given credentials and shiro will compare them on its own somewhere within the flow of the Subject.login(AuthenticationToken) call?
The Javadocs for AuthenticatingRealm.doGetAuthenticationInfo() state (emphasis mine):
Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given authentication token.
For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific log-in logic in addition to just retrieving data - it is up to the Realm implementation.
The method AuthenticatingRealm.getAuthenticationInfo() first calls doGetAuthenticationInfo() then subsequently calls assertCredentialsMatch() using the configured credentialsMatcher:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
So depending on how typical your Realm implementation is, you might want to avoid checking the AuthenticationToken's credentials in the doGetAuthenticationInfo() method, because the getAuthenticationInfo() template method already contains a step to ensure the submitted credentials match.
To specifically address your question if it is your responsibility "to check that the credentials provided in the AuthenticationToken actually match", the answer is yes, but not in the doGetAuthenticationInfo() method. Typically you would perform the credentials comparison within an implementation of the CredentialsMatcher interface, as described here.
Inside doGetAuthenticationInfo(...) you need to verify that the user has provided you with authentication proof.
Here is a pseudo-coded example of what you might do:
protected AuthenticationInfo doGetAutheticationInfo(AuthenticationToken token) {
if(token instanceof UsernamePasswordToken) {
String username = token.getUsername();
// Look up the user by the provide username
UserRecord userRecord = lookupUserRecord(username);
// No record found - don't know who this is
if (userRecord == null) {
throw new UnknownAccountException();
}
// Check for other things, like a locked account, expired password, etc.
// Verify the user
SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(userRecord.getPrincipal(), userRecord.getHashedCredentials(), userRecord.getCredentialsSalt(), getName());
boolean successfulAuthentication = getCredentialsMatcher().doCredentialsMatch(token, sai);
if(successfulAuthentication) {
// Check for anything else that might prevent login (expired password, locked account, etc
if (other problems) {
throw new CredentialsException(); // Or something more specific
}
// Success!
return sai;
} else {
// Bad password
throw new IncorrectCredentialsException();
}
}
// Don't know what to do with this token
throw new CredentialsException();
}
You'll have to write lookupUserRecord(username) or something similar to go lookup the user information including his hashed and salted credentials.
doGetAuthenticationInfo is the main method where authentication is done. SO if you override it generally you are overriding authentication process. If you want to use the process that was defined for that reealm and do some extra things better call super class method first then get its info and then use it so you will not have to change anything. Also in case of jdbcrealm sqls in shiro.ini are automatically mapped. and they will not be changed until you override
setAuthenticationQuery, setUserRolesQuery, etc
You can easily call following method to simulate the actual process then customize it.
AuthenticationInfo info = super.doGetAuthenticationInfo(token);
Note, that super is a reference to the parent, but super() is it's constructor.
like:
public class CustomJdbcRealm extends JdbcRealm
{
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
AuthenticationInfo info = super.doGetAuthenticationInfo(token);
// Your own code here
}
}

How to authenticate and redirect a user to his 'own' page in Jersey REST service

How to authenticate and redirect a user to his own page i.e to www.mysite.com/"user's email".
I am using the following algo which is not working...
userDB in User class:
Map<String,String> userdata=new HashMap<String,String>();
First my login process form :
#Path("/login")
#POST
#Produces(MediaType.TEXT_HTML)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void login(
#FormParam("email") String emailc,
#FormParam("password") String pass,
#Context HttpServletResponse servletResponse
) throws IOException,RuntimeException {
User u1=new User();
pass=u1.getPassword();
emailc=u1.getEmailaddrs();
boolean checked=false;
boolean exists;
exists=u1.userdata.containsKey(emailc);
if(exists){
String mypass =u1.userdata.get(emailc);
if(mypass==pass){
checked=true;
}else{
checked=false;
}
}else{
checked=false;
}
if(!checked){
//User Doesn't exists
servletResponse.sendRedirect("http://localhost:8080/MySite/pages/Create_Profile.html");
}else{
servletResponse.sendRedirect("http://localhost:8080/MySite/{email}"); <<<< How to redirect using #FormParam("email")
}
}
createprofile
#POST
#Produces(MediaType.TEXT_HTML)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void newUser(
#FormParam("email") String email,
#FormParam("password") String password,
#Context HttpServletResponse servletResponse
) throws IOException {
User u = new User(email,password);
User.userdata.put(email,password);
}
Your usage of userdata [Map] looks wrong to me. Is it a part of user class, is it non static or static ?
If it is non static then every time you will do new User() .. that map will be initialized and it will have no data in it. Hence u1.userdata.containsKey(emailc); will be always false.
If you are using a hashmap as a temporary database for dev purposes then, make it static rather keep it in a different class like UserStore or some DB access layer. Exmaple below:
public class UserDAO(){
private static Map<String,User> userdata = new HashMap<String,User>();
public boolean hasUser(String email){
return userdata.contains(email);
}
public User saveUser(String email, String password ...){
//make user object save it in map and return the same
}
// more methods for delete and edit etc.
}
And use this in your REST layer classes like this
exists = userDao.hasUser(email);
Advantages :
Your problem will be solved.
Later on when you move to actual db implementation you will just have to change your UserDao code and rest application code will be just fine. -- Loose coupling :)
Also regarding forward using email
servletResponse.sendRedirect("http://localhost:8080/MySite/{email}"); <<<< How to redirect using #FormParam("email")
add the email parameter there in the url only, if thats what you want:
servletResponse.sendRedirect("http://localhost:8080/MySite/"+emailc);
UPDATE :
See the fundamental thing is that you get request parameters [email , password]. You check it whether it is present in map or not. Now what you are doing wrong here is you create a new user like this User u = new User(); and then get email and password from it emailc = u.getEmail();. This emailc will always be null and your userdata map will always return false for that. You have two choices :
Either set email and password in user object and then get the data from user object.
Use the email and password obtained from request parameters for your logic. Do not alter them
One good practice to follow while programming is that at all times think of your method parameters as final parameters.
UPDATE 2 :
if(mypass==pass){
checked=true;
}else{
checked=false;
}
Change == to equals method. String matching should be done by equals or equalsIgnoreCase method not ==.
You always create a new User without any parameters: User u1=new User();. All these User instances will have the same property values and probably exists is always false.

Jersey : How to send /pass Custom DTO to a Service class in Jersey

I am using Jersey Framework for developing my Webservices. I have a DTO object named UserInfo with setters and getters inside it. I am setting this DTO value initially when the user logs in. How can I pass this user-specific DTO to a Jersey service class?
I have tried setting them inside MultivaluedMap and Form but I was out of luck making it work.
This is my code :
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add("queryTerm", "userdto");
Form f = new Form();
f.add("name", "1001D");
And this is the way I am trying to retrieve the Data.
public class HaiService {
#GET
#Produces("application/json")
#Consumes("application/json")
public String sayPlainTextHello(#Context UriInfo ui) {
MultivaluedMap queryParams=ui.getQueryParameters();
Iterator it=queryParams.keySet().iterator();
String theKey=null;
String returnString="";
while(it.hasNext()) {
theKey=(String)it.next();
System.out.println(queryParams.getFirst(theKey));
}
System.out.println("I am called");
return "Hi";
}
But I was out of luck. Typically my requirement is to store user-specific data on logon, and then retrive that inside the service class.
I am avoiding storing data in session because a user might login with multiple ID's under one browser, which produces the same session id, and there is a chance of data being overwritten for the first logged in user.