web2py access control(접근 제어) 정리.
By SeukWon Kang
회사 세미나용으로 http://web2py.com/books/default/chapter/29/09/access-control 을 요약한 내용 입니다.
접근제어 access control
web2py 는 Role Based Access Control 제공 (RBAC)
역할을 기반으로하는 접근제어로 context-based access control (CBAC) 과는 다르다.
( message의 context에 따라 달라지지 않는다, 상태, 혹은 데이터 )
user - role - job function 형태
web2py 에서는 Auth class를 통해서 구현된다.
아래와 같은 테이블이 필요한데 프로젝트를 만들때 자동으로 코드가 생성된다.
auth_user : 유저이름 , email , 암호 , 상태 (등록 대기, 완료, 블럭됨 )
auth_group : 유저의 그룹(롤) 정보 , 이게 role ( 사용자 역할 )
auth_membership : group - user link table , user - group의 다대 다 연결.
auth_permission : 퍼미션(기능) 정보 : 선택적으로 테이블의 레코드 정보도 포함할수 있다.
auth_event : 감사 정도 테이블
auth_cas : 중앙집중식 인증에 사용. : web2py는 공급자,(선택적으로)소비자 로 기능 가능
롤이나 퍼미션의 이름에 제한은 없으며 개발자가 정하면 된다.
web2py는 정해진 이름에 대한 퍼미션 검사를 수행할뿐..
인증 검사 대상인 유저가 필요한 퍼미션을 가진 그룹에 속해 있는 지 검사 한다.
CRUD 같은 몇몇 특정 퍼미션은 web2py가 미리 알고 있으며 이것들은 데코레이터를
사용하지 않고도 권한 검사를 강제 할수 있다
인증 Authentication
RBAC 을 사용하기 위해선 사용자가 특정지어져야 하는 데 이것은 사용자가 계정을
등록하고 로그인하는 과정을 거쳐서 이루어 진다.
Auth 모듈은 여러가지 로그인 방법을 제공하는데 그중하나는 auth_user 테이블을
사용하는 것이다.
그 외에 Google, PAM, LDAP, Facebook, LinkedIn, Dropbox, OpenID, OAuth등의 3rd party인증 시스템을 사용해서 도 가능하다.
Auth를 사용하려면 모델 파일에 ( 기본 템플릿인 welcome 앰에 이미 포함되어
있다.)
( dal object 가 db라고 가정하고 )
from gluon.tools import Auth
auth = Auth(db)
auth.define_tables()
형태로 쓰면 된다.
Auth 를 만들때 secure=True 인자를 주면 인증페이지가 https 로 되도록 강제한다.
기본적으로 Auth는 cross-site request forgeries (CSRF) 를 막아주는데 이것은
web2py 에 기본적으로 들어 있는 기능을 사용한것이다.
특수한 상황에서는 로그인 세션을 만들거나 하는 오버헤드가 바람직하지 못한
경우가 있기는 하다.
이론적으론 DOS 공격을 받을 수 있다. ( 막지 못한다. )
CSRF 를 안 쓰려면
Auth = Auth(..., csrf_prevention = False)
을 사용한다.
주의 할것은 단지 세션 오버헤드를 피하고 싶어서 이것을 사용하는 것은 추천하지
않으며 대신 Deployment chapter 에 나오는 세션 오버헤드를 줄이는 방법을
사용하라.
과거 버전에는 password 보안을 위해서 CRYPT validator 를 사용했는데
(private/auth.key 파일 ) 현재 버전은 각 row 에 랜덤 생성되는 salt를 사용한다.
기본 로그인ID 는 email을 사용하는데 이를 이름으로 바꾸려면
auth.define_tables(username=True)
여러 앱에서 동일 인증 DB 를 공유하려면
auth.define_tables(migrate=False)
을 사용해서 db migration을 꺼야 할것이다.
Auth를 노출하려면 ( 웹사이트에 ) 컨트롤러 파일에
def user(): return dict(form=auth())
을 넣어야 한다. ( 자동 생성된 default.py에 이미 들어 있음 )
welcome/views/default/user.html 이 로그인 에 사용되는 템플릿이다.
( 일반적 템플릿이프로 커스터 마이징 가능 )
커스터 마이징시 주의 할것은 request.args(0) 이다.
{{if request.args(0)=='login':}}...custom login form...{{pass}}
같은 형태를 사용하게 될것이다.
위 컨트롤러 함수는
http://.../[app]/default/user/register
http://.../[app]/default/user/login
http://.../[app]/default/user/logout
http://.../[app]/default/user/profile
http://.../[app]/default/user/change_password
http://.../[app]/default/user/verify_email
http://.../[app]/default/user/retrieve_username
http://.../[app]/default/user/request_reset_password
http://.../[app]/default/user/reset_password
http://.../[app]/default/user/impersonate
http://.../[app]/default/user/groups
http://.../[app]/default/user/not_authorized
을 노출하며
register : 등록 , 계정 생성, CAPTCHA 기능, 클라이언트 용 패스워드 강도 측정기,
IS_STRONG 을 사용하여 강제 가능
login : 로그인 , 등록후 허가 되었는지 , 블럭된 사용자는 아닌지,
logout : 생각하는 바로 그것이며 ^^;; , 다른 메소드 처럼 로깅되고 , 또 다른 이벤트로 트리거 될수 있다.
profile : 프로파일 편집 기능. auth_user 테이블 내용. ( auth_user은 커스터 마이징 가능하다. )
change_password : 안전하게 사용자가 암호를 바꿀수 있는 기능.
verify_email : email인증을 사용하는 경우 등록시 사용한 메일 주소로 검증용 링크를 보내게 되며 여기로 오게 된다.
retrieve_username : 로그인 아이디로 기본적으로 email을 사용하지만 사용자 이름을 사용하게 설정한 경우 email로 자신의 로그인 아이디 를 받을수 있는 기능이 이 링크를 통해서 제공된다.
request_reset_password. : 패스워드를 잊어 버린 경우 새 패스워드를 자동으로 만들고 그 확인을 email로 받을 수 있는 기능
impersonate : 디버깅이나 지원의 필요에 따라 다른 유저의 권한을
대행(impersonate) 할 수 있는 기능. request.args[0] 에 대행될 사용자 id가 들어
있다. has_permission('impersonate', db.auth_user, user_id) 권한을 가진
사용자만 대행할수 있으며 auth.is_impersonating() 을 통해 현재 대행중인지
확인 할 수 있다.
groups : 현 로그인한 사용자가 속한 그룹목록을 표시.
not_authorized : 권한 없음 에러 페이지 표시
navbar : 각종 헬퍼 링크들 표시
Logout, profile, change_password, impersonate, groups 은 로그인 된 상태에서만 사용가능하다.
위 링크들을 기본적으로 모두 노출 되지만 노출에 제한을 걸 수도 있다.
Auth를 서브클래싱해서 확장,변경 사능하다.
def myregister(): return dict(form=auth.register())
형태로 따로 사용도 가능하다.
@auth.requires_login()
def hello():
return dict(message='hello %(first_name)s' % auth.user)
형태로 접근제어를 한다.
( 꼭 노출된 함수일필요가 없음 )
(Auth가 아니고)auth 오브젝트의 주요 attibute : auth.user auth.user_id auth.user_groups
auth.requires_* 데코레이터 들은 선택적 otherwise 인자가 있다.
(인증실패시 사용할 URL 또는 callable )
Restrictions on registration
아무나 계정의 등록은 자유지만 관리자 허가를 얻어야만 활성화되는 것을 원한다면
auth.settings.registration_requires_approval = True
관리자는 appadmin UI를 통해서 이를 처리 할수 있다.
auth_user 테이블에 registration_key 가 pending 이면 허가 대기 중이란 뜻.
공백이면 허가 됨 이란 뜻.
같은 방법으로 계정 정지(블럭)도 가능하며
이경우는 registration_key 가 blocked 상태가 된다.
주의 할것은 기존 로그인 세션에 영향을 주는 것은 아니다.
( 이미 로그인 되어 있는 동안은 계속 사용가능 )
취향에 따라 disabled 사용해도 된다.
등록 기능 자체를 금지하려면
auth.settings.actions_disabled.append('register')
등록하자마자 로그인 상태로 사용가능하지만 email 검증을 보내려면 ( 일단
로그아웃하면 검증될때까지 로그인 불가 )
auth.settings.registration_requires_approval = True
auth.settings.login_after_registration = True
를 사용한다.
Integration with OpenID, Facebook, etc.
web2py에서 외부 인증을 사용할수 있는데 OpenID, Facebook, LinkedIn, Google,
Dropbox, MySpace, Flickr 등이 있다.
내용 생략.
CAPTCHA and reCAPTCHA
스패머나 등록봇들을 막기 위해 등록시 CAPTCHA 를 사용할수 있다.
web2py는 reCAPTCHA 서비스를 지원하는데 이는 잘 설계 되어 있고
free인서비스이다.
추가 모듈의 설치는 필요 없으며
reCAPTCHA 에 가입한후 PUBLIC_KEY, PRIVATE_KEY 를 받아오면 된다.
auth가 정의된후
from gluon.tools import Recaptcha
auth.settings.captcha = Recaptcha(request,
'PUBLIC_KEY', 'PRIVATE_KEY')
서비스를 작동시키려면 public IP 에서 작동시켜야 한다.
이후 생략.
Customizing Auth
auth 가 만들어진후
auth.define_tables()
전에
auth_user 를 커스터마이징할 수 있다.
## after auth = Auth(db)
auth.settings.extra_fields['auth_user']= [
Field('address'),
Field('city'),
Field('zip'),
Field('phone')]
## before auth.define_tables(username=True)
형식으로 추가 필드를 넣을 수 있다.
auth_user 뿐만 아니라 auth_* 도 같은 방법을 사용할수 있다.
필드추가 뿐 아니라 validators 를 변경할수 있지만
required 가 붙은 것은 지울수 없다.
password, registration_key, reset_password_key, registration_id 필드를
readable=False 과 writable=False 로 만드는 것을 잊지 마라. 그렇지 않으면
보안상 큰 문제가 생긴다. ( 유저에게 보이고 수정가능해 지니까 )
username 필드를 추가하면 email 대신 사용되며
auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)
같은 validator 가 필요할 것이다.
Renaming Auth tables
하지 마라.
내용은 본문참조
Other login methods and login forms
여러 인증 방법을 지원하고 또 새 로그인 방법을 만들수도 있는데 각 지원 방법
코드는
gluon/contrib/login_methods/
에 있다. - 코드를 보시라.
기본적으로 자체 인증이 아니라 외부 인증을 사용하는 방법 설명.
basic 인 경우의 예
from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods.append(
basic_auth('인증서비스 URL'))
auth.settings.login_methods 은 인증 수단의 목록이며 순차적으로 실행 된다.
기본적으로
auth.settings.login_methods = [auth]
로 되어 있다.
auth 인증이 실패하고 외부 인증을 성공하는 경우 그리고 auth가 인증 목록의
처음에 있는 경우
외부 인증을 통과한 사용자의 정보가 auth에 만들어 진다.
이것이 싫다면
순서를 바꾸거나 auth를 목록에서 제거 하면 된다.
후략.
Record versioning
Auth의 수정 기록 켜기.
db.enable_record_versioning(db,
archive_db=None,
archive_names='%(tablename)s_archive',
current_record='current_record'):
데이터의 수정 기록을 보관 하게 된다.
테이블중 modified_by 과 modified_on 필드가 있는 것만 보관 된다.
필드 중에 is_active 이 있는 경우 지워지지 않고 False로 설정되기만 한다.
auth.archive 이나 crud.archive 는 사용하지 말것. enable_record_versioning
으로 대치 되었다.
Mail and Auth
메일 인증은 기본적으로 꺼져 있으며
본문 과 같이 켤수 있다.
메일러를 켜고 auth 의 메일 인증을 켜야 한다.
본문 참고.
Authorization
새유저가 등록되면 해당 유저를 포함하는 새그룹도 같이 만들어진다.
보통 user_[id] 형태가 되는 데 이렇게 새 그룹이 만들어 지는 것이 싫다면
auth.settings.create_user_groups = None
를 사용하면 되지만 추천하지 않는다.
디폴트는
auth.settings.create_user_groups="user_%(id)s"
형태 이다.
auth.settings.everybody_group_id = 5
를 사용하면 모든 새로 생성되는 유저가 5번 그룹에 속하게 된다.
그룹관리(생성, 멤버쉽관리, 퍼미션 부여 )는 appadmin을 통해서 할수 있고
auth 내의 함수들을 통해 프로그램 적으로도 가능하다.
몇 예제 - 본문 참고.
user_id 가 퍼미션을 가지고 있는지 검사.
auth.has_permission('name', 'object', record_id, user_id)
user_id가 read 퍼미션을 가지고 있는 mytable내의 모든 row , user_id 가
생략되면 현재 유저.
rows = db(auth.accessible_query('read', db.mytable, user_id)).select(db.mytable.ALL)
Decorators
실제 권한 검사는 각종 데코레이터를 사용해서 한다.
@auth.requires_*
@auth.requires(auth.user_id==1 or request.client=='127.0.0.1', requires_login=True)
def function_six():
return 'you can read secret documents'
같은 것도 사용가능.
@auth.requires(lambda: check_condition())
def action():
....
같이 callable을 쓰는것이 더 빠르다.
로그인 전 사용자가 권한검사 함수로 진입하면 일단 로그인 페이지로 리다이렉트
된다.
Combining requirements
@auth.requires() 를 사용한다.
인자는 T/F 를 리턴하는 수식.
Authorization and CRUD
접근제어를 하는 또다른 방법은
crud.settings.auth = auth
를 사용해서 각각의 데이터에대해 CRUD 접근을 제어 하는 것이다.
이를 사용하면 사용자는 모든 CRUD 기능에 대해 각각 권한이 필요하게 된다.
사용할수 있는 권한 이름은
"read", "create", "update", "delete", "select", "impersonate"
이다.
본문 예제 참조.
이를 통해 각 데이터에 대한 접근권한을 통제할수있다. ( 소유 이상의 제어가 가능)
Authorization and downloads
파일 다운로드에 대해 제어 하고 싶다면
def download(): return response.download(request, db)
식으로는 안되고
db.define_table('dog',
Field('small_image', 'upload'),
Field('large_image', 'upload'))
db.dog.large_image.authorization = lambda record: auth.is_logged_in() and auth.has_permission('read', db.dog, record.id, auth.user.id)
형태로 authorization 을 사용해야 한다.
이경우는 read제한을 건 예다.
Access Control and Basic Authentication
액션등의 서비스 api에 접근 제어를 하고 싶은 경우는
basic auth를 사용한다.
auth.settings.allow_basic_login = True
를 사용해 enable 하고
wget --user=[username] --password=[password] http://.../[app]/[controller]/give_me_time
def give_me_time():
import time
auth.basic()
if auth.user:
return time.ctime()
else:
return 'Not authorized'
식으로 만든다.
서비스에 권한 제어를 할때 사용할수 있는 유일한 방법인 경우가 많으며 기본적으론
꺼져 있다.
Application Management via privileged users (Experimental)
생략 , 본문 참조.
Manual Authentication
수동으로 인증 로직을 사용할때
user = auth.login_bare(username,password)
Settings and messages
Auth 관련 인자들 정리
본문 참고.
Central Authentication Service
CAS 는 업계 표준 인증 프로토콜,
web2py는 클라이언트,서버를 모두 구현하고 있음.
여러 어플리케이션이 하나의 CAS를 공유해서 SSO를 할수있음.
provider 와 consumer라고 부름.
사용자가 접속했을때 미인증 상태이면 CAS 서버로 redirect , 인증을 거치고 , 서버
- 서버 CAS 인증용 키를 가진채로 돌아온다.
이 키를 사용해서 consumer가 provider에게 background http요청으로 검증한다.
openid와 유사하나 openid는 사용자가 provider를 선택 , CAS는 시스템이
provider를 선택. 보안에 더 유리하다.
사용은
## in provider app
def user(): return dict(form=auth())
## in consumer app
auth = Auth(db,cas_provider = 'http://127.0.0.1:8000/provider/default/user/cas')
과 같이 간단하다. web2py가 이미 기능을 가지고 있음.
Using web2py to authorize non-web2py apps
생략 . 본문 참조.