基于rest-framework對django的RESTful API進(jìn)行權(quán)限設(shè)置

當(dāng)我們通過django框架創(chuàng)建RESTful API對外提供后,我們希望這些API只有相關(guān)權(quán)限的人才可以調(diào)用奕筐,這個(gè)怎么做呢?可以采用在django框架之上rest-framework去做变骡,當(dāng)然必須安裝rest-framework,然后在django的setting中的INSTALLED_APPS加上rest_framework离赫。
基于rest-framework的請求處理,與常規(guī)的url配置不同塌碌,通常一個(gè)django的url請求對應(yīng)一個(gè)視圖函數(shù)渊胸,在使用rest-framework時(shí),我們要基于視圖對象台妆,然后調(diào)用視圖對象的as_view函數(shù)翎猛,as_view函數(shù)中會(huì)調(diào)用rest_framework/views.py中的dispatch函數(shù)胖翰,這個(gè)函數(shù)會(huì)根據(jù)request請求方法,去調(diào)用我們在view對象中定義的對應(yīng)的方法切厘,就像這樣:

urlpatterns = [
    url(
        r"^test/?", testView.as_view(),
    )]

testView是繼承rest-framework中的APIView的View類

from rest_framework import status
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
class testView(APIView):
    authentication_classes = (
        BasicAuthentication,
        # SessionAuthentication,
        # TokenAuthentication,
    )
    permission_classes = (
        IsAuthenticated,
    )
    def get(self, request):
          pass

如果你是用get方法請求test,那么as_view()函數(shù)會(huì)調(diào)用dispatch函數(shù)萨咳,dispatch根據(jù)request.METHOD,這里是get疫稿,去調(diào)用testView類的get方法培他,這就跟通常的url->視圖函數(shù)的流程一樣了。

但是權(quán)限驗(yàn)證是在執(zhí)行請求之前做的遗座,所以其實(shí)就是在dispatch函數(shù)之中做的舀凛,具體見源碼rest-framework/views.py中APIView類中的dispatch函數(shù):

def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)  #重點(diǎn)關(guān)注

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

其實(shí)重點(diǎn)在于 self.initial(request, *args, **kwargs)函數(shù),對于這個(gè)函數(shù)

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)  #重點(diǎn)關(guān)注
        self.check_permissions(request) #重點(diǎn)關(guān)注
        self.check_throttles(request) #重點(diǎn)關(guān)注

