Reputation: 179
I have followed the following links to try and test OAuth2 @PreAuthorise(hasAnyRole('ADMIN', 'TEST') for example but I can't any of the tests to pass or even authenticate.
When I try to access the end point with admin (or any role) it will never authenticate properly. Am I missing something obvious, it seems I have everything just as it is in the examples. I have also tried another alternative to the WithSecurityContext Factory with OAuth Specific Authentication and still no luck. Any help would be appreciated.
https://stackoverflow.com/a/31679649/2594130 and http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test
My Controller I'm testing
@RestController
@RequestMapping("/bookmark/")
public class GroupBookmarkController {
@Autowired
BookmarkService bookmarkService;
/**
* Get list of all bookmarks
*/
@RequestMapping(value = "{groupId}", method = RequestMethod.GET)
@PreAuthorize("hasAnyRole(['ADMIN', 'USER'])")
public ResponseEntity<List<Bookmark>> listAllGroupBookmarks(@PathVariable("groupId") String groupId) throws BookmarkNotFoundException {
List<Bookmark> bookmarks = bookmarkService.findAllBookmarksByGroupId(groupId);
return new ResponseEntity<>(bookmarks, HttpStatus.OK);
}
...
}
My Test class
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BookmarkServiceApplication.class)
@WebAppConfiguration
public class BookmarkServiceApplicationTests {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void loadData() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.alwaysDo(print())
.build();
}
@Test
@WithMockCustomUser(username = "test")
public void getBookmarkAuthorised() throws Exception {
mockMvc.perform(get("/bookmark/nvjdbngkjlsdfngkjlfdsnlkgsd"))
.andExpect(status().is(HttpStatus.SC_OK));
// always 401 here
}
}
My BookmarkServiceApplication
@SpringBootApplication
@EnableResourceServer
public class BookmarkServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookmarkServiceApplication.class, args);
}
}
My WithSecurityContextFactory
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
@Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
UserDetails principal = new User(customUser.username(), "password", true, true, true, true, grantedAuthorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(
principal, principal.getPassword(), principal.getAuthorities());
context.setAuthentication(authentication);
return context;
}
}
My WithSecurityContext Annotation
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
String username() default "user";
String name() default "Test User";
}
Hi @RobWinch I've tried you suggestion with the stateless flag, this helped with part of the answer. However in your reply to this question [Spring OAuth and Boot Integration Test] (https://stackoverflow.com/a/31679649/2594130) you mention
You no longer need to worry about running in stateless mode or not
Why is it that I need to still add the stateless false, is this a bug or are we using it slightly differently?
The other thing I needed to do to get this to work was adding OAuth2Request and OAuth2Authentication to the WithSecurityContextFactory as you can see in the following
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuthUser> {
@Override
public SecurityContext createSecurityContext(WithMockOAuthUser withClient) {
// Get the username
String username = withClient.username();
if (username == null) {
throw new IllegalArgumentException("Username cannot be null");
}
// Get the user roles
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : withClient.roles()) {
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("roles cannot start with ROLE_ Got " + role);
}
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
// Get the client id
String clientId = withClient.clientId();
// get the oauth scopes
String[] scopes = withClient.scope();
Set<String> scopeCollection = Sets.newSet(scopes);
// Create the UsernamePasswordAuthenticationToken
User principal = new User(username, withClient.password(), true, true, true, true, authorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(),
principal.getAuthorities());
// Create the authorization request and OAuth2Authentication object
OAuth2Request authRequest = new OAuth2Request(null, clientId, null, true, scopeCollection, null, null, null,
null);
OAuth2Authentication oAuth = new OAuth2Authentication(authRequest, authentication);
// Add the OAuth2Authentication object to the security context
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(oAuth);
return context;
}
}
Upvotes: 7
Views: 8106
Reputation: 3145
to add some more infos how to set stateless to false:
in your ResourceServerConfigurerAdapter do the following:
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(false);
}
which worked for me.
Upvotes: 1
Reputation: 21730
The problem is that OAuth2AuthenticationProcessingFilter will clear the SecurityContext if it is marked as stateless. To workaround this configure it to allow the state to be populated externally (i.e. stateless = false).
Upvotes: 3