Code for "Spring Security: The Good Parts"
We create a /public
and /private
page. No security is applied.
The /private
page has some tricks to show a logout button ... if some _csrf
token is available.
It's not the case yet, it will be injected by Spring Security later on. When you think about it, it
makes sense: you can't "log in", so you shouldn't be able to "log out" either.
We introduce Spring Security. We create a SecurityConfig
configuration class, where we will store
all of our security-related configuration customizations.
The first customization is to create a custom SecurityFilterChain, to override the defaults that
Spring Boot give us. We make sure that every request must be authenticated, except the few things
that we deem "public", such as /
(public page), the favicon, the css resources, etc. We also
introduce .formLogin()
to ensure form login is enabled for us.
We also introduce our own UserDetailsService, because we don't want to have to copy the random password that Boot generates every time we boot up the app. It's an in-memory implementation, for demo purposes.
Finally, we leverage the Authentication
object that Spring Security creates for us to display the
name of the logged-in user.
We add the dependencies and the code to make SSO login work. It requires adding some properties, and just enabling "oauth2 login" in our previous security configuration. Everything stays the same.
We want a nicer "authentication name" when logged in with Google, rather than the ID we get back, so we tweak our GreetingController to display the e-mail when doing SSO login.
We create our first Filter, the ForbiddenFilter
. It sets the response code to 403 (Forbidden), with
an error message, when the x-forbidden: true
header is present. Otherwise it's a no-op.
We register it in our filter chain. We can register before any filter we know. It is usally
recommended to register it at least before AuthorizationFilter
, which is the filter that does the
checks for http.authorizeHttpRequests
.
You can see the results, e.g. using HTTPie or cURL:
$ curl localhost:8080 -H "x-forbidden: true" -v
$ # or
$ http localhost:8080 x-forbidden:true
We create a filter that authenticates the user. In this case, it's a "robot account" that sends us a
secret password in the X-Robot-Password
header.
If the header is present, we make sure the password is correct. If the password is incorrect, we
reject the request. If the password is fine, we perform the actual authentication by creating an
instance of an appropriate implementation of the Authentication
interface.
If the header is absent, we let the rest of the filter chain decide what to do.
See:
$ curl localhost:8080/private -H "x-robot-password: beep-boop"
$ # or
$ http localhost:8080/private x-robot-password:beep-boop
We create an AuthenticationProvider that will let user "daniel" in, no matter the password.
We also turn on HTTP Basic authentication - and we don't have to do anything to grant the same rights to "daniel".
See:
$ curl localhost:8080/private -u "daniel:foobar"
$ # or
$ http localhost:8080/private --auth "daniel:barfoo"
This leverages the ProviderManager
, which, for examples, produces Spring events when a login
succeeds or fails. We listen to AuthenticationSuccessEvents
and do a System.out.println with the
Authentication class and the Authentication name. Notice how RobotAuthentications are not logged,
as they happen directly in a filter, without using an AuthenticationManager.
We make sure the RobotAuthenticationFilter actually uses an Authentication Manager, like the rest of Spring Security. To do this, we need to create an "unauthenticated" version of the RobotAuthentication. We also need an AuthenticationProvider to handle those RobotAuthentication.
To get an AuthenticationManager, we access the Filter chain's local AuthenticationManager. See the Spring Security without the WebSecurityConfigurerAdapter blog post, which talks about how to access that class. We create a bespoke AbstractHttpConfigurer and put all Robot-related configuration in the class, so we can get the authentication manager.
Most Authentication
filters in Spring Security can be written as an AuthenticationFilter
sublclass. It follows a simple "composition" pattern, where you declard an
AuthenticationConverter
, an AuthenticationManager
, and both success and failure handlers. The
wiring is then done by the base class, which does:
- HttpRequest -> [AuthenticationConverter] -> Authentication
- Authentication -> [AuthenticationManager] -> Authentication in the SecurityContext (if succesful)
- Authentication(Exception) -> [Authentication(Success|Failure)Handler]