Spring security oAuth2 Mock test - spring

I have a rest controller that requires the user to be authenticated. But when the test is run the response is always 401. I am using the "WithSecurityContext" annotation, but it does not work.
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD })
#WithSecurityContext(factory = WithOAuth2AuthenticationSecurityContextFactory.class)
public #interface WithOAuth2Authentication {
String clientId() default "temporal";
String username() default "username";
String[] scopes() default { "read", "write", "trust" };
}
Class implementing annotation
public class WithOAuth2AuthenticationSecurityContextFactory implements WithSecurityContextFactory<WithOAuth2Authentication> {
#Override
public SecurityContext createSecurityContext(WithOAuth2Authentication annotation) {
Set<String> scopes = new HashSet<>();
Collections.addAll(scopes, annotation.scopes());
OAuth2Request oAuth2Request = new OAuth2Request(null, annotation.clientId(), null, true, scopes, null, null, null, null);
Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, new TestingAuthenticationToken(annotation.username(), null, "read"));
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth2Authentication);
return context;
}
}
Unit test
#Before
public void setup() {
this.mapper = new ObjectMapper();
RestDocumentationResultHandler document =
document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()));
this.mock = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.apply(SecurityMockMvcConfigurers.springSecurity())
.alwaysDo(document)
.build();
}
#Test
#WithOAuth2Authentication
public void create() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Accept-Language", "en");
httpHeaders.add("Content-Type", "application/json");
httpHeaders.add("Accept", "application/json");
String JSON = this.mapper.writeValueAsString(new Register.Project());
this.mock.perform(post("/project/create")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.headers(httpHeaders)
.content(JSON))
.andDo(print())
.andExpect(status().isOk());
}
The result of the test
MockHttpServletResponse:
Status = 401
Error message = null
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate, no-store], Pragma=[no-cache, no-cache], Expires=[0], X-Frame-Options=[DENY], WWW-Authenticate=[Bearer realm="oauth2-resource", error="unauthorized", error_description="Full authentication is required to access this resource"], Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
Forwarded URL = null
Redirected URL = null
Cookies = []

Related

How to get token from a REST service with Spring

The service provider supplies me with the header data: grant_type, Content-Type. And the body data: grant_type, username and password. Which I use in Postman where it generates OK token. But in the Spring application it generates an error HttpClientErrorException $ BadRequest: 400 Bad Request.
I have the class to set the body data:
public class BodyToken {
private String grant_type = "password";//set body data
private String username = "User";//set body data
private String password = "123";//set body data
private String access_token;
#JsonGetter("access_token")
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
#JsonGetter("grant_type")
public String getGrant_type() {
return grant_type;
}
#JsonGetter("username")
public String getUsername() {
return username;
}
#JsonGetter("password")
public String getPassword() {
return password;
}
}
This is the controller where the header data is set:
#PostMapping("/TokenGeneration")
#ResponseBody
public BodyToken TokenGeneration() throws IOException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("grant_type", "password");//set header data
headers.set("Content-Type", "application/x-www-form-urlencoded");//set header data
HttpEntity request = new HttpEntity(headers);
headers.add("User-Agent", "Spring's RestTemplate" );
ResponseEntity<BodyToken> response = restTemplate.exchange(
"https://sw/token",
HttpMethod.POST,
request,
BodyToken.class
);
try {
return response.getBody();
} catch (Exception e) {
BodyToken body = new BodyToken();
log.info(e.getMessage());
return body;
}
}
OK was solved with using the Class MultiValueMap and LinkedMultiValueMap. The credentials are added to this new object and it is sent together with the request:
#PostMapping("/TokenGeneration")
#ResponseBody
public BodyToken TokenGeneration() throws IOException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("grant_type", "password");//set header data
headers.set("Content-Type", "application/x-www-form-urlencoded");//set header data
MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();//line solution
body.add("grant_type", "password");//line solution
body.add("username", "user");//line solution
body.add("password", "123");//line solution
HttpEntity request = new HttpEntity(body, headers);//and I add this body to HttpEntity
headers.add("User-Agent", "Spring's RestTemplate" );
ResponseEntity<BodyToken> response = restTemplate.exchange(
"https://sw/token",
HttpMethod.POST,
request,
BodyToken.class
);
try {
return response.getBody();
} catch (Exception e) {
BodyToken body = new BodyToken();
log.info(e.getMessage());
return body;
}
}

spring testRestTemplate basic auth not working for POST requests Httpstatus 302

