web-dev-qa-db-ja.com

pythonを使用してSAML / Shibboleth認証サーバーにログインする

私はpythonを介して大学のサーバーにログインしようとしていますが、適切なHTTP POSTの生成方法、キーと証明書の作成方法、および私が慣れていないプロセスの他の部分で、 SAML仕様に準拠します。私はブラウザでうまくログインできますが、Pythonを使用してログインし、サーバー内の他のコンテンツにアクセスできるようにしたいと思います。

参考までに、- ここはサイトです

Mechanize(フォームの選択、フィールドへの入力、mechanize.Broswer.submit()による送信ボタンコントロールのクリックなど)を使用してログインしようとしましたが、役に立ちませんでした。ログインサイトは毎回スパッツを取得します。

この時点で、タスクに最も適した言語でソリューションを実装できるようになりました。基本的に、プログラムでSAML認証サーバーにログインします。

21
David Perlaza

基本的に理解する必要があるのは、SAML認証プロセスの背後にあるワークフローです。残念ながら、PDFはありません。これは、SAMLで保護されたWebサイトにアクセスしたときにブラウザーがどのようなことを行うかを見つけるのに役立つと思われます。

多分あなたはこのようなものを見る必要があります: http://www.docstoc.com/docs/33849977/Workflow-to-Use-Shibboleth-Authentication-to-Sign そして明らかにこれに: http://en.wikipedia.org/wiki/Security_Assertion_Markup_Language 。特に、このスキームに注意を向けてください。

enter image description here

ドキュメントがso悪いため、SAMLの動作方法を理解しようとしたときに私が行ったのは、ブラウザーが最初から最後まで実行していたすべての手順を書き留めていた(はい!最後。私はOperaを使用してnotを設定して自動リダイレクト(300、301、302応答コードなど)を許可し、Javascriptを有効にしませんでした。次に、サーバーから送信されたすべてのCookie、何をしていて、何のために理由を書いたかを書き留めました。

多分それはあまりにも多くの労力でしたが、このようにして、仕事に適しており、信じられないほど高速で効率的なライブラリをJavaで書くことができました。多分いつか私はそれを公開するでしょう...

SAMLログインでは、IDP(IDプロバイダー)とSP(サービスプロバイダー))の2つのアクターが遊んでいることを理解する必要があります。

A.最初のステップ:ユーザーエージェントがSPにリソースを要求します。

「保護されたウェブサイトへのアクセス」のようなものをクリックする別のページからあなたの質問であなたが参照するリンクに到達したと確信しています。さらに注意すると、たどったリンクがnotであり、認証フォームが表示されているリンクであることがわかります。これは、IDPからSPへのリンクをクリックすることがSAMLのステップであるためです。最初のステップは、実際には、 IDPは、あなたが誰であり、なぜそのリソースにアクセスしようとしているのかを定義します。したがって、基本的に必要なことは、Webフォームにアクセスするためにたどったリンクにリクエストを送信し、Cookieを取得することです。表示されないのは、リンクの背後にある302リダイレクトにエンコードされたSAMLRequest文字列で、接続を確立するIDPに送信されます。

それがプロセス全体を機械化できない理由だと思います。 IDの識別を行わずに、フォームに接続しました。

B. 2番目のステップ:フォームに記入して送信する

これは簡単です。お気をつけください! nowが設定されているCookieは、上記のCookieとは異なります。まったく別のWebサイトに接続しています。これがSAMLが使用される理由です:異なるWebサイト、同じ資格情報。したがって、正常なログインによって提供されるこれらの認証Cookieを別の変数に保存することができます。 IDPは、応答(SAMLRequestの後)、SAMLResponseを送り返します。ログインが終了するWebページのソースコードを取得することを検出する必要があります。実際、このページは応答を含む大きなフォームであり、ページがロードされると、JSのコードによって自動的にサブミットされます。ページのソースコードを取得し、解析して、HTMLの不要なものをすべて削除し、SAMLResponse(暗号化)を取得する必要があります。

C.第3ステップ:SPへの応答の返信

これで、手順を終了する準備ができました。前のステップで取得したSAMLResponseを(フォームをエミュレートしているため、POSTを介して)SPに送信する必要があります。このようにして、アクセスしたい保護されたものへのアクセスに必要なCookieを提供します。

ああ、これで完了です。

繰り返しますが、あなたがしなければならない最も貴重なことは、Operaを使用し、SAMLが行うすべてのリダイレクトを分析することです。次に、コード内でそれらを複製します。それほど難しくはありません。 IDPはSPとはまったく異なることに注意してください。

28
Gianluca

ヘッドレスのPhantomJS Webkitを備えたSeleniumは、CookieやJavaScriptを処理するため、Shibbolethにログインするのに最適です。

インストール:

_$ pip install Selenium
$ brew install phantomjs
_

_from Selenium import webdriver
from Selenium.webdriver.support.ui import Select # for <SELECT> HTML form

driver = webdriver.PhantomJS()
# On Windows, use: webdriver.PhantomJS('C:\phantomjs-1.9.7-windows\phantomjs.exe')

