├── .editorconfig ├── .gitignore ├── README.md ├── build.gradle ├── client-cli ├── build.gradle └── src │ └── main │ ├── groovy │ └── example │ │ ├── client │ │ ├── App.groovy │ │ ├── Hello.groovy │ │ ├── HelloClient.groovy │ │ ├── HelloService.groovy │ │ ├── TwitterClient.groovy │ │ ├── TwitterSearch.groovy │ │ └── TwitterService.groovy │ │ └── feign │ │ ├── HelloClientConfiguration.groovy │ │ └── TwitterClientConfiguration.groovy │ └── resources │ └── application.yml ├── client-web ├── build.gradle └── src │ └── main │ ├── groovy │ └── example │ │ ├── client │ │ ├── App.groovy │ │ ├── AppConfiguration.groovy │ │ ├── AppSecurityConfiguration.groovy │ │ ├── Hello.groovy │ │ ├── HelloClient.groovy │ │ ├── HelloController.groovy │ │ ├── TwitterClient.groovy │ │ ├── TwitterController.groovy │ │ ├── TwitterSearch.groovy │ │ └── UserController.groovy │ │ └── feign │ │ ├── HelloClientConfiguration.groovy │ │ ├── HelloOAuth2FeignRequestInterceptor.groovy │ │ └── TwitterClientConfiguration.groovy │ └── resources │ └── application.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── server ├── build.gradle └── src │ └── main │ ├── groovy │ └── example │ │ └── server │ │ ├── App.groovy │ │ ├── AppConfiguration.groovy │ │ ├── AppSecurityConfiguration.groovy │ │ ├── Hello.groovy │ │ ├── HelloController.groovy │ │ └── UserController.groovy │ └── resources │ └── application.yml └── settings.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | indent_size = 2 8 | 9 | [*.{groovy,java,gradle}] 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Credential 2 | config 3 | 4 | # Gradle 5 | .gradle 6 | build 7 | 8 | # Eclipse 9 | .settings 10 | .classpath 11 | .project 12 | 13 | # IDEA 14 | .idea 15 | *.iml 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Cloud Feign + OAuth 2.0 2 | 3 | This is an example of Spring Cloud Feign and Spring Security OAuth2. 4 | 5 | 6 | - API server 7 | - Spring Boot web server running on port 8081 8 | - Enable request logging (`CommonsRequestLoggingFilter`) 9 | - Enable OAuth authorization server 10 | - Enable OAuth resource server 11 | - Configure OAuth client ID and secret 12 | - Configure resource owners (`theUser1` and `theUser2`) 13 | - Example REST controller 14 | - API client (CLI) 15 | - Spring Boot without web server 16 | - Enable OAuth client access token request logging 17 | - Enable Feign client logging 18 | - Enable Feign client 19 | - Disable Hystrix 20 | - Enable Feign request interceptor for OAuth 2.0 client (`OAuth2FeignRequestInterceptor`) 21 | - Example REST client for the API server using resource owner password grant 22 | - Example REST client for Twitter API using client credentials grant 23 | - API client (web) 24 | - Spring Boot web server running on port 8082 25 | - Enable request logging (`CommonsRequestLoggingFilter`) 26 | - Enable OAuth client access token request logging 27 | - Enable Feign client logging 28 | - Enable Feign client 29 | - Disable Hystrix 30 | - Enable Feign request interceptor for OAuth 2.0 client (`OAuth2FeignRequestInterceptor`) 31 | - Use Client Credentials Grant if not logged in 32 | - Use Resource Owner Password Grant if logged in 33 | - Configure to renew session on login 34 | - Example REST client for the API server using resource owner password grant 35 | - Example REST client for Twitter API using client credentials grant 36 | 37 | 38 | ## Run the client CLI 39 | 40 | ``` 41 | ./gradlew server:bootRun 42 | ``` 43 | 44 | ``` 45 | ./gradlew client-cli:bootRun 46 | ``` 47 | 48 | API client does: 49 | 50 | - Acquire an access token from the API server 51 | - Send a request with the access token to the API server 52 | - Acquire an access token from Twitter API 53 | - Send a request with the access token to Twitter API 54 | 55 | API client: 56 | 57 | ``` 58 | 2016-12-10 22:24:38.257 INFO [-,,,] 16048 --- [ restartedMain] example.client.App : Started App in 20.16 seconds (JVM running for 22.875) 59 | 2016-12-10 22:24:38.384 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.web.client.RestTemplate : Created POST request for "http://localhost:8081/oauth/token" 60 | 2016-12-10 22:24:38.463 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.web.client.RestTemplate : POST request for "http://localhost:8081/oauth/token" resulted in 200 (OK) 61 | 2016-12-10 22:24:38.540 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.w.c.HttpMessageConverterExtractor : Reading [interface org.springframework.security.oauth2.common.OAuth2AccessToken] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@6b4afe9] 62 | 2016-12-10 22:24:38.586 DEBUG [-,,,] 16048 --- [ restartedMain] example.client.HelloClient : [HelloClient#hello] ---> GET http://localhost:8081/hello HTTP/1.1 63 | 2016-12-10 22:24:38.645 DEBUG [-,,,] 16048 --- [ restartedMain] example.client.HelloClient : [HelloClient#hello] <--- HTTP/1.1 200 OK (54ms) 64 | 2016-12-10 22:24:38.765 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.w.c.HttpMessageConverterExtractor : Reading [class example.client.Hello] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@454a8dd2] 65 | 2016-12-10 22:24:38.905 INFO [-,,,] 16048 --- [ restartedMain] example.client.HelloService : Received from API server: example.client.Hello(theUser1) 66 | 2016-12-10 22:24:39.255 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.web.client.RestTemplate : Created POST request for "https://api.twitter.com/oauth2/token" 67 | 2016-12-10 22:24:39.973 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.web.client.RestTemplate : POST request for "https://api.twitter.com/oauth2/token" resulted in 200 (OK) 68 | 2016-12-10 22:24:39.974 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.w.c.HttpMessageConverterExtractor : Reading [interface org.springframework.security.oauth2.common.OAuth2AccessToken] as "application/json;charset=utf-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@7763b804] 69 | 2016-12-10 22:24:40.205 DEBUG [-,,,] 16048 --- [ restartedMain] o.s.w.c.HttpMessageConverterExtractor : Reading [class example.client.TwitterSearch] as "application/json;charset=utf-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@454a8dd2] 70 | 2016-12-10 22:24:40.329 INFO [-,,,] 16048 --- [ restartedMain] example.client.TwitterService : Received from Twitter API: example.client.TwitterSearch([example.client.TwitterSearch$Status(...)]) 71 | ``` 72 | 73 | API server: 74 | 75 | ``` 76 | 2016-12-10 22:24:38.411 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /oauth/token; Attributes: [fullyAuthenticated] 77 | 2016-12-10 22:24:38.411 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@428ee622: Principal: org.springframework.security.core.userdetails.User@693716c: Username: theId; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_USER 78 | 2016-12-10 22:24:38.412 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful 79 | 2016-12-10 22:24:38.412 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object 80 | 2016-12-10 22:24:38.413 DEBUG 15598 --- [tp1430058124-18] o.s.w.f.CommonsRequestLoggingFilter : Before request [uri=/oauth/token;client=127.0.0.1;user=theId;headers={Authorization=[Basic dGhlSWQ6dGhlU2VjcmV0], Accept=[application/json, application/x-www-form-urlencoded], Cache-Control=[no-cache], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], Host=[localhost:8081], Pragma=[no-cache], Content-Length=[72], Content-Type=[application/x-www-form-urlencoded;charset=UTF-8]}] 81 | 2016-12-10 22:24:38.419 DEBUG 15598 --- [tp1430058124-18] o.s.w.f.CommonsRequestLoggingFilter : After request [uri=/oauth/token;client=127.0.0.1;user=theId;headers={Authorization=[Basic dGhlSWQ6dGhlU2VjcmV0], Accept=[application/json, application/x-www-form-urlencoded], Cache-Control=[no-cache], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], Host=[localhost:8081], Pragma=[no-cache], Content-Length=[72], Content-Type=[application/x-www-form-urlencoded;charset=UTF-8]};payload=password=thePassword&grant_type=password&scope=the] 82 | 2016-12-10 22:24:38.424 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally 83 | 2016-12-10 22:24:38.608 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /hello; Attributes: [#oauth2.throwOnError(authenticated)] 84 | 2016-12-10 22:24:38.609 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.provider.OAuth2Authentication@8449a742: Principal: org.springframework.security.core.userdetails.User@af827fdc: Username: theUser1; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, tokenType=BearertokenValue=; Granted Authorities: ROLE_USER 85 | 2016-12-10 22:24:38.609 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful 86 | 2016-12-10 22:24:38.609 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object 87 | 2016-12-10 22:24:38.610 DEBUG 15598 --- [tp1430058124-15] o.s.w.f.CommonsRequestLoggingFilter : Before request [uri=/hello;client=127.0.0.1;user=theUser1;headers={Authorization=[Bearer 4113439f-6847-490c-af9d-1c7240aeb855], X-Span-Name=[http:/hello], Accept=[*/*], X-B3-SpanId=[46f80160a8979524], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], X-B3-Sampled=[0], X-B3-TraceId=[46f80160a8979524], Host=[localhost:8081]}] 88 | 2016-12-10 22:24:38.622 DEBUG 15598 --- [tp1430058124-15] o.s.w.f.CommonsRequestLoggingFilter : After request [uri=/hello;client=127.0.0.1;user=theUser1;headers={Authorization=[Bearer 4113439f-6847-490c-af9d-1c7240aeb855], X-Span-Name=[http:/hello], Accept=[*/*], X-B3-SpanId=[46f80160a8979524], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], X-B3-Sampled=[0], X-B3-TraceId=[46f80160a8979524], Host=[localhost:8081]}] 89 | 2016-12-10 22:24:38.623 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally 90 | ``` 91 | 92 | 93 | ## Run the client web 94 | 95 | ``` 96 | ./gradlew server:bootRun 97 | ``` 98 | 99 | ``` 100 | ./gradlew client-web:bootRun 101 | ``` 102 | 103 | Access to http://localhost:8082/hello and 104 | 105 | 106 | API client: 107 | 108 | ``` 109 | 2017-01-09 14:22:35.663 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] e.f.HelloOAuth2FeignRequestInterceptor : ClientCredentialsResourceDetails 110 | 2017-01-09 14:22:35.771 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] o.s.web.client.RestTemplate : Created POST request for "http://localhost:8081/oauth/token" 111 | 2017-01-09 14:22:35.900 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> POST /oauth/token HTTP/1.1 112 | 2017-01-09 14:22:35.900 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> Authorization: Basic dGhlSWQ6dGhlU2VjcmV0 113 | 2017-01-09 14:22:35.900 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> Accept: application/json, application/x-www-form-urlencoded 114 | 2017-01-09 14:22:35.900 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> Content-Type: application/x-www-form-urlencoded 115 | 2017-01-09 14:22:35.900 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> Content-Length: 44 116 | 2017-01-09 14:22:35.901 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> Host: localhost:8081 117 | 2017-01-09 14:22:35.901 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> Connection: Keep-Alive 118 | 2017-01-09 14:22:35.901 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102) 119 | 2017-01-09 14:22:35.901 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 >> Accept-Encoding: gzip,deflate 120 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << HTTP/1.1 200 OK 121 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << Date: Mon, 09 Jan 2017 05:22:36 GMT 122 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << Cache-Control: no-store 123 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << Pragma: no-cache 124 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << Content-Type: application/json;charset=UTF-8 125 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << X-Content-Type-Options: nosniff 126 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << X-XSS-Protection: 1; mode=block 127 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << X-Frame-Options: DENY 128 | 2017-01-09 14:22:36.289 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] org.apache.http.headers : http-outgoing-0 << Transfer-Encoding: chunked 129 | 2017-01-09 14:22:36.310 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] o.s.web.client.RestTemplate : POST request for "http://localhost:8081/oauth/token" resulted in 200 (OK) 130 | 2017-01-09 14:22:36.347 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] o.s.w.c.HttpMessageConverterExtractor : Reading [interface org.springframework.security.oauth2.common.OAuth2AccessToken] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@7e90c131] 131 | 2017-01-09 14:22:36.391 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] example.client.HelloClient : [HelloClient#hello] ---> GET http://localhost:8081/hello HTTP/1.1 132 | 2017-01-09 14:22:36.548 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] example.client.HelloClient : [HelloClient#hello] <--- HTTP/1.1 200 OK (156ms) 133 | 2017-01-09 14:22:36.629 DEBUG [-,685d38a777d8d24e,30cac0baa49821b7,false] 17935 --- [tp1337171317-20] o.s.w.c.HttpMessageConverterExtractor : Reading [class example.client.Hello] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@73b62672] 134 | ``` 135 | 136 | API server: 137 | 138 | ``` 139 | 2016-12-10 22:32:59.600 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /oauth/token; Attributes: [fullyAuthenticated] 140 | 2016-12-10 22:32:59.600 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@428ee622: Principal: org.springframework.security.core.userdetails.User@693716c: Username: theId; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_USER 141 | 2016-12-10 22:32:59.600 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful 142 | 2016-12-10 22:32:59.600 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object 143 | 2016-12-10 22:32:59.601 DEBUG 15598 --- [tp1430058124-18] o.s.w.f.CommonsRequestLoggingFilter : Before request [uri=/oauth/token;client=127.0.0.1;user=theId;headers={Authorization=[Basic dGhlSWQ6dGhlU2VjcmV0], Accept=[application/json, application/x-www-form-urlencoded], Cache-Control=[no-cache], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], Host=[localhost:8081], Pragma=[no-cache], Content-Length=[72], Content-Type=[application/x-www-form-urlencoded;charset=UTF-8]}] 144 | 2016-12-10 22:32:59.607 DEBUG 15598 --- [tp1430058124-18] o.s.w.f.CommonsRequestLoggingFilter : After request [uri=/oauth/token;client=127.0.0.1;user=theId;headers={Authorization=[Basic dGhlSWQ6dGhlU2VjcmV0], Accept=[application/json, application/x-www-form-urlencoded], Cache-Control=[no-cache], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], Host=[localhost:8081], Pragma=[no-cache], Content-Length=[72], Content-Type=[application/x-www-form-urlencoded;charset=UTF-8]};payload=password=thePassword&grant_type=password&scope=the] 145 | 2016-12-10 22:32:59.607 DEBUG 15598 --- [tp1430058124-18] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally 146 | 2016-12-10 22:32:59.686 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /hello; Attributes: [#oauth2.throwOnError(authenticated)] 147 | 2016-12-10 22:32:59.686 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.provider.OAuth2Authentication@8449a742: Principal: org.springframework.security.core.userdetails.User@af827fdc: Username: theUser1; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, tokenType=BearertokenValue=; Granted Authorities: ROLE_USER 148 | 2016-12-10 22:32:59.687 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful 149 | 2016-12-10 22:32:59.687 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object 150 | 2016-12-10 22:32:59.687 DEBUG 15598 --- [tp1430058124-15] o.s.w.f.CommonsRequestLoggingFilter : Before request [uri=/hello;client=127.0.0.1;user=theUser1;headers={Authorization=[Bearer 4113439f-6847-490c-af9d-1c7240aeb855], X-Span-Name=[http:/hello], Accept=[*/*], X-B3-SpanId=[420a59bc1f5b11f9], X-B3-ParentSpanId=[5f4b7aec6370e786], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], X-B3-Sampled=[0], X-B3-TraceId=[d9e9ef7a60746d62], Host=[localhost:8081]}] 151 | 2016-12-10 22:32:59.691 DEBUG 15598 --- [tp1430058124-15] o.s.w.f.CommonsRequestLoggingFilter : After request [uri=/hello;client=127.0.0.1;user=theUser1;headers={Authorization=[Bearer 4113439f-6847-490c-af9d-1c7240aeb855], X-Span-Name=[http:/hello], Accept=[*/*], X-B3-SpanId=[420a59bc1f5b11f9], X-B3-ParentSpanId=[5f4b7aec6370e786], User-Agent=[Java/1.8.0_102], Connection=[keep-alive], X-B3-Sampled=[0], X-B3-TraceId=[d9e9ef7a60746d62], Host=[localhost:8081]}] 152 | 2016-12-10 22:32:59.691 DEBUG 15598 --- [tp1430058124-15] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally 153 | ``` 154 | 155 | 156 | ## Try the authorization sequence with curl 157 | 158 | Make a request without an access token and the server will return 401. 159 | 160 | ```sh 161 | curl -v http://localhost:8081/hello 162 | ``` 163 | 164 | ```json 165 | {"error":"unauthorized", 166 | "error_description":"Full authentication is required to access this resource"} 167 | ``` 168 | 169 | Acquire an access token. 170 | 171 | ```sh 172 | curl -v -u theId:theSecret \ 173 | http://localhost:8081/oauth/token \ 174 | -d grant_type=password \ 175 | -d username=theUser1 \ 176 | -d password=theResourceOwnerPassword \ 177 | -d scope=foo 178 | ``` 179 | 180 | ```json 181 | {"access_token":"50480ab0-4616-449c-823b-e5eb41ebe44f", 182 | "token_type":"bearer", 183 | "refresh_token":"3c526955-848c-4c85-b22f-ef4879d4a2be", 184 | "expires_in":43199, 185 | "scope":"foo"} 186 | ``` 187 | 188 | Make a request with the access token. 189 | 190 | ```sh 191 | curl -v -H 'Authorization: Bearer 50480ab0-4616-449c-823b-e5eb41ebe44f' \ 192 | http://localhost:8081/hello 193 | ``` 194 | 195 | ```json 196 | {"name":"theUser1"} 197 | ``` 198 | 199 | ```sh 200 | curl -v -H 'Authorization: Bearer 50480ab0-4616-449c-823b-e5eb41ebe44f' \ 201 | http://localhost:8081/user 202 | ``` 203 | 204 | ```json 205 | { 206 | "details": { 207 | "remoteAddress": "0:0:0:0:0:0:0:1", 208 | "sessionId": null, 209 | "tokenValue": "50480ab0-4616-449c-823b-e5eb41ebe44f", 210 | "tokenType": "Bearer", 211 | "decodedDetails": null 212 | }, 213 | "authorities": [ 214 | { 215 | "authority": "ROLE_USER" 216 | } 217 | ], 218 | "authenticated": true, 219 | "userAuthentication": { 220 | "details": { 221 | "grant_type": "password", 222 | "scope": "foo", 223 | "username": "theUser1" 224 | }, 225 | "authorities": [ 226 | { 227 | "authority": "ROLE_USER" 228 | } 229 | ], 230 | "authenticated": true, 231 | "principal": { 232 | "password": null, 233 | "username": "theUser1", 234 | "authorities": [ 235 | { 236 | "authority": "ROLE_USER" 237 | } 238 | ], 239 | "accountNonExpired": true, 240 | "accountNonLocked": true, 241 | "credentialsNonExpired": true, 242 | "enabled": true 243 | }, 244 | "credentials": null, 245 | "name": "theUser1" 246 | }, 247 | "oauth2Request": { 248 | "clientId": "theId", 249 | "scope": [ 250 | "foo" 251 | ], 252 | "requestParameters": { 253 | "grant_type": "password", 254 | "scope": "foo", 255 | "username": "theUser1" 256 | }, 257 | "resourceIds": [], 258 | "authorities": [ 259 | { 260 | "authority": "ROLE_USER" 261 | } 262 | ], 263 | "approved": true, 264 | "refresh": false, 265 | "redirectUri": null, 266 | "responseTypes": [], 267 | "extensions": {}, 268 | "grantType": "password", 269 | "refreshTokenRequest": null 270 | }, 271 | "clientOnly": false, 272 | "principal": { 273 | "password": null, 274 | "username": "theUser1", 275 | "authorities": [ 276 | { 277 | "authority": "ROLE_USER" 278 | } 279 | ], 280 | "accountNonExpired": true, 281 | "accountNonLocked": true, 282 | "credentialsNonExpired": true, 283 | "enabled": true 284 | }, 285 | "credentials": "", 286 | "name": "theUser1" 287 | } 288 | ``` 289 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '1.4.3.RELEASE' 3 | } 4 | 5 | subprojects { 6 | apply plugin: 'groovy' 7 | apply plugin: 'org.springframework.boot' 8 | apply plugin: 'idea' 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | repositories { 14 | jcenter() 15 | } 16 | 17 | dependencies { 18 | // Groovy 19 | compile 'org.codehaus.groovy:groovy-all:2.4.7' 20 | 21 | // Spring 22 | runtime 'org.springframework.boot:spring-boot-devtools' 23 | } 24 | 25 | idea { 26 | module { 27 | inheritOutputDirs = false 28 | outputDir = file("$buildDir/classes/main/") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client-cli/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile 'org.springframework.boot:spring-boot-starter' 3 | compile 'org.springframework.cloud:spring-cloud-starter-feign:1.2.1.RELEASE' 4 | compile 'org.springframework.cloud:spring-cloud-starter-sleuth:1.0.10.RELEASE' 5 | 6 | compile 'org.springframework.cloud:spring-cloud-starter-security:1.1.3.RELEASE' 7 | compile 'org.springframework.security.oauth:spring-security-oauth2' 8 | } 9 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/client/App.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import org.springframework.boot.SpringApplication 4 | import org.springframework.boot.autoconfigure.SpringBootApplication 5 | import org.springframework.cloud.netflix.feign.EnableFeignClients 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client 7 | 8 | import javax.inject.Inject 9 | 10 | @EnableOAuth2Client 11 | @EnableFeignClients 12 | @SpringBootApplication 13 | class App { 14 | @Inject 15 | HelloService helloService 16 | 17 | @Inject 18 | TwitterService twitterService 19 | 20 | void run(String... args) { 21 | def hello = helloService.hello(args) 22 | twitterService.search(hello.name) 23 | } 24 | 25 | static void main(String[] args) { 26 | def applicationContext = SpringApplication.run(App, args) 27 | def app = applicationContext.getBean(App) 28 | app.run(args) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/client/Hello.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import groovy.transform.Immutable 4 | 5 | @Immutable 6 | class Hello { 7 | String name 8 | } 9 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/client/HelloClient.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import example.feign.HelloClientConfiguration 4 | import org.springframework.cloud.netflix.feign.FeignClient 5 | import org.springframework.web.bind.annotation.PathVariable 6 | import org.springframework.web.bind.annotation.RequestMapping 7 | import org.springframework.web.bind.annotation.RequestMethod 8 | 9 | @FeignClient(name = 'hello', url = '${hello.service.url}', configuration = HelloClientConfiguration) 10 | interface HelloClient { 11 | @RequestMapping(value = '/hello', method = RequestMethod.GET) 12 | Hello hello() 13 | 14 | @RequestMapping(value = '/hello/{name}', method = RequestMethod.GET) 15 | Hello hello(@PathVariable('name') String name) 16 | } 17 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/client/HelloService.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.springframework.stereotype.Service 5 | 6 | import javax.inject.Inject 7 | 8 | @Slf4j 9 | @Service 10 | class HelloService { 11 | @Inject 12 | HelloClient client 13 | 14 | Hello hello(String... args) { 15 | if (args.length) { 16 | def hello = client.hello(args.join(',')) 17 | log.info("Received from API server: $hello") 18 | hello 19 | } else { 20 | def hello = client.hello() 21 | log.info("Received from API server: $hello") 22 | hello 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/client/TwitterClient.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import example.feign.TwitterClientConfiguration 4 | import org.springframework.cloud.netflix.feign.FeignClient 5 | import org.springframework.web.bind.annotation.RequestMapping 6 | import org.springframework.web.bind.annotation.RequestMethod 7 | import org.springframework.web.bind.annotation.RequestParam 8 | 9 | @FeignClient(name = 'twitter', url = '${twitter.service.url}', configuration = TwitterClientConfiguration) 10 | interface TwitterClient { 11 | @RequestMapping(value = '/search/tweets.json', method = RequestMethod.GET) 12 | TwitterSearch search(@RequestParam('q') String q) 13 | } 14 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/client/TwitterSearch.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import groovy.transform.Immutable 4 | 5 | @Immutable 6 | class TwitterSearch { 7 | List statuses 8 | 9 | @Immutable 10 | static class Status { 11 | String text 12 | String created_at 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/client/TwitterService.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.springframework.stereotype.Service 5 | 6 | import javax.inject.Inject 7 | 8 | @Slf4j 9 | @Service 10 | class TwitterService { 11 | @Inject 12 | TwitterClient client 13 | 14 | TwitterSearch search(String query) { 15 | def twitterSearch = client.search(query) 16 | log.info("Received from Twitter API: $twitterSearch") 17 | twitterSearch 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/feign/HelloClientConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.feign 2 | 3 | import feign.Logger 4 | import feign.RequestInterceptor 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor 7 | import org.springframework.context.annotation.Bean 8 | import org.springframework.context.annotation.Configuration 9 | import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext 10 | import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails 11 | import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails 12 | 13 | @Configuration 14 | class HelloClientConfiguration { 15 | @Value('${security.oauth2.hello.access-token-uri}') 16 | private String accessTokenUri 17 | 18 | @Value('${security.oauth2.hello.client-id}') 19 | private String clientId 20 | 21 | @Value('${security.oauth2.hello.client-secret}') 22 | private String clientSecret 23 | 24 | @Value('${security.oauth2.hello.scope}') 25 | private String scope 26 | 27 | @Bean 28 | RequestInterceptor oauth2FeignRequestInterceptor() { 29 | new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource()) 30 | } 31 | 32 | OAuth2ProtectedResourceDetails resource() { 33 | def details = new ResourceOwnerPasswordResourceDetails() 34 | details.accessTokenUri = accessTokenUri 35 | details.clientId = clientId 36 | details.clientSecret = clientSecret 37 | details.scope = [scope] 38 | details.username = 'theUser1' 39 | details.password = 'theResourceOwnerPassword' 40 | details 41 | } 42 | 43 | @Bean 44 | Logger.Level feignLoggerLevel() { 45 | Logger.Level.BASIC 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client-cli/src/main/groovy/example/feign/TwitterClientConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.feign 2 | 3 | import feign.RequestInterceptor 4 | import org.springframework.beans.factory.annotation.Value 5 | import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor 6 | import org.springframework.context.annotation.Bean 7 | import org.springframework.context.annotation.Configuration 8 | import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext 9 | import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails 10 | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails 11 | 12 | @Configuration 13 | class TwitterClientConfiguration { 14 | 15 | @Value('${security.oauth2.twitter.access-token-uri}') 16 | private String accessTokenUri 17 | 18 | @Value('${security.oauth2.twitter.client-id}') 19 | private String clientId 20 | 21 | @Value('${security.oauth2.twitter.client-secret}') 22 | private String clientSecret 23 | 24 | @Bean 25 | RequestInterceptor oauth2FeignRequestInterceptor() { 26 | new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource()) 27 | } 28 | 29 | OAuth2ProtectedResourceDetails resource() { 30 | def details = new ClientCredentialsResourceDetails() 31 | details.accessTokenUri = accessTokenUri 32 | details.clientId = clientId 33 | details.clientSecret = clientSecret 34 | details 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client-cli/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.main.web-environment: false 2 | 3 | logging.level: 4 | # Enable logging of access token request 5 | org.springframework.web.client: DEBUG 6 | # Enable request logging 7 | example.client.HelloClient: DEBUG 8 | 9 | hello.service: 10 | url: http://localhost:8081 11 | 12 | twitter.service: 13 | url: https://api.twitter.com/1.1 14 | 15 | feign.hystrix.enabled: false 16 | 17 | security: 18 | oauth2: 19 | hello: 20 | access-token-uri: http://localhost:8081/oauth/token 21 | client-id: theId 22 | client-secret: theSecret 23 | scope: theScope 24 | twitter: 25 | access-token-uri: https://api.twitter.com/oauth2/token 26 | # Credentials goes to /src/main/resources/config/application.yml 27 | #client-id: 28 | #client-secret: 29 | -------------------------------------------------------------------------------- /client-web/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile 'org.springframework.boot:spring-boot-starter-web' 3 | compile 'org.springframework.boot:spring-boot-starter-jetty' 4 | 5 | compile 'org.springframework.cloud:spring-cloud-starter-feign:1.2.1.RELEASE' 6 | compile 'org.springframework.cloud:spring-cloud-starter-sleuth:1.0.10.RELEASE' 7 | 8 | compile 'org.springframework.cloud:spring-cloud-starter-security:1.1.3.RELEASE' 9 | compile 'org.springframework.security.oauth:spring-security-oauth2' 10 | } 11 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/App.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import org.springframework.boot.SpringApplication 4 | import org.springframework.boot.autoconfigure.SpringBootApplication 5 | import org.springframework.cloud.netflix.feign.EnableFeignClients 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client 7 | 8 | @EnableOAuth2Client 9 | @EnableFeignClients 10 | @SpringBootApplication 11 | class App { 12 | static void main(String[] args) { 13 | SpringApplication.run(App, args) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/AppConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import org.springframework.context.annotation.Bean 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.web.filter.CommonsRequestLoggingFilter 6 | 7 | import javax.servlet.Filter 8 | 9 | @Configuration 10 | class AppConfiguration { 11 | @Bean 12 | Filter commonsRequestLoggingFilter() { 13 | def filter = new CommonsRequestLoggingFilter() 14 | filter.includeClientInfo = true 15 | filter.includeQueryString = true 16 | filter.includeHeaders = true 17 | filter.includePayload = true 18 | filter 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/AppSecurityConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import org.springframework.context.annotation.Configuration 4 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity 6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity 7 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 8 | 9 | @Configuration 10 | @EnableWebSecurity 11 | class AppSecurityConfiguration extends WebSecurityConfigurerAdapter { 12 | @Override 13 | protected void configure(AuthenticationManagerBuilder auth) { 14 | auth.inMemoryAuthentication() 15 | .withUser('theUser1').password('thePassword1').roles('USER') 16 | .and() 17 | .withUser('theUser2').password('thePassword2').roles('USER') 18 | } 19 | 20 | @Override 21 | protected void configure(HttpSecurity http) { 22 | http.sessionManagement().sessionFixation().newSession() 23 | 24 | http.authorizeRequests().anyRequest().permitAll() 25 | http.formLogin() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/Hello.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import groovy.transform.Immutable 4 | 5 | @Immutable 6 | class Hello { 7 | String name 8 | } 9 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/HelloClient.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import example.feign.HelloClientConfiguration 4 | import org.springframework.cloud.netflix.feign.FeignClient 5 | import org.springframework.web.bind.annotation.PathVariable 6 | import org.springframework.web.bind.annotation.RequestMapping 7 | import org.springframework.web.bind.annotation.RequestMethod 8 | 9 | @FeignClient(name = 'hello', url = '${hello.service.url}', configuration = HelloClientConfiguration) 10 | interface HelloClient { 11 | @RequestMapping(value = '/hello', method = RequestMethod.GET) 12 | Hello hello() 13 | 14 | @RequestMapping(value = '/hello/{name}', method = RequestMethod.GET) 15 | Hello hello(@PathVariable('name') String name) 16 | } 17 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/HelloController.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import org.springframework.web.bind.annotation.GetMapping 4 | import org.springframework.web.bind.annotation.PathVariable 5 | import org.springframework.web.bind.annotation.RestController 6 | 7 | import javax.inject.Inject 8 | 9 | @RestController 10 | class HelloController { 11 | @Inject 12 | HelloClient client 13 | 14 | @GetMapping('/hello') 15 | Hello helloWorld() { 16 | client.hello() 17 | } 18 | 19 | @GetMapping('/hello/{name}') 20 | Hello helloByName(@PathVariable String name) { 21 | client.hello(name) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/TwitterClient.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import example.feign.TwitterClientConfiguration 4 | import org.springframework.cloud.netflix.feign.FeignClient 5 | import org.springframework.web.bind.annotation.RequestMapping 6 | import org.springframework.web.bind.annotation.RequestMethod 7 | import org.springframework.web.bind.annotation.RequestParam 8 | 9 | @FeignClient(name = 'twitter', url = '${twitter.service.url}', configuration = TwitterClientConfiguration) 10 | interface TwitterClient { 11 | @RequestMapping(value = '/search/tweets.json', method = RequestMethod.GET) 12 | TwitterSearch search(@RequestParam('q') String q) 13 | } 14 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/TwitterController.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import org.springframework.web.bind.annotation.GetMapping 4 | import org.springframework.web.bind.annotation.PathVariable 5 | import org.springframework.web.bind.annotation.RestController 6 | 7 | import javax.inject.Inject 8 | 9 | @RestController 10 | class TwitterController { 11 | @Inject 12 | TwitterClient client 13 | 14 | @GetMapping('/twitter/search/{query}') 15 | TwitterSearch search(@PathVariable String query) { 16 | client.search(query) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/TwitterSearch.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import groovy.transform.Immutable 4 | 5 | @Immutable 6 | class TwitterSearch { 7 | List statuses 8 | 9 | @Immutable 10 | static class Status { 11 | String text 12 | String created_at 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/client/UserController.groovy: -------------------------------------------------------------------------------- 1 | package example.client 2 | 3 | import org.springframework.security.core.Authentication 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.RestController 6 | 7 | @RestController 8 | class UserController { 9 | @GetMapping('/user') 10 | def user(Authentication authentication) { 11 | authentication 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/feign/HelloClientConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.feign 2 | 3 | import feign.Logger 4 | import feign.RequestInterceptor 5 | import org.springframework.context.annotation.Bean 6 | import org.springframework.context.annotation.Configuration 7 | import org.springframework.security.oauth2.client.OAuth2ClientContext 8 | 9 | @Configuration 10 | class HelloClientConfiguration { 11 | @Bean 12 | RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext) { 13 | new HelloOAuth2FeignRequestInterceptor(oAuth2ClientContext) 14 | } 15 | 16 | @Bean 17 | Logger.Level feignLoggerLevel() { 18 | Logger.Level.BASIC 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/feign/HelloOAuth2FeignRequestInterceptor.groovy: -------------------------------------------------------------------------------- 1 | package example.feign 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.springframework.beans.factory.annotation.Value 5 | import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor 6 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory 7 | import org.springframework.security.authentication.AnonymousAuthenticationToken 8 | import org.springframework.security.core.context.SecurityContextHolder 9 | import org.springframework.security.core.userdetails.UserDetails 10 | import org.springframework.security.oauth2.client.OAuth2ClientContext 11 | import org.springframework.security.oauth2.client.http.AccessTokenRequiredException 12 | import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails 13 | import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException 14 | import org.springframework.security.oauth2.client.token.AccessTokenProvider 15 | import org.springframework.security.oauth2.client.token.AccessTokenProviderChain 16 | import org.springframework.security.oauth2.client.token.AccessTokenRequest 17 | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider 18 | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails 19 | import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider 20 | import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider 21 | import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider 22 | import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails 23 | import org.springframework.security.oauth2.common.OAuth2AccessToken 24 | 25 | @Slf4j 26 | class HelloOAuth2FeignRequestInterceptor extends OAuth2FeignRequestInterceptor { 27 | @Value('${security.oauth2.hello.access-token-uri}') 28 | private String accessTokenUri 29 | 30 | @Value('${security.oauth2.hello.client-id}') 31 | private String clientId 32 | 33 | @Value('${security.oauth2.hello.client-secret}') 34 | private String clientSecret 35 | 36 | @Value('${security.oauth2.hello.scope}') 37 | private String scope 38 | 39 | private final OAuth2ClientContext oAuth2ClientContext 40 | 41 | private final AccessTokenProvider accessTokenProvider = { 42 | def requestFactory = new HttpComponentsClientHttpRequestFactory() 43 | def accessTokenProviders = Arrays.asList( 44 | new AuthorizationCodeAccessTokenProvider(), 45 | new ImplicitAccessTokenProvider(), 46 | new ResourceOwnerPasswordAccessTokenProvider(), 47 | new ClientCredentialsAccessTokenProvider() 48 | ) 49 | accessTokenProviders*.requestFactory = requestFactory 50 | new AccessTokenProviderChain(accessTokenProviders) 51 | }() 52 | 53 | def HelloOAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext) { 54 | super(oAuth2ClientContext, null) 55 | this.oAuth2ClientContext = oAuth2ClientContext 56 | } 57 | 58 | @Override 59 | protected OAuth2AccessToken acquireAccessToken() { 60 | acquireAccessToken(resource()) 61 | } 62 | 63 | /** 64 | * @see OAuth2FeignRequestInterceptor#acquireAccessToken() 65 | */ 66 | protected OAuth2AccessToken acquireAccessToken(OAuth2ProtectedResourceDetails resource) 67 | throws UserRedirectRequiredException { 68 | AccessTokenRequest tokenRequest = oAuth2ClientContext.getAccessTokenRequest(); 69 | if (tokenRequest == null) { 70 | throw new AccessTokenRequiredException( 71 | "Cannot find valid context on request for resource '" 72 | + resource.getId() + "'.", 73 | resource); 74 | } 75 | String stateKey = tokenRequest.getStateKey(); 76 | if (stateKey != null) { 77 | tokenRequest.setPreservedState( 78 | oAuth2ClientContext.removePreservedState(stateKey)); 79 | } 80 | OAuth2AccessToken existingToken = oAuth2ClientContext.getAccessToken(); 81 | if (existingToken != null) { 82 | oAuth2ClientContext.setAccessToken(existingToken); 83 | } 84 | OAuth2AccessToken obtainableAccessToken; 85 | obtainableAccessToken = accessTokenProvider.obtainAccessToken(resource, 86 | tokenRequest); 87 | if (obtainableAccessToken == null || obtainableAccessToken.getValue() == null) { 88 | throw new IllegalStateException( 89 | " Access token provider returned a null token, which is illegal according to the contract."); 90 | } 91 | oAuth2ClientContext.setAccessToken(obtainableAccessToken); 92 | return obtainableAccessToken; 93 | } 94 | 95 | private resource() { 96 | def authentication = SecurityContextHolder.context.authentication 97 | if (!authentication || authentication instanceof AnonymousAuthenticationToken) { 98 | def details = new ClientCredentialsResourceDetails() 99 | details.accessTokenUri = accessTokenUri 100 | details.clientId = clientId 101 | details.clientSecret = clientSecret 102 | details.scope = [scope] 103 | log.debug("ClientCredentialsResourceDetails") 104 | details 105 | } else { 106 | def details = new ResourceOwnerPasswordResourceDetails() 107 | details.accessTokenUri = accessTokenUri 108 | details.clientId = clientId 109 | details.clientSecret = clientSecret 110 | details.scope = [scope] 111 | details.username = (authentication.principal as UserDetails).username 112 | details.password = 'theResourceOwnerPassword' // TODO 113 | log.debug("ResourceOwnerPasswordResourceDetails: username=${details.username}") 114 | details 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /client-web/src/main/groovy/example/feign/TwitterClientConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.feign 2 | 3 | import feign.Logger 4 | import feign.RequestInterceptor 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor 7 | import org.springframework.context.annotation.Bean 8 | import org.springframework.context.annotation.Configuration 9 | import org.springframework.security.oauth2.client.OAuth2ClientContext 10 | import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails 11 | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails 12 | 13 | @Configuration 14 | class TwitterClientConfiguration { 15 | @Value('${security.oauth2.twitter.access-token-uri}') 16 | private String accessTokenUri 17 | 18 | @Value('${security.oauth2.twitter.client-id}') 19 | private String clientId 20 | 21 | @Value('${security.oauth2.twitter.client-secret}') 22 | private String clientSecret 23 | 24 | @Bean 25 | RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext) { 26 | new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resource()) 27 | } 28 | 29 | OAuth2ProtectedResourceDetails resource() { 30 | def details = new ClientCredentialsResourceDetails() 31 | details.accessTokenUri = accessTokenUri 32 | details.clientId = clientId 33 | details.clientSecret = clientSecret 34 | details 35 | } 36 | 37 | @Bean 38 | Logger.Level feignLoggerLevel() { 39 | Logger.Level.BASIC 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client-web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging.level: 2 | example: DEBUG 3 | # Enable logging of access token request 4 | org.apache.http.headers: DEBUG 5 | #org.apache.http.wire: DEBUG 6 | org.springframework.web.client: DEBUG 7 | # Enable request logging 8 | example.client.HelloClient: DEBUG 9 | # Enable request logging 10 | org.springframework.web.filter: DEBUG 11 | 12 | server: 13 | port: 8082 14 | 15 | hello.service: 16 | url: http://localhost:8081 17 | 18 | twitter.service: 19 | url: https://api.twitter.com/1.1 20 | 21 | feign.hystrix.enabled: false 22 | 23 | security: 24 | basic: 25 | enabled: false 26 | oauth2: 27 | hello: 28 | access-token-uri: http://localhost:8081/oauth/token 29 | client-id: theId 30 | client-secret: theSecret 31 | scope: theScope 32 | twitter: 33 | access-token-uri: https://api.twitter.com/oauth2/token 34 | # Credentials goes to /src/main/resources/config/application.yml 35 | #client-id: 36 | #client-secret: 37 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int128/feign-oauth2-example/e724522518b1e8f1b8aad846f20d9669db13aaf2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 19 11:45:11 UTC 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /server/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile 'org.springframework.boot:spring-boot-starter-web' 3 | compile 'org.springframework.boot:spring-boot-starter-jetty' 4 | 5 | compile 'org.springframework.boot:spring-boot-starter-security' 6 | compile 'org.springframework.security.oauth:spring-security-oauth2' 7 | } 8 | -------------------------------------------------------------------------------- /server/src/main/groovy/example/server/App.groovy: -------------------------------------------------------------------------------- 1 | package example.server 2 | 3 | import org.springframework.boot.SpringApplication 4 | import org.springframework.boot.autoconfigure.SpringBootApplication 5 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer 7 | 8 | @EnableAuthorizationServer 9 | @EnableResourceServer 10 | @SpringBootApplication 11 | class App { 12 | static void main(String[] args) { 13 | SpringApplication.run(App, args) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/src/main/groovy/example/server/AppConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.server 2 | 3 | import org.springframework.context.annotation.Bean 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.web.filter.CommonsRequestLoggingFilter 6 | 7 | import javax.servlet.Filter 8 | 9 | @Configuration 10 | class AppConfiguration { 11 | @Bean 12 | Filter commonsRequestLoggingFilter() { 13 | def filter = new CommonsRequestLoggingFilter() 14 | filter.includeClientInfo = true 15 | filter.includeQueryString = true 16 | filter.includeHeaders = true 17 | filter.includePayload = true 18 | filter 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/main/groovy/example/server/AppSecurityConfiguration.groovy: -------------------------------------------------------------------------------- 1 | package example.server 2 | 3 | import org.springframework.context.annotation.Configuration 4 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder 5 | import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter 6 | 7 | @Configuration 8 | class AppSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter { 9 | @Override 10 | void init(AuthenticationManagerBuilder auth) { 11 | auth.inMemoryAuthentication() 12 | .withUser('theUser1').password('theResourceOwnerPassword').roles('USER') 13 | .and() 14 | .withUser('theUser2').password('theResourceOwnerPassword').roles('USER') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/src/main/groovy/example/server/Hello.groovy: -------------------------------------------------------------------------------- 1 | package example.server 2 | 3 | import groovy.transform.Immutable 4 | 5 | @Immutable 6 | class Hello { 7 | String name 8 | } 9 | -------------------------------------------------------------------------------- /server/src/main/groovy/example/server/HelloController.groovy: -------------------------------------------------------------------------------- 1 | package example.server 2 | 3 | import org.springframework.security.core.Authentication 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.PathVariable 6 | import org.springframework.web.bind.annotation.RequestMapping 7 | import org.springframework.web.bind.annotation.RestController 8 | 9 | @RestController 10 | @RequestMapping(produces = 'application/json') 11 | class HelloController { 12 | @GetMapping('/hello') 13 | Hello helloWorld(Authentication authentication) { 14 | new Hello(authentication?.name) 15 | } 16 | 17 | @GetMapping('/hello/{name}') 18 | Hello helloByName(@PathVariable String name) { 19 | new Hello(name) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/src/main/groovy/example/server/UserController.groovy: -------------------------------------------------------------------------------- 1 | package example.server 2 | 3 | import org.springframework.security.core.Authentication 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.RestController 6 | 7 | @RestController 8 | class UserController { 9 | @GetMapping('/user') 10 | def user(Authentication authentication) { 11 | authentication 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging.level: 2 | # Enable request logging 3 | org.springframework.web.filter: DEBUG 4 | # Web security access control logging 5 | org.springframework.security.web.access: DEBUG 6 | 7 | server: 8 | port: 8081 9 | 10 | security: 11 | basic: 12 | enabled: false 13 | oauth2.client: 14 | client-id: theId 15 | client-secret: theSecret 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'client-cli' 2 | include 'client-web' 3 | include 'server' --------------------------------------------------------------------------------