Spring Security OAuth2.0

簡介

OAuth 2.0:是用于授權(quán)的行業(yè)標(biāo)準(zhǔn)協(xié)議辉川。 OAuth 2.0致力于簡化客戶端開發(fā)人員的工作叶沛,同時(shí)為Web應(yīng)用程序惰赋,桌面應(yīng)用程序产徊,移動(dòng)電話和客廳設(shè)備提供特定的授權(quán)流程昂勒。 該規(guī)范及其擴(kuò)展正在IETF OAuth工作組內(nèi)開發(fā)。

Spring Security定義的OAuth2.0授權(quán)類型

  • 授權(quán)碼(Authorization Code)
  • 客戶憑證(Client Credentials)
  • 資源所有者密碼憑證(Resource Owner Password Credentials)
  • 刷新令牌(Refresh Token)

Spring Security OAuth2.0原理分析(Authorization Code方式)

OAuth2.0 Client重定向到Authorization Server

image.png
  1. 用戶在登錄頁選擇登錄方式舟铜,比如GitHub戈盈、微信等,請(qǐng)求路徑:oauth2/authorization/{registrationId},以GitHub為例:oauth2/authorization/github 塘娶;
  2. OAuth2AuthorizationRequestRedirectFilter攔截請(qǐng)求归斤,并調(diào)用OAuth2AuthorizationRequestResolver.resolve();
  3. OAuth2AuthorizationRequestResolver校驗(yàn)請(qǐng)求路徑是否正確刁岸,如果正確則接著調(diào)用ClientRegistrationRepository.findByRegistrationId()獲取ClientRegistration注冊(cè)信息(配置)脏里,OAuth2AuthorizationRequestResolver將注冊(cè)信息包裝成OAuth2AuthorizationRequest并返回;
  4. OAuth2AuthorizationRequestRedirectFilter判斷上一步返回的OAuth2AuthorizationRequest不為空虹曙,則接著判斷當(dāng)前授權(quán)類型是否==授權(quán)碼類型迫横,如果是則需要調(diào)用AuthorizationRequestRepository.saveAuthorizationRequest()存儲(chǔ)OAuth2AuthorizationRequest(以Map方式K-state,V-OAuth2AuthorizationRequest存儲(chǔ)到Session中),最后通過RedirectStrategy發(fā)起重定向操作酝碳。

Authorization Server授權(quán)

image.png
  1. OAuth2.0 Client選擇某個(gè)三方授權(quán)中心(這邊以基于spring security搭建的自定義授權(quán)中心為例)矾踱,進(jìn)入custom授權(quán)中心,發(fā)現(xiàn)用戶未登錄疏哗,跳轉(zhuǎn)登錄頁面呛讲;
  2. 用戶登錄成功后,重現(xiàn)GET /oauth/authorize?response_type=code&client_id=client_web&state=SZn-vxg9xmXr6rXFHXHDycthS2YZpvF2iNR32QktNOM%3D&redirect_uri=http://localhost:8080/client/login/oauth2/code/custom返奉,進(jìn)入AuthorizationEndpoint授權(quán)端點(diǎn)贝搁;
  3. 接著進(jìn)入授權(quán)頁面,用戶可以選擇是否授權(quán)芽偏;
  4. 將授權(quán)結(jié)果以POST方式提交給AuthorizationEndpoint雷逆,如果授權(quán)成功,將簽發(fā)code并重定向到客戶端污尉。

Authorization Server授權(quán)成功重定向到OAuth2.0 Client

