Odoo接入CAS统一认证系统

背景:

CAS介绍:

CAS ( Central Authentication Service ),最初由耶鲁大学的Shawn Bayern
开发,后由Jasig社区维护,经过十多年发展,目前已成为影响最大、广泛使用的、基于Java实现的、开源SSO解决方案。cas旨在为 Web应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。 CAS 开始于 2001 年, 并在 2004 年 12 月正式成为JA-SIG 的一个项目。

CAS原理:

在这里插入图片描述
cas单点登录流程理解:

  1. 浏览器请求cas客户端(接入cas的系统)时,cas客户端对当前请求浏览器校验是否存在session(odoo系统中则校验当前cookie是否存在session_id值,根据session_id值判断/data/sessions文件夹内是否存在对应session。如启用redis管理session,则在redis中查询是否存在对应session_id的数据判断是否登录)。
  2. 如校验无登录cas客户端,则返回重定向到cas服务器。链接形如:https://localcas:8443/cas/login?service=http%3A%2F%2F192.168.99.102%3A8069%2Fweb%2Flogin。
    其中https://localcas:8443/cas/login为cas服务器登录地址,service参数值为cas客户端登录地址
  3. 通过用户名密码登录cas认证系统,认证成功后携带ticket参数重定向到cas客户端登录 地址,形如:
    http://192.168.99.102:8069/web/login?ticket=ST-17-3hA0-atfanDFWw4beSnHodOdMCY-DESKTOP-RAY
  4. 浏览器请求重定向地址到cas客户端。
  5. cas客户端获取请求参数ticket,并携带ticket参数请求cas服务端换取用户。
  6. 成功获取用户名则创建session,设置cookie值。让浏览器重定向到cas客户端系统首页。
  7. 没有成功获取用户名则返回cas服务端登录地址。

PS:CAS认证系统通过cookie值中Jsession_id值判断是否通过CAS认证。

核心代码:

  1. 安装python-cas库
pip install python-cas
  1. 引入cas库 修改controllers里登录的方法
# -*- coding:utf-8 -*-

import cas
import odoo
import random
import werkzeug
import xmltodict
import configparser
from odoo import _
from odoo import http
from odoo.service import security
from odoo.addons.web.controllers import main
from odoo.tools import ustr, consteq, frozendict, pycompat
from odoo.http import request, Response, Root, OpenERPSession


class HomeFLih(main.Home):

    # OA单点登录
    @http.route('/web/login', type='http', auth="none",csrf=False, sitemap=False)
    def web_login(self, redirect=None, **kw):
        if 'logoutRequest' in kw:
            return self.web_logout(redirect=redirect,**kw)
        # 生成CAS客户端类实例
        client = cas.CASClient(
            # CAS 版本 ,V1,V2,V3 版本
            version='3',
            # CAS 服务端登录地址,有get_login_url方法拼接/login 生成
            server_url='https://localcas:8443/cas/',
            # CAS 客户端登录地址
            service_url='http://192.168.99.102:8069/web/login',
            # 是否校验ssl证书
            verify_ssl_certificate=False
        )
        # 判断是否有ticket参数
        if 'ticket' in request.params:
            ticket = request.params.get('ticket','')
            # 请求CAS服务端,使用ticket换取用户名
            res = client.verify_ticket(ticket)
            user_name = res[0]
            if not user_name:
                cas_url = client.get_login_url()
                return werkzeug.utils.redirect(cas_url,303)
            # 写死数据库,仅为demo需要,生产环境可读取配置文件赋值
            request.session.db = 'test'
            users = request.env['res.users'].search([('login','=ilike',user_name)],limit=1)
            request.session['uid'] = users.id if users else False
            request.session['ticket'] = ticket
            request.params['login'] = users.login if users else False
            # 随便赋值,用于cas用户校验逻辑
            request.params['password'] = users.login if users else False
            # 用于cas用户校验逻辑
            request.session.context['CAS_SIGNAL_LOGIN'] = True
            # 未找到用户,报错
            if not users:
                return """<script language="javascript" type="text/javascript">
                                            alert('""" + _('The username %s is not found,Plz contact the admin!')%(user_name) + """');
                                            window.opener=null;
                                            window.open("", "_self");
                                            window.close();
                                         </script>"""
            uid = request.session.authenticate(request.session.db, request.params['login'],
                                               request.params['password'])

            if uid is not False:
                request.uid = uid
            return http.redirect_with_hash('/web')
        else:
            cas_url = client.get_login_url()
            return werkzeug.utils.redirect(cas_url,303)
# -*- coding:utf-8 -*-

