webservice 权限认证
》》soapHeader
SOAPHeader案例
服务引用下生成的服务方法参数中会自动加入一个soapHeader的参数,
WEB服务引用则没有,我感觉采用WEB服务引用基于这种验证比较方便,
因为只需将soapHeader实例赋值一次就可以多次调用不同的服务方法。
Asp.NET 认证
在asp.net中,将身份验证分成了两个部分,第一个部分是IIS的身份验证,在用户访问网站时,IIS首先就会对用户进行身份验证,这个身份验证的具体设置在IIS中,这也非本文的重点,在此也不再详细介绍了。只有IIS通过了用户的身份验证之后,
才会进行第二个部分的身份验证,这个部分的身份验证则由asp.net来完成。
》》》asp.net 身份认证的流程
在asp.net中,身份验证过程分为两部分,一部分是IIS中的身份验证,只有通过了IIS中的身份验证之后,才行进行asp.net中的身份验证。一个完整的窗体身份验证流程如下所示:
首先,用户通过浏览器向服务器发送一个网页请求,假设用户要访问index.aspx网页, 那么浏览器就会将请求发送给IIS服务器。
假设在该服务器中将IIS设置成可以匿名访问,那么IIS服务器将会允许访问index.aspx网页。
IIS服务器允许访问之后,将会进入asp.net身份验证。asp.net会去web.config文件中查看节点下的节点中是否拒绝所有匿名用户。如果拒绝了所有匿名用户,服务器则会去查找一个身份验证的Cookie。
如果服务器查找不到该身份验证的Cookie,就会将网页跳转到登录页面。这个登录页面为节点中的loginUrl属性值,默认为网站根目录下的login.aspx。在跳转到到登录页面时,服务器会将当前访问的网页的地址,如index.aspx,作为ReturnUrl参数值附加到login.aspx的URL中。如下所示:
login.aspx?ReturnUrl=%2findex.aspx
用户在登录页面输入用户名和密码(或别的登录信息),并将这些信息提交到服务器。
服务器接收到用户提交的信息之后,判断用户是否为合法用户(判断方式有很多种,但这不是本文的重点),如果用户为合法用户,则创建一个包含窗体身份验证票的Cookie。
在创建完包含窗体身份验证票的Cookie之后,可以使用代码将网页跳回登录前访问的页面,如index.aspx网页(这个时侯ReturnUrl参数就起作用了)。
在登录后访问其它网页时(如跳转后的index.aspx页面),服务器会检测包含窗体身份验证的Cookie,并对用户进行身份验证。身
》》 window 集成认证
在IIS里取消匿名访问权限,若允许匿名访问,就没有必须提供验证凭证了
R2RServiceSerialNumber sN = new R2RServiceSerialNumber();
sN.Url = "http://172.xxxxx/R2RServiceSerialNumber.asmx";
sN.Credentials = new NetworkCredential("用户名", "密码"); //用户名密码
SerialNumber SN = sN.GetSerialNumber("续保");
strSerialNumber = SN.CurrentSerialNumber;
》》 表单认证 FORM认证
<forms
name="name"
loginUrl="URL"
defaultUrl="URL"
protection="[All|None|Encryption|Validation]"
timeout="[MM]"
path="path"
requireSSL="[true|false]"
slidingExpiration="[true|false]">
enableCrossAppRedirects="[true|false]"
cookieless="[UseUri|UseCookie|AutoDetect|UseDeviceProfile]"
domain="domain name"
ticketCompatibilityMode="[Framework20|Framework40]">
<credentials>...</credentials>
</forms>
name:指定要用于身份验证的 HTTP Cookie。如果正在一台服务器上运行多个应用程序并且每个应用程序都需要唯一的 Cookie,则必须在每个应用程序的 Web.config 文件中配置 Cookie 名称。默认值为 ".ASPXAUTH"。
loginUrl:指定如果找不到任何有效的身份验证 Cookie,将请求重定向到的用于登录的 URL。默认值为 login.aspx。
defaultUrl:定义在身份验证之后用于重定向的默认 URL。默认值为 "default.aspx"。 如果登录页面的URL中没有ReturnUrl参数(也就是说,直接访问的登录页面)如果没有设置该属性,默认情况下为default.aspx页面。
protection:指定 Cookie 使用的加密类型(如果有)。默认值为 All。
timeout:指定 Cookie 过期前逝去的时间(以整数分钟为单位)。如果 SlidingExpiration 属性为 true,则 timeout 属性是滑动值,会在接收到上一个请求之后的指定时间(以分钟为单位)后过期。 为防止危及性能并避免向开启 Cookie 警告的用户发出多个浏览器警告,当指定的时间逝去大半时将更新 Cookie。这可能导致精确性受损。默认值为 "30"(30 分钟)。
path:为应用程序发出的 Cookie 指定路径。默认值是斜杠 ( /),这是因为大多数浏览器是区分大小写的,如果路径大小写不匹配,浏览器不会送回 Cookie。
requireSSL:指定是否需要 SSL 连接来传输身份验证 Cookie。默认值为 False。
slidingExpiration:指定是否启用可调过期时间。可调过期将 Cookie 的当前身份验证时间重置为在单个会话期间收到每个请求时过期。默认值为 True。
enableCrossAppRedirects:表明是否将通过身份验证的用户重定向到其他 Web 应用程序中的 URL。默认值为 False。
cookieless:定义是否使用 Cookie 以及 Cookie 的行为。默认值为 UseDeviceProfile.
domain:指定在传出 Forms 身份验证 Cookie 中设置的可选域。此设置的优先级高于 httpCookies 元素中使用的域。默认值为空字符串 ("")。
ticketCompatibilityMode:指定在 Forms 身份验证中对于票证到期日期使用协调世界时 (UTC) 还是本地时间。默认值为 Framework20。
**子元素**
credentials:允许选择在配置文件中定义名称和密码凭据。您还可以实现自定义的密码架构,以使用外部源(如数据库)来控制验证。
Clear 代表 密码是 明文
credentials 案例
》》》
》》FormsAuthentication.SetAuthCookie()方法创建身份验证票据的方法,事实上,这是一个使用缺省的身份验证票据的方法。在asp.net中,Forms身份验证的方式是在用户登录时创建一个身份验证票,然后将这个身份验证票存放在Cookie中,以后整个网站都可以通过这个Cookie来判断用户是否已经登录。如果用户浏览器不支持Cookie,
1。asp.net也可以将票证放在URL的查询字符串中进行传递,
2。放在header里
退出系统要
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage()”将网页跳转到登录页面。
FormsAuthenticationTicket()的
第一个参数为身份验证票的版本号,通常为1;
第二个参数为经过验证的用户名;
第三个参数为票证发出时的本地时间;
第四个参数为票证过期时的本地时间;
第五个参数为是否创建永久的Cookie;
第六个参数,也就是最重要的参数,为要传递的用户数据 【比如用户角色】。如果不需要传递用户数据,可以设为null。但本人建议使用空字符串,否则为票证加密时可能会产生意想不到的问题。
创建完身份验证票之后,可以使用FormsAuthentication类的Encrypt()方法为身份验证票加密,加密后返回一个字符串。
然后创建一个Cookie,FormsAuthentication.FormsCookieName属性可以返回存放身份验证票的Cookie名,也就是web.config中<Forms>小节的name属性值。当然,在这里还可以设置一些Cookie相关的属性,如Cookie的path、expires、domain等,这此就不多介绍了。
最后,将Cookie写入到客户端。
》》获取数据
string ss=System.Web.Security.FormsAuthentication.FormsCookiePath;
string s= System.Web.Security.FormsAuthentication.FormsCookieName; 在web.config中Forms
》》》asp.net 不同目录,不用角色
》》Global.asax
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
//判断正在请求页的用户的身份验证信息是否为空
if (HttpContext.Current.User != null)
{
//判断用户是否已经进行了身份验证
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
//判断当前用户身份验证的方式是否为Forms身份验证方式
if (HttpContext.Current.User.Identity is FormsIdentity)
{
//获得进行了Forms身份验证的用户标识
FormsIdentity UserIdent = (FormsIdentity)(HttpContext.Current.User.Identity);
//从身份验证票中获得用户数据
string UserData = UserIdent.Ticket.UserData;
//分割用户数据得到用户角色数组
string[] rolues = UserData.Split(',');
//从用户标识和角色组初始化GenericPrincipal类
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(UserIdent, rolues);
}
}
}
}
//用户名
string UserName = TextBox1.Text;
//密码
string UserPassword = TextBox2.Text;
//用户角色
string UserRole = "";
//用户身份验证
if ((UserName == "1" && UserPassword == "1") || (UserName == "2" && UserPassword == "2") )
{
//判断用户权限
switch (UserName)
{
case "1":
UserRole = "Admin";
break;
case "2":
UserRole = "backup";
break;
}
//创建一个身份验证票
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, "zen", DateTime.Now, DateTime.Now.AddMinutes(30), false, UserRole);
//将身份验证票加密
string EncrTicket = FormsAuthentication.Encrypt(ticket);
//创建一个Cookie
HttpCookie myCookie = new HttpCookie(FormsAuthentication.FormsCookieName, EncrTicket);
//将Cookie写入客户端
Response.Cookies.Add(myCookie);
//跳转到初始请求页或默认页面
Response.Redirect(FormsAuthentication.GetRedirectUrl("zen", false));
}
Asp.net Window 认证
》》需要在IIS 中 开启Window是身份证验证,其它的关闭
asp.net 项目 web.config 配置
public partial class zen : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var authenticationType = System.Web.HttpContext.Current.User.Identity.AuthenticationType;
var domainUserName = System.Web.HttpContext.Current.User.Identity.Name;
Response.Write("域账号:" + domainUserName + "<br/>");
Response.Write("认证类型:" + authenticationType + "<br/>");
var user = this.GetUserInfo(domainUserName);
if (user != null)
{
Response.Write("登录名:" + user.SAMAccountName + "<br/>");
Response.Write("短名称:" + user.GivenName + "<br/>");
Response.Write("名称:" + user.CN + "<br/>");
Response.Write("邮件:" + user.Email + "<br/>");
}
}
private UserInfo GetUserInfo(string domainUserName)
{
try
{
if (string.IsNullOrEmpty(domainUserName))
{
return null;
}
var userArr = domainUserName.Split('\\');
var domain = userArr[0];
var loginName = userArr[1];
var entry = new DirectoryEntry(string.Concat("LDAP://", domain));
var search = new DirectorySearcher(entry);
search.Filter = string.Format("(SAMAccountName={0})", loginName);
search.PropertiesToLoad.Add("SAMAccountName");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("cn");
search.PropertiesToLoad.Add("mail");
var result = search.FindOne();
if (result != null)
{
var info = new UserInfo();
info.SAMAccountName = result.Properties["SAMAccountName"][0].ToString();
info.GivenName = result.Properties["givenName"][0].ToString();
info.CN = result.Properties["cn"][0].ToString();
info.Email = result.Properties["mail"][0].ToString();
return info;
}
}
catch
{ }
return null;
}
public sealed class UserInfo
{
public string SAMAccountName;
public string GivenName;
public string CN;
public string Email;
}
}
MVC 权限认证
》》》Forms 表单认证
》》访问网页时,先请求AuthorizeCore这个方法,
AuthorizeAttribute 命名空间System.Web.Mvc
public class MyAuthorizeAttribute:AuthorizeAttribute
{
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var roles = ticket.UserData;
var inRoles = false;
foreach (var role in roles.Split(','))
{
if (Roles.Contains(role))
{
inRoles = true;
break;
}
}
return inRoles;
}
}
》》 Home视图下面Index 访问需要Admin 角色才能访问
<form method="post">
<table>
<tr>
<td>用户名</td>
<td><input name="username" type="text" /></td>
</tr>
<tr>
<td>密码</td>
<td><input name="password" type="password" /></td>
</tr>
<tr>
<td colspan="2" ><input type="submit" value="登录"/></td>
</tr>
</table>
</form>
MVC Forms 表单认证
public class AccountController : Controller
{
// GET: Account
public ActionResult Login()
{
string name = Request["username"];
string password = Request["password"];
if (name == "zen" && password == "123456")
{
FormsAuthentication.SetAuthCookie(name, false);
return Redirect(FormsAuthentication.GetRedirectUrl(name,false));
}
else
{
return View();
}
}
}
》》》》 上面是MVC 默认的 AuthorizeAttribute
》》》 自定义ActionFilterAttribute
public class RequiresAuthenticationAttribute: ActionFilterAttribute
{
public string Role { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!string.IsNullOrEmpty(Role))
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
string returnUrl = filterContext.HttpContext.Request.Url.AbsolutePath;
string redirectUrl = string.Format("?ReturnUrl={0}", returnUrl);
string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
else
{
bool isAuthenticated = filterContext.HttpContext.User.IsInRole(Role);
if (!isAuthenticated)
{
throw new UnauthorizedAccessException("You have no right to view the page!");
}
}
}
else
{
throw new InvalidOperationException("No Role Specified!");
}
}
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
//判断正在请求页的用户的身份验证信息是否为空
if (HttpContext.Current.User != null)
{
//判断用户是否已经进行了身份验证
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
//判断当前用户身份验证的方式是否为Forms身份验证方式
if (HttpContext.Current.User.Identity is FormsIdentity)
{
//获得进行了Forms身份验证的用户标识
FormsIdentity UserIdent = (FormsIdentity)(HttpContext.Current.User.Identity);
//从身份验证票中获得用户数据
string UserData = UserIdent.Ticket.UserData;
//分割用户数据得到用户角色数组
string[] rolues = UserData.Split(',');
//从用户标识和角色组初始化GenericPrincipal类
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(UserIdent, rolues);
}
}
}
}
webapi 摘要认证
摘要认证 服务器(IIS)要加入域,不然IIS中的摘要身份认证 启动是灰色的,无法使用。
同时要访问的客户端 也要跟IIS加入同一个域,不然无法请求
下面大致看一下这部分的验证流程:
1:客户端请求 地址;
2:服务端返回401未验证的状态,并且在返回的信息中包含了验证方式Digest,realm的值,QOP(quality of protection)只设置成auth,nonce为一串随机值,在下面的请求中会一直使用到,当过了存活期后服务端将刷新生成一个新的nonce值;
3:客户端接受到请求返回后,将username:realm:password进行HASH运算,假设运算后的值为HA1。又将请求的路径/api/employees进行HASH运算,假设运算后的值为HA2。再将HA1:nonce:nc:cnonce:qop:HA2进行HASH运算,得到的值放在response中。这里的cnonce为客户端生成的nonce值,而nc用于统计,假设开始时为00000001,下次请求后就变成了00000002,不一定每次都加1,但是后面请求中的nc值肯定大于前一次请求中的nc值。
4:服务端收到请求后将验证nonce是否过期,如果过期,那么直接返回401,即第二步的状态。如果没有过期,那么比较nc值,如果比前一次nc值小或者前一次根本没有存储的nc值,那么也将直接返回401状态。如果前面的验证都通过,那么服务端也将按照步骤3中计算最终HASH值的步骤计算出HASH值与客户端的进行比较,然后比较客户端提交过来的HASH值与服务端计算出来的HASH进行比较,不匹配返回401,匹配获取请求的数据并返回状态200。
摘要验证主要就是通过上面的HASH比较的步骤避免掉了基本验证中的安全性问题。
》》》》 摘要式身份认证 流程
HTTP/1.0 401 Unauthorized
Server: HTTPd/0.9
Date: Sun, 10 Apr 2024 20:26:47 GMT
WWW-Authenticate: Digest realm=" 领域",
qop=“auth,auth-int”,
nonce=“dcd98b7102dd2f0e8b11d0f60”,
opaque=“5ccc069c3ebaf9f0171e9517f40e41”
需要注意的是,如果需要IIS支持摘要验证,需要把IIS摘要验证的特性勾上=》按照IIS要勾选
AuthenticationHandler
public class AuthenticationHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
HttpRequestHeaders headers = request.Headers;
if (headers.Authorization != null)
{
Header header = new Header(request.Headers.Authorization.Parameter, request.Method.Method);
if (Nonce.IsValid(header.Nonce, header.NounceCounter))
{
// Just assuming password is same as username for the purpose of illustration
string password = header.UserName;
string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).ToMD5Hash();
string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash();
string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
ha1, header.Nonce, header.NounceCounter, header.Cnonce, "auth", ha2).ToMD5Hash();
if (String.CompareOrdinal(header.Response, computedResponse) == 0)
{
// digest computed matches the value sent by client in the response field.
// Looks like an authentic client! Create a principal.
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, header.UserName),
new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)
};
ClaimsPrincipal principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
HttpContext.Current.User = principal;
}
}
}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", Header.UnauthorizedResponseHeader.ToString()));
}
return response;
}
catch (Exception)
{
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", Header.UnauthorizedResponseHeader.ToString()));
return response;
}
}
}
HashHelper
public static class HashHelper
{
public static string ToMD5Hash(this byte[] bytes)
{
StringBuilder hash = new StringBuilder();
MD5 md5 = MD5.Create();
md5.ComputeHash(bytes)
.ToList()
.ForEach(b => hash.AppendFormat("{0:x2}", b));
return hash.ToString();
}
public static string ToMD5Hash(this string inputString)
{
return Encoding.UTF8.GetBytes(inputString).ToMD5Hash();
}
}
Header
public class Header
{
public Header() { }
public Header(string header, string method)
{
string keyValuePairs = header.Replace("\"", String.Empty);
foreach (string keyValuePair in keyValuePairs.Split(','))
{
int index = keyValuePair.IndexOf("=");
string key = keyValuePair.Substring(0, index);
string value = keyValuePair.Substring(index + 1);
switch (key)
{
case "username": this.UserName = value; break;
case "realm": this.Realm = value; break;
case "nonce": this.Nonce = value; break;
case "uri": this.Uri = value; break;
case "nc": this.NounceCounter = value; break;
case "cnonce": this.Cnonce = value; break;
case "response": this.Response = value; break;
case "method": this.Method = value; break;
}
}
if (String.IsNullOrEmpty(this.Method))
this.Method = method;
}
public string Cnonce { get; private set; }
public string Nonce { get; private set; }
public string Realm { get; private set; }
public string UserName { get; private set; }
public string Uri { get; private set; }
public string Response { get; private set; }
public string Method { get; private set; }
public string NounceCounter { get; private set; }
// This property is used by the handler to generate a
// nonce and get it ready to be packaged in the
// WWW-Authenticate header, as part of 401 response
public static Header UnauthorizedResponseHeader
{
get
{
return new Header()
{
Realm = "RealmOfBadri",
Nonce = WebApplication9.Nonce.Generate()
};
}
}
public override string ToString()
{
StringBuilder header = new StringBuilder();
header.AppendFormat("realm=\"{0}\"", Realm);
header.AppendFormat(", nonce=\"{0}\"", Nonce);
header.AppendFormat(", qop=\"{0}\"", "auth");
return header.ToString();
}
}
》》nonce
public class Nonce
{
private static ConcurrentDictionary<string, Tuple<int, DateTime>>
nonces = new ConcurrentDictionary<string, Tuple<int, DateTime>>();
public static string Generate()
{
byte[] bytes = new byte[16];
using (var rngProvider = new RNGCryptoServiceProvider())
{
rngProvider.GetBytes(bytes);
}
string nonce = bytes.ToMD5Hash();
nonces.TryAdd(nonce, new Tuple<int, DateTime>(0, DateTime.Now.AddMinutes(10)));
return nonce;
}
public static bool IsValid(string nonce, string nonceCount)
{
Tuple<int, DateTime> cachedNonce = null;
nonces.TryGetValue(nonce, out cachedNonce);
if (cachedNonce != null) // nonce is found
{
// nonce count is greater than the one in record
if (Int32.Parse(nonceCount) > cachedNonce.Item1)
{
// nonce has not expired yet
if (cachedNonce.Item2 > DateTime.Now)
{
// update the dictionary to reflect the nonce count just received in this request
nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount),
cachedNonce.Item2);
// Every thing looks ok - server nonce is fresh and nonce count seems to be
// incremented. Does not look like replay.
return true;
}
}
}
return false;
}
}
webapi Http基本认证 Basic
客户端向服务端发送一个携带基于用户名/密码的认证凭证的请求。认证凭证的格式为“{UserName}:{Password}”,并采用Base64编码,经过编码的认证凭证被存放在请求报头Authorization中,Authorization报头值类似:Basic MTIzNDU2OjEyMzQ1Ng==。服务端接收到请求之后,从Authorization报头中提取凭证并对其进行解码,最后采用提取的用户名和密码实施认证。认证成功之后,该请求会得到正常的处理,并回复一个正常的响应。
注:其实basic 的参数传输方式还是一种不错的数据传输加密方式哦,多采用这种前后端数据交互方式的项目颇多,只是一般与https一起使用。更加安全
1、Convert.FromBase64String这句是解密经过BASE64加密的报文中的Authorization值,然后得到带格式的用户登录数据:{UserName}:{Password}
得到用户userid就可以自定义验证用户合法性了
2、HandleUnauthorizedRequest重写这个方法是为了服务器返回basic认证的格式,即前台弹出的那个登录框,
而BASE64加密及报文传输这不能算是basic认证特有,我们的表单数据传输都可以用这种方式
》》》录入正确的账号密码 就可以访问了
那是https 是加密传输的。除此之外,内容在客户端和服务端都是明文显示的哦,大家要注意了
https 是最伟大的发明
》》》BasicAuthorizeAttribute
public class BasicAuthorizeAttribute: AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.Method == HttpMethod.Options) return true;
if (actionContext.Request.Headers.Authorization != null && actionContext.Request.Headers.Authorization.Parameter != null)
{
var authorizationParameter = Convert.FromBase64String(actionContext.Request.Headers.Authorization.Parameter);
var basicArray = Encoding.Default.GetString(authorizationParameter).Split(':');
var userid = basicArray[0];
var password = basicArray[1];
if (userid == "123456" && password == "123456")
{
return true;
}
}
return false;
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var responseMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized);
responseMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(responseMessage);
}
}