image.png
  1. 用戶在Authorization Server授權(quán)成功关面,重定向到OAuth2.0 Client,請(qǐng)求路徑:/login/oauth2/code/{registrationId}十厢,以GitHub為例:/login/oauth2/code/github,OAuth2LoginAuthenticationFilter攔截到該請(qǐng)求捂齐,通過判斷請(qǐng)求參數(shù)是否包含code蛮放、state或者error、state;
  2. 接著OAuth2LoginAuthenticationFilter調(diào)用AuthorizationRequestRepository.removeAuthorizationRequest()刪除AuthorizationRequest并返回奠宜,返回的AuthorizationRequest不為空則走下一步包颁;
  3. 再著OAuth2LoginAuthenticationFilter調(diào)用ClientRegistrationRepository.findByRegistrationId()獲取配置的ClientRegistration,返回的ClientRegistration不為空則走下一步压真;
  4. 然后OAuth2LoginAuthenticationFilter調(diào)用AuthenticationManager.authenticate()娩嚼;
    1. 將認(rèn)證邏輯委托給OAuth2LoginAuthenticationProvider;
    2. OAuth2LoginAuthenticationProvider調(diào)用OAuth2AuthorizationCodeAuthenticationProvider.authenticate()滴肿,OAuth2AuthorizationCodeAuthenticationProvider比較state的值是否一致岳悟,如果一致則調(diào)用OAuth2AccessTokenResponseClient.getTokenResponse()向Authorization Server獲取accessToken;
    3. OAuth2LoginAuthenticationProvider調(diào)用OAuth2UserService.loadUser()向Resource Server獲取用戶信息。
  5. 最后OAuth2LoginAuthenticationFilter調(diào)用OAuth2AuthorizedClientRepository.saveAuthorizedClient()保存授權(quán)用戶信息贵少,OAuth2AuthorizedClientRepository調(diào)用OAuth2AuthorizedClientService.saveAuthorizedClient()以Map<OAuth2AuthorizedClientId, OAuth2AuthorizedClient>形式保存在內(nèi)存中呵俏。

OAuth2.0 Client從Authorization Server獲取accessToken

image.png

OAuth2.0 Client調(diào)用 POST /oauth/token從Authorization Server獲取token信息。

OAuth2.0 Client從Resource Server獲取用戶信息

image.png
  1. 用戶發(fā)起獲取資源請(qǐng)求滔灶,例如:/getUser(路徑可自定義普碎,無特殊要求) ,必須包含Authorization: Bearer token值請(qǐng)求頭录平;
  2. 經(jīng)過BearerTokenAuthenticationFilter麻车,將token解析成BearerToken
    [圖片上傳中...(image.png-b9379b-1608388383917-0)]
    AuthenticationToken對(duì)象,交給AuthenticationManager進(jìn)行認(rèn)證處理斗这;
  3. 成功則繼續(xù)往下處理filterChain.doFilter(request, response)动猬,失敗則交給認(rèn)證失敗處理器來處理。

基于JWT token類型的認(rèn)證

image.png
  1. AuthenticationManager將token認(rèn)證委托給JwtAuthenticationProvider涝影;
  2. JwtAuthenticationProvider通過JwtDecoder解析并校驗(yàn)token信息枣察;
  3. JwtAuthenticationConverter將token信息轉(zhuǎn)化為JwtAuthenticationToken并返回;
JwtDecoder解析并校驗(yàn)token信息

首先了解下JWT的數(shù)據(jù)結(jié)構(gòu):

  • Header:頭部燃逻,由算法和類型兩部分組成序目,頭部基于Base64Url編碼;
{
  "alg": "HS256",
  "typ": "JWT"
}
  • Payload:負(fù)載伯襟,存儲(chǔ)用戶信息和附加數(shù)據(jù)猿涨,負(fù)載基于Base64Url編碼;
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
  • Signature:簽名姆怪,對(duì)頭部叛赚、負(fù)載進(jìn)行簽名;
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

如何驗(yàn)證JWT的有效性稽揭?
頭部和負(fù)載信息隨時(shí)都有可能被篡改俺附,JWT通過Signature簽名方式保證數(shù)據(jù)的安全性。JWT數(shù)據(jù)生產(chǎn)方:采用SHA系列算法將頭部溪掀、負(fù)載生成摘要信息事镣,接著通過私鑰(對(duì)稱或者非對(duì)稱加密算法)進(jìn)行簽名;JWT數(shù)據(jù)消費(fèi)方:通過通過base64UrlDecode解析頭部信息獲取簽名算法揪胃,然后通過接口請(qǐng)求JWT數(shù)據(jù)生產(chǎn)方獲取公鑰璃哟,通過公鑰+頭部的簽名算法驗(yàn)證改JWT的有效性。當(dāng)然除了以外喊递,還要保證該token還沒過期等随闪。

因此:Authorization Server作為JWT生產(chǎn)方,頒發(fā)token骚勘;Resource Server作為JWT消費(fèi)方铐伴,解析Header、Payload信息,獲得簽名算法盛杰,接著調(diào)用/.well-known/jwks.json從Authorization Server獲取公鑰集挽荡,并進(jìn)行驗(yàn)簽操作(Spring Security 采用Nimbus框架支持JWT功能)。

Spring Security OAuth2.0實(shí)戰(zhàn)

