媒介导读
阐发一下为何要用OAuth2战JWT去干
1. **单面登录(SSO)**计划单打登录计划是最多见的处置计划,但是单面登录需要每一个取用户接互的效劳皆必需取认证效劳截至通信,那不单会构成重复,**也会发生大批噜苏的收集流质**;
2. **散布式会话(Session)**计划颠末将用户会话疑息保存正在同享保存中,如Redis,并使用用户会话的ID动作key去完毕散布式哈希映照。当用户会见微效劳时,会话数据就能够从同享保存中获得。该处置计划正在下可用战扩大圆里皆很佳,可是因为会话疑息保留正在同享保存中,以是需要必然的庇护体制庇护数据宁静,因而正在具体的完毕中会具**有比力下的庞大度**。
3. **客户端令牌(Token)**计划令牌由客户端天生,并由认证效劳器署名。正在令牌中会包罗充足的疑息,客户端正在恳求时会将令牌附带正在恳求上,进而为各个微效劳供给用户身份数据。此计划处置了散布式会话计划的宁静性成就,**但是怎样实时注销用户认证疑息则是一个年夜成就,固然能够使用短时间令牌并频仍天取认证效劳器截至校验**,但是其实不能够完全处置。JWT(JSON Web Tokens)长短常着名的客户端令牌处置计划,它充足简朴,而且对于各类情况撑持水平也比力下
4. **客户端令牌取API网闭分离**
颠末正在微效劳架构中施行API网闭,能够将本初的客户端令牌变换为内部会话令牌。一圆里能够有用天躲藏微效劳,另外一圆里颠末API网闭的分歧进口能够完毕令牌的注销处置。正在David Borsos的第两个计划:散布式Session计划中请求开辟者能够将用户会话疑息零丁拎进去截至集合办理。业界比力老练的启源名目有Spring Session,其使用Redis数据库或者慢存体制去完毕Session保存,并颠末过滤器完毕Session数据的主动减载。跟着远多少年云效劳使用的开展,鉴于令牌(Token)的认证使用范畴也愈来愈广。关于鉴于令牌认证凡是包罗上面多少层寄义:
- 令牌是认证用户疑息的汇合,而不但仅是一个故意义的ID。
- 正在令牌中已经包罗充足多的疑息,考证令牌就能够完毕用户身份的校验,进而减少了因为用户考证需要检索数据库的压力,提拔了体系功用。
- 因为令牌是需要效劳器截至署名颁发的,以是假设令牌颠末解码认证,咱们就能够觉得该令牌所包罗的疑息是正当有用的。
-效劳 器会颠末HTTP头部中的Authorization获得令牌疑息并截至查抄,其实不需要正在效劳器端保存所有疑息。
- 颠末效劳器对于令牌的查抄体制,能够将鉴于令牌的认证使用正在鉴于浏览器的客户端战挪动装备的App或者是第三圆使用上。
·能够撑持跨法式挪用。鉴于Cookie是没有许可垮域会见的,而令牌则没有存留那个成就。
综上所述,鉴于令牌的认证因为会包罗认证用户的相干疑息,因而能够颠末考证令牌去完毕用户身份的校验,完整差别于以前鉴于会话的认证。因而,鉴于令牌的那个长处,像T微疑、付出宝、微专及GitHub等,皆拉出了鉴于令牌的认证效劳,用于会见所盛开的API及单面登录。交下来将重心介绍鉴于令牌认证计划中的OAuth 2.0战JWT
各司其职
OAuth 2.0
按照民网https://oauth.net/2/ OAuth 2.0是用于受权的止业尺度和谈。OAuth 2.0勤奋于简化客户端开辟职员的事情,共时为Web使用法式,桌里使用法式,挪动德律风战客堂装备供给一定的受权过程。该标准及其扩大在IETF OAuth事情组内乱开辟。
范畴是OAuth 2.0中的一种体制,用于限定使用法式对于用户帐户的会见。一个使用法式能够恳求一个或者多个范畴,而后正在统一 屏幕中将此疑息显现给用户,而且颁布给该使用法式的会见令牌将限于所授与的范畴。
简朴面道便需要二部门:
认证效劳端(认证及天生token) 、认证资本效劳端(会见其余效劳内乱的资本需要校验)
为何呢?
正在现在的微效劳中,一个名目可以有许多的营业效劳器,假设营业效劳器没有受信赖的时候,多个效劳器之间使用差异的token对于用户来讲是担心齐的,因为所有一个效劳器拿到token均可以仿冒用户来另外一个效劳器处置营业,您的效劳器便十分担心齐。
如何干呢?
营业效劳器要念获得认证效劳器的信赖,便只可使用认证效劳器的公钥派死的公钥去校验那个营业效劳器可否有权力处置。
简朴道一下如何获得公钥
天生一个jks,搁正在认证效劳器中- keytool -genkeypair -alias kevin_key -keyalg RSA -keypass 123456 -keystore kevin_key.jks -storepass 123456
复造代码
导出公钥- keytool -list -rfc --keystore kevin_key.jks | openssl x509 -inform pem -pubkey
复造代码 保留文原public_key.txt- -----BEGIN PUBLIC KEY-----
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxeI6+R6DsGs5RW21Xu1Fur7iPwGjyngN3SCnwPtdR9jTrQ8EIak+gyjpI/g7gIacHIZKMlVFWoEgjQ7+hIQ5FHBrmSR/S81ezCFjYSjBbdrHYQjMRpn4mEWFmQhIyTRhg1Pb5oTUlWx+L3wc45r6JFdMOlgkKBvfo/7lzwGhxeNp10rfoJcnGDhlfZ3PmoIOYmvg7Z8UwszZpYHWf98164m3hMiPyc81iiy/DEE60OVVepyvynfBwg1aGDyA64w63FZ/2dSwfQ/7VQ7WWJb7oVoIy5pyHslWMuQJPpNCxpOgmb19AgC1GojDSL7WAEq+2gQFrb+7k4PyBdsRYzR9DQIDAQAB
- -----END PUBLIC KEY-----
复造代码 公钥文献需要搁正在每一个营业效劳器中
OAuth 2.0客户端受权情势
消耗中一般接纳受权码情势,简朴来讲即是您要沉定背url认证效劳器获得受权码(code),正在获得会见令牌。
简朴绘了一个过程图
鄙人里过程图中第一步以后,会沉定背到类似于- http://localhost:8080/token/oauth/authorize?client_id=client1&response_type=code&redirect_uri=/token
复造代码 会前去一个code,假定code=2
正在会见- http://localhost:8080/oauth/token?client_id=client1&grant_type=authorization_code&redirect_uri=/token&code=2
复造代码 附带上code值,这时候便会前去access_token
另有一个成就:上面url的恳求的路子需要保留正在数据库中,需要新修一个表,牢固的字段,没有需要映照真体类,表中的统统字段必需把握- CREATE TABLE `oauth_client_details` (
- `client_id` varchar(255) NOT NULL COMMENT '用于唯一标记每个客户端
- (client);备案时必需挖写(也能够效劳端主动天生),那个字段是必需的,
- 理论使用也有嚷app_key',
- `resource_ids` varchar(255) DEFAULT NULL COMMENT '客户端能会见的资
- 源id汇合,备案客户端时,按照理论需要可挑选资本id,也能够按照差别的
- 额备案过程,付与对于应的额资本id',
- `client_secret` varchar(255) DEFAULT NULL COMMENT '备案挖写大概效劳
- 端主动天生,理论使用也有嚷app_secret, 必须要有前缀代表减稀方法',
- `scope` varchar(255) DEFAULT NULL COMMENT '指定client的权力范畴,
- 好比读写权力,好比挪动端仍是web端权力',
- `authorized_grant_types` varchar(255) DEFAULT NULL COMMENT '可选值
- 受权码情势:authorization_code,暗码情势:password,革新token: refresh_token, 隐式情势: implicit: 客户端情势: client_credentials。撑持多个用逗号分开\n\n作家:输出昵称便止\n链交:',
- `web_server_redirect_uri` varchar(255) DEFAULT NULL COMMENT '客户端
- 沉定背uri,authorization_code战implicit需要该值截至校验,备案时挖写,可为空',
- `authorities` varchar(255) DEFAULT NULL COMMENT '指定用户的权力范畴
- ,假设受权的历程需要用户登岸,该字段没有生效,implicit战
- client_credentials需要 可为空',
- `access_token_validity` int(11) DEFAULT NULL COMMENT '树立
- access_token的有用时间(秒)默认(606012,12小时) 可空',
- `refresh_token_validity` int(11) DEFAULT NULL COMMENT '树立
- refresh_token有用期(秒)默认(606024*30, 30天) 可空',
- `additional_information` varchar(4096) DEFAULT NULL COMMENT '
- 值必需是json格局 可空',
- `autoapprove` varchar(255) DEFAULT NULL COMMENT '默认false,合用于
- authorization_code情势,树立用户可否主动approval操纵,树立true跳过
- 用户确认受权操纵页里,间接跳到redirect_uri',
- PRIMARY KEY (`client_id`) USING BTREE
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复造代码 那里给出原尝试中的某些值,client1代表浏览器会见,client2代表app会见- INSERT INTO `oauth_client_details` VALUES ('client1', 'client1,client2', '{noop}secret1', 'app', 'authorization_code,refresh_token', '/token', NULL, 36000, 36000, NULL, 'true');
- INSERT INTO `oauth_client_details` VALUES ('client2', 'client1,client2', '{noop}secret2', 'app', 'authorization_code,refresh_token', '/token', '', 604800, 2592000, '', 'true');
复造代码 那里借需要数据库保留受权用户,用于校验- CREATE TABLE `sys_user` (
- `id` varchar(150) NOT NULL,
- `phone` varchar(50) DEFAULT NULL,
- `email` varchar(100) DEFAULT NULL,
- `password` varchar(150) NOT NULL,
- `disable` int(11) NOT NULL,
- `create_time` datetime DEFAULT NULL COMMENT '创立时间',
- `update_time` datetime DEFAULT NULL COMMENT '革新时间',
- `ip` varchar(150) DEFAULT NULL,
- PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复造代码 JWT
咱们发明不,咱们已经获得到了token,为何借要有jwt呢,那便需要理解他们的天生体制。
token天生的实在即是一个UUID,战营业不涓滴的干系,如许戴去最年夜的成就,即是需要野生耐久化处置token(像处置散布式下的sessionId一致)。可是jwt便没有需要,因为自包罗,以是token里怀孕份考证疑息,没有需要干背景耐久化处置,前端屡屡恳求被庇护的资本时恳求头里戴上该token就能够完毕。
按照https://jwt.io/introduction/介绍:
JSON Web令牌(JWT)是一个盛开尺度(RFC 7519),它界说了一种松散临时包罗的方法,用于正在各圆之间动作JSON工具宁静天传输疑息。因为此疑息是颠末数字署名的,因而能够被考证战信赖。能够使用秘密(使用HMAC算法)或者使用RSA或者ECDSA的公钥/公钥对于对于JWT截至署名。
固然能够对于JWT截至减稀以正在双方之间供给失密性,但是咱们将重心存眷已经署名的令牌。署名的令牌能够考证此中包罗的申明的残破性,而减稀的令牌则将那些申明躲藏正在其余圆的眼前。当使用公钥/公钥对于对于令牌截至署名时,署名借证实只需持有公钥的一刚才是对于其截至署名的一圆。
甚么时候该当使用JSON Web令牌?
如下是JSON Web令牌有效的一点儿情况:
- 受权:那是使用JWT的最多见计划。一朝用户登录,每一个后绝恳求将包罗JWT,进而许可用户会见该令牌许可的路由,效劳战资本。简单登录是现今普遍使用JWT的一项功用,因为它的开销很小而且能够正在差别的域中轻快使用。
- 疑息交流:JSON Web令牌是正在各圆之间宁静天传输疑息的佳办法。因为能够对于JWT截至署名(比方,使用公钥/公钥对于),以是您能够肯定收件人是他们所道的人。别的,因为署名是使用标头战有用背载计较的,因而您借能够考证实质可否已被窜改。
JSON Web令牌构造是甚么?
JSON Web令牌以松散的方法由三部门构成,那些部门由面(.)分开,别离是:
标头
标头凡是由二部门构成:令牌的范例(即JWT)战所使用的署名算法,比方HMAC SHA256或者RSA。
比方:- {
- "alg": "HS256",
- "typ": "JWT"
- }
复造代码 有用载荷
令牌的第两部门是有用背载,此中包罗申明。申明是相关真体(一般为用户)战其余数据的申明。索赚有如下三品种型:备案的,大众的战公众索赚。
- 已经备案的权力请求:那些是一组非自愿性的但是倡议使用的预约义权力请求,以供给一组有效的,可互操纵的权力请求。此中一点儿是: iss(收回者), exp(到期时间), sub(中心), aud(受寡)等。
请留神,申明称呼仅是三个字符,因为JWT是松散的。
- 大众申明:使用JWT的人能够随便界说那些申明。可是为制止抵触,应正在 IANA JSON Web令牌备案表中界说它们,或者将其界说为包罗抗抵触称呼空间的URI。
- 公众权力:那些皆是使用它们附和并既没有是当事人之间成立同享疑息的自界说申明备案或者公家的权力请求。
有用背载示例可以是:- {
- "sub": "1234567890",
- "name": "John Doe",
- "admin": true
- }
复造代码 请留神,关于已经署名的令牌,此疑息固然能够避免窜改,但是所有人均可以读与。除非将其减稀,不然请勿将秘密疑息搁进JWT的有用背载或者报头元艳中。
署名
要创立署名部门,您必需获得编码的标头,编码的有用载荷,秘密,标头中指定的算法,并对于其截至署名。
比方,假设要使用HMAC SHA256算法,则将颠末如下方法创立署名:- HMACSHA256(
- base64UrlEncode(header) + "." +
- base64UrlEncode(payload),
- secret)
复造代码 署名用于考证消息正在全部过程当中不变动,而且关于使用公钥截至署名的令牌,它借能够考证JWT的收收者是它所道的实在身份。
下图显现了一个JWT,它已经对于先前的标头战有用背载截至了编码,并用一个秘密截至了署名。
假设您念使用JWT并将那些观点付诸实践,则能够使用http://jwt.io Debugger解码,考证战天生JWT。
JWT使用
以前咱们道了,咱们需要将UUID的token变换成有效户疑息的JWT token,才没有需要身份的考证,如何样来获得UUID的token呢?如何来获得用户数据呢?
起首道道如何获得用户疑息, 正在登录时,将恳求到
http://172.16.xx.xx:9999/AUTH/auth/login
留神参数是牢固格局,不成改正(username,password)deviceType代表是APP仍是浏览器自界说
- private JSONObject requestToken(String account, String password, String deviceType) {
- String result = null;
- try {
- RestTemplate restTemplate = new RestTemplate();
- HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
- HttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build();
- factory.setHttpClient(httpClient);
- restTemplate.setRequestFactory(factory);
-
-
- MultiValueMap<String, Object> sendMap = new LinkedMultiValueMap<>();
- sendMap.add(&#34;username&#34;, account);
- sendMap.add(&#34;password&#34;, password);
- result = RestTemplateUtil.postForEntityFormData(restTemplate, http://172.16.xx.xx:9999/AUTH/auth/login, sendMap, deviceType);
- logger.info(&#34;认证中间前去成果-------》》》》》&#34; + result);
- } catch (Exception e) {
- logger.error(&#34;error&#34;, e);
- throw new Exception(&#34;500&#34;, e);
- }
- return JSON.parseObject(result);
- }
复造代码 WebSecurityConfigurerAdapter 将会拦阻到那个恳求,获得到username
按照username获得到用户的具体疑息,并颠末userDetailsService()查验可否有目前用户的疑息,留神那个办法前去了UserDetails,分析UserDetails里无数据了,便利咱们获得要增加的userId字段。那里借需要留神:configure办法第一个用于设置用户滥觞于数据库,第两个设置路子拦阻,successHandler沉定背url那里给出successHandler代码- import java.io.IOException;
-
-
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
-
- import org.bifu.distributed.auth.constant.AuthContants;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
- import org.springframework.stereotype.Component;
-
-
- @Component
- public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
- private final static Logger logger = LoggerFactory.getLogger(MyAuthenticationSuccessHandler.class);
-
-
- @Value(value = &#34;${prefix.auth}&#34;)
- private String authPrefix; // /token
-
-
- @Value(value = &#34;${oauth.redirectUrl}&#34;)
- private String redirectUrl; // /token
-
-
- @Override
- public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
- Authentication authentication) throws IOException, ServletException {
- String deviceType = request.getHeader(&#34;deviceType&#34;);
- logger.info(&#34;会见装备-----------》》》&#34; + deviceType);
- if (deviceType == null || &#34;&#34;.equals(deviceType)) {
- deviceType = &#34;browser&#34;;
- }
- // 沉定背url到 /token 交心
- if (&#34;browser&#34;.equals(deviceType)) {
- response.sendRedirect(&#34;http://localhost:8080:oauth/authorize?client_id=client1&response_type=code&redirect_uri=/token&#34;);
- } else if (&#34;app&#34;.equals(deviceType)) {
- response.sendRedirect(http://localhost:8080:oauth/authorize?client_id=client2&response_type=code&redirect_uri=/token);
- }
- }
-
-
- }
复造代码 沉定背到那个交心- /**
- *受权 ,登录
- */
- @ResponseBody
- @RequestMapping(value = &#34;/token&#34;)
- public ResultDTO<TokenResultDTO> token(HttpServletRequest request, HttpServletResponse response,
- RedirectAttributes attributes) {
- logger.info(&#34;准备获得token, 获得code = {}&#34;, request.getParameter(&#34;code&#34;));
- TokenResultDTO result = this.userService.token(attributes, request, response);
- logger.info(&#34;获得到token= {}&#34;, JSONObject.toJSONString(result));
- return new ResultDTO<TokenResultDTO>(AuthContants.CODE_200, AuthContants.KEYWORD_SUCCESS, result);
- }
复造代码 token办法,那里需要留神一个面,那里前去参数李获得的userId即是UserDetails树立的userId- public TokenResultDTO token(RedirectAttributes attributes, HttpServletRequest request,
- HttpServletResponse response) {
- try {
-
-
- String code = request.getParameter(&#34;code&#34;);
- if (StringUtils.isEmpty(code)) {
- throw new BusinessException(AuthContants.CODE_EXCEPTION);
- }
- // 收收恳求token
- String deviceType = &#34;browser&#34;;
- if (request.getHeader(&#34;deviceType&#34;) != null && !&#34;&#34;.equals(request.getHeader(&#34;deviceType&#34;))) {
- deviceType = request.getHeader(&#34;deviceType&#34;);
- }
- HttpHeaders headers = new HttpHeaders();
- HttpEntity<String> entity = new HttpEntity<String>(headers);
- TokenResultDTO tokenResultDTO = null;
- if (&#34;browser&#34;.equals(deviceType)) {
- tokenResultDTO = this.browserRestTemplate.postForObject(
- &#34; http://localhost/oauth/token?client_id=client1&grant_type=authorization_code&redirect_uri=/token&code=&#34; + code,
- entity, TokenResultDTO.class);
- } else if (&#34;app&#34;.equals(deviceType)) {
- tokenResultDTO = this.appRestTemplate.postForObject(
- &#34; http://localhost/oauth/token?client_id=client2&grant_type=authorization_code&redirect_uri=/token&code=&#34; + code,
- entity, TokenResultDTO.class);
- }
-
-
- return new TokenResultDTO(tokenResultDTO.getAccess_token(), tokenResultDTO.getRefresh_token(),
- tokenResultDTO.getUserId(), tokenResultDTO.getExpires_in());
- } catch (BusinessException e) {
-
- logger.error(&#34;token?&#34;);
- throw new Exception(&#34;500&#34;, e.getMessage());
- } catch (Exception e) {
-
- logger.error(&#34;token?&#34;);
- throw new Exception(&#34;500&#34;, e.getMessage());
- }
- }
复造代码 咱们获得token以前,而后咱们需要增强token,让前去的数据增加一个userId字段
揭一下认证效劳器的代码:正在增强办法中咱们瞅到
additionalInformation.put(&#34;userId&#34;, securityUserDTO.getId());
已经将userId增加到token分外疑息中,可是SecurityUserDTO 是如何去的呢?- public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
- OAuth2Authentication authentication)
- {
- SecurityUserDTO securityUserDTO =
- (SecurityUserDTO) authentication.getUserAuthentication().getPrincipal();
复造代码 能够瞅到SecurityUserDTO 是颠末OAuth2Authentication获得的,可是
OAuth2Authentication如何获得数据的呢?
揭出部门代码 SecurityUserDTO - public class SecurityUserDTO implements UserDetails {
-
- private static final long serialVersionUID = 1L;
-
- private String id;
-
- private String username;
-
- private String password;
-
- private Integer disable;
-
- private Collection<? extends GrantedAuthority> authorities;
-
- public SecurityUserDTO() {
- }
复造代码 咱们加入UserDetails检查 一下源码- /**
- * Provides core user information.
- *
- * <p>
- * Implementations are not used directly by Spring Security for security purposes. They
- * simply store user information which is later encapsulated into {@link Authentication}
- * objects. This allows non-security related user information (such as email addresses,
- * telephone numbers etc) to be stored in a convenient location.
- * <p>
- * Concrete implementations must take particular care to ensure the non-null contract
- * detailed for each method is enforced. See
- * {@link org.springframework.security.core.userdetails.User} for a reference
- * implementation (which you might like to extend or use in your code).
- *
- * @see UserDetailsService
- * @see UserCache
- *
- / ** *供给中心用户疑息。* * <p> *出于宁静目标,Spring Security没有会
- 间接使用完毕。它们*仅保存用户疑息,那些疑息随即启拆正在{@link
- Authentication} *工具中。那许可将取宁静相关的用户疑息(比方电子邮件
- 地点,*德律风号码等)保存正在便利的职位。* <p> *具体完毕必需非分特别当心
- ,以保证为每一个办法自愿施行具体的非空条约。请参阅* {@link org.
- springframework.security.core.userdetails.User}以获得参照*完毕
- (您可以期望扩大或者正在代码中使用)。* * @请参阅UserDetailsService
- * @请参阅UserCache *
复造代码 瞅到那句话:仅保存用户疑息,那些疑息随即启拆正在{@link Authentication} *工具中,咱们便大白了吧,其实在前面道到的用户校验中,咱们树立了UserDetails
而且它将疑息保存到了Authentication中。- public Authentication getUserAuthentication() {
- return userAuthentication;
- }
复造代码 就能够获得到用户疑息了吧。完善!!!
佳了,到那一步咱们便完毕了JWT对于token的增强,那末如何包管宁静性呢?
OAuth2供给了JwtAccessTokenConverter完毕,增加jwtSigningKey,以今生成秘钥,以此截至署名- // 非对于称减稀
- KeyStoreKeyFactory keyStoreKeyFactory =
- new KeyStoreKeyFactory(new ClassPathResource(&#34;kevin_key.jks&#34;),
- &#34;123456&#34;.toCharArray());
- accessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair(&#34;kevin_key&#34;));
- return accessTokenConverter;
复造代码 到那里根本上便好未几了,揭上一个挨印的日记,阐发一下全部过程
归纳
颠末日记能够出
- 用户输出账号、暗码,恳求/auth/login到认证效劳器
- 认证效劳器按照username查验用户,寄存全部UserDetails
- 认证效劳器拦阻恳求鉴别可否正当,假设正当加入successHandler截至url沉定背
- 沉定背获得的code值持续会见/token,这时候JWT会对于token增加,增加咱们需要前去给前段的字段
- 得到access_token战需要的字段
目前代码托管到尔的Gitee :
https://gitee.com/ran_song/Limit
欢送斧正交换哦!!
欢送存眷尔的微疑公家号<搜刮:汀雨条记>,会尾收一点儿最新文章哦!
上面是尔的小我私家网站:http://ransongv587.com
本文链交: |