万人空巷的近义词>>>>在网上打鱼手机怎么开通???

WebApi接口安全认证——HTTP之摘要认证 - 尽善而为 - ITeye技术网站
博客分类:
摘要访问认证是一种协议规定的Web服务器用来同网页浏览器进行认证信息协商的方法。它在密码发出前,先对其应用哈希函数,这相对于HTTP基本认证发送明文而言,更安全。从技术上讲,摘要认证是使用随机数来阻止进行密码分析的MD5加密哈希函数应用。它使用HTTP协议。
一、摘要认证基本流程:
1.客户端请求 (无认证)
GET /dir/index.html HTTP/1.0
Host: localhost
2.服务器响应
服务端返回401未验证的状态,并且返回WWW-Authenticate信息,包含了验证方式Digest,realm,qop,nonce,opaque的值。其中:
Digest:认证方式;
realm:领域,领域参数是强制的,在所有的盘问中都必须有,它的目的是鉴别SIP消息中的机密,在SIP实际应用中,它通常设置为SIP代理服务器所负责的域名;
qop:保护的质量,这个参数规定服务器支持哪种保护方案,客户端可以从列表中选择一个。值 “auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。需要看更详细的描述,请参阅RFC2617;
nonce:为一串随机值,在下面的请求中会一直使用到,当过了存活期后服务端将刷新生成一个新的nonce值;
opaque:一个不透明的(不让外人知道其意义)数据字符串,在盘问中发送给用户。
HTTP/1.0 401 Unauthorized
Server: HTTPd/0.9
Date: Sun, 10 Apr :47 GMT
WWW-Authenticate: Digest realm="",
qop="auth,auth-int",
nonce="dcd98be8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9fe41"
3.客户端请求
(用户名 "Mufasa", 密码 "Circle Of Life")
客户端接受到请求返回后,进行HASH运算,返回Authorization参数
其中:realm,nonce,qop由服务器产生;
uri:客户端想要访问的URI;
nc:“现时”计数器,这是一个16进制的数值,即客户端发送出请求的数量(包括当前这个请求),这些请求都使用了当前请求中这个“现时”值。例如,对一个给定的“现时”值,在响应的第一个请求中,客户端将发送“nc=”。这个指示值的目的,是让服务器保持这个计数器的一个副本,以便检测重复的请求。如果这个相同的值看到了两次,则这个请求是重复的;
cnonce:这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护;
response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令。
response计算过程:
HA1=MD5(A1)=MD5(username:realm:password)
如果 qop 值为“auth”或未指定,那么 HA2 为
HA2=MD5(A2)=MD5(method:digestURI)
如果 qop 值为“auth-int”,那么 HA2 为
HA2=MD5(A2)=MD5(method:digestURI:MD5(entityBody))
如果 qop 值为“auth”或“auth-int”,那么如下计算 response:
response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)
如果 qop 未指定,那么如下计算 response:
response=MD5(HA1:nonce:HA2)
GET /dir/index.html HTTP/1.0
Host: localhost
Authorization: Digest username="Mufasa",
nonce="dcd98be8b11d0f600bfb0c093",
uri="/dir/index.html",
cnonce="0a4f113b",
response="6629faeef1",
opaque="5ccc069c403ebaf9fe41"
4.服务器响应
当服务器接收到摘要响应,也要重新计算响应中各参数的值,并利用客户端提供的参数值,和服务器上存储的口令,进行比对。如果计算结果与收到的客户响应值是相同的,则客户已证明它知道口令,因而客户的身份验证通过。
HTTP/1.0 200 OK
二、服务端验证
编写一个自定义消息处理器,需要通过System.Net.Http.DelegatingHandler进行派生,并重写SendAsync方法。
public class AuthenticationHandler : DelegatingHandler
protected async override Task&HttpResponseMessage& SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
HttpRequestHeaders headers = request.H
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.UserN
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.once, "auth", ha2).ToMD5Hash();
if (pareOrdinal(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 =
if (HttpContext.Current != null)
HttpContext.Current.User =
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));
catch (Exception)
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));
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("=", System.StringComparison.Ordinal);
string key = keyValuePair.Substring(0, index);
string value = keyValuePair.Substring(index + 1);
switch (key)
case "username": this.UserName =
case "realm": this.Realm =
case "nonce": this.Nonce =
case "uri": this.Uri =
case "nc": this.NounceCounter =
case "cnonce": once =
case "response": this.Response =
case "method": this.Method =
if (String.IsNullOrEmpty(this.Method))
this.Method =
public string Cnonce { }
public string Nonce { }
public string Realm { }
public string UserName { }
public string Uri { }
public string Response { }
public string Method { }
public string NounceCounter { }
// 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
return new Header()
Realm = "MyRealm",
Nonce = WebApiDemo.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();
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)));
public static bool IsValid(string nonce, string nonceCount)
Tuple&int, DateTime& cachedNonce =
//nonces.TryGetValue(nonce, out cachedNonce);
nonces.TryRemove(nonce, out cachedNonce);//每个nonce只允许使用一次
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.
需要使用摘要验证可在代码里添加Attribute [Authorize],如:
[Authorize]
public class ProductsController : ApiController
最后Global.asax里需注册下
GlobalConfiguration.Configuration.MessageHandlers.Add(
new AuthenticationHandler());
三、客户端的调用
这里主要说明使用WebClient调用
public static string Request(string sUrl, string sMethod, string sEntity, string sContentType,
out string sMessage)
sMessage = "";
using (System.Net.WebClient client = new System.Net.WebClient())
client.Credentials = CreateAuthenticateValue(sUrl);
client.Headers = CreateHeader(sContentType);
Uri url = new Uri(sUrl);
byte[] bytes = Encoding.UTF8.GetBytes(sEntity);
switch (sMethod.ToUpper())
case "GET":
buffer = client.DownloadData(url);
case "POST":
buffer = client.UploadData(url, "POST", bytes);
buffer = client.UploadData(url, "POST", bytes);
return Encoding.UTF8.GetString(buffer);
catch (WebException ex)
sMessage = ex.M
var rsp = ex.Response as HttpWebR
var httpStatusCode = rsp.StatusC
var authenticate = rsp.Headers.Get("WWW-Authenticate");
return "";
catch (Exception ex)
sMessage = ex.M
return "";
关键代码,在这里添加用户认证,使用NetworkCredential
private static CredentialCache CreateAuthenticateValue(string sUrl)
CredentialCache credentialCache = new CredentialCache();
credentialCache.Add(new Uri(sUrl), "Digest", new NetworkCredential("Lime", "Lime"));
return credentialC
至此整个认证就ok了。
浏览 41662
zrj_software
浏览: 143928 次
来自: 南京
用了这种验证的WEBAPI怎么调用啊? 我按你的方法写了,调用 ...
正在找这方面的资料,很有用!
运行之后结果还没没有出来怎么回事啊?
没看到什么实际意义
android 不能直连远程数据库么?APP与服务端保持登录状态 -
- ITeye技术网站
由于APP向服务端发起请求属于跨域访问,每次访问在服务端都会产生一个新的session,因此APP客户端与web端不同,无法通过session来保持登录状态。
为了维护app用户的登录状态,我们可以利用token来实现。
客户端输入账号密码,发起登录请求,服务端在登录接口验证通过后,给客户端返回一个任意字符串,既token,生成算法可随机,token必须与用户的账户关联,如用userid和token形成键值对,保存在内存中(redis)。客户端拿到这个token后,就相当于被服务端承认正常登录成功了,在之后所有需要验证的请求中,带上token,服务端验证token是否存在,是否有效。
出于安全考虑,token在每次登录时重新生成,并可以设置有效期,每次有效操作后更新token的时间戳,保证token有效期往后延续。
为了避免token被截获,伪造非法请求,在每次请求时,可以用userid+token+时间戳+密钥+请求参数,进行签名,服务端验证token,同时验证签名,以保证请求的安全性。
浏览: 2280 次53300人阅读
App接口设计与开发(32)
用PHP做服务器接口客户端用http协议POST访问安全性一般怎么做
我的问题是,如果不做安全相关处理的话,一些可能改变数据库的操作可能会遭遇垃圾数据提交什么的,毕竟要找到这些信息只要找个http包就可以了
系统无用户登录
新手问题(从来没做过服务端开发),如果可以,给几个主流方法的链接,多谢
直观总结方法二:
1.请求头里带用户username和password,到服务器端做验证,通过才继续下边业务逻辑。
有点:防止了服务器端api被随意调用。
缺点:每次都交互用户名和密码,交互量大,且密码明文传输不安全。
2.第一次请求,要求username和password,验证通过,种cookie到客户端,app保存cookie值。
每次请求带上cookie。
点评:和pc上浏览器认证的原理一样了。
以上两点,只有注册用户,才能有权访问业务逻辑,而app有大量的不需要注册数据api
3.制定一个token生成规则,按某些服务器端和客户端都拥有的共同属性生成一个随机串,客户端生成这个串,服务器收到请求也校验这个串。
缺点:随机串生成规则要保密。
比如:一个使用php框架的工程,框架每次交互都会有 module和action两个参数做路由,这样的话,我就可以用下边这个规则来生成token
app要请求用户列表,api是“index.php?module=user&action=list”
app生成token = md5sum ('user'.''.'#$@%!'.list) = 880fed4ca2aabd20ae9a5dd;
实际发起请求为 “index.php?module=user&action=list&token=880fed4ca2aabd20ae9a5dd”
服务器端接到请求用同样方法计算token,
$module = $_GET['module'];
$action = $_GET['action'];
$token = md5sum($module.date('Y-m-d',time()).'#$@%!*'.$action);
if($token != $_GET['token']){
alarm('access deny');
先提这三个,都在项目中用过。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3071955次
积分:19667
积分:19667
排名:第393名
原创:24篇
转载:764篇
评论:852条
(1)(5)(3)(74)(30)(126)(18)(3)(1)(3)(7)(16)(43)(1)(5)(2)(2)(1)(18)(6)(3)(27)(10)(7)(6)(2)(7)(8)(1)(6)(27)(22)(9)(29)(30)(42)(19)(34)(10)(27)(83)(17)(17)利用缓存实现APP端与服务器接口交互的Session控制_Java教程_动态网站制作指南
利用缓存实现APP端与服务器接口交互的Session控制
来源:人气:2090
与传统B/S模式的Web系统不同,移动端APP与服务器之间的接口交互一般是C/S模式,这种情况下如果涉及到用户登录的话,就不能像Web系统那样依赖于Web容器来管理了,因为APP每发一次请求都会在服务器端创建一个新的Session。而有些涉及到用户隐私或者资金交易的接口又必须确认当前用户登录的合法性,如果没有登录或者登录已过期则不能进行此类操作。我见过一种&偷懒&的方式,就是在用户第一次登录之后,保存用户的ID在本地存储中,之后跟服务器交互的接口都通过用户ID来标识用户身份。
这种方式主要有两个弊端:
只要本地存储的用户ID没有被删掉,就始终可以访问以上接口,不需要重新登录,除非增加有效期的判断或者用户主动退出;
接口安全性弱,因为用户ID对应了里的用户唯一标识,别人只要能拿到用户ID或者伪造一个用户ID就可以使用以上接口对该用户进行非法操作。
综上考虑,可以利用缓存在服务器端模拟Session管理机制来解决这个问题,当然这只是目前我所知道的一种比较简单有效的解决APP用户Session的方案。如果哪位朋友有其它好的方案,欢迎在下面留言交流。
这里用的缓存框架是Ehcache,下载地址http://www.ehcache.org/downloads/,当然也可以用Memcached或者其它的。之所以用Ehcache框架,一方面因为它轻量、快速、集成简单等,另一方面它也是Hibernate中默认的Cacheovider,对于已经集成了Hibernate的项目不需要再额外添加Ehcache的jar包了。
有了Ehcache,接着就要在Spring配置文件里添加相应的配置了,配置信息如下:
1 &!-- 配置缓存管理器工厂 --&
2 &bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"&
&property name="configLocation" value="classpath:ehcache." /&
&property name="shared" value="true" /&
6 &!-- 配置缓存工厂,缓存名称为myCache --&
7 &bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"&
&property name="cacheName" value="myCache" /&
&property name="cacheManager" ref="cacheManager" /&
10 &/bean&
另外,Ehcache的配置文件ehcache.xml里的配置如下:
1 &?xml version="1.0" encoding="gbk"?&
2 &ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"&
&diskStore path=".io.tmpdir" /&
&!-- 配置一个默认缓存,必须的 --&
&defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false" /&
&!-- 配置自定义缓存 maxElementsInMemory:缓存中允许创建的最大对象数 eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前, 两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,
如果该值是 0 就意味着元素可以停顿无穷长的时间。 timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,
这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。 overflowToDisk:内存不足时,是否启用磁盘缓存。 memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。 --&
&cache name="myCache" maxElementsInMemory="10000" eternal="true" overflowToDisk="true" memoryStoreEvictionPolicy="LFU" /&
14 &/ehcache&
配置好Ehcache之后,就可以直接通过@Autowired或者@Resource缓存实例了。示例代码如下:
1 @Component
2 public class Memory {
@Autowired
private C // 注意这里引入的Cache是net.sf.ehcache.Cache
public void setValue(String key, String value) {
ehcache.put(new Element(key, value));
public Object getValue(String key) {
Element element = ehcache.get(key);
return element != null ? element.getValue() : null;
缓存准备完毕,接下来就是模拟用户Session了,实现思路是这样的:
用户登录成功后,服务器端按照一定规则生成一个Token令牌,Token是可变的,也可以是固定的(后面会说明);
将Token作为key,用户信息作为value放到缓存中,设置有效时长(比如30分钟内没有访问就失效);
将Token返回给APP端,APP保存到本地存储中以便请求接口时带上此参数;
通过拦截器拦截所有涉及到用户隐私安全等方面的接口,验证请求中的Token参数合法性并检查缓存是否过期;
验证通过后,将Token值保存到线程存储中,以便当前线程的操作可以通过Token直接从缓存中索引当前登录的用户信息。
综上所述,APP端要做的事情就是登录并从服务器端获取Token存储起来,当访问用户隐私相关的接口时带上这个Token标识自己的身份。服务器端要做的就是拦截用户隐私相关的接口验证Token和登录信息,验证后将Token保存到线程变量里,之后可以在其它操作中取出这个Token并从缓存中获取当前用户信息。这样APP不需要知道用户ID,它拿到的只是一个身份标识,而且这个标识是可变的,服务器根据这个标识就可以知道要操作的是哪个用户。
对于Token是否可变,处理细节上有所不同,效果也不一样。
Token固定的情况:服务器端生成Token时将用户名和密码一起进行加密,即MD5(username+pass)。这样对于同一个用户而言,每次登录的Token是相同的,用户可以在多个客户端登录,共用一个Session,当用户密码变更时要求用户重新登录;
Token可变的情况:服务器端生成Token时将用户名、密码和当前时间戳一起MD5加密,即MD5(username+password+timestamp)。这样对于同一个用户而言,每次登录的Token都是不一样的,再清除上一次登录的缓存信息,即可实现唯一用户登录的效果。
为了保证同一个用户在缓存中只有一条登录信息,服务器端在生成Token后,可以再单独对用户名进行MD5作为Seed,即MD5(username)。再将Seed作为key,Token作为value保存到缓存中,这样即便Token是变化的,但每个用户的Seed是固定的,就可以通过Seed索引到Token,再通过Token清除上一次的登录信息,避免重复登录时缓存中保存过多无效的登录信息。
基于Token的Session控制部分代码如下:
1 @Component
2 public class Memory {
@Autowired
* 关闭缓存管理器
@PreDestroy
protected void shutdown() {
if (ehcache != null) {
ehcache.getCacheManager().shutdown();
* 保存当前登录用户信息
* @param loginUser
public void saveLoginUser(LoginUser loginUser) {
// 生成seed和token值
String seed = MD5Util.getMD5Code(loginUser.getUsername());
String token = TokenProcessor.getInstance().generateToken(seed, true);
// 保存token到登录用户中
loginUser.setToken(token);
// 清空之前的登录信息
clearLoginInfoBySeed(seed);
// 保存新的token和登录信息
String timeout = getSystemValue(SystemParam.TOKEN_TIMEOUT);
int ttiExpiry = NumberUtils.toInt(timeout) * 60; // 转换成秒
ehcache.put(new Element(seed, token, false, ttiExpiry, 0));
ehcache.put(new Element(token, loginUser, false, ttiExpiry, 0));
* 获取当前线程中的用户信息
public LoginUser currentLoginUser() {
Element element = ehcache.get(ThreadTokenHolder.getToken());
return element == null ? null : (LoginUser) element.getValue();
* 根据token检查用户是否登录
* @param token
public boolean checkLoginInfo(String token) {
Element element = ehcache.get(token);
return element != null && (LoginUser) element.getValue() != null;
* 清空登录信息
public void clearLoginInfo() {
LoginUser loginUser = currentLoginUser();
if (loginUser != null) {
// 根据登录的用户名生成seed,然后清除登录信息
String seed = MD5Util.getMD5Code(loginUser.getUsername());
clearLoginInfoBySeed(seed);
* 根据seed清空登录信息
* @param seed
public void clearLoginInfoBySeed(String seed) {
// 根据seed找到对应的token
Element element = ehcache.get(seed);
if (element != null) {
// 根据token清空之前的登录信息
ehcache.remove(seed);
ehcache.remove(element.getValue());
Token拦截器部分代码如下:
1 public class TokenInterceptor extends HandlerInterceptorAdapter {
@Autowired
private List&String& allowL // 放行的URL列表
private static final PathMatcher PATH_MATCHER = new AntPathMatcher();
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 判断请求的URI是否运行放行,如果不允许则校验请求的token信息
if (!checkAllow(request.getRequestURI())) {
// 检查请求的token值是否为空
String token = getTokenFromRequest(request);
response.setContentType(MediaType._JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-cache, must-revalidate");
if (StringUtils.isEmpty(token)) {
response.getWriter().write("Token不能为空");
response.getWriter().close();
return false;
if (!memory.checkLoginInfo(token)) {
response.getWriter().write("Session已过期,请重新登录");
response.getWriter().close();
return false;
ThreadTokenHolder.setToken(token); // 保存当前token,用于Controller层获取登录用户信息
return super.preHandle(request, response, handler);
* 检查URI是否放行
* @param URI
* @return 返回检查结果
private boolean checkAllowAccess(String URI) {
if (!URI.startsWith("/")) {
URI = "/" + URI;
for (String allow : allowList) {
if (PATH_MATCHER.match(allow, URI)) {
return true;
return false;
* 从请求信息中获取token值
* @param request
* @return token值
private String getTokenFromRequest(HttpServletRequest request) {
// 默认从header里获取token值
String token = request.getHeader(Constants.TOKEN);
if (StringUtils.isEmpty(token)) {
// 从请求信息中获取token值
token = request.getParameter(Constants.TOKEN);
public List&String& getAllowList() {
return allowL
public void setAllowList(List&String& allowList) {
this.allowList = allowL
到这里,已经可以在一定程度上确保接口请求的合法性,不至于让别人那么容易伪造用户信息,即便别人通过非法手段拿到了Token也只是临时的,当缓存失效后或者用户重新登录后Token一样无效。如果服务器接口安全性要求更高一些,可以换成SSL协议以防请求信息被窃取。
优质网站模板

我要回帖

更多关于 万人空巷什么意思 的文章

 

随机推荐