123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- [[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 `getRootConfigClasses()`
- [[message-web-application-inititializer-java]]
- [source,java]
- ----
- public class MvcWebApplicationInitializer extends
- AbstractAnnotationConfigDispatcherServletInitializer {
- @Override
- protected Class<?>[] getRootConfigClasses() {
- return new Class[] { WebSecurityConfig.class };
- }
- // ... other overrides ...
- }
- ----
- [[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();
- }
- ----
|