# Service selection
# Here I had to select my school among others 
driver.get("http://ent.unr-runn.fr/uPortal/")
select = Select(driver.find_element_by_name('user_idp'))
select.select_by_visible_text('ENSICAEN')
driver.find_element_by_id('IdPList').submit()

# Login page (https://cas.ensicaen.fr/cas/login?service=https%3A%2F%2Fshibboleth.ensicaen.fr%2Fidp%2FAuthn%2FRemoteUser)
# Fill the login form and submit it
driver.find_element_by_id('username').send_keys("myusername")
driver.find_element_by_id('password').send_keys("mypassword")
driver.find_element_by_id('fm1').submit()

# Now connected to the home page
# Click on 3 links in order to reach the page I want to scrape
driver.find_element_by_id('tabLink_u1240l1s214').click()
driver.find_element_by_id('formMenu:linknotes1').click()
driver.find_element_by_id('_id137Pluto_108_u1240l1n228_50520_:tabledip:0:_id158Pluto_108_u1240l1n228_50520_').click()

# Select and print an interesting element by its ID
page = driver.find_element_by_id('_id111Pluto_108_u1240l1n228_50520_:tableel:tbody_element')
print page.text
_

注意:

  • 開発中に、Firefoxを使用して、実行中の内容をプレビューしますdriver = webdriver.Firefox()
  • このスクリプトは現状のまま提供され、対応するリンクが付いているため、コードの各行をページの実際のソースコードと比較できます(少なくともログインするまで)。
6

上記のStéphaneBruckertからの回答を拡張すると、Seleniumを使用して認証Cookieを取得した後でも、必要に応じてリクエストに切り替えることができます。

import requests
cook = {i['name']: i['value'] for i in driver.get_cookies()}
driver.quit()
r = requests.get("https://protected.ac.uk", cookies=cook)
4
bjw

Shibboleth認証プロセスの詳細な説明 こちら を見つけることができます。

2
andrebask

MechanizeはJavascriptを処理しないことを除いて、同様に作業を行うことができます。認証は成功しましたが、一度ホームページにアクセスすると、そのようなリンクをロードできませんでした。

<a href="#" id="formMenu:linknotes1"
   onclick="return oamSubmitForm('formMenu','formMenu:linknotes1');">

Javascriptが必要な場合は、 PhantomJSを使用したSelenium を使用することをお勧めします。それ以外の場合は、このスクリプトからインスピレーションを得られることを願っています。

#!/usr/bin/env python
#coding: utf8
import sys, logging
import mechanize
import cookielib
from BeautifulSoup import BeautifulSoup
import html2text

br = mechanize.Browser() # Browser
cj = cookielib.LWPCookieJar() # Cookie Jar
br.set_cookiejar(cj) 

# Browser options
br.set_handle_equiv(True)
br.set_handle_gzip(True)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)

# Follows refresh 0 but not hangs on refresh > 0
br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1)

# User-Agent
br.addheaders = [('User-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36')]

br.open('https://ent.unr-runn.fr/uPortal/')
br.select_form(nr=0)
br.submit()

br.select_form(nr=0)
br.form['username'] = 'myusername'
br.form['password'] = 'mypassword'
br.submit()

br.select_form(nr=0)
br.submit()

rs = br.open('https://ent.unr-runn.fr/uPortal/f/u1240l1s214/p/esup-mondossierweb.u1240l1n228/max/render.uP?pP_org.Apache.myfaces.portlet.MyFacesGenericPortlet.VIEW_ID=%2Fstylesheets%2Fetu%2Fdetailnotes.xhtml')

# Eventually comparing the cookies with those on Live HTTP Header: 
print "Cookies:"
for cookie in cj:
    print cookie

# Displaying page information
print rs.read()
print rs.geturl()
print rs.info();

# And that last line didn't work
rs = br.follow_link(id="formMenu:linknotes1", nr=0)
1

大学のページのSAML認証でも同様の問題に直面しました。

基本的な考え方は、requests.sessionオブジェクトを使用して、ほとんどのhttpリダイレクトとcookieの格納を自動的に処理します。ただし、JavaScriptの両方を使用したリダイレクトも多数あり、これにより、単純な要求ソリューションを使用した場合に複数の問題が発生しました。

私は fiddler を使用して、ブラウザが大学サーバーに対して行ったすべてのリクエストを追跡して、見逃したリダイレクトを埋めました。それは本当にプロセスを簡単にしました。

私の解決策は理想からはほど遠いですが、うまくいくようです。

1
Arthur.V

私は受け入れられた答えに続いてこのコードを書きました。これは2つの別々のプロジェクトで私のために働きました

import mechanize
from bs4 import BeautifulSoup
import urllib2
import cookielib


cj = cookielib.CookieJar()
br = mechanize.Browser()
br.set_handle_robots(False)
br.set_cookiejar(cj)

br.set_handle_equiv(True)
br.set_handle_gzip(True)
br.set_handle_redirect(True)
br.set_handle_refresh(False)
br.set_handle_referer(True)
br.set_handle_robots(False)

