I have a requirement that I need to connect a small API to an Oracle DB for few GET ops. Now in dev since there is only 1 instance, it's quite easy to provide. But when it comes to production, the DBs are located in 5 different countries and hence are 5 different instances (All of them are exactly the same except for the data). My idea is to run them via application.properties with active profile and then connect them to application-{active-profile}.properties. Something like below:
application.properties:
spring.profiles.active=DB1
spring.application.name=demo
application-DB1.properties:
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:oracle:thin:#//host1:port/SID
spring.datasource.username=username
spring.datasource.password=password
Configuration class:
#Configuration
#ConfigurationProperties("spring.datasource")
public class DBConfiguration {
private String url;
private String username;
private String password;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Profile("dev")
#Bean
public String devDBConnection(){
System.out.println("URL:"+getUrl());
System.out.println("username:"+getUsername());
System.out.println("password:"+getPassword());
return "DEV DB connection";
}
#Profile("uat")
#Bean
public String uatDBConnection() {
System.out.println("URL:" + getUrl());
System.out.println("username:" + getUsername());
System.out.println("password:" + getPassword());
return "UAT DB connection";
}
}
So when I have to enable the API for another DB, say DB-3 , all I have to do is change the active.profile=DB3 and create a new file application-DB3.properties.
Does this seem a standard practice or is there an way to do this more efficiently ?
A good practice is to pass such configuration as environment variables or system properties.
So you don't need to change the code/configuration when these variables change.
Related
I'm having a hard time figuring out why this request encounter 400 BAD request :
{
email: "ccc#gmail.com"
lastfmUsername: "bluecun"
password: "$2a$10$if246VMeosRCNJibodEhueXGyQNiAHeJd3KVHi7WedjByECYeXO5."
username: "bluecun"
}
Here is my model and controller code :
public class User {
private Long id;
private String username;
private String lastfmUsername;
private String email;
private String password;
...
}
#RequestMapping(value = "/register", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public
#ResponseBody
void register(#RequestBody User user) throws Exception {
semanticGraphDao.saveUser(user);
}
Thanks for the answers.
First you need to check if you JSON is wellformed, which means: properties surrounded by quotes and a colon between each property, for example:
{
"email": "ccc#gmail.com",
"lastfmUsername": "bluecun",
"password": "$2a$10$if246VMeosRCNJibodEhueXGyQNiAHeJd3KVHi7WedjByECYeXO5.",
"username": "bluecun"
}
On the top of that, check the constructor of your User class. It must have a default constructor:
public class User {
private Long id;
private String username;
private String lastfmUsername;
private String email;
private String password;
public User() {
}
// Getters and Setters
}
And finally, check Spring boot logs - probably it is showing some kind of exception from Jackson, which will guide you on how to solve the mapping issue.
#SpringBootApplication
public class SpringDataSolarApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataSolarApplication.class, args);
}
#Bean
SolrTemplate solrTemplate() {
return new SolrTemplate(solrClientFactory());
}
#Bean
SolrClientFactory solrClientFactory() {
Credentials credentials = new UsernamePasswordCredentials("solr", "SolrRocks");
return new HttpSolrClientFactory(solrClient(), credentials , "BASIC");
}
#Bean
SolrClient solrClient() {
return new HttpSolrClient.Builder("http://localhost:8983/solr").build();
}
}
public interface EmployeeRepository extends SolrCrudRepository{
Employee findByName(String name);
}
#RestController
public class EmployeeController {
#Autowired
private EmployeeRepository repository;
#PostConstruct
public void addEmployees() {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("373", "Basant", new String[] { "Bangalore", "BTM" }));
employees.add(new Employee("908", "Santosh", new String[] { "Hyderbad", "XYZ" }));
employees.add(new Employee("321", "Sagar", new String[] { "Pune", "PQR" }));
repository.saveAll(employees);
}
#GetMapping("/getALL")
public Iterable<Employee> getEmployees() {
return repository.findAll();
}
#GetMapping("/getEmployee/{name}")
public Employee getEmployeeByName(#PathVariable String name) {
return repository.findByName(name);
}
}
the getALL operation is working fine but the save operation failed with this error. Please help
Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:225) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) ~[httpclient-4.5.7.jar:4.5.7]
... 63 common frames omitted
Came across same issue and solved with extending HttpSolrClient and applying same backend approach with recommended way mentioned on Solr docs but getting credentials from constructor not setting on each request.
class CustomSolrClient extends HttpSolrClient {
#Nullable
private final String username;
#Nullable
private final String password;
CustomSolrClient(Builder builder, String username, String password) {
super(builder);
this.username = username;
this.password = password;
}
#Override
public NamedList<Object> request(SolrRequest request, ResponseParser processor, String collection) throws SolrServerException, IOException {
HttpRequestBase method = createMethod(request, collection);
if (username != null && password != null) {
String userPass = username + ":" + password;
String encoded = Base64.byteArrayToBase64(userPass.getBytes(UTF_8));
method.setHeader(new BasicHeader("Authorization", "Basic " + encoded));
}
return executeMethod(method, processor, request instanceof V2Request || request.getPath().contains("/____v2"));
}
}
And create bean using that:
#Bean
public SolrClient solrClient() {
return new CustomSolrClient(new HttpSolrClient.Builder(properties.getHost()), properties.getUsername(), properties.getPassword());
}
This may seem as an ugly approach but if you check HttpSolrClientFactory sources it's even more uglier which actually accesses private field of HttpClient belongs to Solr client.
I am working on Web API with AngularJS. I had implemented Web API token mechanism few days ago and able to login the application using the access token. I have used external DB table instead of ASP.NET identity table to authorize user.
I want to store user information in class so that it can be accessed easily from different controllers after User logged in. Currently I am using ClaimsIdentity in Controller Class to get the user information.
UserIdentityViewModel.cs
public class UserIdentityViewModel
{
public string UserName { get; set; }
public Guid UserId { get; set; }
}
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
var myProvider = new AuthorizationServerProvider();
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = myProvider
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
AuthorizationServerProvider.cs
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated(); //
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
string userId = context.UserName;
string password = context.Password;
EmployeeAccessBLL chkEmpAccessBLL = new EmployeeAccessBLL();
EmployeeAccessViewModel vmEmployeeAccess = chkEmpAccessBLL.CheckEmployeeAccess(Convert.ToInt32(userId), password);
if(vmEmployeeAccess != null)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("username", vmEmployeeAccess.EmpName));
identity.AddClaim(new Claim("userid", Convert.ToString(vmEmployeeAccess.EmployeeId)));
UserIdentityViewModel vmUser = new UserIdentityViewModel();
vmUser.UserId = vmEmployeeAccess.EmployeeId;
vmUser.UserName = vmEmployeeAccess.EmpName;
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "Provided username and password is incorrect");
return;
}
}
}
EventController.cs
public class StreamEventController : ApiController
{
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
//Able to get User Information from Identity.Claims
var identity = (ClaimsIdentity)User.Identity;
string userId = identity.Claims
.Where(c => c.Type == "userid")
.Select(c => c.Value).FirstOrDefault();
//Not able to get User Information from following as new object instance gets created
UserIdentityViewModel vmUser = new UserIdentityViewModel();
vmEvent.CreatedBy = vmUser.UserId;
vmEvent.ModifiedBy = vmUser.UserId;
}
}
Instead of writing "Identity.Claims" in each method of every controller I want to use simple get/set approach or any other methodology to get User Information . The use of Static class is also bad in my opinion as it will store one information of user and multiple user login information gets missed.
Please help me and share with me the best approach that has been used in other Web API projects for login.
You can add a private variable which will be set in the constructor of the controller, like this:
// Should only be used in protected methods.
private ClaimsIdentity ThisUser = null;
public MyController()
{
if (User.Identity.IsAuthenticated)
ThisUser = (ClaimsIdentity)User.Identity;
}
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = ThisUser.FindFirstValue("userid");
}
Or create a User class where you load all properties:
private UserClass ThisUser = null;
public MyController()
{
if (User.Identity.IsAuthenticated)
ThisUser = new UserClass(User);
}
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = ThisUser.UserId;
}
Where UserClass is something like:
public class UserClass
{
public string UserId { get; private set; }
public UserClass(IPrincipal user)
{
UserId = user.FindFirstValue("userid");
}
}
But this is just overhead for the same thing.
You can consider to move things to an extension. In that case you get something like:
public static class RequestExtensions
{
public static UserClass GetUser(this HttpRequestMessage request)
{
return new UserClass(request.GetOwinContext().Authentication.User);
}
public static ClaimsIdentiy GetUser2(this HttpRequestMessage request)
{
return new (ClaimsIdentity)request.GetOwinContext().Authentication.User;
}
}
Which you can call:
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = Request.GetUser.UserId;
string userId2 = Request.GetUser2.FindFirstValue("userid");
}
I think I would go for Request.GetUser2.FindFirstValue("userid");
The code is meant to give you an idea. I didn't test the code but I think it should work.
I am trying to enable both JDBA and Active Directory Authentication , i made great progress but currently i am stuck as userDetailsService is trying to compare the password in LdapUserDetails which does not exist . When checking the log i see it is able to query the user and authenticate and get the roles correctly.
I know i should use bindService or so , but i couldn't find till now how to do that.
Below is what i did .
in WebSecurityConfigurerAdapter
#Autowired
public void configureGlobal(UserDetailsService userDetailsService,UserLdapRepositoryUserDetailsService userLdapRepositoryUserDetailsService,AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService).
and()
.userDetailsService(userLdapRepositoryUserDetailsService);
}
For LDAP Configuration
#Bean
public BaseLdapPathContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://XXXXX:389/dc=XXXXXX,dc=co");
//contextSource.setUserDn("CN=Ali Shahbour,OU=Users,DC=XXXXXXX,DC=co");
contextSource.setUserDn("XXXXXX");
contextSource.setPassword("XXXXXX");
return contextSource;
}
#Bean
#Autowired
public LdapUserSearch userSearch(BaseLdapPathContextSource contextSource) {
FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch("", "(uid={0})", contextSource);
return userSearch;
}
#Bean
#Autowired
public LdapAuthoritiesPopulator authoritiesPopulator(BaseLdapPathContextSource contextSource) {
DefaultLdapAuthoritiesPopulator authoritiesPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, "OU=CDRMonitor");
authoritiesPopulator.setGroupSearchFilter("(member={0})");
//authoritiesPopulator.setRolePrefix("ROLE");
authoritiesPopulator.setSearchSubtree(true);
//authoritiesPopulator.setConvertToUpperCase(true);
return authoritiesPopulator;
}
As for the LdapUserDetailsService
#Service("userLdapRepositoryUserDetailsService")
public class UserLdapRepositoryUserDetailsService extends LdapUserDetailsService {
private final UserRepository userRepository;
#Autowired
public UserLdapRepositoryUserDetailsService(LdapUserSearch userSearch,
LdapAuthoritiesPopulator authoritiesPopulator,UserRepository userRepository) {
super(userSearch, authoritiesPopulator);
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails userDetails = super.loadUserByUsername(username);
//User user = userRepository.findByEmail(username);
User user = new User();
return new LdapUserRepositoryUserDetails(user, userDetails);
}
#Override
public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
super.setUserDetailsMapper(userDetailsMapper);
}
private final static class LdapUserRepositoryUserDetails extends User implements LdapUserDetails {
private final LdapUserDetailsImpl ldapUserDetailsImpl;
private LdapUserRepositoryUserDetails(User user,UserDetails userDetails) {
super(user);
ldapUserDetailsImpl = (LdapUserDetailsImpl) userDetails;
}
private static final long serialVersionUID = 5639683223516504866L;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetailsImpl.getAuthorities();
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return ldapUserDetailsImpl.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return ldapUserDetailsImpl.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return ldapUserDetailsImpl.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return ldapUserDetailsImpl.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return ldapUserDetailsImpl.isEnabled();
}
#Override
public String getDn() {
return ldapUserDetailsImpl.getDn();
}
}
}
LDAP and SQL in an authentication context are in general not used together,
because LDAP BIND authentication sends the password to the LDAP server instead of retrieving a hash value of the password.
The intended procedure of LDAP Authentication is as follows:
Usually a spring security UsernamePassword filter is used to pick up the credentials and is active by default. e.g. if you us a login form, when submitting the form, this filter picks up the credentials.
Next an LDAP Authentication Provider performs a login (LDAPBindAuthenticator) against the LDAP server (LDAP ContextSource) to verify the credentials.
If the login is successful, the LDAP Authentication Provider searches the LDAP for a user entry. This can be customised by providing a 'usersearch' spring bean.
If the user entry is found, the LDAP Authority mapper will map attributes of the user entry to groups/roles in spring security. By default this are all OU attributes
Lastly a new authentication object is made with the username and retrieved groups from the LDAP.
How to integrate LDAP Authentication using Spring XML is explained here.
http://docs.spring.io/spring-security/site/docs/3.1.x/reference/ldap.html
I am new to ASP.NET MVC 5 and so I am trying to use it as much as possible to learn it by practice.
So I am thinking of using the new OWIN implementation of ASP.NET MVC to implement the authentication and authorization of my project. That said, I am building the project in a way that it can work with various types of databases.
So far I have used generic ADO.NET elements (e.g. DbDataReader etc) and I have refused to use any ORM. So I am wondering if I can go ahead with using the new identity system of ASP.NET or will I be bound to Entity Framework and SQL Server if I do so?
Not that simple. Not that hard either.
You'll have to write your custom implementation of:
IUserStore<TUser>
IUserPasswordStore<TUser>
IUserTwoFactorStore<TUser>
IUserClaimStore<TUser>
IRoleStore<TRole>
IUserSecurityStampStore<TUser, string>
IUserRoleStore<TUser, string>
UserManager<TUser>
Then create your own user implementation, from IUser<TKey>, like:
public class MyUser : IUser<string>
{
public string Id { get; set; }
public string UserName { get; set; }
}
Finally, from NuGet, remove AspNet.Identity.EntityFramework, which will remove EntityFramework too if you're not using it elsewhere.
Wherever your code breaks, rewrite it to use your custom implementations.
Tip
Create a MyUserRepository which implements items from 1 to 7.
Then, create a MyUserManager which implements item 8.
It will be damn easy to wire that up in place of default AspNet.Identity.EntityFramework classes.
To piggy-back on what ALMMa said, when I was working on my own custom implementation, I found this article to be invaluable:
Overview of Custom Storage Providers for ASP.NET Identity
It details no only what Interfaces need to be implemented, but goes into detail on how to implement them and gives code sample references to an actual MySQL implementation.
You just need to override some classes in the following manner to get basic role based authentication working without Entity Framework and SQL..
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
public class ApplicationUser : IUser
{
public ApplicationUser()
{
Id = Guid.NewGuid().ToString();
Roles = new List<string>();
}
public virtual string Email { get; set; }
public List<string> Roles { get; set; }
public virtual string Password { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public string Id { get; }
public string UserName { get; set; }
public virtual void AddRole(string role)
{
Roles.Add(role);
}
public virtual void RemoveRole(string role)
{
Roles.Remove(role);
}
}
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var manager =
new ApplicationUserManager(
new UserStoreService<ApplicationUser>(context.Get<ApplicationDbContext>().Users));
manager.PasswordHasher = new FusionPasswordHasher();
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
return manager;
}
public virtual async Task<IdentityResult> AddUserToRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Add user to each role using UserRoleStore
foreach (var role in roles.Where(role => !userRoles.Contains(role)))
await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are added
return await UpdateAsync(user).ConfigureAwait(false);
}
public virtual async Task<IdentityResult> RemoveUserFromRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Remove user to each role using UserRoleStore
foreach (var role in roles.Where(userRoles.Contains))
await userRoleStore.RemoveFromRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are removed
return await UpdateAsync(user).ConfigureAwait(false);
}
}
If you want to read all the users in one short and store in the memory than you use the below style. And I strongly recommend you to read user only at the time of login for the you need to add your logic in "UserStoreService" class.
public class ApplicationDbContext : IDisposable
{
private ApplicationDbContext(IList<ApplicationUser> users)
{
Users = users;
}
public IList<ApplicationUser> Users { get; set; }
public void Dispose()
{
}
public static ApplicationDbContext Create()
{
//You can use any database and hook it here
var users = new List<ApplicationUser>
{
new ApplicationUser
{
UserName = "a#a.com",
Email = "a#a.com",
Password = "test",
Roles = new List<string> {"Admin", "Admin2"}
},
new ApplicationUser
{
UserName = "a#a2.com",
Email = "a#a2.com",
Password = "test2",
Roles = new List<string> {"Admin"}
}
};
return new ApplicationDbContext(users);
}
}
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await userManager.FindAsync(context.UserName.ToLower(), context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
try
{
var oAuthIdentity = await userManager.CreateIdentityAsync(user, context.Options.AuthenticationType);
var cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"client_id", context.ClientId == null ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(oAuthIdentity, props);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
catch (Exception ex)
{
Trace.TraceError("FUSION Error ::: " + ex.Message + ex.InnerException);
Trace.TraceError(ex.Message);
}
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (var property in context.Properties.Dictionary)
if (property.Value != null)
context.AdditionalResponseParameters.Add(property.Key, property.Value);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
context.Validated();
return Task.FromResult<object>(null);
}
}
public class AppPasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
return password;
}
public PasswordVerificationResult VerifyHashedPassword
(string hashedPassword, string providedPassword)
{
if (hashedPassword == HashPassword(providedPassword))
return PasswordVerificationResult.Success;
return PasswordVerificationResult.Failed;
}
}
Method like "FindByNameAsync" ; where you need to read user from db on demand/login
public class UserStoreService<TUser> : IUserStore<TUser>,
IUserPasswordStore<TUser>,
IUserRoleStore<TUser>
where TUser : ApplicationUser
{
private readonly IList<TUser> _users;
public UserStoreService(IList<TUser> users)
{
_users = users;
}
public virtual Task SetPasswordHashAsync(TUser user, string passwordHash)
{
user.Password = passwordHash;
return Task.FromResult(0);
}
public virtual Task<string> GetPasswordHashAsync(TUser user)
{
return Task.FromResult(user.Password);
}
public virtual Task<bool> HasPasswordAsync(TUser user)
{
return Task.FromResult(user.Password != null);
}
public virtual Task AddToRoleAsync(TUser user, string roleName)
{
user.AddRole(roleName);
return Task.FromResult(0);
}
public virtual Task RemoveFromRoleAsync(TUser user, string roleName)
{
user.RemoveRole(roleName);
return Task.FromResult(0);
}
public virtual Task<IList<string>> GetRolesAsync(TUser user)
{
return Task.FromResult((IList<string>) user.Roles);
}
public virtual Task<bool> IsInRoleAsync(TUser user, string roleName)
{
return Task.FromResult(user.Roles.Contains(roleName));
}
public virtual void Dispose()
{
}
public virtual Task CreateAsync(TUser user)
{
user.CreatedTime = DateTime.Now;
user.UpdatedTime = DateTime.Now;
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task UpdateAsync(TUser user)
{
// todo should add an optimistic concurrency check
user.UpdatedTime = DateTime.Now;
_users.Remove(user);
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task DeleteAsync(TUser user)
{
return Task.FromResult(_users.Remove(user));
}
public virtual Task<TUser> FindByIdAsync(string userId)
{
return Task.FromResult(_users.FirstOrDefault(u => u.Id == userId));
}
public virtual Task<TUser> FindByNameAsync(string userName)
{
// todo exception on duplicates? or better to enforce unique index to ensure this
return Task.FromResult(_users.FirstOrDefault(u => u.Email == userName));
}
}
[Authorize(Roles = "Admin")]
public class RolesController : ApiController
{
public IEnumerable<string> Get()
{
return new[] {"value3", "value4"};
}
}
Source Code (github)
It is bound to Entity Framework and SQL Server by default, but you can easily plug in other data stores such as SharePoint, Windows Azure Storage Table Service, NoSQL databases, etc., and you get to retain control of the database schema.
Further Reading
Introduction to ASP.NET Identity