OAuth2.0 Client

  1. 引入依賴
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-client</artifactId>
            <version>5.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
            <version>5.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>
  1. 修改配置文件application.yml
server:
  port: 8080
  servlet:
    context-path: /client
spring:
  thymeleaf:
    cache: false
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: b8a7914d0895b3c086f4
            client-secret: 097b313fd4b4375066dc9ad22c92b124792687d2
          custom:
            client-id: client_web
            client-secret: secret
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/client/login/oauth2/code/custom
        provider:
          custom:
            authorization-uri: http://localhost:8081/oauth2authorizationserver/oauth/authorize
            token-uri: http://localhost:8081/oauth2authorizationserver/oauth/token
            user-info-uri: http://localhost:8082/resourceserver
            user-name-attribute: name

3.相關(guān)代碼
OAuth2LoginController.java

@Controller
public class OAuth2LoginController {

    @GetMapping("/")
    public String index(Model model,
                        @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
                        @AuthenticationPrincipal OAuth2User oauth2User) {
        model.addAttribute("userName", oauth2User.getName());
        model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
        model.addAttribute("userAttributes", oauth2User.getAttributes());
        return "index";
    }
}

index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <title>Spring Security - OAuth 2.0 Login</title>
    <meta charset="utf-8" />
</head>
<body>
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()">
    <div style="float:left">
        <span style="font-weight:bold">User: </span><span sec:authentication="name"></span>
    </div>
    <div style="float:none">&nbsp;</div>
    <div style="float:right">
        <form action="#" th:action="@{/logout}" method="post">
            <input type="submit" value="Logout" />
        </form>
    </div>
</div>
<h1>OAuth 2.0 Login with Spring Security</h1>
<div>
    You are successfully logged in <span style="font-weight:bold" th:text="${userName}"></span>
    via the OAuth 2.0 Client <span style="font-weight:bold" th:text="${clientName}"></span>
</div>
<div>&nbsp;</div>
<div>
    <span style="font-weight:bold">User Attributes:</span>
    <ul>
        <li th:each="userAttribute : ${userAttributes}">
            <span style="font-weight:bold" th:text="${userAttribute.key}"></span>: <span th:text="${userAttribute.value}"></span>
        </li>
    </ul>
</div>
</body>
</html>

OAuth2.0 Authorization Server

  1. 引入依賴
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.1.2</version>
        </dependency>
  1. 修改配置文件application.yml
server:
  port: 8081
  servlet:
    context-path: /oauth2authorizationserver
  1. 相關(guān)代碼
    SecurityConfig.java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .mvcMatchers("/.well-known/jwks.json").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
                User.withDefaultPasswordEncoder()
                        .username("admin")
                        .password("admin")
                        .roles("USER")
                        .build());
    }
}

AuthorizationServerConfiguration.java

@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        // @formatter:off
        clients.inMemory()
                .withClient("client_web")
                .redirectUris("http://localhost:8080/client/login/oauth2/code/custom")
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("message:read", "message:write")
                .authorities("oauth2")
                .secret("{noop}secret")
                .accessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1))
                .refreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1));
        // @formatter:on
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // @formatter:off
        endpoints
                .authenticationManager(this.authenticationConfiguration.getAuthenticationManager())
                .tokenStore(tokenStore())
                .accessTokenConverter(accessTokenConverter());
        // @formatter:on
    }

    @Bean
    public KeyPair keyPair() {
        try {
            String privateExponent = "3851612021791312596791631935569878540203393691253311342052463788814433805390794604753109719790052408607029530149004451377846406736413270923596916756321977922303381344613407820854322190592787335193581632323728135479679928871596911841005827348430783250026013354350760878678723915119966019947072651782000702927096735228356171563532131162414366310012554312756036441054404004920678199077822575051043273088621405687950081861819700809912238863867947415641838115425624808671834312114785499017269379478439158796130804789241476050832773822038351367878951389438751088021113551495469440016698505614123035099067172660197922333993";
            String modulus = "18044398961479537755088511127417480155072543594514852056908450877656126120801808993616738273349107491806340290040410660515399239279742407357192875363433659810851147557504389760192273458065587503508596714389889971758652047927503525007076910925306186421971180013159326306810174367375596043267660331677530921991343349336096643043840224352451615452251387611820750171352353189973315443889352557807329336576421211370350554195530374360110583327093711721857129170040527236951522127488980970085401773781530555922385755722534685479501240842392531455355164896023070459024737908929308707435474197069199421373363801477026083786683";
            String exponent = "65537";

            RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
            RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return new KeyPair(factory.generatePublic(publicSpec), factory.generatePrivate(privateSpec));
        } catch ( Exception e ) {
            throw new IllegalArgumentException(e);
        }
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(this.keyPair());

        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(new SubjectAttributeUserTokenConverter());
        converter.setAccessTokenConverter(accessTokenConverter);

        return converter;
    }

    /**
     * 擴(kuò)展響應(yīng)屬性
     */
    public static class SubjectAttributeUserTokenConverter extends DefaultUserAuthenticationConverter {
        @Override
        public Map<String, ?> convertUserAuthentication(Authentication authentication) {
            Map<String, Object> response = new LinkedHashMap<>();
            response.put("sub", authentication.getName());
            if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
                response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
            }
            return response;
        }
    }
}

