Simatic S7 1212C AC DC Rly reading temperature from PT1000 and offering via rest apiIn the previous post (RESTful API for Simatic S7-1200 PLC & Python Client (Part 1)) I showed how to design and implement a simple ReST API for the Simatic S7-1200 and the integrated web server. A client (written in python) was accessing the API. It was assumed, that no access restrictions were configured for the PLC and therefore the ReST API was accessible by everyone (anonymous user). Such a configuration is not conceivable for productive environments.

In this post I will shortly introduce how to use python (with the requests library) to login through the login form presented on the welcome page when you access the Simatic S7 using a web browser over HTTP/S. Once you have retrieved the authentication cookie successfully, you can access your API and your user-defined pages with your own clients.

 

The Login Process

Simatic S7 web app login form

Basically, the login process on the Simatic S7 is quite simple, but it is limited to be done through the web form only which is displayed on the welcome page. There is no other possibility. If you look into the official documentation, the login process is always described by referring to the provided login form.

When using another client than a web browser (as I showed in the previous post), it is not possible to fill in this html form, but it is possible to use our client to post the data to the web server. The server then responds with a session cookie which corresponds to the state of authentication. This means that this session cookie has to be handed over to the server with every following request.

The procedure

  1. Prepare http headers to be send with the request. If no http headers are provided, the authentication will fail, the server answers with status code 400.
  2. Prepare the login payload as a json string. Provide username as 'Login', password as 'password' and 'Redirection' stays empty. The json string with the credentials looks like this: {'Login': 'WebUser', 'Password': '123456789', 'Redirection': ''}
  3.  Send the request to the server using python request library, include the http headers and store the response.
  4. The respond status code should now be 200 and the content should be the HTML code of the first page which hides behind the login. The above mentioned session/authentication cookies is not send inside the http response header, instead, it is send as part of the response payload inside the HTML code.
  5. Extract the authentication cookie from the HTML code using python BeautifulSoup
  6. Set the authentication cookie that it can be transferred with the following requests (inside the http header)
  7. Request (get or post) the API using python request library (as shown in the previous post) but ensure that the session/authentication cookie is transferred with the request - here the http headers can be omitted
  8. Handle API response - requested payload
  9. Logout

Simple python script

#####################################
# by Johannes Kinzig (M. Sc.)		#
# https://johanneskinzig.de			#
#####################################

import requests
import BeautifulSoup

## Tested with S7 1212C AC/DC/Rly and FW Version 4.2.1

#########################################
#           Login                       #
#########################################
payload_login = {'Login': 'WebUser', 'Password': '123456789', 'Redirection': ''}
posturl_login = 'https://192.168.178.50/FormLogin'

headers = {
    'Host': '192.168.178.50',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': 'https://192.168.178.50/Portal/Portal.mwsl',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': '45',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1'
}

session = requests.Session()
login_response = session.post(posturl_login, data=payload_login, headers=headers, verify=False)
print login_response.status_code
print login_response.headers
#print login_response.cookies.get_dict() # will stay empty
webpage =  login_response.content

#########################################
#     Extract authentication Cookie     #
#########################################
webpage_soup = BeautifulSoup.BeautifulSoup(webpage)
auth_cookie_part = webpage_soup.find('input', attrs={'name': 'Cookie'})
auth_cookie_part = str(auth_cookie_part)
auth_cookie = auth_cookie_part.split('"')[5]
print "Authentication cookie: ", auth_cookie

#########################################
#     Set Authentication Cookie         #
#########################################
s7cookies = dict(siemens_ad_session=auth_cookie, coming_from_login='true')

#########################################
# Run Actions (require authentication)  #
#########################################
payload_control = {'"webHMIData".webHMI_DO1_User': '1', '"webHMIData".webHMI_DO0_User': '1'}
usage = session.post('https://192.168.178.50/awp/AnalogInputs/api.io', cookies=s7cookies, data=payload_control, verify=False)
print usage.status_code
print usage.headers
print usage.content

#########################################
#           Logout                      #
#########################################
payload_logout = {'Cookie': auth_cookie, 'Redirection': ''}
posturl_logout = 'https://192.168.178.50/FormLogin?LOGOUT'
logout = session.post(posturl_logout, cookies=s7cookies, headers=headers, data=payload_logout, verify=False)
print logout.status_code
print logout.headers
#print logout.content

Sample Project Download

(Same project as provided in the previous post)

7z

webHMI-DataProvider

SHA-256: FC55A5CE7F9AB6F2E57EC61A0ED1DF42B6685941AB8DBB83D0B5F47AF22BC51F
Size: 31.25 MB