在开始写本节内容前,我使用Nancy.Authentication.Token实现的Token认证,但是就在我开始写本节内容的时,我看到Nancyfx的文档中的内容更新
所以我改为使用Nancy.Authentication.Stateless自己实现Token认证
- 新一个空的Asp.net Web项目,添加Nuget包
- Owin
- Nancy
- Nancy.Owin
- Nancy.Bootstrappers.Autofac
- Microsoft.Owin.Host.SystemWeb
- Nancy.Authentication.Stateless
- EntityFramework
- Mysql.Data
- Mysql.Data.Entity
-
从上一节中的WebSite项目中拷贝以下几个文件,修改命名空间,修改Bootstrapper去掉Form认证的相关代码
- 在Models文件夹下新建一个AuthModel类文件,代码如下
using System.Collections.Generic; using Nancy.Security; using Nop.Core.Caching; namespace WebSite.WebApi.Models { /// <summary> /// 代表经过认证的用户 /// </summary> public class UserIdentity : IUserIdentity { public UserIdentity(string userName) : this(userName, new List<string>()) { } public UserIdentity(string userName, IEnumerable<string> claims) { this.UserName = userName; this.Claims = claims; } public IEnumerable<string> Claims { get; private set; } public string UserName { get; private set; } } /// <summary> /// 包含生成token和校验token的静态方法 /// </summary> public class UserMapper { private static readonly MemoryCacheManager manager = new MemoryCacheManager(); /// <summary> /// 根据token获取用户信息,检测用户是否有效 /// </summary> /// <param name="token"></param> /// <returns></returns> public static IUserIdentity GetUserFromAccessToken(string token) { if (string.IsNullOrEmpty(token)) { return null; } return manager.Get<UserIdentity>(token); } /// <summary> /// 生成一个新的token,并缓存 /// </summary> /// <param name="userName"></param> /// <returns></returns> public static string GenerateToken(string userName) { string token= Guid.NewGuid().ToString(); //token有效期 manager.Set(token, new UserIdentity(userName),60*24); return token; } } }
- 在Bootstrapper的RequestStartup事件中添加配置stateless认证.
pipelines.AfterRequest.AddItemToEndOfPipeline(x => { x.Response.Headers.Add("Access-Control-Allow-Origin", "*"); x.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,DELETE,PUT,OPTIONS"); }); var configuration =new StatelessAuthenticationConfiguration(nancyContext => { //返回null代码token无效或用户未认证 string token = nancyContext.Request.Headers.Authorization; if (!string.IsNullOrEmpty(token)) { return UserMapper.GetUserFromAccessToken(token); } else { return null; } }); StatelessAuthentication.Enable(pipelines, configuration);
- 在UserService中添加根据Id查找用户信息的方法,并在IUserService中添加对应接口
/// <summary> /// Gets the User by identifier. /// </summary> /// <returns>The User by identifier.</returns> /// <param name="id">Identifier.</param> public virtual User GetUserById(int id) { if (id == 0) return null; string key = string.Format(Users_BY_ID_KEY, id); var user = _cacheManager.Get(key, () => userRepository.GetById(id)); return user; }
- 在Controller中建一个类AuthController类,用于验证用户并生成Token,代码如下
private IUserService service; public AuthController(IUserService service) : base("token") { this.service = service; Post["/"] = x => { var user = this.Bind<User>(); return GetToken(user.UserName, user.Password); }; } private object GetToken(string username, string password) { DataResult<User> result = service.ValidateUser(username, password); if (result.Result == 0) { return new { error = "认证失败!", error_description = result.Message, }; } else { return new { access_token = UserMapper.GenerateToken(username), }; } }
- 添加UserController,根据Id获取用户信息,并限定访问接口必须认证后才能使用
public class UserController:NancyModule { private IUserService service; public UserController(IUserService service):base("api/user") { //限制认证 this.RequiresAuthentication(); this.service = service; //异步模型 Get["/{Id}", true] = async (_, ct) => { User user = await Task.Run(() => { return service.GetUserById(_.Id); }); return user; }; } }
- 建一个WebSite.Test的控制台应用程序,通过nuget添加RestSharp引用包,在Main函数中添加以下代码测试接口
RestClient client = new RestClient("http://localhost:56751"); string result = string.Empty; var request = new RestRequest("/token"); var body = new { grant_type = "password", username = "admin", password = "1234567" }; request.AddObject(body); IRestResponse response = client.Post(request); result = response.Content; dynamic content = SimpleJson.DeserializeObject(result); ; if (response.StatusCode != HttpStatusCode.OK) { string error = content.error_description; return; } string token = content.access_token; var request2 = new RestRequest("/api/user/1"); request2.AddHeader("Authorization", token); IRestResponse response2 = client.Get(request2); if (response2.StatusCode == HttpStatusCode.OK) { result = response2.Content; }