I am using Spring's org.springframework.boot.test.web.client.TestRestTemplate for testing controller code.
I can test the GET APIs simply by using testRestTemplate.withBasicAuth("test", "test").exchange(...), but the same way isn't working for the POST endpoints in same controller.
It returns HttpStatus 302 found with the following ResponseEntity:
<302,[Set-Cookie:"JSESSIONID=332C559B7CABE5682EE9910A6FF834DA; Path=/; HttpOnly", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY", Location:"http://localhost:61598/login", Content-Length:"0", Date:"Thu, 09 Jul 2020 14:24:18 GMT", Server:"Application Server"]>
Code of controller:
#GetMapping(value = "/filterable_columns", produces = "application/json")
public List<FilterableField> filterableFieldList() {
log.info("Received request for list of filterable fields");
return metaDataService.filterableFieldList(referenceDataService.getSchemaUri());
}
#PostMapping(value = "/search", produces = "application/json")
public List<Map<String, Object>> filteredSearch(#RequestBody FilteredSearchRequest filteredSearchRequest) throws IllegalAccessException {
log.info("Received request for filtered search");
return referenceDataService.filteredSearch(filteredSearchRequest);
}
Controller Test:
#Test // This works as expected
void filterableFieldList() {
val reply = testRestTemplate.withBasicAuth("test", "test")
.exchange("/reference_data/filterable_columns",
HttpMethod.GET, null,
new ParameterizedTypeReference<List<FilterableField>>() {
});
assertEquals(HttpStatus.OK, reply.getStatusCode());
assertFalse(Objects.requireNonNull(reply.getBody()).isEmpty());
}
#Test // This does not work
void filteredSearch() {
val reply = testRestTemplate.withBasicAuth("test", "test")
.exchange("/reference_data/search",
HttpMethod.POST,
new HttpEntity<>(new FilteredSearchRequest()),
new ParameterizedTypeReference<List<Map<String, Object>>>() {
}
);
System.out.println(reply);
assertEquals(HttpStatus.OK, reply.getStatusCode());
}
AdfsSecurityConfiguration.java:
#Configuration
#ConditionalOnProperty(prefix = "moneta.security.adfs", name = "enabled", matchIfMissing = true)
public class AdfsSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Environment environment;
private final AdfsConfigurer<HttpSecurity> adfsConfigurer;
public AdfsSecurityConfiguration(final Environment environment, final AdfsConfigurer<HttpSecurity> adfsConfigurer) {
this.environment = environment;
this.adfsConfigurer = adfsConfigurer;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
if (!isRunningLocally()) {
http.requiresChannel().anyRequest().requiresSecure();
}
http.apply(adfsConfigurer).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.requestMatchers(EndpointRequest.to("keepalive", "info", "health", "env"), EndpointRequest.toLinks())
.permitAll().anyRequest().authenticated().and().csrf().disable();
}
private boolean isRunningLocally() {
return environment.acceptsProfiles(Profiles.of("default"));
}
}
application-test.yml:
spring:
security:
user:
name: test
password: test

Why is swagger ignoring my authentication?

I am trying to secure a swagger interface withj OpenId Connect.
I can login using OIDC and swagger shows me as authorised:
but when I do 'try it out' the authentication is ignored and a login box pops up:
In my class which extends SpringBootServletInitializer I have:
#Bean
#ConditionalOnProperty("security.oauth2.client.clientId")
public SecurityScheme securityScheme(Environment environment, OAuth2ClientProperties clientProperties) {
String authorizationUri = environment.getRequiredProperty("security.oauth2.client.user-authorization-uri");
String accessTokenUri = environment.getRequiredProperty("security.oauth2.client.access-token-uri");
LoginEndpoint loginEndpoint = new LoginEndpoint(authorizationUri);
TokenRequestEndpoint tokenRequestEndpoint =
new TokenRequestEndpoint(authorizationUri, clientProperties.getClientId(), clientProperties.getClientSecret());
TokenEndpoint tokenEndpoint = new TokenEndpoint(accessTokenUri, "auth_code");
GrantType grantType = new AuthorizationCodeGrant(tokenRequestEndpoint, tokenEndpoint);
AuthorizationScope authorizationScope = new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobal);
return new OAuthBuilder()
.name(securitySchemaOAuth2)
.grantTypes(Arrays.asList(grantType))
.scopes(Arrays.asList(authorizationScope))
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Arrays.asList(new SecurityReference(securitySchemaOAuth2, authorizationScopes));
}
#Bean
SecurityConfiguration security(OAuth2ClientProperties clientProperties) {
return new SecurityConfiguration(
clientProperties.getClientId(),
clientProperties.getClientSecret(),
securitySchemaOAuth2,
"test-app",
"apiKey",
ApiKeyVehicle.HEADER,
"api_key",
" " /*scope separator*/);
}
#Bean
public SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex("/v1/.*")).build();
}
and a class with:
#ApiModel(value = "Template", description = "Template of REST APIs")
#RestController
#RequestMapping("/v1")
public class TemplateServiceImplementation {
...
#ApiOperation(httpMethod = "GET", value = "Call Get method",
notes = "See Get method")
#RequestMapping(method = RequestMethod.GET, value = "/calltemplate/{param}/", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Structure> callGet(#PathVariable("param") String param, HttpServletRequest hreq) {
MultiValueMap<String, String> mapParams = new LinkedMultiValueMap<String, String>();
mapParams.add("param", param);
Structure structure = restTemplate.getForObject(callGetEndpoint, Structure.class, mapParams);
ResponseEntity<Structure> thisresponse = new ResponseEntity<Structure>(structure, HttpStatus.OK);
return thisresponse;
}
Sorry for all the code. So how do I make the GET use my OIDC authentication?
When I cancel the login, curl is:
curl -X GET --header 'Accept: application/json' --header 'Authorization: Bearer eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMy lots more encrypted text' 'http://localhost:8080/v1/calltemplate/%7B%20%20%20%22id%22%3A%20%22string%22%2C%20%20%20%22name%22%3A%20%22string%22%2C%20%20%20%22path%22%3A%20%22string%22%2C%20%20%20%22version%22%3A%20%22string%22%20%7D/'
request url:
http://localhost:8080/v1/calltemplate/%7B%20%20%20%22id%22%3A%20%22string%22%2C%20%20%20%22name%22%3A%20%22string%22%2C%20%20%20%22path%22%3A%20%22string%22%2C%20%20%20%22version%22%3A%20%22string%22%20%7D/
and other response variables:
EDIT Forgot to metion my WebSecurityConfig:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui",
"/swagger-resources/**", "/configuration/**", "/swagger-ui.html"
, "/webjars/**", "/csrf", "/");
}
UPDATE
Output of network:

