Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreOn behalf of the community, I’m pleased to announce the release of Spring Security 5.0.0 M4. This release includes bug fixes, new features, and is based off of Spring Framework 5.0.0 RC4. You can find complete details in the changelog. The highlights of the release include:
There are a number of improvements to the HttpSecurity.oauth2Login()
DSL.
You can now configure the Token Endpoint with a custom implementation of an AuthorizationGrantTokenExchanger
or SecurityTokenRepository<AccessToken>
, as follows:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.tokenEndpoint()
.authorizationCodeTokenExchanger(this.authorizationCodeTokenExchanger())
.accessTokenRepository(this.accessTokenRepository());
}
We’ve also added the capability of customizing the request paths for the Authorization Endpoint and Redirection Endpoint:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.authorizationEndpoint()
.requestMatcher(new AntPathRequestMatcher("/custom-path/{clientAlias}"))
.and()
.redirectionEndpoint()
.requestMatcher(new AntPathRequestMatcher("/custom-path/callback/{clientAlias}"));
}
As with all AbstractAuthenticationProcessingFilter
's in Spring Security, you can also set a custom AuthenticationSuccessHandler
and AuthenticationFailureHandler
:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.successHandler(this.customAuthenticationSuccessHandler())
.failureHandler(this.customAuthenticationFailureHandler());
}
We’ve introduced the SecurityTokenRepository<T extends SecurityToken>
abstraction, which is responsible for the persistence of SecurityToken
's.
The initial implementation InMemoryAccessTokenRepository
provides the persistence of AccessToken
's.
In an upcoming release we’ll also provide an implementation that supports the persistence of Refresh Token’s.
A couple of minor improvements were introduced to the IdToken
along with some final implementation details for JwtClaimAccessor
,
StandardClaimAccessor
and IdTokenClaimAccessor
, which provide convenient access to claims
in their associated constructs, for example, Jwt
, IdToken
, UserInfo
.
We’ve added the capability for an AuthorizationRequestRepository
to persist the Authorization Request to a Cookie
.
The current default implementation persists in the HttpSession
, however, a custom implementation may be provided to persist to a Cookie
instead.
Support was also added for URI
variables configured in the redirect-uri
for the AuthorizationCodeRequestRedirectFilter
.
There were a couple of minor updates to the properties for configuring an OAuth 2.0 Client. The configuration below outlines the current structure. You will notice that there is support for configuring multiple clients, for example, google, github, okta, etc.
security:
oauth2:
client:
google:
client-id: your-app-client-id
client-secret: your-app-client-secret
client-authentication-method: basic
authorization-grant-type: authorization_code
redirect-uri: "{scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}"
scope: openid, profile, email, address, phone
authorization-uri: "https://accounts.google.com/o/oauth2/v2/auth"
token-uri: "https://www.googleapis.com/oauth2/v4/token"
user-info-uri: "https://www.googleapis.com/oauth2/v3/userinfo"
user-name-attribute-name: "sub"
jwk-set-uri: "https://www.googleapis.com/oauth2/v3/certs"
client-name: Google
client-alias: google
github:
...
okta:
...
A complete example for using the new Spring Security OAuth 2.0 / OpenID Connect 1.0 login feature can be found in the Spring Security samples at oauth2login. The guide will walk you through the steps for setting up the sample application for OAuth 2.0 login using an external OAuth 2.0 or OpenID Connect 1.0 Provider.
Spring Security’s Reactive support now includes method security by leveraging Reactor’s Context. The highlights are below, but you can find a complete example of it in action in samples/javaconfig/hellowebflux-method
The first step is to use @EnableReactiveMethodSecurity
to enable support for @PreAuthorize
and @PostAuthorize
annotations. This step ensures that the objects are properly proxied.
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
The next step is to create a service that is annotated with @PreAuthorize
or @PostAuthorize
. For example:
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {
Spring Security’s WebFlux support will then ensure that the Reactor Context will be populated with the current user which is used to determine if access is granted or denied.
Spring Security’s standard @WithMockUser
and related annotations has been updated to work with Reactive Method Security. For example:
@RunWith(SpringRunner.class)
// ...
public class HelloWorldMessageServiceTests {
@Autowired
HelloWorldMessageService messages;
@Test
public void messagesWhenNotAuthenticatedThenDenied() {
StepVerifier.create(this.messages.findMessage())
.expectError(AccessDeniedException.class)
.verify();
}
@Test
@WithMockUser
public void messagesWhenUserThenDenied() {
StepVerifier.create(this.messages.findMessage())
.expectError(AccessDeniedException.class)
.verify();
}
@Test
@WithMockUser(roles = "ADMIN")
public void messagesWhenAdminThenOk() {
StepVerifier.create(this.messages.findMessage())
.expectNext("Hello World!")
.verifyComplete();
}
}
The test support also works nicely with TestWebClient
. For example:
@RunWith(SpringRunner.class)
// ...
public class HelloWebfluxMethodApplicationTests {
@Autowired
ApplicationContext context;
WebTestClient rest;
@Before
public void setup() {
this.rest = WebTestClient
.bindToApplicationContext(this.context)
// Setup Spring Security Test Support
.apply(springSecurity())
.configureClient()
.filter(basicAuthentication())
.build();
}
@Test
public void messageWhenNotAuthenticated() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isUnauthorized();
}
// --- authenticate with HTTP Basic ---
@Test
public void messageWhenUserThenForbidden() throws Exception {
this.rest
.get()
.uri("/message")
.attributes(robsCredentials())
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN)
.expectBody().isEmpty();
}
@Test
public void messageWhenAdminThenOk() throws Exception {
this.rest
.get()
.uri("/message")
.attributes(adminCredentials())
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World!");
}
// --- Use @WithMockUser ---
@Test
@WithMockUser
public void messageWhenWithMockUserThenForbidden() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN)
.expectBody().isEmpty();
}
@Test
@WithMockUser(roles = "ADMIN")
public void messageWhenWithMockAdminThenOk() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World!");
}
// --- Use mutateWith ---
@Test
public void messageWhenMockUserThenForbidden() throws Exception {
this.rest
.mutateWith(mockUser())
.get()
.uri("/message")
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN)
.expectBody().isEmpty();
}
@Test
public void messageWhenMockAdminThenOk() throws Exception {
this.rest
.mutateWith(mockUser().roles("ADMIN"))
.get()
.uri("/message")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World!");
}
// ...
}
WebFlux security now supports form based log in and provides a default log in page to ease getting started. For example, the samples/javaconfig/hellowebflux[samples/javaconfig/hellowebflux allows users to authenticate using form based log in with a default log in page.
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapUserDetailsRepository userDetailsRepository() {
UserDetails user = User.withUsername("user")
.password("user")
.roles("USER")
.build();
return new MapUserDetailsRepository(user);
}
}
We decided to make the default log in page link to an external CSS file to make it look nicer without needing to bundle CSS. What do you think?
If you are not connected to the internet, the log in page falls back to an unstyled page. Of course, you can provide your own custom log in page as well.
Similar to the servlet world, we have also added content negotiation support for WebFlux security. For example, when requesting a protected resource without being authenticated our minimal example from WebFlux Form Log In will produce a log in page in a web browser and a WWW-Authenticate
response from a command line.
---
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
content-length: 0
We have refined the way WebFlux authentication and session management works to give greater flexability than the servlet counterpart. For example, our minimal example from WebFlux Form Log In will produce the following result when authenticating using form based log in:
POST /login HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 27
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: localhost:8080
username=user&password=user
HTTP/1.1 302 Found Cache-Control: no-cache, no-store, max-age=0, must-revalidate Expires: 0 Location: / Pragma: no-cache X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1 ; mode=block content-length: 0 set-cookie: SESSION=1e04aa3c-5a15-42ed-9e25-933fd0e44b2a; HTTPOnly
However, the very same code will produce the following response for HTTP Basic authentication
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Basic dXNlcjp1c2Vy
Connection: keep-alive
Host: localhost:8080
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Expires: 0
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
transfer-encoding: chunked
{
"message": "Hello user!"
}
Notice that the form based login has a SESSION cookie in the response, but HTTP Basic does not. This is done with a single HttpSecurity
configuration (we are splitting the application into slices).
If you have feedback on this release, I encourage you to reach out via StackOverflow, GitHub Issues, or via the comments section. You can also ping me @rob_winch or Joe @joe_grandja on Twitter.
Of course the best feedback comes in the form of contributions.
Project Site | Reference | Guides | Help