Skip to content

Commit 378e35f

Browse files
committed
Revert "Update to Spring Security 5.x"
This reverts commit 6f61c50.
1 parent 6f61c50 commit 378e35f

File tree

48 files changed

+2022
-703
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2022
-703
lines changed

README.adoc

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,34 @@ projects: [spring-security,spring-security-oauth,spring-boot]
1010
:star: {asterisk}
1111
:all: {asterisk}{asterisk}
1212

13-
= Social Login with Spring Boot and OAuth 2.0
13+
= Social Login with Spring Boot and OAuth2
1414

15-
This guide shows you how to build a sample app doing various things with "social login" using https://tools.ietf.org/html/rfc6749[OAuth 2.0] and https://projects.spring.io/spring-boot/[Spring Boot].
15+
This guide shows you how to build a sample app doing various things with "social login" using https://tools.ietf.org/html/rfc6749[OAuth2] and https://projects.spring.io/spring-boot/[Spring Boot].
16+
It starts with a simple, single-provider single-sign on, and works up to a self-hosted OAuth2 Authorization Server with a choice of authentication providers (https://developers.facebook.com[Facebook] or https://developer.github.com/[Github]).
17+
The samples are all single-page apps using Spring Boot and Spring OAuth on the back end.
18+
They also all use plain https://jquery.org/[jQuery] on the front end, but the changes needed to convert to a different JavaScript framework or to use server side rendering would be minimal.
1619

17-
It starts with a simple, single-provider single-sign on, and works up to a client with a choice of authentication providers:
18-
https://github.com/settings/developers[GitHub] or https://developers.google.com/identity/protocols/OpenIDConnect[Google].
19-
20-
The samples are all single-page apps using Spring Boot and Spring Security on the back end.
21-
They also all use plain https://jquery.org/[jQuery] on the front end.
22-
But, the changes needed to convert to a different JavaScript framework or to use server-side rendering would be minimal.
23-
24-
All samples are implemented using the native OAuth 2.0 support in https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2[Spring Boot].
20+
Because one of the samples is a full OAuth2 Authorization Server we have used the https://docs.spring.io/spring-security-oauth2-boot/docs/current/reference/htmlsingle/[shim JAR] which supports bridging from Spring Boot 2.0 to the old Spring Security OAuth2 library.
21+
The simpler samples could also be implemented using the native OAuth2 support in https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2[Spring Boot] security features.
22+
The configuration is very similar.
2523

2624
include::overview.adoc[]
2725

2826
include::simple/README.adoc[leveloffset=+1]
2927
include::click/README.adoc[leveloffset=+1]
3028
include::logout/README.adoc[leveloffset=+1]
31-
include::two-providers/README.adoc[leveloffset=+1]
29+
include::manual/README.adoc[leveloffset=+1]
30+
include::github/README.adoc[leveloffset=+1]
31+
include::auth-server/README.adoc[leveloffset=+1]
3232
include::custom-error/README.adoc[leveloffset=+1]
3333

3434
== Conclusion
3535

3636
We have seen how to use Spring Boot and Spring Security to build apps in a number of styles with very little effort.
37-
The main theme running through all of the samples is authentication using an external OAuth 2.0 provider.
38-
37+
The main theme running through all of the samples is "social" login using an external OAuth2 provider.
38+
The final sample could even be used to provide such a service "internally" because it has the same basic features that the external providers have.
3939
All of the sample apps can be easily extended and re-configured for more specific use cases, usually with nothing more than a configuration file change.
40-
Remember if you use versions of the samples in your own servers to register with GitHub (or similar) and get client credentials for your own host addresses.
40+
Remember if you use versions of the samples in your own servers to register with Facebook or Github (or similar) and get client credentials for your own host addresses.
4141
And remember not to put those credentials in source control!
4242

4343
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/footer.adoc[]

auth-server/README.adoc

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
:star: {asterisk}
2+
:all: {asterisk}{asterisk}
3+
4+
[[_social_login_authserver]]
5+
= Hosting an Authorization Server
6+
7+
In this section we modify the <<_social_login_github,github>> app we built by making the app into a fully-fledged OAuth2 Authorization Server, still using Facebook and Github for authentication, but able to create its own access tokens. These tokens could then be used to secure back end resources, or to do SSO with other applications that we happen to need to secure the same way.
8+
9+
== Tidying up the Authentication Configuration
10+
11+
Before we start with the Authorization Server features, we are going to just tidy up the configuration code for the two external providers.
12+
There is some code that is duplicated in the `ssoFilter()` method, so we pull that out into a shared method:
13+
14+
.SocialApplication.java
15+
[source,java]
16+
----
17+
private Filter ssoFilter() {
18+
CompositeFilter filter = new CompositeFilter();
19+
List<Filter> filters = new ArrayList<>();
20+
filters.add(ssoFilter(facebook(), "/login/facebook"));
21+
filters.add(ssoFilter(github(), "/login/github"));
22+
filter.setFilters(filters);
23+
return filter;
24+
}
25+
----
26+
27+
The new convenience method has all the duplicated code from the old method:
28+
29+
.SocialApplication.java
30+
[source,java]
31+
----
32+
private Filter ssoFilter(ClientResources client, String path) {
33+
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
34+
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
35+
filter.setRestTemplate(template);
36+
UserInfoTokenServices tokenServices = new UserInfoTokenServices(
37+
client.getResource().getUserInfoUri(), client.getClient().getClientId());
38+
tokenServices.setRestTemplate(template);
39+
filter.setTokenServices(tokenServices);
40+
return filter;
41+
}
42+
----
43+
44+
and it uses a new wrapper object `ClientResources` that consolidates the `OAuth2ProtectedResourceDetails` and the `ResourceServerProperties` that were declared as separate `@Beans` in the last version of the app:
45+
46+
.SocialApplication.java
47+
[source,java]
48+
----
49+
class ClientResources {
50+
51+
@NestedConfigurationProperty
52+
private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
53+
54+
@NestedConfigurationProperty
55+
private ResourceServerProperties resource = new ResourceServerProperties();
56+
57+
public AuthorizationCodeResourceDetails getClient() {
58+
return client;
59+
}
60+
61+
public ResourceServerProperties getResource() {
62+
return resource;
63+
}
64+
}
65+
----
66+
67+
NOTE: the wrapper uses `@NestedConfigurationProperty` to instructs the annotation processor to crawl that type for meta-data as well since it does not represents a single value but a complete nested type.
68+
69+
With this wrapper in place we can use the same YAML configuration as before, but a single method for each provider:
70+
71+
.SocialApplication.java
72+
[source,java]
73+
----
74+
@Bean
75+
@ConfigurationProperties("github")
76+
public ClientResources github() {
77+
return new ClientResources();
78+
}
79+
80+
@Bean
81+
@ConfigurationProperties("facebook")
82+
public ClientResources facebook() {
83+
return new ClientResources();
84+
}
85+
----
86+
87+
== Enabling the Authorization Server
88+
89+
If we want to turn our application into an OAuth2 Authorization Server, there isn't a lot of fuss and ceremony, at least to get started with some basic features (one client and the ability to create access tokens).
90+
An Authorization Server is nothing more than a bunch of endpoints, and they are implemented in Spring OAuth2 as Spring MVC handlers.
91+
We already have a secure application, so it's really just a matter of adding the `@EnableAuthorizationServer` annotation:
92+
93+
.SocialApplication.java
94+
[source,java]
95+
----
96+
@SpringBootApplication
97+
@RestController
98+
@EnableOAuth2Client
99+
@EnableAuthorizationServer
100+
public class SocialApplication extends WebSecurityConfigurerAdapter {
101+
102+
...
103+
104+
}
105+
----
106+
107+
with that new annotation in place Spring Boot will install all the necessary endpoints and set up the security for them, provided we supply a few details of an OAuth2 client we want to support:
108+
109+
.application.yml
110+
[source,yaml]
111+
----
112+
security:
113+
oauth2:
114+
client:
115+
client-id: acme
116+
client-secret: acmesecret
117+
scope: read,write
118+
auto-approve-scopes: '.*'
119+
----
120+
121+
This client is the equivalent of the `facebook.client{star}` and `github.client{star}` that we need for the external authentication.
122+
With the external providers we had to register and get a client ID and a secret to use in our app.
123+
In this case we are providing our own equivalent of the same feature, so we need (at least one) client for it to work.
124+
125+
NOTE: We have set the `auto-approve-scopes` to a regex matching all scopes.
126+
This is not necessarily where we would leave this app in a real system, but it gets us something working quickly without having to replace the whitelabel approval page that Spring OAuth2 would otherwise pop up for our users when they wanted an access token.
127+
To add an explicit approval step to the token grant we would need to provide a UI replacing the whitelabel version (at `/oauth/confirm_access`).
128+
129+
To finish the Authorization Server we just need to provide security configuration for its UI.
130+
In fact there isn't much of a user interface in this simple app, but we still need to protect the `/oauth/authorize` endpoint, and make sure that the home page with the "Login" buttons is visible.
131+
That's why we have this method:
132+
133+
```java
134+
@Override
135+
protected void configure(HttpSecurity http) throws Exception {
136+
http.antMatcher("/**") // <1>
137+
.authorizeRequests()
138+
.antMatchers("/", "/login**", "/webjars/**").permitAll() // <2>
139+
.anyRequest().authenticated() // <3>
140+
.and().exceptionHandling()
141+
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")) // <4>
142+
...
143+
}
144+
```
145+
<1> All requests are protected by default
146+
<2> The home page and login endpoints are explicitly excluded
147+
<3> All other endpoints require an authenticated user
148+
<4> Unauthenticated users are re-directed to the home page
149+
150+
== How to Get an Access Token
151+
152+
Access tokens are now available from our new Authorization Server.
153+
The simplest way to get a token up to now is to grab one as the "acme" client.
154+
You can see this if you run the app and curl it:
155+
156+
```
157+
$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=client_credentials
158+
{"access_token":"370592fd-b9f8-452d-816a-4fd5c6b4b8a6","token_type":"bearer","expires_in":43199,"scope":"read write"}
159+
```
160+
161+
Client credentials tokens are useful in some circumstances (like testing that the token endpoint works), but to take advantage of all the features of our server we want to be able to create tokens for users.
162+
To get a token on behalf of a user of our app we need to be able to authenticate the user.
163+
If you were watching the logs carefully when the app started up you would have seen a random password being logged for the default Spring Boot user (per the https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security[Spring Boot User Guide]).
164+
You can use this password to get a token on behalf of the user with id "user":
165+
166+
```
167+
$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=...
168+
{"access_token":"aa49e025-c4fe-4892-86af-15af2e6b72a2","token_type":"bearer","refresh_token":"97a9f978-7aad-4af7-9329-78ff2ce9962d","expires_in":43199,"scope":"read write"}
169+
```
170+
171+
where "..." should be replaced with the actual password.
172+
This is called a "password" grant, where you exchange a username and password for an access token.
173+
174+
Password grant is also mainly useful for testing, but can be appropriate for a native or mobile application, when you have a local user database to store and validate the credentials.
175+
For most apps, or any app with "social" login, like ours, you need the "authorization code" grant, and that means you need a browser (or a client that behaves like a browser) to handle redirects and cookies, and render the user interfaces from the external providers.
176+
177+
== Creating a Client Application
178+
179+
A client application for our Authorization Server that is itself a web application is easy to create with Spring Boot. Here's an example:
180+
181+
.ClientApplication.java
182+
[source,java]
183+
----
184+
@EnableAutoConfiguration
185+
@Configuration
186+
@EnableOAuth2Sso
187+
@RestController
188+
public class ClientApplication {
189+
190+
@RequestMapping("/")
191+
public String home(Principal user) {
192+
return "Hello " + user.getName();
193+
}
194+
195+
public static void main(String[] args) {
196+
new SpringApplicationBuilder(ClientApplication.class)
197+
.properties("spring.config.name=client").run(args);
198+
}
199+
200+
}
201+
----
202+
203+
NOTE: The `ClientApplication` class MUST NOT be created in the same package (or a sub-package) of the `SocialApplication` class.
204+
Otherwise, Spring will load some `ClientApplication` auto-configurations while starting the `SocialApplication` server, resulting in startup errors.
205+
206+
The ingredients of the client are a home page (just prints the user's name), and an explicit name for a configuration file (via `spring.config.name=client`).
207+
When we run this app it will look for a configuration file which we provide as follows:
208+
209+
.client.yml
210+
[source,yaml]
211+
----
212+
server:
213+
port: 9999
214+
context-path: /client
215+
security:
216+
oauth2:
217+
client:
218+
client-id: acme
219+
client-secret: acmesecret
220+
access-token-uri: http://localhost:8080/oauth/token
221+
user-authorization-uri: http://localhost:8080/oauth/authorize
222+
resource:
223+
user-info-uri: http://localhost:8080/me
224+
----
225+
226+
The configuration looks a lot like the values we used in the main app, but with the "acme" client instead of the Facebook or Github ones.
227+
The app will run on port 9999 to avoid conflicts with the main app.
228+
And it refers to a user info endpoint "/me" which we haven't implemented yet.
229+
230+
Note that the `server.context-path` is set explicitly, so if you run the app to test it remember the home page is http://localhost:9999/client.
231+
Clicking on that link should take you to the auth server and once you you have authenticated with the social provider of your choice you will be redirected back to the client app.
232+
233+
NOTE: The context path has to be explicit if you are running both the client and the auth server on localhost, otherwise the cookie paths clash and the two apps cannot agree on a session identifier.
234+
235+
== Protecting the User Info Endpoint
236+
237+
To use our new Authorization Server for single sign on, just like we have been using Facebook and Github, it needs to have a `/user` endpoint that is protected by the access tokens it creates.
238+
So far we have a `/user` endpoint, and it is secured with cookies created when the user authenticates.
239+
To secure it in addition with the access tokens granted locally we can just re-use the existing endpoint and make an alias to it on a new path:
240+
241+
.SocialApplication.java
242+
[source,java]
243+
----
244+
@RequestMapping({ "/user", "/me" })
245+
public Map<String, String> user(Principal principal) {
246+
Map<String, String> map = new LinkedHashMap<>();
247+
map.put("name", principal.getName());
248+
return map;
249+
}
250+
----
251+
252+
NOTE: We have converted the `Principal` into a `Map` so as to hide the parts that we don't want to expose to the browser, and also to unify the behaviour of the endpoint between the two external authentication providers.
253+
In principle we could add more detail here, like a provider-specific unique identifier for instance, or an e-mail address if it's available.
254+
255+
The "/me" path can now be protected with the access token by declaring that our app is a Resource Server (as well as an Authorization Server).
256+
We create a new configuration class (as an inner class in the main app, but it could also be split out into a separate standalone class):
257+
258+
.SocialApplication.java
259+
[source,java]
260+
----
261+
@Configuration
262+
@EnableResourceServer
263+
protected static class ResourceServerConfiguration
264+
extends ResourceServerConfigurerAdapter {
265+
@Override
266+
public void configure(HttpSecurity http) throws Exception {
267+
http
268+
.antMatcher("/me")
269+
.authorizeRequests().anyRequest().authenticated();
270+
}
271+
}
272+
----
273+
274+
In addition we need to specify an `@Order` for the main application security:
275+
276+
.SocialApplication.java
277+
[source,java]
278+
----
279+
@SpringBootApplication
280+
...
281+
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
282+
public class SocialApplication extends WebSecurityConfigurerAdapter {
283+
...
284+
}
285+
----
286+
287+
The `@EnableResourceServer` annotation creates a security filter with `@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER-1)` by default, so by moving the main application security to `@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)` we ensure that the rule for "/me" takes precedence.
288+
289+
== Testing the OAuth2 Client
290+
291+
To test the new features you can just run both apps and visit http://localhost:9999/client in your browser.
292+
The client app will redirect to the local Authorization Server, which then gives the user the usual choice of authentication with Facebook or Github.
293+
Once that is complete control returns to the test client, the local access token is granted and authentication is complete (you should see a "Hello" message in your browser).
294+
If you are already authenticated with Github or Facebook you may not even notice the remote authentication.

0 commit comments

Comments
 (0)