CAS是一个企业级的开源项目,其github地址为:https://github.com/apereo/cas,其Net客户端的git地址为:https://github.com/apereo/dotnet-cas-client,因为本篇内容的目标是已有系统快速接入CAS,所以CAS服务端的部署等不在本篇内容范围内。

首先需要说明的是,CAS的Net客户端目前并不支持Net Core,但看项目Issues已经有Core客户端的相关计划。

要接入CAS,你可以通过nuget下载DotNetCasClient,其地址为:https://www.nuget.org/packages/DotNetCasClient,安装完毕后,CAS需要用到的配置,会被自动地添加在web.config中,然后你要做的,就是按照这篇文章的内容,进行相应配置的修改,需要注意的是casClientConfig.proxyTicketManager,如果无需代理时,请删除该配置,否则CAS服务端响应结果可能会为UNAUTHORIZED_SERVICE_PROXY

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationFailure code='UNAUTHORIZED_SERVICE_PROXY'>
            The supplied service &#039;http://localhost:11518/Restricted/AuthenticatedUsersOnly/default.aspx&#039; is not authorized to use CAS proxy authentication.
    </cas:authenticationFailure>
</cas:serviceResponse>

上面的内容是我用DotNetCasClient解决方案示例项目ExampleWebSite试运行时服务端响应的内容,为保证配置正确,你可以在MVC项目之前,先通过ExampleWebSite进行配置测试,顺便再吐槽下这个示例项目居然是建的网站!

我们可以通过CasAuthenticationTicket来判断系统能获取到CAS的登录信息,根据ExampleWebSite下的CookieViewer.ascx,我简单的写了个Helper类,用于获取CasAuthenticationTicket

    public static class CasClientHelper
    {
        public static CasAuthenticationTicket GetCasAuthenticationTicket(this HttpRequestBase request)
        {
            CasAuthenticationTicket casTicket = null;
            var ticketCookie = request.Cookies[FormsAuthentication.FormsCookieName];
            if (ticketCookie != null && !string.IsNullOrWhiteSpace(ticketCookie.Value))
            {
                var ticket = FormsAuthentication.Decrypt(ticketCookie.Value);
                if (ticket != null && CasAuthentication.ServiceTicketManager != null)
                {
                    casTicket = CasAuthentication.ServiceTicketManager.GetTicket(ticket.UserData);
                }
            }
            return casTicket;
        }
    }

下面就是最常见的AuthorizeAttribute,我在下面简单的写了个示例代码CasAuthorizeAttribute,具体说明都通过代码注释的方式写在了代码中

    using DotNetCasClient;
    using DotNetCasClient.Utils;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Security;
    public class CasAuthorizeAttribute : AuthorizeAttribute
    {
        int _loginStatus;//此处简单示例:0未登录 1已登录 2状态错误 3无权限
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            var casTicket = filterContext.HttpContext.Request.GetCasAuthenticationTicket();
            //因为除了用户是否登录外,还需要判断权限,所以一般相关判断都在此处执行
            if (casTicket != null && casTicket.Assertion != null
                && !string.IsNullOrWhiteSpace(casTicket.Assertion.PrincipalName))
            {
                //这里假设PrincipalName与本地系统的用户唯一Id进行匹配
                var identity = filterContext.RequestContext.HttpContext.User.Identity;
                //可能HttpContext.User会包含多个Identity,与你使用的登录实现有关
                //所以下面的判断方法需按实际情况进行调整
                if (!identity.IsAuthenticated || !identity.Name.Equals(casTicket.Assertion.PrincipalName, StringComparison.OrdinalIgnoreCase))
                {
                    //SSOLogin,本地根据用户唯一Id进行登录
                    //即根据casTicket.Assertion.PrincipalName进行用户登录
                    //根据登录结果,将结果赋值给_loginStatus
                }
                else
                {
                    //唯一Id相同,且当前系统已登录,则认为用户是登录状态
                    this._loginStatus = 1;
                }
                if (this._loginStatus == 1)
                {
                    //进行权限判定
                    //如果无权限,则将_loginStatus赋值此处举例用的3
                    //this._loginStatus = 3;
                }
            }
            base.OnAuthorization(filterContext);//执行了基类的OnAuthorization才会执行AuthorizeCore
        }
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            //正常这里应该是OnAuthorization数据准备后,相应判断依据参数通过全局变量传递
            //例如这里的_loginStatus
            return this._loginStatus == 1;
        }
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            //AuthorizeCore返回false时,会执行此部分代码
            //此时同样可根据OnAuthorization准备的数据进行判断
            //根据判断结果不同,可跳转至不同的页面
            //如果是未登陆,则跳至Cas的单点登录服务地址
            //如果是本地系统判断无用户,或用户状态不正确,那应该跳至相应的无用户页面
            //如果是无权限,因为在已有系统接入时,不太可能再接入Cas的角色权限部分
            //所以大概率会采用系统原有权限,所以此时理论应该上应该跳至本地的无权限显示页面
            base.HandleUnauthorizedRequest(filterContext);
            switch (this._loginStatus)
            {
                case 2://无用户
                    filterContext.Result = new RedirectResult("~/Account/NoUser");
                    break;
                case 3://无权限
                    filterContext.Result = new RedirectResult("~/Account/NotAuthorized");
                    break;
                default://未登录
                    var redirectUrl = UrlUtil.ConstructLoginRedirectUrl(false, true);
                    filterContext.Result = new RedirectResult(redirectUrl);
                    //如果实际发现并没跳转至Cas登录页面,那么你可以尝试下面的方法,指定true强制结束当前响应并跳转
                    //HttpContext.Current.Response.Redirect(redirectUrl, true); 
                    break;
            }
        }
    }

然后用CasAuthorizeAttribute替换你项目中原来的AuthorizeAttribute即可。

最后,如果以上内容还帮助不到你,你可以尝试看下CasAuthenticationModule源码部分,你会发现CasAuthentication本身提供了各种跳转方式,另外自定义https也无需通过修改源码的方式解决,你只需要在GlobalApplication_Start中增加如下代码即可

            System.Net.ServicePointManager.ServerCertificateValidationCallback 
                += (s, x, c, e) => true;
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