self.perform_authentication(request) 驗(yàn)證某個(gè)用戶

        """
        Perform authentication on the incoming request.

        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user

這里request.user其實(shí)是一個(gè)@property的函數(shù)

    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            self._authenticate()
        return self._user

所以關(guān)注self._authenticate()函數(shù)就好了

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        Returns a three-tuple of (authenticator, user, authtoken).
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self) #重點(diǎn)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

驗(yàn)證用戶就是authenticator.authenticate途蒋,那么self.authenticators從哪兒來的呢猛遍?
關(guān)注文章開頭給出的testView類中的

authentication_classes = (
        BasicAuthentication,
    )
    permission_classes = (
        IsAuthenticated,
    )

authentication_classes 里面放的就是可以用來驗(yàn)證一個(gè)用戶的類,他是一個(gè)元組碎绎,驗(yàn)證用戶時(shí)螃壤,按照這個(gè)元組順序,直到驗(yàn)證通過或者遍歷整個(gè)元組還沒有通過筋帖。
同理self.check_permissions(request)是驗(yàn)證該用戶是否具有API的使用權(quán)限奸晴。關(guān)于對view控制的其他類都在rest-framework/views.py的APIView類中定義了。

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

具體可參見http://www.django-rest-framework.org/api-guide/views/
所以日麸,這里剩下的就是實(shí)現(xiàn)校驗(yàn)用戶的BasicAuthentication類了寄啼。對于像BasicAuthentication這樣的類,必須實(shí)現(xiàn)authenticate方法代箭,并且返回一個(gè)用戶墩划,賦值給request.user,這個(gè)request.user就是系統(tǒng)中進(jìn)行用戶認(rèn)證的user對象嗡综,后續(xù)的權(quán)限驗(yàn)證一般都是通過判斷request.user的user對象是否擁有某個(gè)權(quán)限乙帮。rest-framework默認(rèn)的就是BasicAuthentication,也就是跟admin登陸用的一樣的認(rèn)證极景。其源碼如下:

class BasicAuthentication(BaseAuthentication):
    """
    HTTP Basic authentication against username/password.
    """
    www_authenticate_realm = 'api'

    def authenticate(self, request):
        """
        Returns a `User` if a correct username and password have been supplied
        using HTTP Basic authentication.  Otherwise returns `None`.
        """
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'basic':
            return None

        if len(auth) == 1:
            msg = _('Invalid basic header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid basic header. Credentials string should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
        except (TypeError, UnicodeDecodeError, binascii.Error):
            msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
            raise exceptions.AuthenticationFailed(msg)

        userid, password = auth_parts[0], auth_parts[2]
        # userid就是用戶名  password 就是密碼
        return self.authenticate_credentials(userid, password)

    def authenticate_credentials(self, userid, password):
        """
        Authenticate the userid and password against username and password.
        """
        credentials = {
            get_user_model().USERNAME_FIELD: userid,
            'password': password
        }
        user = authenticate(**credentials) #重點(diǎn)關(guān)注

        if user is None:
            raise exceptions.AuthenticationFailed(_('Invalid username/password.'))

        if not user.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (user, None)

    def authenticate_header(self, request):
        return 'Basic realm="%s"' % self.www_authenticate_realm

對于上述函數(shù)調(diào)用流程察净,重點(diǎn)關(guān)注user = authenticate(**credentials),這里的authenticate其實(shí)是from django.contrib.auth import authenticate導(dǎo)入的authenticate,因?yàn)樵谡{(diào)用時(shí)authenticate前面沒有加self或者其他對象盼樟,在rest-framework的authentication.py中全局的authenticate就只有開始import的authenticate氢卡,那么在django/contrib/auth/init.py中的authenticate源碼如下:

def authenticate(**credentials):
    """
    If the given credentials are valid, return a User object.
    """
    for backend, backend_path in _get_backends(return_tuples=True):
        try:
            inspect.getcallargs(backend.authenticate, **credentials)
        except TypeError:
            # This backend doesn't accept these credentials as arguments. Try the next one.
            continue

        try:
            user = backend.authenticate(**credentials) #重點(diǎn)關(guān)注
        except PermissionDenied:
            # This backend says to stop in our tracks - this user should not be allowed in at all.
            break
        if user is None:
            continue
        # Annotate the user object with the path of the backend.
        user.backend = backend_path
        return user

    # The credentials supplied are invalid to all backends, fire signal
    user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials))

這里的backend其實(shí)就是settings中指定的AUTHENTICATION_BACKENDS,一般也就是django/contrib/auth/backends.py中的ModelBackend類晨缴,那么看看backend.authenticate干了什么译秦?

class ModelBackend(object):
    """
    Authenticates against settings.AUTH_USER_MODEL.
    """

    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            user = UserModel._default_manager.get_by_natural_key(username)
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

也就是去跟數(shù)據(jù)庫比對,用戶名和密碼是否匹配。如果匹配返回user

接下來就到了self.check_permissions(request)筑悴,

    def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )

如果存在驗(yàn)證不通過们拙,那么就執(zhí)行self.permission_denied,

    def permission_denied(self, request, message=None):
        """
        If request is not permitted, determine what kind of exception to raise.
        """
        if request.authenticators and not request.successful_authenticator:
            raise exceptions.NotAuthenticated()
        raise exceptions.PermissionDenied(detail=message)

然后這個(gè)異常在dispatch函數(shù)中被捕捉阁吝,當(dāng)做結(jié)果傳遞給response睛竣。

對于API的權(quán)限,也可以在settings中進(jìn)行全局設(shè)置求摇,具體過程可參照:
http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
整個(gè)練習(xí)的開始是:
http://www.django-rest-framework.org/tutorial/quickstart/
而關(guān)于BasicAuthentication認(rèn)證的解釋,可以參見:
https://www.ibm.com/support/knowledgecenter/en/SSGMCP_5.1.0/com.ibm.cics.ts.internet.doc/topics/dfhtl2a.html

當(dāng)一次授權(quán)通過后殊者,再一次訪問這個(gè)API時(shí)与境,這時(shí)候的用戶名和密碼從哪兒來的?下一次來訪問的時(shí)候就是通過服務(wù)器通過cookie返回給client的sessionid去驗(yàn)證猖吴,通過谷歌瀏覽器用F12調(diào)試可以得到驗(yàn)證摔刁,下一次通過瀏覽器訪問時(shí),就會(huì)帶上類似下面的內(nèi)容:Cookie:csrftoken=FsvNBXNdyyUvECZwTMpj59DAnGPPurRFM8RqFQoVuvizeQ6OB1nSK3KxS8mjJiWE; sessionid=xxtj52tsqgow9kbur6e304fd1ygn7603海蔽。至于指定的BasicAuthentication認(rèn)證為什么第二次會(huì)走SessionAuthentication認(rèn)證共屈,暫時(shí)還不知道。

如果APIView類中的authentication_classes使用的是SessionAuthentication去驗(yàn)證党窜,那么就要在請求頭部帶上sessionid拗引,請求如下:

#!/usr/bin/env python
#coding=utf-8

import urllib2 
url = 'http://127.0.0.1:8000/testapiview'
#headers={'Authorization': 'Token cc6d79b3669ceaea45efe028ad8e23fdc978b786'}
headers = {'Cookie': 'csrftoken=FsvNBXNdyyUvECZwTMpj59DAnGPPurRFM8RqFQoVuvizeQ6OB1nSK3KxS8mjJiWE; sessionid=xxtj52tsqgow9kbur6e304fd1ygn7603'}

request = urllib2.Request(url)
for header in headers:
    request.add_header(header,headers[header])

res = urllib2.urlopen(request)

那可能會(huì)問sessionid從哪兒來,按照常規(guī)幌衣,我們登陸一個(gè)系統(tǒng)后矾削,服務(wù)端會(huì)根據(jù)我們第一次登陸提供的用戶名和密碼還有訪問的域名等其他信息生成一個(gè)session對象保存在服務(wù)端,并通過寫cookie返回給client豁护,當(dāng)下一次訪問相同的域名時(shí)就會(huì)在cookie中帶上相應(yīng)的sessionid信息哼凯,SessionAuthentication模塊根據(jù)sessionid去進(jìn)行權(quán)限驗(yàn)證。

同理楚里,如果如果APIView類中的authentication_classes使用的是TokenAuthentication去驗(yàn)證断部,那么就要在請求頭部帶上Token信息,代碼例子跟上面的session驗(yàn)證一樣班缎,只是把header換成token蝴光。同樣,token從哪兒來呢吝梅?Token一般是在服務(wù)器上跟用戶一起綁定生成的虱疏,然后存放在token數(shù)據(jù)庫中。在rest-framework中苏携,要是用Token驗(yàn)證做瞪,那么在settings中的INSTALL_APP中還要加上'rest_framework.authtoken',用來生成存放token的數(shù)據(jù)庫,當(dāng)創(chuàng)建好token后装蓬,下一次訪問時(shí)著拭,帶上Token就好了。我們一般都是用帶token的方式進(jìn)行訪問牍帚,在傳輸token過程中一般用https防止token泄漏儡遮,當(dāng)然我們也最好讓token有時(shí)效性,然后定時(shí)更新token暗赶,這樣保證多數(shù)情況下鄙币,即使token泄漏也不會(huì)造成很大安全風(fēng)險(xiǎn)。這個(gè)例子可以參考:
https://chrisbartos.com/articles/how-to-implement-token-authentication-with-django-rest-framework/
http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

當(dāng)然蹂随,你也可以自己定制用戶認(rèn)證的類十嘿,但是要明確一點(diǎn),調(diào)用這個(gè)認(rèn)證的類的authenticate函數(shù)一定要返回一個(gè)用戶對象給request.user還有request.auth岳锁,后續(xù)的權(quán)限驗(yàn)證都是依據(jù)這兩個(gè)進(jìn)行的绩衷,比如下面:

#!/usr/bin/env python
# coding: utf-8

import logging
from urlparse import urljoin
from urllib import quote as urlquote
from datetime import datetime

from django.conf import settings

from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions

import requests

from core.utils import getitems, retry, iso86012datetime

logger = logging.getLogger(__name__)

#定義一個(gè)用戶user類,authenticate函數(shù)會(huì)返回這樣一個(gè)實(shí)例給request.user
#這個(gè)類可以參照django/contrib/auth/models.py中的User類
class KeystoneTenant(object): 
    is_staff = True
    is_superuser = False

    def __init__(self, id, name, uid, user):
        self.id = id
        self.name = name
        self.uid = uid
        self.user = user
-------------------------------------省略---------------------------------------
    @property
    def pk(self):
        return self.id

    @property  #必須包含的方法
    def username(self):
        return self.name

    @property #必須包含的方法
    def email(self):
        return settings.ADMIN_EMAIL

    @property   #必須包含的方法
    def is_authenticated(self):
        return self.is_staff

    @classmethod
    def from_access_info(cls, access_info):
        if not access_info:
            return None

        tenant = cls(
            id=getitems(access_info, ["access", "token", "tenant", "id"]),
            name=getitems(access_info, ["access", "token", "tenant", "name"]),
            uid=getitems(access_info, ["access", "user", "id"]),
            user=getitems(access_info, ["access", "user", "name"]),
        )
        tenant.is_superuser = bool(getitems(access_info, [
            "access", "metadata", "is_admin",
        ], cls.is_superuser))
        tenant.is_staff = bool(getitems(access_info, [
            "access", "token", "tenant", "enabled",
        ], cls.is_staff))

        return tenant


def get_x_auth_token(tenant_name, user_name, password):
    logger.info("Get X-AUTH-TOKEN by tenant: %s", tenant_name)
    response = requests.post(
        urljoin(
            settings.KEYSTONE_ENDPOINT, "/v2.0/tokens"
        ), json={
            "auth": {
                "passwordCredentials": {
                    "username": user_name,
                    "password": password,
                },
                "tenantName": tenant_name,
            }
        },
    )
    result = response.json()
    try:
        token = result["access"]["token"]["id"]
        expiry = result["access"]["token"]["expires"]
    except KeyError:
        logger.exception(
            "Unexpected response from keystone service: %s", result,
        )
        raise
    return token, iso86012datetime(expiry)


#放在authentication_classes 中的用于進(jìn)行用戶認(rèn)證的類
class KeystoneV2Authentication(BaseAuthentication):
    admin_token = None
    admin_token_expiry = None

    def get_admin_token(self):
        if (
            settings.KEYSTONE_TOKEN_CACHE and
            KeystoneV2Authentication.admin_token_expiry
        ):
            now = datetime.utcnow()
            expiry_delta = now - KeystoneV2Authentication.admin_token_expiry
            if expiry_delta.total_seconds() > 300:
                return KeystoneV2Authentication.admin_token

        admin_token, admin_token_expiry = get_x_auth_token(
            settings.KEYSTONE_TENANT,
            settings.KEYSTONE_USER,
            settings.KEYSTONE_PASSWORD,
        )
        KeystoneV2Authentication.admin_token = admin_token
        KeystoneV2Authentication.admin_token_expiry = admin_token_expiry
        return admin_token
-------------------------------------省略---------------------------------------

    #這個(gè)函數(shù)一定要返回一個(gè)User實(shí)例和auth屬性
    def authenticate(self, request):
        token = request.META.get("HTTP_X_AUTH_TOKEN")
        if not token:
            raise exceptions.AuthenticationFailed("X-Auth-Token is required")

        try:
            response = retry(
                settings.DEFAULT_RETRY_TIMES,
                requests.get,
                urljoin(
                    settings.KEYSTONE_ENDPOINT,
                    "/v2.0/tokens/%s" % urlquote(token),
                ),
                headers={
                    "X-Auth-Token": self.get_admin_token(),
                }
            )
        except Exception as err:
            logger.exception(err)
            raise exceptions.AuthenticationFailed(
                "Authorization error",
            )

        if response.status_code == 404:
            raise exceptions.AuthenticationFailed(
                "Authorization failed for token",
            )
        elif response.status_code == 401:
            self.admin_token = None
            raise exceptions.AuthenticationFailed(
                "Keystone rejected admin token, resetting",
            )
        elif response.status_code != 200:
            raise exceptions.AuthenticationFailed(
                "Bad response code while validating token: %s" % (
                    response.status_code
                ),
            )

        access_info = response.json()
        tenant = KeystoneTenant.from_access_info(access_info)
        if not tenant.is_staff:
            raise exceptions.AuthenticationFailed(
                "Tenant inactive or deleted",
            )

        self._set_auth_headers(request, tenant)
        return (tenant, None)

請求流程如下APIview.as_view -> dispatch -> initial(驗(yàn)證權(quán)限激率,方法是否是被允許的等一系列的操作) -> 根據(jù)請求方法調(diào)用APIview中的對應(yīng)的方法咳燕,比如get, put乒躺, post招盲,對于get, put聪蘸, post等這些方法宪肖,我們可以在自己實(shí)現(xiàn)的view類中直接定義這些方法,也可以繼承rest-framework/mixins.py中以及rest-framework/gernerics.py定義好了很多類中對應(yīng)的get健爬, put等操作控乾,比如RetrieveModelMixin類定義了查詢操作

class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)

可能會(huì)疑問,這個(gè)retrieve函數(shù)誰去調(diào)用呢娜遵? 還記得上面APIview中的dispatch方法么蜕衡,dispatch會(huì)根據(jù)請求方法調(diào)用,對應(yīng)的比如get设拟。那么get的查詢操作如何和retrieve這個(gè)函數(shù)結(jié)合起來呢慨仿?見rest-framework處理流程分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纳胧,一起剝皮案震驚了整個(gè)濱河市镰吆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跑慕,老刑警劉巖万皿,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摧找,死亡現(xiàn)場離奇詭異,居然都是意外死亡牢硅,警方通過查閱死者的電腦和手機(jī)蹬耘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來减余,“玉大人综苔,你說我怎么就攤上這事∥徊恚” “怎么了如筛?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抒抬。 經(jīng)常有香客問我妙黍,道長,這世上最難降的妖魔是什么瞧剖? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮可免,結(jié)果婚禮上抓于,老公的妹妹穿的比我還像新娘。我一直安慰自己浇借,他們只是感情好捉撮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妇垢,像睡著了一般巾遭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闯估,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天灼舍,我揣著相機(jī)與錄音,去河邊找鬼涨薪。 笑死骑素,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刚夺。 我是一名探鬼主播献丑,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼侠姑!你這毒婦竟也來了创橄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤莽红,失蹤者是張志新(化名)和其女友劉穎妥畏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咖熟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年圃酵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馍管。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡郭赐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出确沸,到底是詐尸還是另有隱情捌锭,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布罗捎,位于F島的核電站观谦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏桨菜。R本人自食惡果不足惜豁状,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望倒得。 院中可真熱鬧泻红,春花似錦、人聲如沸霞掺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菩彬。三九已至缠劝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骗灶,已是汗流浹背惨恭。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耙旦,地道東北人喉恋。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像母廷,于是被迫代替她去往敵國和親轻黑。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容