how can i permit to http header to read strings from JSON?

i'm trying to test a rest authentification method but i'm stucked in some errors within Postman. The error is about HHTP header it can't reads the message from JSON, the exception message is: HttpMessageNotReadableException
here's the RestController class:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth")
public class AuthController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
EmployeRepository employeRepository;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
JwtTokenProvider tokenProvider;
/**
* METHODE D'AUTHENTIFICATION
*
* #param loginRequest
* #return
*/
#PostMapping("/signin")
#ApiImplicitParams(#ApiImplicitParam(name = "Authorization", value = "Bearer token", required = true, dataType = "String", paramType = "header"))
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Optional<Employe> ListEmployees = employeRepository.findByMail(loginRequest.getEmail());
List<Employe> listEmpl = new ArrayList<>();
ListEmployees.ifPresent(listEmpl::add);
if (listEmpl.size() == 1) {
final Employe empl = listEmpl.stream().findFirst().get();
Boolean matches = passwordEncoder.matches(loginRequest.getPassword(),
listEmpl.stream().findFirst().get().getMp());
if (matches.equals(true)) {
if ( empl.getMail()!= null) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
headers.add("Authorization", new JwtAuthenticationResponse(jwt).getAccessToken());
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
} else if (empl.getMp()!= null) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
empl.getMail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
}
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}}
the POSTMAN URI:
http://localhost:8082/api/auth/signin
the message that i get:
{
"timestamp": 1548681879270,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Required request body is missing: public org.springframework.http.ResponseEntity<?> com.Cynapsys.Pointage.Controller.AuthController.authenticateUser(com.Cynapsys.Pointage.Model.LoginRequest)",
"path": "/api/auth/signin"
}
You are missing main data in your question and that is your request body for /signin POST call.
But your error message is giving required hint to solve your issue.
"message": "Required request body is missing: public
org.springframework.http.ResponseEntity
com.Cynapsys.Pointage.Controller.AuthController.authenticateUser(com.Cynapsys.Pointage.Model.LoginRequest)"
You are missing required LoginRequest in your call.

Server returned HTTP response code: 400 using Spring-Security AuthenticationProvider