import logging
from odoo.http import request
from odoo.exceptions import AccessDenied
from odoo import api, fields, models, tools, SUPERUSER_ID, _

_logger = logging.getLogger(__name__)

# 修改源码密码校验
class ResUsers(models.Model):
    _inherit = 'res.users'

    @classmethod
    def _login(cls, db, login, password, context={}):
        if not password:
            raise AccessDenied()
        ip = request.httprequest.environ['REMOTE_ADDR'] if request else 'n/a'
        try:
            with cls.pool.cursor() as cr:
                self = api.Environment(cr, SUPERUSER_ID, context)[cls._name]
                with self._assert_can_auth():
                    user = self.search(self._get_login_domain(login))
                    if not user:
                        raise AccessDenied()
                    user = user.sudo(user.id)
                    user._check_credentials(password)
                    user._update_last_login()
        except AccessDenied:
            _logger.info("Login failed for db:%s login:%s from %s", db, login, ip)
            raise
        _logger.info("Login successful for db:%s login:%s from %s", db, login, ip)
        return user.id

    @classmethod
    def authenticate(cls, db, login, password, user_agent_env):
        """Verifies and returns the user ID corresponding to the given
          ``login`` and ``password`` combination, or False if there was
          no matching user.
           :param str db: the database on which user is trying to authenticate
           :param str login: username
           :param str password: user password
           :param dict user_agent_env: environment dictionary describing any
               relevant environment attributes
        """
        if user_agent_env and user_agent_env.get('context'):
            uid = cls._login(db, login, password, context=user_agent_env['context'])
        else:
            uid = cls._login(db, login, password)
        if user_agent_env and user_agent_env.get('base_location'):
            with cls.pool.cursor() as cr:
                env = api.Environment(cr, uid, {})
                if env.user.has_group('base.group_system'):
                    # Successfully logged in as system user!
                    # Attempt to guess the web base url...
                    try:
                        base = user_agent_env['base_location']
                        ICP = env['ir.config_parameter']
                        if not ICP.get_param('web.base.url.freeze'):
                            ICP.set_param('web.base.url', base)
                    except Exception:
                        _logger.exception("Failed to update web.base.url configuration parameter")
        return uid

    def _check_credentials(self, password):
        # convert to base_crypt if needed
        if self.env.context.get('CAS_SIGNAL_LOGIN', False):
            return
        else:
            return super(ResUsers, self)._check_credentials(password)

实现效果:
在这里插入图片描述

cas单点登出流程理解:

  1. 客户端主动登出:失效客户端系统session,重定向到CAS服务端登出页面,执行CAS验证系统登出操作。
  2. CAS服务端或其他第三方客户端执行登出:当
    CAS服务端或其他第三方客户端执行登出时,CAS验证系统也执行登出操作。CAS验证系统登出时,将请求第三方系统,将登出用户的ticket传到第三方系统服务端,让第三方执行登出操作。

核心代码:

  1. 修改controllers里登录的方法
# -*- coding:utf-8 -*-

import cas
import odoo
import random
import werkzeug
import xmltodict
import configparser
from odoo import _
from odoo import http
from odoo.service import security
from odoo.addons.web.controllers import main
from odoo.tools import ustr, consteq, frozendict, pycompat
from odoo.http import request, Response, Root, OpenERPSession


class Home(main.Home):
    # OA单点登出
    @http.route('/web/logout', type='http', auth="none",csrf=False, sitemap=False)
    def web_logout(self, redirect=None, **kw):
        logoutRequest = kw.get('logoutRequest','')
        root = xmltodict.parse(logoutRequest)
        ticket = root.get('samlp:LogoutRequest', {}).get('samlp:SessionIndex', '')
        # 搜索odoo session文件,执行登出操作。
        # 循环访问本地session文件效率很低,可改用数据库存储ticket与session_uid关系,或使用radis代替本地文件存储session信息
        for rec in http.root.session_store.list():
           logout_session = http.root.session_store.get(rec)
           if logout_session.get('ticket') == ticket:
               logout_session.logout(keep_db=True)
               http.root.session_store.save(logout_session)
        #        #http.root.session_store.delete(logout_session)
        response = request.make_response('')
        return response
class Session(main.Session):

    @http.route('/web/session/logout', type='http', auth="none")
    def logout(self, redirect='/web'):

        client = cas.CASClient(
            version='3',
            server_url='https://localcas:8443/cas/',
            service_url='http://192.168.99.102:8069/web/login',
            verify_ssl_certificate=False
        )
        request.session.logout(keep_db=True)
        return werkzeug.utils.redirect(client.get_logout_url(client.service_url), 303)

实现效果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值