Spring Cloud Security offers a set of primitives for building secure +applications and services with minimum fuss. A declarative model which +can be heavily configured externally (or centrally) lends itself to +the implementation of large systems of co-operating, remote components, +usually with a central indentity management service. It is also extremely +easy to use in a service platform like Cloud Foundry. Building on +Spring Boot and Spring Security OAuth2 we can quickly create systems that +implement common patterns like single sign on, token relay and token +exchange.
+Quickstart
+OAuth2 Single Sign On
+Here’s a Spring Cloud "Hello World" app with HTTP Basic +authentication and a single user account:
+@Grab('spring-boot-starter-security')
+@Controller
+class Application {
+
+ @RequestMapping('/')
+ String home() {
+ 'Hello World'
+ }
+
+}
+You can run it with spring run app.groovy and watch the logs for the password (username is "user"). So far this is just the default for a Spring Boot app.
Here’s a Spring Cloud app with OAuth2 SSO:
+@Controller
+@EnableOAuth2Sso
+class Application {
+
+ @RequestMapping('/')
+ String home() {
+ 'Hello World'
+ }
+
+}
+Spot the difference? This app will actually behave exactly the same as +the previous one, because it doesn’t know it’s OAuth2 credentals +yet.
+You can register an app in github quite easily, so try that if you +want a production app on your own domain. If you are happy to test on +localhost:8080, then set up these properties in your application +configuration:
+spring:
+ oauth2:
+ client:
+ clientId: bd1c0a783ccdd1c9b9e4
+ clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
+ accessTokenUri: https://github.com/login/oauth/access_token
+ userAuthorizationUri: https://github.com/login/oauth/authorize
+ clientAuthenticationScheme: form
+ resource:
+ userInfoUri: https://api.github.com/user
+ preferTokenInfo: false
+run the app above and it will redirect to github for authorization. If +you are already signed into github you won’t even notice that it has +authenticated. These credentials will only work if your app is +running on port 8080.
+To limit the scope that the client asks for when it obtains an access token
+you can set spring.oauth2.client.scope (comma separated or an array in YAML). By
+default the scope is empty and it is up to to Authorization Server to
+decide what the defaults should be, usually depending on the settings in
+the client registration that it holds.
|
+ Note
+ |
++The examples above are all Groovy scripts. If you want to write the +same code in Java (or Groovy) you need to add Spring Security OAuth2 +to the classpath (e.g. see the +sample here). + | +
OAuth2 Protected Resource
+You want to protect an API resource with an OAuth2 token? Here’s a +simple example (paired with the client above):
+@Grab('spring-cloud-starter-security')
+@RestController
+@EnableOAuth2Resource
+class Application {
+
+ @RequestMapping('/')
+ def home() {
+ [message: 'Hello World']
+ }
+
+}
+and
+spring:
+ oauth2:
+ resource:
+ userInfoUri: https://api.github.com/user
+ preferTokenInfo: false
+More Detail
+Single Sign On
+An app will activate @EnableOAuth2Sso if you bind provide the
+following properties in the Environment:
-
+
-
+
+spring.oauth2.client.*with*equal toclientId,clientSecret, +accessTokenUri,userAuthorizationUriand one of:++-
+
-
+
+spring.oauth2.resource.userInfoUrito use the "/me" resource +(e.g. "https://uaa.run.pivotal.io/userinfo" on PWS), or
+ -
+
+spring.oauth2.resource.tokenInfoUrito use the token decoding endpoint +(e.g. "https://uaa.run.pivotal.io/check_token" on PWS).
+
++If you specify both the
+userInfoUriand thetokenInfoUrithen +you can set a flag to say that one is preferred over the other +(preferTokenInfo=trueis the default). Or
+ -
+
-
+
+spring.oauth2.resource.jwt.keyValueto +decode a JWT token locally, where the key is a verification key. The +verification key value is either a symmetric secret or PEM-encoded +RSA public key. If you don’t have the key and it’s public you can +provide a URI where it can be downloaded (as a JSON object with a +"value" field) withspring.oauth2.resource.jwt.keyUri. E.g. on PWS:++++$ curl https://uaa.run.pivotal.io/token_key +{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"}+
+
|
+ Warning
+ |
+
+If you use the spring.oauth2.resource.jwt.keyUri the authorization
+server needs to be running when your application starts up. It will
+log a warning if it can’t find the key, and tell you what to do to fix
+it.
+ |
+
You can set the preferred scope (as a comma-separated list or YAML
+array) in spring.oauth2.client.scope. It defaults to empty, in which case
+most Authorization Servers will ask the user for approval for the
+maximum allowed scope for the client.
There is also a setting for spring.oauth2.client.clientAuthenticationScheme which
+defaults to "header" (but you might need to set it to "form" if, like
+Github for instance, your OAuth2 provider doesn’t like header
+authentication). The spring.oauth2.client.* properties are bound to an instance
+of AuthorizationCodeResourceDetails so all its properties can be specified.
Token Type in User Info
+Google (and certain other 3rd party identity providers) is more strict
+about the token type name that is sent in the headers to the user info
+endpoint. The default is "Bearer" which suits most providers and
+matches the spec, but if you need to change it you can set
+spring.oauth2.resource.tokenType.
Customizing the RestTemplate
+The SSO (and Resource Server) features use an OAuth2RestTemplate
+internally to fetch user details for authentication. This is provided
+as a qualified @Bean with id "userInfoRestTemplate", but you
+shouldn’t need to know that to just use it. The default should be fine
+for most providers, but occasionally you might need to add additional
+interceptors, or change the request authenticator (which is how the
+token gets attached to outgoing requests). To add a customization just
+create a bean of type UserInfoRestTemplateCustomizer - it has a
+single method that will be called after the bean is created but before
+it is initialized. The rest template that is being customized here is
+only used internally to carry out authentication (in the SSO or
+Resource Server use cases).
A second {@link OAuth2RestTemplate} is available for autowiring if you +want to use it for back channel calls, and if there is a +token-authenticated user (in a web application) it will have the token +injected for you.
+|
+ Tip
+ |
+
+
+
+To set an RSA key value in YAML use the "pipe" continuation +marker to split it over multiple lines ("|") and remember to indent +the key value (it’s a standard YAML language feature). Example: +
+
+
+
+
+ |
+
Access Decision Rules
+By default the whole application will be secured with OAuth2 with the
+same access rule ("authenticated"). This includes the Actuator
+endpoints, which you might prefer to be secured differently, so Spring
+Cloud Security provides a configurer callback that lets you change the
+matching and access rules for OAuth2 authentication. Any bean of type
+OAuth2SsoConfigurer (there is a convenient empty base class) will
+get 2 callbacks, one to set the request matchers for the OAuth2
+filter, and one with the full HttpSecurity builder (so you can set
+up all sorts of behaviour, but the main application is to control
+access rules).
The default login path, i.e. the one that triggers the redirect to the
+OAuth2 Authorization Server, is "/login". It will always be added to
+the matching patterns for the OAuth2 SSO, even if you have
+OAuth2SsoConfigurer beans as well. The default logout path is
+"/logout" and it gets similar treatment, as does the "home" page
+(which is the logout success page, defaults to "/"). Those paths can
+be overriden by setting spring.oauth2.sso.*' (`loginPath, logoutPath and
+home.path).
For example if you want the resources under "/ui/**" to be protected with OAuth2:
+@Configuration
+@EnableOAuth2Sso
+@EnableAutoConfiguration
+protected static class TestConfiguration extends OAuth2SsoConfigurerAdapter {
+ @Override
+ public void match(RequestMatchers matchers) {
+ matchers.antMatchers("/ui/**");
+ }
+}
+In this case the rest of the application will default to the normal +Spring Boot access control (Basic authentication, or whatever custom +filters you put in place).
+Integrating with the Actuator Endpoints
+The Spring Boot Actuator endpoints ("/env", "/metrics", etc.) if
+present will, by default, be protected by the standard Spring Boot
+basic authentication. The SSO authentication filter is added in a
+position directly behind the filter that intercepts requests to the
+Actuator endpoints by default (i.e.
+ManagementProperties.BASIC_AUTH_ORDER + 1 which is
+Ordered.LOWEST_PRECEDENCE-9 or 2147483636). If you want to change
+the order you can set spring.oauth2.sso.filterOrder. If you do that
+and the value is less than the default, then you will need to consider
+setting the access rules for the Actuator, since they will become
+accessible to all authenticated users who sign on with the external
+provider. One way to do that would be to set
+management.contextPath=/admin (for instance) and use an
+OAuth2SsoConfigurer to set the access rules, e.g.
@Configuration
+ @EnableOAuth2Sso
+ @EnableAutoConfiguration
+ protected static class TestConfiguration extends OAuth2SsoConfigurerAdapter {
+ @Override
+ public void configure(HttpSecurity http) {
+ http.authorizeRequests()
+ .antMatchers("/admin/**").role("ADMIN")
+ .anyRequest().authenticated();
+ }
+ }
+Resource Server
+The @EnableOAuth2Resource annotation will protect your API endpoints
+if you have the same environment settings as the SSO client, except
+that it doesn’t need a tokenUri or authorizationUri, and it also
+doesn’t need a clientId and clientSecret if it isn’t using the
+tokenInfoUri (i.e. if it has jwt.* or userInfoUri).
By default all your endpoints are protected (i.e. "/**") but you can
+pick and choose by adding a ResourceServerConfigurerAdapter (standard
+Spring OAuth feature), e.g. to protect only the "/api/**" resources
@RestController
+@EnableOAuth2Resource
+class Application extends ResourceServerConfigurerAdapter {
+
+ @Override
+ public void configure(HttpSecurity http) throws Exception {
+ http.requestMatchers()
+ .antMatchers("/api/**")
+ .and()
+ .authorizeRequests()
+ .anyRequest().authenticated();
+ }
+
+ @RequestMapping("/api")
+ public String home() {
+ return "Hello World";
+ }
+
+}
+Customizing the JWT Token Converter
+When a resource server accepts an access token as a JWT, it has to
+convert it to an Authentication so that Spring Security can do its
+access decisions. Different token providers might support JWT tokens
+with different contents, so Spring OAuth2 has an abstraction for
+converting the token into security domain objects
+(AccessTokenConverter). You can modify the default behaviour easily
+by providing a @Bean of type JwtAccessTokenConverterConfigurer,
+e.g.
@Component
+public class JwtCustomization extends DefaultAccessTokenConverter implements
+ JwtAccessTokenConverterConfigurer {
+
+ @Override
+ public void configure(JwtAccessTokenConverter converter) {
+ converter.setAccessTokenConverter(this);
+ }
+
+ ... // implement custom AccessTokenConverter here
+
+}
+Token Relay
+A Token Relay is where an OAuth2 consumer acts as a Client and +forwards the incoming token to outgoing resource requests. The +consumer can be a pure Client (like an SSO application) or a Resource +Server.
+Client Token Relay
+If your app has a
+Spring
+Cloud Zuul embedded reverse proxy (using @EnableZuulProxy) then you
+can ask it to forward OAuth2 access tokens downstream to the services
+it is proxying. Thus the SSO app above can be enhanced simply like this:
@Controller
+@EnableOAuth2Sso
+@EnableZuulProxy
+class Application {
+
+}
+and it will (in addition to loggin the user in and grabbing a token)
+pass the authentication token downstream to the /proxy/*
+services. If those services are implemented with
+@EnableOAuth2Resource then they will get a valid token in the
+correct header.
How does it work? The @EnableOAuth2Sso annotation pulls in
+spring-cloud-starter-security (which you could do manually in a
+traditional app), and that in turn triggers some autoconfiguration for
+a ZuulFilter, which itself is activated because Zuul is on the
+classpath (via @EnableZuulProxy). The
+filter
+just extracts an access token from the currently authenticated user,
+and puts it in a request header for the downstream requests.
Resource Server Token Relay
+If your app has @EnableOAuth2Resource and also is a Client (i.e. it
+has a spring.oauth2.client.clientId, even if it doesn’t use it),
+then the OAuth2RestOperations that is provided for @Autowired
+users by Spring Cloud (it is declared as @Primary) will also forward
+tokens. If you don’t want to forward tokens (and that is a valid
+choice, since you might want to act as yourself, rather than the
+client that sent you the token), then you only need to create your own
+OAuth2RestOperations instead of autowiring the default one. Here’s
+a basic example showing the use of the autowired rest template ("foo.com"
+is a Resource Server accepting the same tokens as the surrounding app):
@Autowired
+private OAuth2RestOperations restTemplate;
+
+@RequestMapping("/relay")
+public String relay() {
+ ResponseEntity<String> response =
+ restTemplate.getForEntity("https://foo.com/bar", String.class);
+ return "Success! (" + response.getBody() + ")";
+}
+Configuring Authentication Downstream of a Zuul Proxy
+You can control the authorization behaviour downstream of an
+@EnableZuulProxy through the proxy.auth.* settings. Example:
proxy:
+ auth:
+ routes:
+ customers: oauth2
+ stores: passthru
+ recommendations: none
+In this example the "customers" service gets an OAuth2 token relay, +the "stores" service gets a passthrough (the authorization header is +just passed downstream), and the "recommendations" service has its +authorization header removed. The default behaviour is to do a token +relay if there is a token available, and passthru otherwise.
+See + +ProxyAuthenticationProperties for full details.
+