I'm building a RESTful authentication service and I'm having trouble connecting to it. I get the dreaded "Server returned HTTP response code: 400" when trying to retrieve the response. This seems very odd. I would think I'd get this error when sending the request.
I'm using this service as part of a Spring-Security AuthenticationProvider. I'm currently using a simulator instead of the actual service for testing. It won't connect to either the simulator or the service.
Here is the calling method:
public <T> T invoke(String service, Object request, Class<T> responseType) throws IOException {
ObjectMapper mapper = new ObjectMapper();
URL url = new URL("http://localhost:8888/simulator/rest" + service);
HttpURLConnection uc = (HttpURLConnection) url.openConnection();
uc.setRequestMethod("POST");
uc.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
uc.setDoOutput(true);
uc.setDoInput(true);
uc.connect();
OutputStream out = uc.getOutputStream();
mapper.writeValue(out, request);
out.close();
return mapper.readValue(uc.getInputStream(), responseType);
}
Here is the code that calls this method:
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken("thomas", "thomas");
UsernamePasswordAuthenticationToken response =
invoke("/authenticate", token, UsernamePasswordAuthenticationToken.class);
Here is the simulator method that gets called:
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
#ResponseBody
public UsernamePasswordAuthenticationToken authenticate(
#RequestBody UsernamePasswordAuthenticationToken userNameAndPassword) {
String userName = (String) userNameAndPassword.getPrincipal();
String password = (String) userNameAndPassword.getCredentials();
if (userName.equalsIgnoreCase("thomas")) {
if (userName.equals(password)) {
UsernamePasswordAuthenticationToken response =
new UsernamePasswordAuthenticationToken(
userName,
password,
new ArrayList<GrantedAuthority>());
return response;
}
}
return new UsernamePasswordAuthenticationToken(userName, password);
}
The line that causes the error is the :
mapper.readValue(uc.getInputStream(), responseType);
If can't see any issues with this code. Must have been looking at it too long. Need new eyes on the problem.
BTW, this REST service and simulator has been used successfully with other operations.
Additional Information:
The error occurs in the uc.getInputStream() call. The HttpURLConnection.inputStream = null.
Also, the headers for the request are as follows:
If this helps, here are the headers for this request:
[WARN] 400 - POST /simulator/rest/authenticate (127.0.0.1) 1417 bytes
Request headers
Content-Type: application/json;charset=UTF-8
X-Tenant: 1
Authorization: 0000013770b132a1dfcbfe0a694542b244534e0e406cfa857660c904daa89af91d0ac769
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.6.0_26
Host: localhost:8888
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 112
Response headers
Set-Cookie: JSESSIONID=1r02p7yvm8mzs;Path=/
X-UA-Compatible: IE=9
Content-Type: text/html; charset=iso-8859-1
Content-Length: 1417
Here is the my Token code:
import java.util.List;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
public class SerializedAuthenticationToken extends UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = 2783395505630241326L;
private Object principal;
private Object credentials;
/**
* no-arg constructor to satisfy Serializable.
*/
public SerializedAuthenticationToken() {
super(null, null);
}
/**
* constructor.
*/
public SerializedAuthenticationToken(Object principal, Object credentials) {
super(null, null);
setPrincipal(principal);
setCredentials(credentials);
}
/**
* constructor with List<GrantedAuthorities>.
*/
public SerializedAuthenticationToken(Object principal, Object credentials, List<GrantedAuthority> authorities) {
super(null, null, authorities);
setPrincipal(principal);
setCredentials(credentials);
}
public Object getPrincipal() {
return principal;
}
public void setPrincipal(Object principal) {
this.principal = principal;
}
public Object getCredentials() {
return credentials;
}
public void setCredentials(Object credentials) {
this.credentials = credentials;
}
public void setName(Object name) {
}
}
I also now am getting a new stack trace:
org.codehaus.jackson.map.JsonMappingException: Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead (through reference chain: com.mckesson.shared.util.SerializedAuthenticationToken["authenticated"])
You need to create a Dummy token to do that. Here is my test to verify it:
public class JacksonTest {
#Test
public void test() throws Exception {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("thomas", "thomas");
String tokenStr = mapper.writeValueAsString(token);
/* WON'T WORK
UsernamePasswordAuthenticationToken auth = mapper.readValue(tokenStr, UsernamePasswordAuthenticationToken.class);
*/
MyToken auth = mapper.readValue(tokenStr, MyToken.class);
String authStr = mapper.writeValueAsString(auth);
Assert.assertThat(tokenStr.equals(authStr), is(true));
}
private static class MyToken extends UsernamePasswordAuthenticationToken {
private Object principal;
private Object credentials;
private static final long serialVersionUID = -5045038656629236029L;
public MyToken() {
super(null, null);
}
public MyToken(Object principal, Object credentials) {
super(null, null);
this.principal = principal;
this.credentials = credentials;
}
/**
* #return the principal
*/
public Object getPrincipal() {
return principal;
}
/**
* #param principal the principal to set
*/
public void setPrincipal(Object principal) {
this.principal = principal;
}
/**
* #return the credentials
*/
public Object getCredentials() {
return credentials;
}
/**
* #param credentials the credentials to set
*/
public void setCredentials(Object credentials) {
this.credentials = credentials;
}
public void setName(Object name) {
}
}
}
Can't spot the exact issue, but I think a contributor to the problem is that fact that you're unnecessarily dropping down to such a low-level API to accomplish something for which some very reasonable abstractions exist. Have a look at Spring's RestTemplate to see a more elegant means or writing client code against a RESTful service. Whatever you're doing wrong, chances are RestTemplate will get it right.

Resources