JwkSetEndpoint.java

@FrameworkEndpoint
public class JwkSetEndpoint {
    @Autowired
    private KeyPair keyPair;

    @GetMapping("/.well-known/jwks.json")
    @ResponseBody
    public Map<String, Object> getKey() {
        RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
        RSAKey key = new RSAKey.Builder(publicKey).build();
        return new JWKSet(key).toJSONObject();
    }
}

OAuth2.0 Resource Server

  1. 引入依賴
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
            <version>5.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
            <version>5.4.1</version>
        </dependency>
  1. 修改配置文件application.yml
server:
  port: 8082
  servlet:
      context-path: /resourceserver
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:8081/oauth2authorizationserver/.well-known/jwks.json
  1. 相關(guān)代碼
    OAuth2ResourceServerSecurityConfiguration.java
@EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUri;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
                .authorizeRequests((authorizeRequests) ->
                        authorizeRequests
                                .antMatchers(HttpMethod.GET, "/message/**").hasAuthority("SCOPE_message:read")
                                .antMatchers(HttpMethod.POST, "/message/**").hasAuthority("SCOPE_message:write")
                                .anyRequest().authenticated()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        // @formatter:on
    }

    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
    }
}

OAuth2ResourceServerController.java

@RestController
public class OAuth2ResourceServerController {
    private static final Map<String, String> USER_MAP = new HashMap<>();

    static {
        USER_MAP.put("admin", "{\n" +
                "\"name\":\"admin\",\n" +
                "\"age\":\"20\",\n" +
                "\"realName\":\"管理員\"\n" +
                "}");
    }

    @GetMapping("/")
    public String index(@AuthenticationPrincipal Jwt jwt) {
        return USER_MAP.get(jwt.getSubject());
    }

    @GetMapping("/message")
    public String message() {
        return "secret message";
    }

    @PostMapping("/message")
    public String createMessage(@RequestBody String message) {
        return String.format("Message was created. Content: %s", message);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末即供,一起剝皮案震驚了整個(gè)濱河市定拟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌逗嫡,老刑警劉巖青自,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驱证,居然都是意外死亡延窜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門抹锄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逆瑞,“玉大人,你說我怎么就攤上這事伙单』窀撸” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵吻育,是天一觀的道長念秧。 經(jīng)常有香客問我,道長布疼,這世上最難降的妖魔是什么摊趾? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮游两,結(jié)果婚禮上砾层,老公的妹妹穿的比我還像新娘。我一直安慰自己贱案,他們只是感情好梢为,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著轰坊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祟印。 梳的紋絲不亂的頭發(fā)上肴沫,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蕴忆,去河邊找鬼颤芬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的站蝠。 我是一名探鬼主播汰具,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼菱魔!你這毒婦竟也來了留荔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤澜倦,失蹤者是張志新(化名)和其女友劉穎聚蝶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藻治,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碘勉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桩卵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片验靡。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雏节,靈堂內(nèi)的尸體忽然破棺而出胜嗓,到底是詐尸還是另有隱情,我是刑警寧澤矾屯,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布兼蕊,位于F島的核電站,受9級(jí)特大地震影響件蚕,放射性物質(zhì)發(fā)生泄漏孙技。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一排作、第九天 我趴在偏房一處隱蔽的房頂上張望牵啦。 院中可真熱鬧,春花似錦妄痪、人聲如沸哈雏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裳瘪。三九已至,卻和暖如春罪针,著一層夾襖步出監(jiān)牢的瞬間彭羹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工泪酱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留派殷,地道東北人还最。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像毡惜,于是被迫代替她去往敵國和親拓轻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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