br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1)

br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]


br.open("The URL goes here")

br.select_form(nr=0)

br.form['username'] = 'Login Username'
br.form['password'] = 'Login Password'
br.submit()

br.select_form(nr=0)
br.submit()

response = br.response().read()
print response
1
draysams

Shibbolizedページにログインできる簡単なPythonスクリプトを作成しました。

最初に、FirefoxでライブHTTPヘッダーを使用して、ターゲットにした特定のShibbolizedページのリダイレクトを監視しました。

次に、_urllib.request_(Python 3.4で)を使用して簡単なスクリプトを書きましたが、_urllib2_ Python 2.x同じ機能です。_urllib.request_に続くデフォルトのリダイレクトが目的に合っていることがわかりましたが、_urllib.request.HTTPRedirectHandler_をサブクラス化し、このサブクラス(クラスShibRedirectHandler)に追加すると便利です。すべてのhttp_error_302イベントのハンドラ。

このサブクラスでは、パラメーターの値を出力しました(デバッグ目的)。デフォルトのリダイレクトフォローを使用するには、ハンドラーをreturn HTTPRedirectHandler.http_error_302(self, args...)で終了する必要があることに注意してください(つまり、基本クラスのhttp_errror_302ハンドラーへの呼び出し)。

Shibbolized Authenticationでurllibを機能させるための最も重要なコンポーネントは、Cookie処理が追加されたOpenerDirectorを作成することです。次のようにOpenerDirectorをビルドします。

_cookieprocessor = urllib.request.HTTPCookieProcessor()
opener = urllib.request.build_opener(ShibRedirectHandler, cookieprocessor)
response = opener.open("https://shib.page.org")
_

開始するための完全なスクリプトを次に示します(提供したいくつかの模擬URLを変更し、有効なユーザー名とパスワードを入力する必要があります)。これはPython 3クラスを使用します; Python2でこれを機能させるには、urllib.requestをurllib2に、urlib.parseをurlparseに置き換えます:

_import urllib.request
import urllib.parse

#Subclass of HTTPRedirectHandler. Does not do much, but is very
#verbose. prints out all the redirects. Compaire with what you see
#from looking at your browsers redirects (using live HTTP Headers or similar)
class ShibRedirectHandler (urllib.request.HTTPRedirectHandler):
    def http_error_302(self, req, fp, code, msg, headers):
        print (req)
        print (fp.geturl())
        print (code)
        print (msg)
        print (headers)
        #without this return (passing parameters onto baseclass) 
        #redirect following will not happen automatically for you.
        return urllib.request.HTTPRedirectHandler.http_error_302(self,
                                                          req,
                                                          fp,
                                                          code,
                                                          msg,
                                                          headers)

cookieprocessor = urllib.request.HTTPCookieProcessor()
opener = urllib.request.build_opener(ShibRedirectHandler, cookieprocessor)

#Edit: should be the URL of the site/page you want to load that is protected with Shibboleth
(opener.open("https://shibbolized.site.example").read())

#Inspect the page source of the Shibboleth login form; find the input names for the username
#and password, and edit according to the dictionary keys here to match your input names
loginData = urllib.parse.urlencode({'username':'<your-username>', 'password':'<your-password>'})
bLoginData = loginData.encode('ascii')

#By looking at the source of your Shib login form, find the URL the form action posts back to
#hard code this URL in the mock URL presented below.
#Make sure you include the URL, port number and path
response = opener.open("https://test-idp.server.example", bLoginData)
#See what you got.
print (response.read())
_
1
chladni

すでに回答されていますが、うまくいけばこれが誰かを助けるでしょう。私はSAML Webサイトからファイルをダウンロードするタスクを持っていて、StéphaneBruckertの回答から助けを得ました。

ヘッドレスを使用する場合、ログインのリダイレクトに必要な間隔で待ち時間を指定する必要があります。ブラウザーがログインしたら、私はそこからのCookieを使用し、それをリクエストモジュールと一緒に使用してファイルをダウンロードしました Got help from this

これは私のコードがどのように見えるかです-

from Selenium import webdriver
from Selenium.webdriver.chrome.options import Options  #imports

things_to_download= [a,b,c,d,e,f]     #The values changing in the url
options = Options()
options.headless = False
driver = webdriver.Chrome('D:/chromedriver.exe', options=options)
driver.get('https://website.to.downloadfrom.com/')
driver.find_element_by_id('username').send_keys("Your_username") #the ID would be different for different website/forms
driver.find_element_by_id('password').send_keys("Your_password")
driver.find_element_by_id('logOnForm').submit()
session = requests.Session()
cookies = driver.get_cookies()
for things in things_to_download:    
    for cookie in cookies: 
        session.cookies.set(cookie['name'], cookie['value'])
    response = session.get('https://website.to.downloadfrom.com/bla/blabla/' + str(things_to_download))
    with open('Downloaded_stuff/'+str(things_to_download)+'.pdf', 'wb') as f:
        f.write(response.content)            # saving the file
driver.close()
1
TheBroda