You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
15 KiB
368 lines
15 KiB
|
|
[[jc]] |
|
= Java Configuration |
|
|
|
General support for https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-java[Java Configuration] was added to Spring Framework in Spring 3.1. |
|
Since Spring Security 3.2 there has been Spring Security Java Configuration support which enables users to easily configure Spring Security without the use of any XML. |
|
|
|
If you are familiar with the xref:servlet/configuration/xml-namespace.adoc#ns-config[Security Namespace Configuration] then you should find quite a few similarities between it and the Security Java Configuration support. |
|
|
|
NOTE: Spring Security provides https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration[lots of sample applications] which demonstrate the use of Spring Security Java Configuration. |
|
|
|
== Hello Web Security Java Configuration |
|
|
|
The first step is to create our Spring Security Java Configuration. |
|
The configuration creates a Servlet Filter known as the `springSecurityFilterChain` which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, etc) within your application. |
|
You can find the most basic example of a Spring Security Java Configuration below: |
|
|
|
[[jc-hello-wsca]] |
|
[source,java] |
|
---- |
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.context.annotation.*; |
|
import org.springframework.security.config.annotation.authentication.builders.*; |
|
import org.springframework.security.config.annotation.web.configuration.*; |
|
|
|
@EnableWebSecurity |
|
public class WebSecurityConfig { |
|
|
|
@Bean |
|
public UserDetailsService userDetailsService() { |
|
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); |
|
manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build()); |
|
return manager; |
|
} |
|
} |
|
---- |
|
|
|
There really isn't much to this configuration, but it does a lot. |
|
You can find a summary of the features below: |
|
|
|
* Require authentication to every URL in your application |
|
* Generate a login form for you |
|
* Allow the user with the *Username* _user_ and the *Password* _password_ to authenticate with form based authentication |
|
* Allow the user to logout |
|
* https://en.wikipedia.org/wiki/Cross-site_request_forgery[CSRF attack] prevention |
|
* https://en.wikipedia.org/wiki/Session_fixation[Session Fixation] protection |
|
* Security Header integration |
|
** https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security[HTTP Strict Transport Security] for secure requests |
|
** https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx[X-Content-Type-Options] integration |
|
** Cache Control (can be overridden later by your application to allow caching of your static resources) |
|
** https://msdn.microsoft.com/en-us/library/dd565647(v=vs.85).aspx[X-XSS-Protection] integration |
|
** X-Frame-Options integration to help prevent https://en.wikipedia.org/wiki/Clickjacking[Clickjacking] |
|
* Integrate with the following Servlet API methods |
|
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser()[HttpServletRequest#getRemoteUser()] |
|
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()[HttpServletRequest#getUserPrincipal()] |
|
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String)[HttpServletRequest#isUserInRole(java.lang.String)] |
|
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login(java.lang.String,%20java.lang.String)[HttpServletRequest#login(java.lang.String, java.lang.String)] |
|
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#logout()[HttpServletRequest#logout()] |
|
|
|
=== AbstractSecurityWebApplicationInitializer |
|
|
|
The next step is to register the `springSecurityFilterChain` with the war. |
|
This can be done in Java Configuration with https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-container-config[Spring's WebApplicationInitializer support] in a Servlet 3.0+ environment. |
|
Not suprisingly, Spring Security provides a base class `AbstractSecurityWebApplicationInitializer` that will ensure the `springSecurityFilterChain` gets registered for you. |
|
The way in which we use `AbstractSecurityWebApplicationInitializer` differs depending on if we are already using Spring or if Spring Security is the only Spring component in our application. |
|
|
|
* <<abstractsecuritywebapplicationinitializer-without-existing-spring>> - Use these instructions if you are not using Spring already |
|
* <<abstractsecuritywebapplicationinitializer-with-spring-mvc>> - Use these instructions if you are already using Spring |
|
|
|
[[abstractsecuritywebapplicationinitializer-without-existing-spring]] |
|
=== AbstractSecurityWebApplicationInitializer without Existing Spring |
|
|
|
If you are not using Spring or Spring MVC, you will need to pass in the `WebSecurityConfig` into the superclass to ensure the configuration is picked up. |
|
You can find an example below: |
|
|
|
[source,java] |
|
---- |
|
import org.springframework.security.web.context.*; |
|
|
|
public class SecurityWebApplicationInitializer |
|
extends AbstractSecurityWebApplicationInitializer { |
|
|
|
public SecurityWebApplicationInitializer() { |
|
super(WebSecurityConfig.class); |
|
} |
|
} |
|
---- |
|
|
|
The `SecurityWebApplicationInitializer` will do the following things: |
|
|
|
* Automatically register the springSecurityFilterChain Filter for every URL in your application |
|
* Add a ContextLoaderListener that loads the <<jc-hello-wsca,WebSecurityConfig>>. |
|
|
|
[[abstractsecuritywebapplicationinitializer-with-spring-mvc]] |
|
=== AbstractSecurityWebApplicationInitializer with Spring MVC |
|
|
|
If we were using Spring elsewhere in our application we probably already had a `WebApplicationInitializer` that is loading our Spring Configuration. |
|
If we use the previous configuration we would get an error. |
|
Instead, we should register Spring Security with the existing `ApplicationContext`. |
|
For example, if we were using Spring MVC our `SecurityWebApplicationInitializer` would look something like the following: |
|
|
|
[source,java] |
|
---- |
|
import org.springframework.security.web.context.*; |
|
|
|
public class SecurityWebApplicationInitializer |
|
extends AbstractSecurityWebApplicationInitializer { |
|
|
|
} |
|
---- |
|
|
|
This would simply only register the springSecurityFilterChain Filter for every URL in your application. |
|
After that we would ensure that `WebSecurityConfig` was loaded in our existing ApplicationInitializer. |
|
For example, if we were using Spring MVC it would be added in the `getServletConfigClasses()` |
|
|
|
[[message-web-application-inititializer-java]] |
|
[source,java] |
|
---- |
|
public class MvcWebApplicationInitializer extends |
|
AbstractAnnotationConfigDispatcherServletInitializer { |
|
|
|
@Override |
|
protected Class<?>[] getServletConfigClasses() { |
|
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class }; |
|
} |
|
|
|
// ... other overrides ... |
|
} |
|
---- |
|
|
|
The reason for this is that Spring Security needs to be able to inspect some Spring MVC configuration in order to appropriately configure xref:servlet/authorization/authorize-http-requests.adoc#_request_matchers[underlying request matchers], so they need to be in the same application context. |
|
Placing Spring Security in `getRootConfigClasses` places it into a parent application context that may not be able to find Spring MVC's `HandlerMappingIntrospector`. |
|
|
|
==== Configuring for Multiple Spring MVC Dispatchers |
|
|
|
If desired, any Spring Security configuration that is unrelated to Spring MVC may be placed in a different configuration class like so: |
|
|
|
[source,java] |
|
---- |
|
public class MvcWebApplicationInitializer extends |
|
AbstractAnnotationConfigDispatcherServletInitializer { |
|
|
|
@Override |
|
protected Class<?>[] getRootConfigClasses() { |
|
return new Class[] { NonWebSecurityConfig.class }; |
|
} |
|
|
|
@Override |
|
protected Class<?>[] getServletConfigClasses() { |
|
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class }; |
|
} |
|
|
|
// ... other overrides ... |
|
} |
|
---- |
|
|
|
This can be helpful if you have multiple instances of `AbstractAnnotationConfigDispatcherServletInitializer` and don't want to duplicate the general security configuration across both of them. |
|
|
|
[[jc-httpsecurity]] |
|
== HttpSecurity |
|
|
|
Thus far our <<jc-hello-wsca,WebSecurityConfig>> only contains information about how to authenticate our users. |
|
How does Spring Security know that we want to require all users to be authenticated? |
|
How does Spring Security know we want to support form based authentication? |
|
Actually, there is a bean that is being invoked behind the scenes called `SecurityFilterChain`. |
|
It is configured with the following default implementation: |
|
|
|
[source,java] |
|
---- |
|
@Bean |
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
http |
|
.authorizeRequests(authorize -> authorize |
|
.anyRequest().authenticated() |
|
) |
|
.formLogin(withDefaults()) |
|
.httpBasic(withDefaults()); |
|
return http.build(); |
|
} |
|
---- |
|
|
|
The default configuration above: |
|
|
|
* Ensures that any request to our application requires the user to be authenticated |
|
* Allows users to authenticate with form based login |
|
* Allows users to authenticate with HTTP Basic authentication |
|
|
|
You will notice that this configuration is quite similar the XML Namespace configuration: |
|
|
|
[source,xml] |
|
---- |
|
<http> |
|
<intercept-url pattern="/**" access="authenticated"/> |
|
<form-login /> |
|
<http-basic /> |
|
</http> |
|
---- |
|
|
|
== Multiple HttpSecurity |
|
|
|
We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks. |
|
The key is to register multiple `SecurityFilterChain` ``@Bean``s. |
|
For example, the following is an example of having a different configuration for URL's that start with `/api/`. |
|
|
|
[source,java] |
|
---- |
|
@EnableWebSecurity |
|
public class MultiHttpSecurityConfig { |
|
@Bean <1> |
|
public UserDetailsService userDetailsService() throws Exception { |
|
// ensure the passwords are encoded properly |
|
UserBuilder users = User.withDefaultPasswordEncoder(); |
|
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); |
|
manager.createUser(users.username("user").password("password").roles("USER").build()); |
|
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build()); |
|
return manager; |
|
} |
|
|
|
@Bean |
|
@Order(1) <2> |
|
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { |
|
http |
|
.antMatcher("/api/**") <3> |
|
.authorizeHttpRequests(authorize -> authorize |
|
.anyRequest().hasRole("ADMIN") |
|
) |
|
.httpBasic(withDefaults()); |
|
return http.build(); |
|
} |
|
|
|
@Bean <4> |
|
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception { |
|
http |
|
.authorizeHttpRequests(authorize -> authorize |
|
.anyRequest().authenticated() |
|
) |
|
.formLogin(withDefaults()); |
|
return http.build(); |
|
} |
|
} |
|
---- |
|
|
|
<1> Configure Authentication as normal |
|
<2> Register an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first. |
|
<3> The `http.antMatcher` states that this `HttpSecurity` will only be applicable to URLs that start with `/api/` |
|
<4> Register another instance of `SecurityFilterChain`. |
|
If the URL does not start with `/api/` this configuration will be used. |
|
This configuration is considered after `apiFilterChain` since it has an `@Order` value after `1` (no `@Order` defaults to last). |
|
|
|
[[jc-custom-dsls]] |
|
== Custom DSLs |
|
|
|
You can provide your own custom DSLs in Spring Security. |
|
For example, you might have something that looks like this: |
|
|
|
[source,java] |
|
---- |
|
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> { |
|
private boolean flag; |
|
|
|
@Override |
|
public void init(HttpSecurity http) throws Exception { |
|
// any method that adds another configurer |
|
// must be done in the init method |
|
http.csrf().disable(); |
|
} |
|
|
|
@Override |
|
public void configure(HttpSecurity http) throws Exception { |
|
ApplicationContext context = http.getSharedObject(ApplicationContext.class); |
|
|
|
// here we lookup from the ApplicationContext. You can also just create a new instance. |
|
MyFilter myFilter = context.getBean(MyFilter.class); |
|
myFilter.setFlag(flag); |
|
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class); |
|
} |
|
|
|
public MyCustomDsl flag(boolean value) { |
|
this.flag = value; |
|
return this; |
|
} |
|
|
|
public static MyCustomDsl customDsl() { |
|
return new MyCustomDsl(); |
|
} |
|
} |
|
---- |
|
|
|
NOTE: This is actually how methods like `HttpSecurity.authorizeRequests()` are implemented. |
|
|
|
The custom DSL can then be used like this: |
|
|
|
[source,java] |
|
---- |
|
@EnableWebSecurity |
|
public class Config { |
|
@Bean |
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
http |
|
.apply(customDsl()) |
|
.flag(true) |
|
.and() |
|
...; |
|
return http.build(); |
|
} |
|
} |
|
---- |
|
|
|
The code is invoked in the following order: |
|
|
|
* Code in `Config`s configure method is invoked |
|
* Code in `MyCustomDsl`s init method is invoked |
|
* Code in `MyCustomDsl`s configure method is invoked |
|
|
|
If you want, you can add `MyCustomDsl` to `HttpSecurity` by default by using `SpringFactories`. |
|
For example, you would create a resource on the classpath named `META-INF/spring.factories` with the following contents: |
|
|
|
.META-INF/spring.factories |
|
---- |
|
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl |
|
---- |
|
|
|
Users wishing to disable the default can do so explicitly. |
|
|
|
[source,java] |
|
---- |
|
@EnableWebSecurity |
|
public class Config { |
|
@Bean |
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
http |
|
.apply(customDsl()).disable() |
|
...; |
|
return http.build(); |
|
} |
|
} |
|
---- |
|
|
|
[[post-processing-configured-objects]] |
|
== Post Processing Configured Objects |
|
|
|
Spring Security's Java Configuration does not expose every property of every object that it configures. |
|
This simplifies the configuration for a majority of users. |
|
Afterall, if every property was exposed, users could use standard bean configuration. |
|
|
|
While there are good reasons to not directly expose every property, users may still need more advanced configuration options. |
|
To address this Spring Security introduces the concept of an `ObjectPostProcessor` which can be used to modify or replace many of the Object instances created by the Java Configuration. |
|
For example, if you wanted to configure the `filterSecurityPublishAuthorizationSuccess` property on `FilterSecurityInterceptor` you could use the following: |
|
|
|
[source,java] |
|
---- |
|
@Bean |
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
http |
|
.authorizeRequests(authorize -> authorize |
|
.anyRequest().authenticated() |
|
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { |
|
public <O extends FilterSecurityInterceptor> O postProcess( |
|
O fsi) { |
|
fsi.setPublishAuthorizationSuccess(true); |
|
return fsi; |
|
} |
|
}) |
|
); |
|
return http.build(); |
|
} |
|
----
|
|
|