Lab M02P01
The Spring Security framework provides authentication and access control to the Spring based Java applications.
-
There is simple REST API provided by the application. Use postman to call API operation http://localhost:8080/library/books. It should return list of book details.
-
To secure existing REST API, you need to add Spring boot Security starter to project dependencies. Open the build.gradle script and add
implementation('org.springframework.boot:spring-boot-starter-security')into dependencies section. Now, restart the application and try to call http://localhost:8080/library/books again. The Spring security auto-configuration protects all HTTP endpoints with HTTP basic Authorization by default, so the HTTP call should fail with HTTP status 401:Unauthorized. Spring security configures default user named user and generates random password during startup. Check the application console for message: Using generated security password: Now add the HTTP Basic Authorization header to request defined in Postman (use Authorization Tab) and provide user as Username and generated password from console as Password. Check if http://localhost:8080/library/books returns expected data. If you open link http://localhost:8080/library/books in web browser, the Spring redirects you to default login form. -
The default security configuration is good for quick prototyping, but not very usable in production. In order to tweak Spring security, you need to add some configuration. There is the SecurityConfig class in configuration package. You can configure the SecurityFilterChain bean and its details. The HttpSecurity class provides fluent API for security configuration. The task is to protect only /library context with HTTP basic Authorization (Application exposes also http://localhost:8080/hello endpoint which we won't protect). Modify the SecurityConfig as following:
- Add @Configuration annotation
- Add @EnableWebSecurity annotation
- Implement factory method for SecurityFilterChain bean: securityFilterChain(HttpSecurity http)
Hint:
Note: Above is the example of lambda style Spring Security configuration. From verzion 7 on, this will be the only way how to configure Spring Security.@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authz) -> authz .requestMatchers("/library/**").authenticated() .anyRequest().permitAll() ).httpBasic(withDefaults()); return http.build(); } }Now test the REST API. The /hello/ should not require Authorization. The /library must require Authorization.
-
Now, try simple RBAC (Role Based) Access Control. Users' credentials and their roles are typically stored in database. The Spring Security enables to define in memory User storage. You can use it to implement and test various users and roles and to protect Java class methods from unauthorized access. Open the SecurityConfig class and add @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) annotation to enable security Annotations, which can be used to protect Java class methods.
- prePostEnabled = true : Enables @PreAuthorize annotation
- securedEnabled = true : Enables @Secured annotation
Now add the UserDetailsService Bean which configures User details and roles. Spring security provides the InMemoryUserDetailsManager class which implements the UserDetailsService interface. Configure two users:
- user with password user and role USER
- admin with password admin and roles USER and ADMIN
Hint:
@Bean public InMemoryUserDetailsManager inMemoryUserDetailsManager() throws Exception { return new InMemoryUserDetailsManager( User.withUsername("admin") .password("{noop}admin") .roles("ADMIN", "USER" ).build(), User.withUsername("user") .password("{noop}user") .roles("USER").build()); } -
In order to protect Java class method use the @PreAuthorize, or the @Secured annotations. You can protect Rest Controller class or Service class. Let's modify the LibraryController class and:
- Grant access only user with ADMIN role to access getAllBooks() method
- Grant access only user with any role (__USER_ or ADMIN) to access getBookById method
Use both @PreAuthorize and @Secured annotations.
Hint:
Now, restart the application and check, if security works as expected.@Secured("ROLE_ADMIN") public List<BookDTO> getAllBooks() {...} @PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')") public BookDTO getBookById(...){...} -
Spring provides also test support. You can use well known MockMvc with security setup. Add spring security test support module into build.gradle:
testImplementation 'org.springframework.security:spring-security-test'There is SecurityTest class prepared for you. First setup MockMvc bean with security.
Then add test to get books via /library/books API operation:@BeforeEach public void setup() { mvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .apply(springSecurity()) .build(); }7. Bonus: Spring Security is able to fire various authorization events, so application can observe auth failures for instance. Implement event listener on AuthorizationDeniedEvent in application. Use https://docs.spring.io/spring-security/reference/servlet/authorization/events.html as reference.@Test @WithUserDetails("admin") void allBooksAuthentication() throws Exception { this.mvc.perform(get("/library/books")) .andExpect(status().is2xxSuccessful()).andReturn(); }
Spring security defaults
The Spring Security protects endpoint by HTTP Basic Authorization. It generates the default user and random password. Notice the generated password is logged during application start.
Using generated security password: <value>
- You can tune default user/password by spring.security.user.name and spring.security.user.password in application.properties.