Git地址:待上传
TestServiceImpl.svc中的Service 需要对应 webconfig中的serviceActivations下service节点
redis windows端程序去网上下载,redis账号在web.config中配置,但是要注意ServiceStack.Redis库配置密码时无法连接redis服务
redis第三方库为ServiceStack.Redis
开启或关闭认证过滤或需要忽略认证的接口、资源、文件夹 均在App_Data/Config/Auth.config中配置
------------------------------------------------------------------------------
测试接口一览
http://localhost:23322/TestServiceImpl.svc/help
1、模拟登录
http://localhost:23322/TestServiceImpl.svc/Test/Login GET
{
"code": 0,
"message":"登录成功,看看能不能调用需要认证接口",
"result": {
"AccessToken":"5842735E-78C9-4235-833F-8029D489AA84",
"UserId":"88888"
}
}
2、需要登陆的接口需要添加头
http://localhost:23322/TestServiceImpl.svc/Test/Auth/1/5 GET
Content-Type application/json
accesstoken 5842735E-78C9-4235-833F-8029D489AA84
accessid 88888
{
"code": 0,
"message":"请求成功啦",
"result":"Param1=1,Param2=5"
}
3、会忽略登录认证的接口
http://localhost:23322/TestServiceImpl.svc/Test/IgnoreAuth GET
{
"code": 0,
"message":"请求成功啦",
"result":null
}
核心代码
--------------AuthInterceptor.cs
using com.jiangjiesheng.auth;
using com.jiangjiesheng.auth.Constant;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Text.RegularExpressions;
using System.Web;
namespace com.jiangjiesheng.auth.Interceptors
{//IDispatchMessageInspector IClientMessageInspector
/**
*
* 20180429 拦截器
* 江节胜 dev@jiangjiesheng.cn
*
*/
public class AuthInterceptor : IDispatchMessageInspector
{
// 备份桌面的Weiz.Redis文件夹
String AuthConfigPath = JSConfigTool.getAppDataConfigPath(JSConfigTool.DataConfigPath.AuthConfig);
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
bool isOpenAuth = Boolean.Parse(JSConfigTool.getAppDataConfig(AuthConfigPath,"authConfig/isOpenAuth"));
if (!isOpenAuth)//关闭验证
{
return true;
}
HttpRequest currentRequest = HttpContext.Current.Request;
String rawUrl = currentRequest.RawUrl;
if (!isSkipCheck(rawUrl))
{
// var user = GetHeaderValue("keyval");
// throw new Exception("未经授权的访问!"); 没能处理异常 但是这个是必须的 WCF_ExceptionHandler : IErrorHandler 添加后还是提示没有处理异常
// return ResponseStatus.getCode(Status.NEED_LOGIN);
// throw new FaultException(string.Format("Exception accessing database:{0}",
//"ceshi "), new FaultCode("Connect to database"));
//获取登录数据 然后 放行 ,如果实在获取不到action 方法名 ,就通过参数来数据
var accessToken = HttpContext.Current.Request.Headers["accesstoken"];
var accessId = HttpContext.Current.Request.Headers["accessid"];
if (String.IsNullOrWhiteSpace(accessToken) || String.IsNullOrWhiteSpace(accessId))
{
ServerResponser = ServerResponse.create(ResponseStatus.getCode(Status.ILLEGAL_REQUEST), ResponseStatus.getDesc(Status.ILLEGAL_REQUEST),"");
this.ReplyAndAbortRequest(r, channel, instanceContext);
return false;
}
ServerResponsequery = UserInfoCache.getUserInfo(accessId, accessToken);
bool isLogined = query.IsSuccess();
if (!isLogined)
{
ServerResponser = ServerResponse.create(ResponseStatus.getCode(Status.NEED_LOGIN), query.message,"");
this.ReplyAndAbortRequest(r, channel, instanceContext);
return false;
}
//刷新有效时间放在getUserInfo() 中了
return false;
}
return true;
}
public void BeforeSendReply(ref Message reply, object correlationState)// //AfterReceiveRequest 的返回值 将在BeforeSendReply 中 correlationState 上读取
{
Message returnV = reply;
Console.Write(reply.ToString());
}
private string GetHeaderValue(string name, string ns =)
{
var RequestMessage = OperationContext.Current.RequestContext.RequestMessage;
var headers = OperationContext.Current.IncomingMessageHeaders;
var index = headers.FindHeader(name, ns);
if (index > -1)
return headers.GetHeader(index);
else
return null;
}
private void ReplyAndAbortRequest(T t, IClientChannel channel, InstanceContext instanceContext)//
{
String res = JsonHelper.ObjectToJsonNotForSerialize(t);
HttpContext.Current.Response.AddHeader("Content-Type","application/json");//实测两个都可以
HttpContext.Current.Response.ContentType ="application/json";//实测两个都可以
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("Content-Length","");//去掉Transfer-Encoding:chunked 头必须 但是某些情况导致请求不能中断
HttpContext.Current.Response.Write(JsonHelper.ObjectToJsonNotForSerialize(t));
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
HttpContext.Current.Response.Close();//这里处理结束后不要在BeforeSendReply()中继续调用
instanceContext.Abort();//起关键作用 中断
channel.Abort();
channel.Close();
HttpContext.Current.Request.Abort();
}
private bool isSkipCheck(String rawUrl)
{
if (string.IsNullOrEmpty(rawUrl))
{
return false;
}
String authIgnorePathStr = JSConfigTool.getAppDataConfig(AuthConfigPath,"authConfig/authIgnorePath");
authIgnorePathStr = authIgnorePathStr.Replace("\\r\\n","").Replace("\\\"","\"").Replace("\r\n","").Replace(" ","");
string[] arr = Regex.Split(authIgnorePathStr,"###", RegexOptions.IgnoreCase);
foreach (var urlin arr)
{
if (String.IsNullOrWhiteSpace(url))//最后一个是空
{
continue;
}
if (rawUrl.IndexOf(url) > -1)
{
return true;
}
}
return false;
}
}
}
--------RedisCacheHelper.cs
using ServiceStack.Redis;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
namespace com.jiangjiesheng.auth
{
/**
*
* 20180429 redis (ServiceStack.Redis)
* 江节胜 dev@jiangjiesheng.cn
*
*/
public class RedisCacheHelper
{
private static readonly PooledRedisClientManager pool =null;
private static readonly string[] redisHosts =null;
public static int RedisMaxReadPool = int.Parse(ConfigurationManager.AppSettings["redis_max_read_pool"]);
public static int RedisMaxWritePool = int.Parse(ConfigurationManager.AppSettings["redis_max_write_pool"]);
static RedisCacheHelper()//static 会优先初始化,测试如果不通过的话使用单例模式写
{
var redisHostStr = ConfigurationManager.AppSettings["redis_server_session"];
if (!string.IsNullOrEmpty(redisHostStr))
{
redisHosts = redisHostStr.Split(',');
if (redisHosts.Length > 0)
{
//var client = new RedisClient("127.0.0.1", 6379);
pool =new PooledRedisClientManager(redisHosts, redisHosts,
new RedisClientManagerConfig()
{
MaxWritePoolSize = RedisMaxWritePool,
MaxReadPoolSize = RedisMaxReadPool,
AutoStart =true
});
}
}
}
public static Int32 Add(string key, T value, DateTime expiry)
{
if (value ==null)
{
return 0;
}
if (expiry <= DateTime.Now)
{
Remove(key);
return 0;
}
try
{
if (pool !=null)
{
using (var r = pool.GetClient())
{
if (r !=null)
{
r.SendTimeout = 1000;
r.Set(key, value, expiry - DateTime.Now);
}
}
}
}
catch (Exception ex)
{
string msg = string.Format("{0}:{1}发生异常!{2}","cache","存储", key);
throw ex;
return 0;
}
return 1;
}
public static void Add(string key, T value, TimeSpan slidingExpiration)
{
if (value ==null)
{
return;
}
if (slidingExpiration.TotalSeconds <= 0)
{
Remove(key);
return;
}
try
{
if (pool !=null)
{
using (var r = pool.GetClient())
{
if (r !=null)
{
r.SendTimeout = 1000;
r.Set(key, value, slidingExpiration);
}
}
}
}
catch (Exception ex)
{
string msg = string.Format("{0}:{1}发生异常!{2}","cache","存储", key);
throw ex;
}
}
public static T Get(string key)
{
if (string.IsNullOrEmpty(key))
{
return default(T);
}
T obj =default(T);
try
{
if (pool !=null)
{
using (var r = pool.GetClient())
{
if (r !=null)
{
r.SendTimeout = 1000;
obj = r.Get(key);
}
}
}
}
catch (Exception ex)
{
string msg = string.Format("{0}:{1}发生异常!{2}","cache","获取", key);
throw ex;
}
return obj;
}
public static void Remove(string key)
{
try
{
if (pool !=null)
{
using (var r = pool.GetClient())
{
if (r !=null)
{
r.SendTimeout = 1000;
r.Remove(key);
}
}
}
}
catch (Exception ex)
{
string msg = string.Format("{0}:{1}发生异常!{2}","cache","删除", key);
throw ex;
}
}
public static bool Exists(string key)
{
try
{
if (pool !=null)
{
using (var r = pool.GetClient())
{
if (r !=null)
{
r.SendTimeout = 1000;
return r.ContainsKey(key);
}
}
}
}
catch (Exception ex)
{
string msg = string.Format("{0}:{1}发生异常!{2}","cache","是否存在", key);
throw ex;
}
return false;
}
public static bool Expire(string key,TimeSpan expireIn)
{
try
{
if (pool !=null)
{
using (var r = pool.GetClient())
{
if (r !=null)
{
r.SendTimeout = 1000;
return r.ExpireEntryIn(key,expireIn);
}
}
}
}
catch (Exception ex)
{
string msg = string.Format("{0}:{1}发生异常!{2}","cache","是否存在", key);
throw ex;
}
return false;
}
}
}
--------UserInfoCache.cs
using com.jiangjiesheng.auth;
using com.jiangjiesheng.auth.Constant;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace com.jiangjiesheng.auth
{
/**
*
* 20180429 登录用户信息读取、保存、更新等
* 江节胜 dev@jiangjiesheng.cn
*
*/
public class UserInfoCache
{
public const Double userValidTime = 12;
static String AuthConfigPath = JSConfigTool.getAppDataConfigPath(JSConfigTool.DataConfigPath.AuthConfig);
///
/// 存值统一使用手机号(方便管理)
///
/// 特别注意:此服务不能用作SSO登录,因为校验了accesstoken,不需要,后期如果要做SSO,就要在客户端保存一个独立固定值key,相当于cookie值
///
/// 成功时 msg 中返回key
///
public static ServerResponsesetOrUpdateUserInfo(AppUserInfoEntity user)
{
bool isOpenAuth = Boolean.Parse(JSConfigTool.getAppDataConfig(AuthConfigPath,"authConfig/isOpenAuth"));
if (!isOpenAuth)//关闭验证
{
return ServerResponse.create(-1,"保存登陆信息失败:配置设置为不使用接口校验","");
}
String accessToken = (Keys.KEY_REDIS_USER_ACCESSTOKEN_PREFIX + System.Guid.NewGuid().ToString()).ToUpper();//accessToken
try
{
//存的时候使用openId取值
//取值的时候使用使用accessToken+(手机号或者openId)校验
if (user !=null && !String.IsNullOrWhiteSpace(user.UserId))
{
user.AccessToken = accessToken;//用于读取时校验accessToken和accessId是否匹配
String userJson = JsonHelper.ObjectToJsonNotForSerialize(user);
String accessId = (Keys.KEY_REDIS_USER_ACCESSID_PREFIX + user.UserId);// accessId
var nowT = DateTime.Now;//UtcNow
var expiredT = nowT.AddHours(userValidTime);
int result = RedisCacheHelper.Add(accessId, userJson, expiredT);
if (result == 1)
{
return ServerResponse.create(0,"保存登陆信息成功", accessToken);//成功的时候返回 reidsKey
}
return ServerResponse.create(-1,"保存登陆信息失败", accessToken);
}
return ServerResponse.create(-1,"保存登陆信息失败:数据异常", accessToken);
}
catch (Exception ex)
{
return ServerResponse.create(-1,"保存登陆信息失败:Redis服务异常,请联系管理员", accessToken);
}
}
public static ServerResponseupdateUserInfoExpireTime(String reidsKey, TimeSpan timeSpan)//TimeSpan.FromHours(userValidTime)
{
try
{
bool res = RedisCacheHelper.Expire(reidsKey, timeSpan);
if (res ==true)
{
return ServerResponse.create(0,"设置成功",null);
}
return ServerResponse.create(-1,"设置失败",null);
}
catch (Exception ex)
{
return ServerResponse.create(-1,"设置失败:Redis服务异常,请联系管理员",null);
}
}
///
///
///
/// 手机号或者openid都行
///
///
public static ServerResponsegetUserInfo(String accessId, String reidsKey)//reidsKey 外部提交
{
try
{
if (String.IsNullOrWhiteSpace(accessId) || String.IsNullOrWhiteSpace(reidsKey))
{
return ServerResponse.create(-1,"读取失败:无效请求",null);
}
String _accessId = (Keys.KEY_REDIS_USER_ACCESSID_PREFIX + accessId);// accessId
String userJson = RedisCacheHelper.Get(_accessId);//通过手机号取值
if (String.IsNullOrWhiteSpace(userJson))
{
return ServerResponse.create(-1,"用户未登录或登录失效",null);
}
userJson = userJson.Replace("\\r\\n","").Replace("\\\"","\"");
AppUserInfoEntity user = JsonHelper.JsonToObjectNotForSerialize(userJson);
if (String.Equals(user.UserId, accessId) && String.Equals(user.AccessToken, reidsKey))//redis保存和接口提交的做一次校验
{
//刷新有效时间
var nowT = DateTime.Now;//UtcNow
var expiredT = TimeSpan.FromHours(userValidTime);
UserInfoCache.updateUserInfoExpireTime(_accessId, expiredT);
return ServerResponse.create(0,"读取用户缓存信息成功", user);
}
return ServerResponse.create(-1,"读取失败:非法请求", user);
}
catch (Exception ex)
{
return ServerResponse.create(-1,"读取失败:Redis服务异常,请联系管理员",null);
}
}
}
}
-----------JSConfigTool.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
namespace com.jiangjiesheng.auth
{
/**
*
* 读取App_Data下自定义配置文件
*
* 20180125 江节胜 dev@jiangjiesheng.cn
*
*/
public class JSConfigTool
{
public enum DataConfigPath//枚举
{
WeChatOfficalAccount,//微信公众号
SMSSender,//短信发送,
AuthConfig//接口安全校验配置
}
public static String getAppDataConfigPath(DataConfigPath path)
{
switch (path)
{
case DataConfigPath.WeChatOfficalAccount:
return "~/App_Data/Config/WeiXin.config";
case DataConfigPath.SMSSender:
return "~/App_Data/Config/SMS.config";
case DataConfigPath.AuthConfig:
return "~/App_Data/Config/Auth.config";
default:
return null;
}
}
///
/// 20180125 江节胜 dev@jiangjiesheng.cn
///
/// 获取XML配置的信息
///
/// ~/App_Data/Config/WeiXin.config
/// weixin/appid (注意加上父节点)
public static String getAppDataConfig(String xmlConfigPath, String xmlNodeName)
{
XmlDocument Xml =new XmlDocument();
Xml.Load(HttpContext.Current.Server.MapPath(xmlConfigPath));
XmlNode xmlNode = Xml.SelectSingleNode(xmlNodeName);
if (xmlNode !=null)
{
return xmlNode.InnerText;
}
return null;
}
///
/// 20180125 江节胜 dev@jiangjiesheng.cn
///
/// 设置XML配置的信息
///
/// ~/App_Data/Config/WeiXin.config
/// weixin/appid (注意加上父节点)
///
///
public static Boolean setAppDataConfig(String xmlConfigPath, String xmlNodeName, String value)
{
XmlDocument Xml =new XmlDocument();
Xml.Load(HttpContext.Current.Server.MapPath(xmlConfigPath));
XmlNode xmlNode = Xml.SelectSingleNode(xmlNodeName);
if (xmlNode !=null)
{
xmlNode.InnerText = value;
Xml.Save(HttpContext.Current.Server.MapPath(xmlConfigPath));
return true;
}
return false;
}
}
}