{"id":1415,"date":"2023-03-19T11:42:40","date_gmt":"2023-03-19T03:42:40","guid":{"rendered":"https:\/\/www.appblog.cn\/?p=1415"},"modified":"2023-04-28T21:07:12","modified_gmt":"2023-04-28T13:07:12","slug":"spring-security-oauth2-authentication-server-in-redis-mode","status":"publish","type":"post","link":"https:\/\/www.appblog.cn\/index.php\/2023\/03\/19\/spring-security-oauth2-authentication-server-in-redis-mode\/","title":{"rendered":"Spring Security OAuth2 Redis \u6a21\u5f0f\u4e0b\u8ba4\u8bc1\u670d\u52a1\u5668"},"content":{"rendered":"<h2>\u56db\u79cd\u6388\u6743\u7801\u6a21\u5f0f<\/h2>\n<ul>\n<li>\u6388\u6743\u7801\u6a21\u5f0f<\/li>\n<li>\u5bc6\u7801\u6a21\u5f0f<\/li>\n<li>\u5ba2\u6237\u7aef\u6a21\u5f0f<\/li>\n<li>\u7b80\u5316\u6a21\u5f0f<\/li>\n<\/ul>\n<p><!-- more --><\/p>\n<h3>\u5bc6\u7801\u6a21\u5f0f<\/h3>\n<ul>\n<li><code>grant_type<\/code>:\u6388\u6743\u7c7b\u578b\uff0c\u5fc5\u9009\uff0c\u6b64\u5904\u56fa\u5b9a\u503c\u201cpassword\u201d<\/li>\n<li><code>username<\/code>\uff1a\u8868\u793a\u7528\u6237\u540d\uff0c\u5fc5\u9009<\/li>\n<li><code>password<\/code>\uff1a\u8868\u793a\u7528\u6237\u5bc6\u7801\uff0c\u5fc5\u9009<\/li>\n<li><code>scope<\/code>\uff1a\u6743\u9650\u8303\u56f4\uff0c\u53ef\u9009<\/li>\n<\/ul>\n<p>\uff081\uff09\u83b7\u53d6access_token<\/p>\n<pre><code>http:\/\/localhost:9003\/oauth\/token?username=user&amp;password=password&amp;grant_type=password&amp;client_id=client&amp;client_secret=secret<\/code><\/pre>\n<pre><code class=\"language-json\">{\n    &quot;access_token&quot;:&quot;9a263f17f894488fa0973136e6809198&quot;,\n    &quot;token_type&quot;:&quot;bearer&quot;,\n    &quot;refresh_token&quot;:&quot;1d9b97d9eeb74c62adf9f834868518e3&quot;,\n    &quot;expires_in&quot;:59,\n    &quot;scope&quot;:&quot;all&quot;,\n    &quot;client_id&quot;:&quot;client_password&quot;\n}<\/code><\/pre>\n<p>\uff082\uff09\u901a\u8fc7refresh_token\u83b7\u53d6access_token<\/p>\n<pre><code>http:\/\/localhost:9003\/oauth\/token?grant_type=refresh_token&amp;refresh_token=1d9b97d9eeb74c62adf9f834868518e3&amp;client_id=client_password&amp;client_secret=secret<\/code><\/pre>\n<pre><code class=\"language-json\">{\n    &quot;access_token&quot;:&quot;9229d299bf264b0d8771306d1da71f42&quot;,\n    &quot;token_type&quot;:&quot;bearer&quot;,\n    &quot;refresh_token&quot;:&quot;1d9b97d9eeb74c62adf9f834868518e3&quot;,\n    &quot;expires_in&quot;:59,\n    &quot;scope&quot;:&quot;all&quot;,\n    &quot;client_id&quot;:&quot;client_password&quot;\n}<\/code><\/pre>\n<h3>\u6388\u6743\u7801\u6a21\u5f0f<\/h3>\n<ul>\n<li><code>client_id<\/code>\uff1a\u5ba2\u6237\u7aefID\uff0c\u5fc5\u9009<\/li>\n<li><code>response_type<\/code>\uff1a\u5fc5\u987b\u4e3acode\uff0c\u5fc5\u9009<\/li>\n<li><code>redirect_uri<\/code>\uff1a\u56de\u8c03url\uff0c\u5fc5\u9009<\/li>\n<\/ul>\n<p>\uff081\uff09\u83b7\u53d6\u6388\u6743\u7801<\/p>\n<pre><code>http:\/\/localhost:9003\/oauth\/authorize?client_id=auth_code&amp;response_type=code&amp;redirect_uri=http:\/\/localhost:9003\/auth_user\/get_auth_code<\/code><\/pre>\n<p>\uff082\uff09\u83b7\u53d6access_token<\/p>\n<pre><code>http:\/\/localhost:9003\/oauth\/token?grant_type=authorization_code&amp;code=S4x5Yi&amp;client_id=auth_code&amp;client_secret=secret&amp;redirect_uri=http:\/\/localhost:9003\/auth_user\/get_auth_code<\/code><\/pre>\n<pre><code class=\"language-json\">{\n    &quot;access_token&quot;:&quot;46d216c6e19c4761a95c3313883574d7&quot;,\n    &quot;token_type&quot;:&quot;bearer&quot;,\n    &quot;refresh_token&quot;:&quot;5124987a8e4643088de2ba03bdb24730&quot;,\n    &quot;expires_in&quot;:59,\n    &quot;scope&quot;:&quot;select replace insert update del&quot;,\n    &quot;client_id&quot;:&quot;auth_code&quot;\n}<\/code><\/pre>\n<p>\u82e5\u62a5\u9519\u5982\u4e0b\uff1a<\/p>\n<pre><code class=\"language-json\">{&quot;status&quot;:405,&quot;message&quot;:&quot;Request method &#039;GET&#039; not supported&quot;}<\/code><\/pre>\n<p>\u53c2\u8003\uff1a<a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/github.com\/lexburner\/oauth2-demo\/issues\/3\">https:\/\/github.com\/lexburner\/oauth2-demo\/issues\/3<\/a><\/p>\n<pre><code class=\"language-java\">@Configuration\npublic class OAuthSecurityConfig extends AuthorizationServerConfigurerAdapter {\n...\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        ...\n        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); \/\/add get method\n        ...\n\n        endpoints.tokenServices(tokenServices);\n    }\n...\n}<\/code><\/pre>\n<p>\uff083\uff09\u901a\u8fc7refresh_token\u83b7\u53d6access_token<\/p>\n<pre><code>http:\/\/localhost:9003\/oauth\/token?grant_type=refresh_token&amp;refresh_token=5124987a8e4643088de2ba03bdb24730&amp;client_id=auth_code&amp;client_secret=secret<\/code><\/pre>\n<pre><code class=\"language-json\">{\n    &quot;access_token&quot;:&quot;c90e2b29cc2e4b9192e68566148db8cf&quot;,\n    &quot;token_type&quot;:&quot;bearer&quot;,\n    &quot;refresh_token&quot;:&quot;604baf02b5294daa951fea6fede6a398&quot;,\n    &quot;expires_in&quot;:59,\n    &quot;scope&quot;:&quot;select replace insert update del&quot;,\n    &quot;client_id&quot;:&quot;auth_code&quot;\n}<\/code><\/pre>\n<pre><code>D:\\Server\\Redis-x64-3.2.100&gt;redis-cli.exe\n127.0.0.1:6379&gt; select 0\nOK\n127.0.0.1:6379&gt; keys *\n 1) &quot;refresh:604baf02b5294daa951fea6fede6a398&quot;\n 2) &quot;access_to_refresh:c90e2b29cc2e4b9192e68566148db8cf&quot;\n 3) &quot;auth_to_access:a724bdfe1be1f3d30a3c89764c2adca9&quot;\n 4) &quot;refresh_auth:604baf02b5294daa951fea6fede6a398&quot;\n 5) &quot;refresh_to_access:604baf02b5294daa951fea6fede6a398&quot;\n 6) &quot;codeCache&quot;\n 7) &quot;client_id_to_access:auth_code&quot;\n 8) &quot;uname_to_access:auth_code:admin&quot;\n 9) &quot;auth:c90e2b29cc2e4b9192e68566148db8cf&quot;\n10) &quot;access:c90e2b29cc2e4b9192e68566148db8cf&quot;\n11) &quot;test&quot;\n127.0.0.1:6379&gt;<\/code><\/pre>\n<p>\uff084\uff09\u901a\u8fc7refresh_token\u83b7\u53d6\u65b0\u7684access_token\u65f6\u53ef\u4ee5\u81ea\u5b9a\u4e49\u7528\u6237\u4fe1\u606f\u9a8c\u8bc1service<\/p>\n<pre><code>http:\/\/localhost:9003\/auth_user\/get_token_info?access_token=c90e2b29cc2e4b9192e68566148db8cf<\/code><\/pre>\n<pre><code class=\"language-json\">{\n    &quot;authorities&quot;:[\n        {\n            &quot;authority&quot;:&quot;{&quot;interfaces&quot;:[&quot;\/a\/b&quot;,&quot;\/a\/c&quot;,&quot;\/oauth\/token&quot;]}&quot;\n        },\n        {\n            &quot;authority&quot;:&quot;{&quot;username&quot;:&quot;admin&quot;}&quot;\n        }\n    ],\n    &quot;details&quot;:{\n        &quot;remoteAddress&quot;:&quot;0:0:0:0:0:0:0:1&quot;,\n        &quot;sessionId&quot;:&quot;8233149F60205EF21BAD74B80BFE7F06&quot;\n    },\n    &quot;authenticated&quot;:true,\n    &quot;principal&quot;:{\n        &quot;password&quot;:null,\n        &quot;username&quot;:&quot;admin&quot;,\n        &quot;authorities&quot;:[\n            {\n                &quot;authority&quot;:&quot;{&quot;interfaces&quot;:[&quot;\/a\/b&quot;,&quot;\/a\/c&quot;,&quot;\/oauth\/token&quot;]}&quot;\n            },\n            {\n                &quot;authority&quot;:&quot;{&quot;username&quot;:&quot;admin&quot;}&quot;\n            }\n        ],\n        &quot;accountNonExpired&quot;:true,\n        &quot;accountNonLocked&quot;:true,\n        &quot;credentialsNonExpired&quot;:true,\n        &quot;enabled&quot;:true\n    },\n    &quot;credentials&quot;:null,\n    &quot;name&quot;:&quot;admin&quot;\n}<\/code><\/pre>\n<h3>Client\u6a21\u5f0f<\/h3>\n<ul>\n<li><code>client_id<\/code>: \u5ba2\u6237\u7aefID\uff0c\u5fc5\u9009<\/li>\n<li><code>client_secret<\/code>: \u5ba2\u6237\u7aef\u5bc6\u7801\uff0c\u5fc5\u9009<\/li>\n<li><code>grant_type<\/code>: \u5fc5\u987b\u4e3apassword\uff0c\u5fc5\u9009<\/li>\n<li><code>scope<\/code>: \u6388\u6743\u8303\u56f4\uff0c\u5fc5\u9009<\/li>\n<\/ul>\n<pre><code>http:\/\/localhost:9003\/oauth\/token?grant_type=client_credentials&amp;scope=insert&amp;client_id=client&amp;client_secret=secret<\/code><\/pre>\n<pre><code class=\"language-json\">{\n    &quot;access_token&quot;:&quot;a1016035f36f493391648bb57b1bf6a7&quot;,\n    &quot;token_type&quot;:&quot;bearer&quot;,\n    &quot;expires_in&quot;:59,\n    &quot;scope&quot;:&quot;insert&quot;,\n    &quot;client_id&quot;:&quot;client&quot;\n}<\/code><\/pre>\n<h3>\u6781\u7b80\u6a21\u5f0f<\/h3>\n<pre><code>http:\/\/localhost:9003\/oauth\/authorize?client_id=client_implicit&amp;response_type=token&amp;redirect_uri=http:\/\/localhost:9003\/auth_user\/get_token_info<\/code><\/pre>\n<pre><code class=\"language-json\">{\n    &quot;authorities&quot;:[\n        {\n            &quot;authority&quot;:&quot;{&quot;interfaces&quot;:[&quot;\/a\/b&quot;,&quot;\/a\/c&quot;,&quot;\/oauth\/token&quot;]}&quot;\n        },\n        {\n            &quot;authority&quot;:&quot;{&quot;username&quot;:&quot;admin&quot;}&quot;\n        }\n    ],\n    &quot;details&quot;:{\n        &quot;remoteAddress&quot;:&quot;0:0:0:0:0:0:0:1&quot;,\n        &quot;sessionId&quot;:&quot;9B935C3B239869DF4A840B57F512F2E7&quot;\n    },\n    &quot;authenticated&quot;:true,\n    &quot;principal&quot;:{\n        &quot;password&quot;:null,\n        &quot;username&quot;:&quot;admin&quot;,\n        &quot;authorities&quot;:[\n            {\n                &quot;authority&quot;:&quot;{&quot;interfaces&quot;:[&quot;\/a\/b&quot;,&quot;\/a\/c&quot;,&quot;\/oauth\/token&quot;]}&quot;\n            },\n            {\n                &quot;authority&quot;:&quot;{&quot;username&quot;:&quot;admin&quot;}&quot;\n            }\n        ],\n        &quot;accountNonExpired&quot;:true,\n        &quot;accountNonLocked&quot;:true,\n        &quot;credentialsNonExpired&quot;:true,\n        &quot;enabled&quot;:true\n    },\n    &quot;credentials&quot;:null,\n    &quot;name&quot;:&quot;admin&quot;\n}<\/code><\/pre>\n<h2>\u7528\u6237\u8ba4\u8bc1<\/h2>\n<pre><code class=\"language-java\">\/**\n * @Description: \u7528\u6237\u8ba4\u8bc1\n * @Package: cn.appblog.security.oauth2.service.UserAuthDetailsService\n * @Version: 1.0\n *\/\n@Component\npublic class UserAuthDetailsService implements UserDetailsService {\n    @Autowired\n    private PasswordEncoder passwordEncoder;\n\n    \/**\n     * \u6839\u636e\u7528\u6237\u540d\u67e5\u8be2\u7528\u6237\u89d2\u8272\u3001\u6743\u9650\u7b49\u4fe1\u606f\n     *\/\n    @Override\n    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n        GrantedAuthority authority = new UserGrantedAuthority(&quot;username&quot;, username);\n\n        JSONArray array = new JSONArray();\n        array.add(&quot;\/a\/b&quot;);\n        array.add(&quot;\/a\/c&quot;);\n        array.add(&quot;\/oauth\/token&quot;);\n        GrantedAuthority interfaces = new UserGrantedAuthority(&quot;interfaces&quot;, array);\n\n        \/**\n         isEnabled \u8d26\u6237\u662f\u5426\u542f\u7528\n         isAccountNonExpired \u8d26\u6237\u6ca1\u6709\u8fc7\u671f\n         isCredentialsNonExpired \u8eab\u4efd\u8ba4\u8bc1\u662f\u5426\u662f\u6709\u6548\u7684\n         isAccountNonLocked \u8d26\u6237\u6ca1\u6709\u88ab\u9501\u5b9a\n         *\/\n        return new User(username, passwordEncoder.encode(&quot;123456&quot;),\n                true,\n                true,\n                true,\n                true,\n                Arrays.asList(authority, interfaces));\n    }\n\n}<\/code><\/pre>\n<h2>OAuth2\u6388\u6743\u670d\u52a1\u914d\u7f6e<\/h2>\n<p>\u5728Security OAuth2\u6388\u6743\u670d\u52a1\u914d\u7f6e\u7c7b\u4e2d\u6dfb\u52a0\u4e0a\u81ea\u5b9a\u4e49\u7684\u7528\u6237\u4fe1\u606f\u6821\u9a8c\u7c7b<\/p>\n<pre><code class=\"language-java\">\/**\n * \u7528\u6765\u914d\u7f6e\u6388\u6743\uff08authorization\uff09\u4ee5\u53ca\u4ee4\u724c\uff08token)\u7684\u8bbf\u95ee\u7aef\u70b9\u548c\u4ee4\u724c\u670d\u52a1\uff08token services\uff09\n *\/\n@Override\npublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n    DefaultTokenServices tokenServices = new DefaultTokenServices();\n    \/\/token\u6301\u4e45\u5316\u5bb9\u5668\n    tokenServices.setTokenStore(tokenStore);\n    \/\/\u5ba2\u6237\u7aef\u4fe1\u606f\n    tokenServices.setClientDetailsService(endpoints.getClientDetailsService());\n    \/\/\u81ea\u5b9a\u4e49token\u751f\u6210\n    tokenServices.setTokenEnhancer(tokenEnhancer());\n    \/\/access_token \u7684\u6709\u6548\u65f6\u957f (\u79d2), \u9ed8\u8ba4 12 \u5c0f\u65f6\n    tokenServices.setAccessTokenValiditySeconds(60 * 1);\n    \/\/refresh_token \u7684\u6709\u6548\u65f6\u957f (\u79d2), \u9ed8\u8ba4 30 \u5929\n    tokenServices.setRefreshTokenValiditySeconds(60 * 2);\n    \/\/\u662f\u5426\u652f\u6301refresh_token\uff0c\u9ed8\u8ba4false\n    tokenServices.setSupportRefreshToken(true);\n    \/\/\u662f\u5426\u590d\u7528refresh_token,\u9ed8\u8ba4\u4e3atrue(\u5982\u679c\u4e3afalse,\u5219\u6bcf\u6b21\u8bf7\u6c42\u5237\u65b0\u90fd\u4f1a\u5220\u9664\u65e7\u7684refresh_token,\u521b\u5efa\u65b0\u7684refresh_token)\n    tokenServices.setReuseRefreshToken(false);\n\n    endpoints\n            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) \/\/ add get method\n            \/\/\u901a\u8fc7authenticationManager\u5f00\u542f\u5bc6\u7801\u6388\u6743\n            .authenticationManager(authenticationManager)\n            \/\/\u81ea\u5b9a\u4e49refresh_token\u5237\u65b0\u4ee4\u724c\u5bf9\u7528\u6237\u4fe1\u606f\u7684\u68c0\u67e5\uff0c\u4ee5\u786e\u4fdd\u7528\u6237\u4fe1\u606f\u4ecd\u7136\u6709\u6548\n            .userDetailsService(userAuthDetailsService)\n            \/\/token\u76f8\u5173\u670d\u52a1\n            .tokenServices(tokenServices)\n            .pathMapping(&quot;\/oauth\/confirm_access&quot;, &quot;\/custom\/confirm_access&quot;)\n            \/\/\u81ea\u5b9a\u4e49\u5f02\u5e38\u8f6c\u6362\u5904\u7406\u7c7b\n            .exceptionTranslator(webResponseExceptionTranslator);\n}<\/code><\/pre>\n<h2>\u81ea\u5b9a\u4e49token\u751f\u6210<\/h2>\n<ul>\n<li>\u81ea\u5b9a\u4e49\u4e00\u4e2a\u5b9e\u73b0TokenEnhancer\u63a5\u53e3\u7684token\u589e\u5f3a\u5668<\/li>\n<\/ul>\n<pre><code class=\"language-java\">**\n * @Description: \u7528\u6237\u81ea\u5b9a\u4e49token\u4ee4\u724c\uff0c\u5305\u62ecaccess_token\u548crefresh_token\n * @Package: cn.appblog.security.oauth2.enhancer.UserTokenEnhancer\n * @Version: 1.0\n *\/\npublic class UserTokenEnhancer implements TokenEnhancer {\n    \/**\n     * \u91cd\u65b0\u5b9a\u4e49\u4ee4\u724ctoken\n     *\/\n    @Override\n    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {\n        if (accessToken instanceof DefaultOAuth2AccessToken) {\n            DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;\n            token.setValue(getToken());\n            \/\/\u4f7f\u7528DefaultExpiringOAuth2RefreshToken\u7c7b\u751f\u6210refresh_token\uff0c\u81ea\u5e26\u8fc7\u671f\u65f6\u95f4\uff0c\u5426\u5219\u4e0d\u751f\u6548\uff0crefresh_token\u4e00\u76f4\u6709\u6548\n            DefaultExpiringOAuth2RefreshToken refreshToken = (DefaultExpiringOAuth2RefreshToken) token.getRefreshToken();\n            \/\/OAuth2RefreshToken refreshToken = token.getRefreshToken();\n            if (refreshToken instanceof DefaultExpiringOAuth2RefreshToken) {\n                token.setRefreshToken(new DefaultExpiringOAuth2RefreshToken(getToken(), refreshToken.getExpiration()));\n            }\n            Map&lt;String, Object&gt; additionalInformation = Maps.newHashMap();\n            additionalInformation.put(&quot;client_id&quot;, authentication.getOAuth2Request().getClientId());\n            \/\/\u6dfb\u52a0\u989d\u5916\u914d\u7f6e\u4fe1\u606f\n            token.setAdditionalInformation(additionalInformation);\n            return token;\n        }\n        return accessToken;\n    }\n\n    \/**\n     * \u751f\u6210\u81ea\u5b9a\u4e49token\n     *\/\n    private String getToken() {\n        return StringUtils.join(UUID.randomUUID().toString().replace(&quot;-&quot;, &quot;&quot;));\n    }\n}<\/code><\/pre>\n<ul>\n<li>\u5c06\u81ea\u5b9a\u4e49\u7684token\u589e\u5f3a\u5668\u52a0\u5165IOC\u5bb9\u5668\u4e2d<\/li>\n<\/ul>\n<pre><code class=\"language-java\">\/**\n * \u81ea\u5b9a\u4e49\u751f\u6210\u4ee4\u724ctoken\n *\/\n@Bean\npublic TokenEnhancer tokenEnhancer() {\n    return new UserTokenEnhancer();\n}<\/code><\/pre>\n<ul>\n<li>\u5c06token\u589e\u5f3a\u5668\u52a0\u5165\u6388\u6743\u914d\u7f6e\u7aef\u70b9<\/li>\n<\/ul>\n<pre><code class=\"language-java\">\/**\n * \u7528\u6765\u914d\u7f6e\u6388\u6743\uff08authorization\uff09\u4ee5\u53ca\u4ee4\u724c\uff08token)\u7684\u8bbf\u95ee\u7aef\u70b9\u548c\u4ee4\u724c\u670d\u52a1\uff08token services\uff09\n *\/\n@Override\npublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n    DefaultTokenServices tokenServices = new DefaultTokenServices();\n    ...\n    tokenServices.setTokenEnhancer(tokenEnhancer());\n    ...\n}<\/code><\/pre>\n<h2>\u81ea\u5b9a\u4e49token\u8fc7\u671f\u65f6\u957f<\/h2>\n<pre><code class=\"language-java\">\/**\n * \u7528\u6765\u914d\u7f6e\u6388\u6743\uff08authorization\uff09\u4ee5\u53ca\u4ee4\u724c\uff08token)\u7684\u8bbf\u95ee\u7aef\u70b9\u548c\u4ee4\u724c\u670d\u52a1\uff08token services\uff09\n *\/\n@Override\npublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n    DefaultTokenServices tokenServices = new DefaultTokenServices();\n    \/\/token\u6301\u4e45\u5316\u5bb9\u5668\n    tokenServices.setTokenStore(tokenStore);\n    \/\/\u5ba2\u6237\u7aef\u4fe1\u606f\n    tokenServices.setClientDetailsService(endpoints.getClientDetailsService());\n    \/\/\u81ea\u5b9a\u4e49token\u751f\u6210\n    tokenServices.setTokenEnhancer(tokenEnhancer());\n    \/\/access_token \u7684\u6709\u6548\u65f6\u957f (\u79d2), \u9ed8\u8ba4 12 \u5c0f\u65f6\n    tokenServices.setAccessTokenValiditySeconds(60 * 1);\n    \/\/refresh_token \u7684\u6709\u6548\u65f6\u957f (\u79d2), \u9ed8\u8ba4 30 \u5929\n    tokenServices.setRefreshTokenValiditySeconds(60 * 2);\n    \/\/\u662f\u5426\u652f\u6301refresh_token\uff0c\u9ed8\u8ba4false\n    tokenServices.setSupportRefreshToken(true);\n    \/\/\u662f\u5426\u590d\u7528refresh_token, \u9ed8\u8ba4\u4e3atrue(\u5982\u679c\u4e3afalse, \u5219\u6bcf\u6b21\u8bf7\u6c42\u5237\u65b0\u90fd\u4f1a\u5220\u9664\u65e7\u7684refresh_token, \u521b\u5efa\u65b0\u7684refresh_token)\n    tokenServices.setReuseRefreshToken(false);\n\n    ...\n}<\/code><\/pre>\n<h2>\u8ba4\u8bc1\u670d\u52a1\u5668\u914d\u7f6e &#8211; token\u5b58\u5165redis\u7f13\u5b58<\/h2>\n<ul>\n<li>\u4f7f\u7528Redis\u7f13\u5b58\u9700\u8981\u5f15\u5165\u7684\u4f9d\u8d56<\/li>\n<\/ul>\n<pre><code class=\"language-xml\">&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;\/artifactId&gt;\n&lt;\/dependency&gt;<\/code><\/pre>\n<ul>\n<li>\u8ba4\u8bc1\u670d\u52a1\u5668\u914d\u7f6e\u4ee3\u7801<\/li>\n<\/ul>\n<pre><code class=\"language-java\">\/**\n * @Description: @EnableAuthorizationServer\u6ce8\u89e3\u5f00\u542fOAuth2\u6388\u6743\u670d\u52a1\u673a\u5236, \u4f18\u5148\u7ea7\u987a\u5e8forder=0\n * @Package: cn.appblog.security.oauth2.config.OAuth2ServerConfig\n * @Version: 1.0\n *\/\n@Configuration\n@EnableAuthorizationServer\npublic class AuthorizationServerConfigure extends AuthorizationServerConfigurerAdapter {\n    @Autowired\n    private AuthenticationManager authenticationManager;\n    @Autowired\n    private OAuth2ClientDetailsService oAuth2ClientDetailsService;\n    @Autowired\n    private UserAuthDetailsService userAuthDetailsService;\n    @Autowired\n    private WebResponseExceptionTranslator webResponseExceptionTranslator;\n    @Autowired\n    private TokenStore tokenStore;\n    @Autowired\n    private OAuthTokenAuthenticationFilter oAuthTokenAuthenticationFilter;\n\n    \/**\n     * \u7528\u6765\u914d\u7f6e\u5ba2\u6237\u7aef\u8be6\u60c5\u670d\u52a1\uff08ClientDetailsService\uff09\uff0c\u5ba2\u6237\u7aef\u8be6\u60c5\u4fe1\u606f\u5728\u8fd9\u91cc\u521d\u59cb\u5316\uff0c\n     * \u4f60\u53ef\u4ee5\u628a\u5ba2\u6237\u7aef\u8be6\u60c5\u4fe1\u606f\u5199\u6b7b\u4e5f\u53ef\u4ee5\u5199\u5165\u5185\u5b58\u6216\u8005\u6570\u636e\u5e93\u4e2d\n     *\/\n    @Override\n    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {\n        \/\/\u4f7f\u7528\u81ea\u5b9a\u4e49ClientDetailsService\u521d\u59cb\u5316\u914d\u7f6e\n        clients.withClientDetails(oAuth2ClientDetailsService);\n    }\n\n    \/**\n     * \u7528\u6765\u914d\u7f6e\u6388\u6743\uff08authorization\uff09\u4ee5\u53ca\u4ee4\u724c\uff08token)\u7684\u8bbf\u95ee\u7aef\u70b9\u548c\u4ee4\u724c\u670d\u52a1\uff08token services\uff09\n     *\/\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        DefaultTokenServices tokenServices = new DefaultTokenServices();\n        \/\/token\u6301\u4e45\u5316\u5bb9\u5668\n        tokenServices.setTokenStore(tokenStore);\n        \/\/\u5ba2\u6237\u7aef\u4fe1\u606f\n        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());\n        \/\/\u81ea\u5b9a\u4e49token\u751f\u6210\n        tokenServices.setTokenEnhancer(tokenEnhancer());\n        \/\/access_token \u7684\u6709\u6548\u65f6\u957f (\u79d2), \u9ed8\u8ba4 12 \u5c0f\u65f6\n        tokenServices.setAccessTokenValiditySeconds(60 * 1);\n        \/\/refresh_token \u7684\u6709\u6548\u65f6\u957f (\u79d2), \u9ed8\u8ba4 30 \u5929\n        tokenServices.setRefreshTokenValiditySeconds(60 * 2);\n        \/\/\u662f\u5426\u652f\u6301refresh_token\uff0c\u9ed8\u8ba4false\n        tokenServices.setSupportRefreshToken(true);\n        \/\/\u662f\u5426\u590d\u7528refresh_token,\u9ed8\u8ba4\u4e3atrue(\u5982\u679c\u4e3afalse,\u5219\u6bcf\u6b21\u8bf7\u6c42\u5237\u65b0\u90fd\u4f1a\u5220\u9664\u65e7\u7684refresh_token,\u521b\u5efa\u65b0\u7684refresh_token)\n        tokenServices.setReuseRefreshToken(false);\n\n        endpoints\n                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) \/\/ add get method\n                \/\/\u901a\u8fc7authenticationManager\u5f00\u542f\u5bc6\u7801\u6388\u6743\n                .authenticationManager(authenticationManager)\n                \/\/\u81ea\u5b9a\u4e49refresh_token\u5237\u65b0\u4ee4\u724c\u5bf9\u7528\u6237\u4fe1\u606f\u7684\u68c0\u67e5\uff0c\u4ee5\u786e\u4fdd\u7528\u6237\u4fe1\u606f\u4ecd\u7136\u6709\u6548\n                .userDetailsService(userAuthDetailsService)\n                \/\/token\u76f8\u5173\u670d\u52a1\n                .tokenServices(tokenServices)\n                \/**\n                 pathMapping\u7528\u6765\u914d\u7f6e\u7aef\u70b9URL\u94fe\u63a5\uff0c\u7b2c\u4e00\u4e2a\u53c2\u6570\u662f\u7aef\u70b9URL\u9ed8\u8ba4\u5730\u5740\uff0c\u7b2c\u4e8c\u4e2a\u53c2\u6570\u662f\u4f60\u8981\u66ff\u6362\u7684URL\u5730\u5740\n                 \u4e0a\u9762\u7684\u53c2\u6570\u90fd\u662f\u4ee5\u201c\/\u201d\u5f00\u5934\uff0c\u6846\u67b6\u7684URL\u94fe\u63a5\u5982\u4e0b\uff1a\n                 \/oauth\/authorize\uff1a\u6388\u6743\u7aef\u70b9\u3002\u5bf9\u5e94\u7684\u7c7b\uff1aAuthorizationEndpoint.java\n                 \/oauth\/token\uff1a\u4ee4\u724c\u7aef\u70b9\u3002\u5bf9\u5e94\u7684\u7c7b\uff1aTokenEndpoint.java\n                 \/oauth\/confirm_access\uff1a\u7528\u6237\u786e\u8ba4\u6388\u6743\u63d0\u4ea4\u7aef\u70b9\u3002\u5bf9\u5e94\u7684\u7c7b\uff1aWhitelabelApprovalEndpoint.java\n                 \/oauth\/error\uff1a\u6388\u6743\u670d\u52a1\u9519\u8bef\u4fe1\u606f\u7aef\u70b9\n                 \/oauth\/check_token\uff1a\u7528\u4e8e\u8d44\u6e90\u670d\u52a1\u8bbf\u95ee\u7684\u4ee4\u724c\u89e3\u6790\u7aef\u70b9\n                 \/oauth\/token_key\uff1a\u63d0\u4f9b\u516c\u6709\u5bc6\u5319\u7684\u7aef\u70b9\uff0c\u5982\u679c\u4f60\u4f7f\u7528JWT\u4ee4\u724c\u7684\u8bdd\n                 *\/\n                .pathMapping(&quot;\/oauth\/confirm_access&quot;, &quot;\/custom\/confirm_access&quot;)\n                \/\/\u81ea\u5b9a\u4e49\u5f02\u5e38\u8f6c\u6362\u5904\u7406\u7c7b\n                .exceptionTranslator(webResponseExceptionTranslator);\n    }\n\n    \/**\n     * \u7528\u6765\u914d\u7f6e\u4ee4\u724c\u7aef\u70b9\uff08Token Endpoint\uff09\u7684\u5b89\u5168\u7ea6\u675f\n     *\/\n    @Override\n    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {\n        security.tokenKeyAccess(&quot;permitAll()&quot;)\n                .checkTokenAccess(&quot;permitAll()&quot;)\n                \/**\n                 * \u4e3b\u8981\u662f\u8ba9\/oauth\/token\u652f\u6301client_id\u548cclient_secret\u505a\u767b\u9646\u8ba4\u8bc1\n                 * \u5982\u679c\u5f00\u542f\u4e86allowFormAuthenticationForClients\uff0c\u90a3\u4e48\u5c31\u5728BasicAuthenticationFilter\u4e4b\u524d\n                 * \u6dfb\u52a0ClientCredentialsTokenEndpointFilter,\u4f7f\u7528ClientDetailsUserDetailsService\u6765\u8fdb\u884c\n                 * \u767b\u9646\u8ba4\u8bc1\n                 *\/\n                .allowFormAuthenticationForClients()\n                \/\/oauth\/token\u7aef\u70b9\u8fc7\u6ee4\u5668\n                .addTokenEndpointAuthenticationFilter(oAuthTokenAuthenticationFilter);\n    }\n\n    \/**\n     * ApprovalStore\u7528\u6237\u4fdd\u5b58\u3001\u68c0\u7d22\u548c\u64a4\u9500\u7528\u6237\u5ba1\u6279\u7684\u754c\u9762\n     *\/\n    \/*\n    @Bean\n    public ApprovalStore approvalStore() throws Exception {\n        TokenApprovalStore store = new TokenApprovalStore();\n        store.setTokenStore(tokenStore());\n        return store;\n    }\n    *\/\n\n    \/*\n    @Bean\n    public UserApprovalHandler userApprovalHandler1(){\n        TokenStoreUserApprovalHandler userApprovalHandler = new TokenStoreUserApprovalHandler();\n        userApprovalHandler.setTokenStore(tokenStore());\n        return userApprovalHandler;\n    }\n    *\/\n\n    \/**\n     * \u81ea\u5b9a\u4e49\u751f\u6210\u4ee4\u724ctoken\n     *\/\n    @Bean\n    public TokenEnhancer tokenEnhancer() {\n        return new UserTokenEnhancer();\n    }\n\n}<\/code><\/pre>\n<ul>\n<li>Redis\u7f13\u5b58\u914d\u7f6e<\/li>\n<\/ul>\n<pre><code>##\u5355\u673a\u5e94\u7528\u73af\u5883\u914d\u7f6e\nspring.redis.host=127.0.0.1\nspring.redis.port=6379\n#spring.redis.password=\n##Redis\u6570\u636e\u5e93\u7d22\u5f15\uff0c\u9ed8\u8ba40\nspring.redis.database=0\n#spring.redis.timeout=\n\n##redis\u8fde\u63a5\u6c60\u914d\u7f6e\n## \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5c0f\u7a7a\u95f2\u8fde\u63a5\uff0c\u9ed8\u8ba40\nspring.redis.jedis.pool.min-idle=0\n## \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5927\u7a7a\u95f2\u8fde\u63a5\uff0c\u9ed8\u8ba48\nspring.redis.jedis.pool.max-idle=8\n## \u8fde\u63a5\u6c60\u6700\u5927\u963b\u585e\u7b49\u5f85\u65f6\u95f4\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09\uff0c\u9ed8\u8ba4-1ms\nspring.redis.jedis.pool.max-wait=-1ms\n##\u8fde\u63a5\u6c60\u6700\u5927\u8fde\u63a5\u6570\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09\uff0c\u9ed8\u8ba48\nspring.redis.jedis.pool.max-active=8<\/code><\/pre>\n<h2>\u57fa\u4e8eSpring Security\u7684\u5b89\u5168\u8ba4\u8bc1<\/h2>\n<blockquote>\n<p>\u66ff\u6362\u7684\u6838\u5fc3\u662f\u5c06<code>InMemoryTokenStore<\/code>\u5bf9\u8c61\u66f4\u6362\u4e3a<code>RedisTokenStore<\/code>\u5bf9\u8c61\uff0c\u5e76\u4f20\u9012\u4e00\u4e2a<code>RedisConnectionFactory<\/code>\u63a5\u53e3\uff0c\u63a5\u53e3\u7684\u5177\u4f53\u5b9e\u73b0\u7c7b\u662f<code>JedisConnectionFactory<\/code>\u7c7b\uff1b<\/p>\n<\/blockquote>\n<p>RedisConnectionFactory\u53ef\u4ee5\u901a\u8fc7\u5982\u4e0b\u4e09\u4e2a\u914d\u7f6e\u7c7b\u5e94\u7528\u5728\u4e0d\u540c\u7684\u5e94\u7528\u573a\u666f\uff1a<\/p>\n<ul>\n<li><code>RedisStandaloneConfiguration:RedisConnectionFactory<\/code> \u5de5\u5382\u7c7b\u5355\u673a\u6a21\u5f0f\u7684\u914d\u7f6e\u7c7b<\/li>\n<li><code>RedisSentinelConfiguration:RedisConnectionFactory<\/code> \u5de5\u5382\u7c7b\u9ad8\u53ef\u7528\u6a21\u5f0f\u7684\u914d\u7f6e\u7c7b<\/li>\n<li><code>RedisClusterConfiguration:RedisConnectionFactory<\/code> \u5de5\u5382\u7c7b\u96c6\u7fa4\u6a21\u5f0f\u7684\u914d\u7f6e\u7c7b<\/li>\n<\/ul>\n<pre><code class=\"language-java\">\/**\n * @Description: \u542f\u52a8\u57fa\u4e8eSpring Security\u7684\u5b89\u5168\u8ba4\u8bc1, \u4f18\u5148\u7ea7\u987a\u5e8forder=100\n * @Package: cn.appblog.security.oauth2.config.WebSecurityConfigurer\n * @Version: 1.0\n *\/\n@Configuration\n@EnableWebSecurity(debug = true)\n@Order(2)\npublic class BaseSecurityConfig extends WebSecurityConfigurerAdapter {\n    @Autowired\n    private RedisConnectionFactory redisConnectionFactory;\n    @Autowired\n    private UserAuthDetailsService authUserDetailsService;\n    @Autowired\n    private UserAuthenticationFailureHandler authenticationFailureHandler;\n    @Autowired\n    private UserAccessDeniedHandler accessDeniedHandler;\n    @Autowired\n    private UserLogoutSuccessHandler logoutSuccessHandler;\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        http.csrf().disable();\n        http\n\/\/                .requestMatchers().antMatchers(&quot;\/oauth\/**&quot;,&quot;\/login\/**&quot;,&quot;\/logout\/**&quot;, &quot;\/test\/**&quot;)\n\/\/                .and()\n\/\/                .authorizeRequests()\n\/\/                .antMatchers(&quot;\/oauth\/**&quot;).authenticated()\n\/\/                .and()\n\/\/                .formLogin().loginPage(&quot;\/test\/login&quot;).permitAll(); \/\/\u65b0\u589elogin form\u652f\u6301\u7528\u6237\u767b\u5f55\u53ca\u6388\u6743\n                .requestMatchers().antMatchers(&quot;\/oauth\/**&quot;,&quot;\/login\/**&quot;,&quot;\/logout\/**&quot;, &quot;\/test\/**&quot;)\n                .and()\n                \/\/ \u6307\u5b9a\u652f\u6301\u57fa\u4e8e\u8868\u5355\u7684\u8eab\u4efd\u9a8c\u8bc1, \u5982\u679c\u672a\u6307\u5b9aFormLoginConfigurer#loginPage(String), \u5219\u5c06\u751f\u6210\u9ed8\u8ba4\u767b\u5f55\u9875\u9762\n                .formLogin()\n                \/\/ \u81ea\u5b9a\u4e49\u767b\u5f55\u9875url, \u9ed8\u8ba4\u4e3a\/login\n                .loginPage(&quot;\/test\/login&quot;)\n                \/\/ \u767b\u5f55\u8bf7\u6c42\u62e6\u622a\u7684url, \u4e5f\u5c31\u662fform\u8868\u5355\u63d0\u4ea4\u65f6\u6307\u5b9a\u7684action\n                .loginProcessingUrl(&quot;\/user\/login&quot;)\n                \/\/ \u7528\u6237\u540d\u7684\u8bf7\u6c42\u5b57\u6bb5 username\n                .usernameParameter(&quot;username&quot;)\n                \/\/ \u5bc6\u7801\u7684\u8bf7\u6c42\u5b57\u6bb5 \u9ed8\u8ba4\u4e3apassword\n                .passwordParameter(&quot;password&quot;)\n                \/\/ \u767b\u5f55\u6210\u529f\n                \/\/ .successHandler(authenticationSuccessHandler)\n                \/\/ \u767b\u5f55\u5931\u8d25\n                .failureHandler(authenticationFailureHandler)\n                \/\/ \u65e0\u6761\u4ef6\u5141\u8bb8\u8bbf\u95ee\n                .permitAll()\n                .and()\n                .requestMatchers()\n                .anyRequest()\n                .and()\n                .authorizeRequests()\n                .antMatchers(&quot;\/oauth\/**&quot;)\n                .authenticated()\n                \/\/.permitAll()\n                \/* .and()\n                  \/\/ \u5176\u5b83\u7684\u8bf7\u6c42\u8981\u6c42\u5fc5\u987b\u6709\u8eab\u4efd\u8ba4\u8bc1\n                  .authorizeRequests()\n                  .anyRequest()\n                  .authenticated()\n                  *\/\n                .and()\n                .logout()\n                .logoutSuccessHandler(logoutSuccessHandler)\n                .permitAll()\n                .and()\n                \/\/ \u8ba4\u8bc1\u8fc7\u7684\u7528\u6237\u8bbf\u95ee\u65e0\u6743\u9650\u8d44\u6e90\u65f6\u7684\u5904\u7406\n                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);\n        http.csrf().disable();\n    }\n\n    @Override\n    public void configure(WebSecurity web) throws Exception {\n        \/\/\u5ffd\u7565swagger\u8bbf\u95ee\u6743\u9650\u9650\u5236\n        \/\/ web.ignoring().antMatchers(\n        \/\/ &quot;\/userlogin&quot;,\n        \/\/ &quot;\/userlogout&quot;,\n        \/\/ &quot;\/userjwt&quot;,\n        \/\/ &quot;\/v2\/api-docs&quot;,\n        \/\/ &quot;\/swagger-resources\/configuration\/ui&quot;,\n        \/\/ &quot;\/swagger-resources&quot;,\n        \/\/ &quot;\/swagger-resources\/configuration\/security&quot;,\n        \/\/ &quot;\/swagger-ui.html&quot;,\n        \/\/ &quot;\/css\/**&quot;,\n        \/\/ &quot;\/js\/**&quot;,\n        \/\/ &quot;\/images\/**&quot;,\n        \/\/ &quot;\/webjars\/**&quot;,\n        \/\/ &quot;**\/favicon.ico&quot;,\n        \/\/ &quot;\/index&quot;);\n        super.configure(web);\n    }\n\n    \/**\n     * Spring Security\u8ba4\u8bc1\u670d\u52a1\u4e2d\u7684\u76f8\u5173\u5b9e\u73b0\u91cd\u65b0\u5b9a\u4e49\n     *\/\n    @Override\n    protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n        \/\/ \u52a0\u5165\u81ea\u5b9a\u4e49\u7684\u5b89\u5168\u8ba4\u8bc1\n        auth.userDetailsService(this.authUserDetailsService)\n                .passwordEncoder(this.passwordEncoder())\n                .and()\n                .authenticationProvider(smsAuthenticationProvider())\n                .authenticationProvider(authenticationProvider());\n    }\n\n    @Override\n    @Bean\n    public AuthenticationManager authenticationManagerBean() throws Exception {\n        return super.authenticationManagerBean();\n    }\n\n    \/**\n     * Spring security\u8ba4\u8bc1Bean\n     *\/\n    @Bean\n    public AuthenticationProvider authenticationProvider() {\n        AuthenticationProvider authenticationProvider = new UserAuthenticationProvider();\n        return authenticationProvider;\n    }\n\n    @Bean\n    public AuthenticationProvider smsAuthenticationProvider() {\n        AuthenticationProvider authenticationProvider = new UserSmsAuthenticationProvider();\n        return authenticationProvider;\n    }\n\n    \/**\n     * \u81ea\u5b9a\u4e49\u52a0\u5bc6\n     *\/\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n\n    \/**\n     * OAuth2 token\u6301\u4e45\u5316\u63a5\u53e3\n     *\/\n    @Bean\n    public TokenStore tokenStore() {\n        \/\/token\u4fdd\u5b58\u5728\u5185\u5b58\u4e2d\uff08\u4e5f\u53ef\u4ee5\u4fdd\u5b58\u5728\u6570\u636e\u5e93\u3001Redis\u4e2d\uff09\n        \/\/\u5982\u679c\u4fdd\u5b58\u5728\u4e2d\u95f4\u4ef6\uff08\u6570\u636e\u5e93\u3001Redis\uff09\uff0c\u90a3\u4e48\u8d44\u6e90\u670d\u52a1\u5668\u4e0e\u8ba4\u8bc1\u670d\u52a1\u5668\u53ef\u4ee5\u4e0d\u5728\u540c\u4e00\u4e2a\u5de5\u7a0b\u4e2d\n        \/\/\u6ce8\u610f\uff1a\u5982\u679c\u4e0d\u4fdd\u5b58access_token\uff0c\u5219\u6ca1\u6cd5\u901a\u8fc7access_token\u53d6\u5f97\u7528\u6237\u4fe1\u606f\n        \/\/return new InMemoryTokenStore();\n        return new RedisTokenStore(redisConnectionFactory);\n    }\n}<\/code><\/pre>\n<p>\u672c\u6587\u8f6c\u8f7d\u53c2\u8003 <a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/blog.csdn.net\/yaomingyang\/column\/info\/41645\" title=\"\u539f\u6587\">\u539f\u6587<\/a> \u5e76\u52a0\u4ee5\u8c03\u8bd5<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u56db\u79cd\u6388\u6743\u7801\u6a21\u5f0f \u6388\u6743\u7801\u6a21\u5f0f \u5bc6\u7801\u6a21\u5f0f \u5ba2\u6237\u7aef\u6a21\u5f0f \u7b80\u5316\u6a21\u5f0f \u5bc6\u7801\u6a21\u5f0f grant_type:\u6388\u6743\u7c7b\u578b\uff0c\u5fc5\u9009\uff0c\u6b64 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[354],"tags":[353,153],"class_list":["post-1415","post","type-post","status-publish","format-standard","hentry","category-spring-security","tag-oauth2","tag-redis"],"_links":{"self":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/1415","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/comments?post=1415"}],"version-history":[{"count":0,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/1415\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/media?parent=1415"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/categories?post=1415"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/tags?post=1415"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}