How do I test a REST Controller that uses Oauth2 (client)? I need to mock the oauth2 and I am stuck. Any help would be appreciated.
Hope that this answer may help.
Actually, when using OAuth2 with a ResourceServerConfiguration, you will have a stateless security, which will throw away any effort in mocking users beforehand.
What you should do to mock users is:
Create a TestOnly loaded ResourceServerConfiguration which overrides your standard one in this way:
public class TestConfiguration extends ResourceServerConfiguration {
#Override
public void configure(ResourceServerSecurityConfigurer res) {
res.stateless(false);
super.configure(resources);
}
}
add the #WithMockUser to the tests:
#Test
#WithMockUser(username ="username_admin", authorities = {"I_AM_LEGEND"})
public void myTestWithUser() throws Exception {
this.mvc.perform(get("/getUsername")).andExpect(content().text().contains("username_admin"));
}
#Test
public void myTestWithNoUser() throws Exception {
this.mvc.perform(get("/getUsername")).andExpect(status().isUnauthorized());
}
NOTE: I wrote the code from memory
Related
Im having an issue with spring boot. I am trying to block one specific endpoint called /users/name/ but when i configure it on httpSecurity I can still call the endpoint. I need to block this specific endpoint below is my code.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class AuthConfigClass extends
WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/users/name/").permitAll()
.anyRequest().authenticated().and().httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication().withUser("admin")
.password("{noop}password").roles("USER");
}
}
And this is the RestController. Note please that the intention of this app is to make it vulnerable to attacks like the OWASP TOP API so no worries about security issues please tough I accept suggestions.
#Api(value="Users Endpoint and maintenance only for prvileged users")
#RequestMapping("/users")
public class RestControllerMain {
private final UserRespository userRespository;
#Autowired
public RestControllerMain(UserRespository userRespository) {
this.userRespository = userRespository;
}
//Excessive Data Exposure OWASP TOP 10
#RequestMapping(value="/", method=RequestMethod.GET)
public Iterable<User> getAllUsers() {
return userRespository.findAll();
}
#RequestMapping(value="/", method=RequestMethod.POST)
public void UserInsert(#RequestBody User user) {
userRespository.save(user);
}
//null pointer exception and SQL injection OWASP TOP 10 API.
#RequestMapping(value="/name/{user}", method=RequestMethod.GET)
public String mainUser(#PathVariable ("user")String username) {
if(!username.matches("/[\\t\\r\\n]|(--[^\\r\\n]*)|(\\/\\*[\\w\\W]*?(?=\\*)\\*\\/)/gi\n" )) {
return "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘‘VALUE’’.";
}
return "SQL Injection not found";
}
//XSS Also in the OWASP TOP API.
#RequestMapping(value="/search", method=RequestMethod.GET)
public String getMeUSer(#RequestBody User user) {
return "Nice to meet you" + user.getName();
}
// OWASP TOP 10 API. Broken Object Level
#RequestMapping(value="/{id}")
public Optional<User> getUserById(#PathVariable Long id) {
return userRespository.findById(id);
}
}
Please help me figure this I ended up following a tutorial.
As I understood, you want to require authentication for "users/name/{user}" endpoint, but your configuration states
.antMatchers("/users/name/")
whereas it should be
.antMatchers("/users/name/**")
where "**" means any matching pattern. But you if want to grant access after checking the privileges, as you stated in the description of controller, you should configure Spring's authorization and add
#Secured("ROLE_VIEWER, ROLE_ADMIN")
before service or controller methods, which will block any user who doesn't have those roles.
So I have many rest controllers and I would like to write some reusable test approach for authorization
#RestController //1
public class PolicyController {
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/group")
ResponseEntity subgrups(String policy) {
// impl
}
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/participants")
ResponseEntity participants(String policy) {
// impl
}
}
#RestController//2
public class GroupController {
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/group/{group}"
ResponseEntity subgroups(String policy, String group) {
// impl
}
}
#RestController //...n
When we follow good practice we should write test for every line of code, so probably I should write exactly so many duplicated tests as amount of controller * amount of methods so it would be a huge amount of such duplicated code
#WebMvcTest(controllers = PolicyController.class)
public class PolicyControllerTest {
//...mock all controller dependencies
#Autowired
private MockMvc mockMvc;
#MockBean
private PolicySecurity policyApi;
#Autowired
private SecurityService securityService;
#Test
public void whenSearchingForGroupAndHasAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(false);
mockMvc.perform(get("/policies/{policy}/group", "123")
.contentType("application/json"))
.andExpect(status().isOk());
}
#Test
public void whenSearchingForGroupAndHasntAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(true);
mockMvc.perform(get("/policies/{policy}/group", "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
#Test
public void whenSearchingForParticipantsAndHasAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(false);
mockMvc.perform(get("/policies/{policy}/participants", "123")
.contentType("application/json"))
.andExpect(status().isOk());
}
#Test
public void whenSearchingForParticipantsAndHasntAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(true);
mockMvc.perform(get("/policies/{policy}/participants", "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
}
this is only one controller with two methods, just imagine how many code there will be for 5 controllers and 30 endpoints, any idea how to write it in more maintainable way ?
Few pointers on what can be done. You can group your APIs based on the security configuration and then maintain a list of APIs for that group. This way you can just call one write one method for each authorization configuration where you will iterate through each API and run the mockMvc perform API method.
for(api in api_list) {
mockMvc.perform(get(api, "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
If the security configuration changes for any one API in future, then the test case for the whole group will fail. Only problem with this approach is that maintaining the test cases will be difficult over time because you would need to keep updating the API lists everytime you change the authorization.
Note that it is always better to write isolated test cases for each API method to test only that unit.
I am trying to get familiar with spring boot security, my API is a simple GET/POST to 127.0.0.1:8080/employee/
with a simple autoconfiguration like this.
#Configuration
public class SecurityConfig implements WebMvcConfigurer {
#Configuration
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1").password("{noop}user1").authorities("USER").and()
.withUser("user2").password("{noop}user2").authorities("USER").and()
.withUser("admin").password("{noop}admin").authorities("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/employee/**").authorizeRequests().anyRequest().hasRole("ADMIN");
}
}
}
This always gives me 403 Forbidden.
Have tried this:-
antMatcher("/employee*"), works but for any user. Can I get some help in understanding how this pattern works, I just need to restrict "/employee" or "/employee/" or "/employee/1" to Admin.
Your current configuration will only restrict any paths under employee e.g. employee/1 but not /employee. Additionally you are not doing anything with the employee matcher as you go back to authorizeRequests, then configure anyRequest has a role ADMIN
To restrict employee and any path underneath it to ADMIN.
http.authorizeRequests().antMatchers("/employee**", "/employee/**").hasRole("ADMIN").httpBasic();
Using ** will capture directories in path.
See
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html
and
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/util/matcher/AntPathRequestMatcher.html
Using a pattern value of /** or ** is treated as a universal match, which will match any request. Patterns which end with /** (and have no other wildcards) are optimized by using a substring match — a pattern of /aaa/** will match /aaa, /aaa/ and any sub-directories, such as /aaa/bbb/ccc.
I would also recommend testing your security configuration via the #WebMvcTest slice test
https://www.baeldung.com/spring-security-integration-tests
From the above a simple example would be,
#RunWith(SpringRunner.class)
#WebMvcTest(SecuredController.class)
public class SecuredControllerWebMvcIntegrationTest {
#Autowired
private MockMvc mvc;
// ... other methods
#WithMockUser(roles= {"admin"})
#Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
mvc.perform(get("/employee/1").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
//Repeated for other Paths
}
I have this little OAuth server class and I am using Spring Boot 2.0.4 and the spring-security-oauth2-autoconfigure 2.0.0.RELEASE dependency :
#RestController
#SpringBootApplication
#EnableAuthorizationServer
#Order(200) // really needed ?
public class MyOAuthServerApplication extends WebSecurityConfigurerAdapter {
#RequestMapping({ "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/me").authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails mary =
User.withUsername("mary")
.password("{bcrypt}$2a$10$B3NUb0x.MYnSfx7WJItrvO/ymEQwLCKQNehmCuA8keL1uTyHizI0i")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(mary);
}
public static void main(String[] args) {
SpringApplication.run(MyOAuthServerApplication.class, args);
}
}
This seems to work well with and without the #Order(200) annotation.
So is this annotation really needed ?
The Order annotation is used to define the injection precedence.
Read more her: https://www.baeldung.com/spring-order
In your case it's because of the EnableResourceServer annotation. And you must keep the annotation.
From the doc:
The #EnableResourceServer annotation creates a security filter with
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER-1) by default, so by
moving the main application security to
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) we ensure that the
rule for "/me" takes precedence.
Please find the tutorial here: https://spring.io/guides/tutorials/spring-boot-oauth2/
You need it if you have another WebSecurityConfigurerAdapter configuration.
For example if you allow users to login via login form with a different UserDetailsService and so on. Then this should be tried before your oauth authentification and thus needs a lower order, for example #Order(199).
Another example would be different configuration for your API access.
If you don't have any other configuration, then you don't need to set the order.
Setting the order to 200 also seems to be an arbitrary value, that should simply be higher then the others and thus executed last.
I have a resource endpoint that injects a #PathParam into constructor, i.e., different instance per #PathParam value. It all works fine in Jetty. But now I'm trying to write unit tests using Jersey Test Framework, and it seems that the test framework only supports one registered endpoint per type.
So if I do something like this:
#Path("/users")
public class MyResource {
public MyResource(#PathParam("userId") int userId) {
}
#Path("{userId}")
public String get() {
}
}
public class MyTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected Application configure() {
return new ResourceConfig()
.register(new MyResource(1))
.register(new MyResource(2));
}
#Test
public void test2() {
target("/users/1").request().get();
}
#Test
public void test2() {
target("/users/2").request().get();
}
}
I see that both test1 and test2 are invoking the instance of MyResource(1). Is this expected? Is there a solution to invoke the correct instance?
You should register the resource as a class. Jersey will create it for you. And handle all the injections.
"The example I posted is dumbed down. In reality, my resource constructor has another injected object that I need to mock. So how would I specify a mocked object parameter for the constructor?"
You can do something like
#Mock
private Service service;
#Override
public ResourceConfig configure() {
MockitoAnnotations.initMocks(this);
return new ResourceConfig()
.register(MyResource.class)
.register(new AbstractBinder() {
#Override
protected configure() {
bind(service).to(Service.class);
}
});
}
#Test
public void test() {
when(service.getSomething()).thenReturn("Something");
// test
}
Assuming you are already using the built in HK2 DI, and have an #Inject annotation on the constructor of your resource class, this should work. In the AbstractBinder we are making the mock object injectable. So now Jersey can inject it into your resource.
See Also:
Jersey - How to mock service