为了实现“先CAS验证,再微信验证”的双重认证流程,并优化登录体验,我们需要对代码进行重构。核心思路是使用一个自定义的ActionFilterAttribute来串联整个流程,并利用 Session 和 Cookie 来管理不同阶段的登录状态。
以下是优化后的详细代码和注释。
核心思路
- 双重验证过滤器 (
DualAuthAttribute):创建一个自定义的ActionFilterAttribute,它将按顺序检查CAS和微信的登录状态。 - CAS优先:过滤器首先检查用户是否已通过CAS认证。如果没有,则重定向到CAS登录页。
- 微信其次:CAS认证通过后,过滤器再检查微信登录状态。它优先检查Cookie,如果没有则检查Session,最后才引导用户进行微信授权。
- 状态持久化:
- CAS:其登录状态由
FormsAuthentication的Cookie(.ASPXAUTH)管理,服务器端通过Session存储用户信息。 - 微信:登录成功后,将用户信息存入
Session,同时将OpenID存入一个持久的Cookie。下次访问时,通过Cookie中的OpenID自动恢复Session,避免重复授权。
- CAS:其登录状态由
第一步:创建双重认证过滤器
这个过滤器将取代你原有的[WechatAuth]和[Authorize],成为业务页面的统一入口。
usingSystem;usingSystem.Configuration;usingSystem.Web;usingSystem.Web.Mvc;usingSystem.Web.Security;usingDotNetCasClient.Security;// 引用 CAS 命名空间namespaceYourNamespace.Filters// 请替换为你的实际命名空间{publicclassDualAuthAttribute:ActionFilterAttribute{privatereadonlystring_wechatAppId=ConfigurationManager.AppSettings["WeixinAppId"];privatereadonlystring_wechatAppSecret=ConfigurationManager.AppSettings["WeixinAppSecret"];publicoverridevoidOnActionExecuting(ActionExecutingContextfilterContext){varrequest=filterContext.HttpContext.Request;varsession=filterContext.HttpContext.Session;varresponse=filterContext.HttpContext.Response;// --- 第一阶段:CAS 统一身份认证 ---// 检查当前请求的用户是否已通过 CAS 认证if(!(filterContext.HttpContext.User?.Identity.IsAuthenticated??false)){// 未通过 CAS 认证,重定向到 CAS 登录页// DotNetCasClient 会自动处理重定向,这里我们只需返回即可// 但为了明确,我们可以手动触发,或者直接让 [Authorize] 处理// 这里我们采用最可靠的方式:手动重定向stringcasLoginUrl=ConfigurationManager.AppSettings["CasServerLoginUrl"];filterContext.Result=newRedirectResult(casLoginUrl);return;}// CAS 认证通过,将用户信息存入 Session,供后续使用// 这里我们简单地将 CAS 用户名存入 Sessionif(session["CasUser"]==null){session["CasUser"]=filterContext.HttpContext.User.Identity.Name;}// --- 第二阶段:微信授权认证 ---// 1. 检查 Session 中是否已有微信用户信息if(session["WechatUserInfo"]!=null){// 微信已登录,直接放行base.OnActionExecuting(filterContext);return;}// 2. Session 中没有,检查 Cookie 中是否有 OpenID (用于自动登录)HttpCookieopenIdCookie=request.Cookies["WechatOpenId"];if(openIdCookie!=null&&!string.IsNullOrEmpty(openIdCookie.Value)){// 找到了 OpenID,可以从数据库或缓存中恢复完整的用户信息// 这里为了演示,我们假设恢复了一个简单的 UserInfo 对象// 实际项目中,你应该根据 openIdCookie.Value 查询数据库varuserInfo=RecoverUserInfoFromOpenId(openIdCookie.Value);if(userInfo!=null){session["WechatUserInfo"]=userInfo;base.OnActionExecuting(filterContext);return;}}// 3. Session 和 Cookie 都没有,检查 URL 中是否带有微信回调的 codestringcode=request.QueryString["code"];if(string.IsNullOrEmpty(code)){// 没有 code 也没有登录态 -> 重定向到微信授权页面stringcurrentUrl=request.Url.AbsoluteUri;stringredirectUri=HttpUtility.UrlEncode(currentUrl);stringwechatUrl=string.Format("https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirect_uri={1}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect",_wechatAppId,redirectUri);filterContext.Result=newRedirectResult(wechatUrl);return;}// 4. 有 code -> 说明是微信跳回来的,需要换取用户信息try{// 第一步:通过 code 换取网页授权凭证 (access_token + openid)varoauthResult=OAuthApi.GetAccessToken(_wechatAppId,_wechatAppSecret,code);if(oauthResult.errcode==0){stringaccessToken=oauthResult.access_token;stringopenId=oauthResult.openid;// 第二步:拉取用户详细信息varuserInfo=OAuthApi.GetUserInfo(accessToken,openId);// 第三步:建立本地 Session 登录态session["WechatUserInfo"]=userInfo;// 第四步:将 OpenID 存入 Cookie,实现下次自动登录// 设置过期时间,例如7天varcookie=newHttpCookie("WechatOpenId",openId){Expires=DateTime.Now.AddDays(7),HttpOnly=true,// 防止 XSS 攻击Secure=FormsAuthentication.RequireSSL// 仅在 HTTPS 下发送};response.Cookies.Add(cookie);// 第五步:清除 URL 中的 code 参数并重定向,防止刷新重复提交stringcleanUrl=request.Url.GetLeftPart(UriPartial.Path);filterContext.Result=newRedirectResult(cleanUrl);}else{filterContext.Result=newContentResult{Content=$"微信授权失败:{oauthResult.errmsg}(Code:{oauthResult.errcode})"};}}catch(Exceptionex){filterContext.Result=newContentResult{Content="系统错误:"+ex.Message};}}/// <summary>/// 根据 OpenID 从数据库或缓存中恢复用户信息/// </summary>privateOAuthUserInfoRecoverUserInfoFromOpenId(stringopenId){// TODO: 根据 openId 查询你的数据库,获取完整的用户信息// 这里仅作示例// var user = _userService.GetByOpenId(openId);// return user;returnnull;// 如果找不到,返回 null,流程会继续引导用户重新授权}}}第二步:修改业务控制器
现在,业务控制器变得非常简洁,只需要应用我们新创建的[DualAuth]过滤器即可。
usingSystem.Web.Mvc;usingYourNamespace.Filters;// 引用你的过滤器命名空间usingYourNamespace.Models;// 引用你的模型命名空间namespaceYourNamespace.Controllers{publicclassSuggestController:BaseController{System_SuggestBllsuggestBll=newSystem_SuggestBll();System_Suggest_FeedbackBllsuggestFeedbackBll=newSystem_Suggest_FeedbackBll();Base_UserBllUserBll=newBase_UserBll();// 应用双重认证过滤器[DualAuth]publicActionResultIndex(stringid=""){// 1. 获取建议列表varlist=suggestBll.Repository().FindListTop(3);ViewBag.SuggestList=list;// 2. 获取已认证的用户信息// 此时,Session 中一定同时存在 "CasUser" 和 "WechatUserInfo"varcasUser=Session["CasUser"]asstring;varwechatUser=Session["WechatUserInfo"]asOAuthUserInfo;// 3. 业务逻辑处理// AddUser(casUser); // 根据你的业务需求处理// 4. 将用户信息传递给视图ViewBag.CasUser=casUser;ViewBag.WechatUser=wechatUser;returnView(wechatUser);}}}第三步:更新 Web.config 配置
确保你的Web.config中有必要的配置项。
<configuration><appSettings><!-- CAS 配置 --><addkey="CasServerLoginUrl"value="http://uis.xxxxx.net/cas/login"/><!-- 微信配置 --><addkey="WeixinAppId"value="你的微信AppID"/><addkey="WeixinAppSecret"value="你的微信AppSecret"/></appSettings><!-- ... 其他配置 ... --></configuration>流程总结
- 用户访问
Suggest/Index。 [DualAuth]过滤器首先检查User.Identity.IsAuthenticated。- 如果为
false,用户被重定向到 CAS 登录页。 - CAS 登录成功后,用户被带回
Suggest/Index,此时User.Identity.IsAuthenticated为true。 - 过滤器接着检查
Session["WechatUserInfo"]。 - 如果为空,再检查
Request.Cookies["WechatOpenId"]。 - 如果 Cookie 中有
OpenID,则调用RecoverUserInfoFromOpenId恢复用户信息到Session。 - 如果 Cookie 中也没有,则重定向到微信授权页。
- 微信授权成功后,带着
code回到Suggest/Index。 - 过滤器用
code换取用户信息,存入Session,并将OpenID写入Cookie。 - 双重认证全部通过,执行业务
Index方法,展示页面。