I'm trying to implement integration test using RestAssured library and Spring MVC REST oAuth2 secured endpoint.
This is my test:
#Test
public void testCreateDecision() throws Exception {
File createDecisionJsonFile = ResourceUtils.getFile(getClass().getResource("/json/decisions/create-decision.json"));
// #formatter:off
final String createDecisionRequest = FileUtils.readFileToString(createDecisionJsonFile)
.replace("{{name}}", "Test decision name")
.replace("{{description}}", "Test decision description");
// #formatter:on
String accessToken = getAccessToken("user", "user");
// #formatter:off
given()
.auth()
.oauth2(accessToken, OAuthSignature.HEADER)
.body(createDecisionRequest)
.contentType("application/json; charset=UTF-8")
.when()
.post(format("http://localhost:%d/api/v1.0/decisions/create", port))
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("id", notNullValue())
.body("createDate", notNullValue());
// #formatter:on
}
The accessToken is valid but I'm continuously getting 401 http code.
What could be wrong with my code ?
I know this is an old post, but just wanted to document this in case someone else needed the answer.
I was able to implement using the following format:
First retrieve the token (in my case I did not store user tokens, jut got them before each test)
// we need to get the oauth token before we can perform the request
private void authenticateUser(String username, String password) {
String response =
given()
.parameters("username", username, "password", password,
"grant_type", "password", "scope", "read write",
"client_id", "clientapp", "client_secret", "123456")
.auth()
.preemptive()
.basic("clientapp","123456")
.when()
.post("/oauth/token")
.asString();
JsonPath jsonPath = new JsonPath(response);
accessToken = jsonPath.getString("access_token");
}
And them on the test I used the retrieved token:
#Test
public void testGetUserDefaultUserOwner() {
authenticateUser(testData.user1.getLogin(), "1");
User user =
given()
.auth().oauth2(accessToken)
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.expect()
.log().all()
.statusCode(HttpStatus.OK.value())
.when()
.get(USER_RESOURCE, testData.user1.getId())
.as(User.class);
assertThat(user).isEqualTo(testData.user1);
}
I am using Restassured and AssertJ for the tests, and SpringBoot with OAuth2 for the Rest APIs.
I have reimplemented my test using OAuth2RestTemplate:
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setUsername("user");
resourceDetails.setPassword("user");
resourceDetails.setAccessTokenUri(format("http://localhost:%d/oauth/token", port));
resourceDetails.setClientId("clientapp");
resourceDetails.setClientSecret("123456");
resourceDetails.setGrantType("password");
resourceDetails.setScope(asList("read", "write"));
DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
OAuth2RestTemplate auth2RestTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
auth2RestTemplate.setMessageConverters(asList(new MappingJackson2HttpMessageConverter()));
Assert.assertNotNull(auth2RestTemplate.getAccessToken());
DecisionRequest decisionRequest = new DecisionRequest(name, description, parentDecisionId);
auth2RestTemplate.postForObject(format("http://localhost:%d/api/v1.0/decisions/create", port), decisionRequest, Decision.class);
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved here.</p>
</body></html>
Related
I have run into a very strange problem, and I am guessing that I am missing something in my setup.
I have an WebAPI that is secured by an IdentityServer4. It's only using Client_credentials. If i write the wrong ClientId och ClientSecret that user is not Authenticated, and I can't connect to my WebAPI. But if I write the wrong scope name the request is still processed and I get my response back, the strange part is that an exception is thrown, but for some reason it's ignored by the .NET Core Framework.
Here are some debug info from my output window.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET https://localhost:44360/v1/bookings
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: '[PII is hidden]'. Did not match: validationParameters.ValidAudience: '[PII is hidden]' or validationParameters.ValidAudiences: '[PII is hidden]'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateAudience(IEnumerable`1 audiences, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Bearer was not authenticated. Failure message: IDX10214: Audience validation failed. Audiences: '[PII is hidden]'. Did not match: validationParameters.ValidAudience: '[PII is hidden]' or validationParameters.ValidAudiences: '[PII is hidden]'.
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api)'
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Get", controller = "Bookings"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.ActionResult`1[System.Collections.Generic.IEnumerable`1[System.String]]] Get() on controller TRS.BookingService.Api.Controllers.BookingsController (TRS.BookingService.Api).
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api) - Validation state: Valid
TRS.BookingService.Api.Controllers.BookingsController:Information: Getting all bookings
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 96.2159ms.
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor:Information: Executing ObjectResult, writing value of type 'System.String[]'.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api) in 280.2344ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api)'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 1345.3829ms 200 application/json; charset=utf-8
So even that there is an exception thrown that says that the token isn't validated the request is still allowed to continue and execute and the response is sent back to the client.
This is how the ConfigureServices looks like:
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:44392/";
options.Audience = "FAKE_SCOPE";
});
And the Configure() Methods
app.UseAuthentication();
app.UseMvc();
This is how the JWT Token looks like:
{
"nbf": 1562062882,
"exp": 1562066482,
"iss": "https://localhost:44392",
"aud": [
"https://localhost:44392/resources",
"bookingApi"
],
"client_id": "clientId",
"scope": [
"bookingApi"
]
}
And this is the Client code calling the API.
var idpUrl = "https://localhost:44392/";
var clientId = "clientId";
var clientSecret = "secret";
var scope = "bookingApi";
var accessToken = await GetAccessTokenAsync(new Uri(idpUrl), clientId, clientSecret, scope);
string content = await GetContent(new Uri("https://localhost:44360/v1/bookings"), accessToken);
I guess I have missed something when it comes to Authorization, I have tried different
services.Authorization()
In the ConfigureServices() methods but it doesn't help, guess I have written it wrong.
Best Regards
Magnus
I came across this article after hitting the same issue. After much banging of head I discovered that it was caused in my case by using services.AddMvcCore() (with .AddJsonFormatters().AddDataAnnotations() in my case) rather than services.AddMvc(). Only with .AddMvcCore() do I get a 401 for token validation failure.
Seems that you need to add .AddAuthorization() to the mix when using .AddMvcCore as it is not added by default. Without it, the token validation fails, but the request pipeline continues quite happily.
After spending a day trying to figure out why it's not working I decided to step-thru the Microsoft code and found this in the AuthenticationMiddleware.
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (schemes == null)
{
throw new ArgumentNullException(nameof(schemes));
}
_next = next;
Schemes = schemes;
}
public IAuthenticationSchemeProvider Schemes { get; set; }
public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
// Give any IAuthenticationRequestHandler schemes a chance to handle the request
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
return;
}
}
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
{
context.User = result.Principal;
}
}
await _next(context);
}
}
And basically what happends is that the result has a Failure properties on it that contains my authentication exception, but since there is no check for that in the code, it will continue with the request to the next middleware in the pipeline. So I basically wrote my own AuthenticationMiddleware adding a check if the Failure has a value then return 403.
var defaultAuthenticate = await _schemas.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
context.User = result.Principal;
if (result?.Failure != null)
throw new AuthorizationException(result.Failure.Message);
}
await _next(context);
}
catch (AuthorizationException ex) when (!context.Response.HasStarted)
{
_logger.LogWarning(ex, "Unauthorized access encountered.");
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
It's however not something that would except me to do, so if somebody knows why i need to do this I would be glad for the information.
I have two clients for my SignalR app: Javascript client for web browsers and Android client (Gurgen/SignalR-.net-core-android-client) that use JWT Bearer Authentication.
I added this attribute to my SignalR Hub:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme + "," + CookieAuthenticationDefaults.AuthenticationScheme)]
public class MyHub : Hub
And my Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
//…
services.AddAuthentication()
.AddCookie(cfg => cfg.SlidingExpiration = true)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
services.AddSignalR();
//…
Android client login successfully with header authentication bearer token. But web client comes failed connecting to the hub (401 Unauthorized).
When I remove [Authorize] attribute the javascript client works!
What is default Javascript SignalR Client AuthenticationScheme? Or what is the issue I made?
I use dotnet core 2.1, Microsoft.AspNetCore.SignalR and my IDE is Visual Studio for Mac.
This is from SignalR docs:
In standard web APIs, bearer tokens are sent in an HTTP header. However, SignalR is unable to set these headers in browsers when using some transports. When using WebSockets and Server-Sent Events, the token is transmitted as a query string parameter.
services.AddAuthentication(options =>
{
// ...
})
.AddJwtBearer(options =>
{
// ...
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
// Sending the access token in the query string is required due to
// a limitation in Browser APIs. We restrict it to only calls to the
// SignalR hub in this code.
// See https://learn.microsoft.com/aspnet/core/signalr/security#access-token-logging
// for more information about security considerations when using
// the query string to transmit the access token.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
im trying to integrate spring security with a custom angular 2 login, that is a specific endpoint of my app is protected with spring security, trying to access it will redirect to /login that is handled in angular 2. as things stands now i have no clue as to how to perform the login and grant access to the backend API once logged.
i am configuring spring security as follows:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers("/api/someEndpoint/**")
.hasRole(ADMIN_ROLE).and().formLogin()
.loginPage("/login").and().logout();
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
as I had the default login everything worked fine, but I have found myself unable to create a working angular 2 login integration.
I tried the following code in angular 2 to no avail:
login(loginDetails:Object) {
console.log(loginDetails)
const headers = new Headers({ 'Content-Type': 'application/json' });
const options = new RequestOptions({ headers: headers });
const body = JSON.stringify(loginDetails);
console.log(headers);
console.log(body);
return this.http.post(this.loginUrl, body, options)
}
as far as I know spring security defaults for username and password variable names are "username" and "password", which i am sure are being passed in the request body so when passing some invalid user data like {"username":"admin", "password" : "pass"}I should be redirected to /login?error or something, and when successfully authenticated I should be redirected to /welcome and stay authenticated
I have the user and pass defined in my db and my custom userDetailsService checks against it
any answers, comments or questions are welcome
Once you're working with an API you've to use the HTTP Basic authentication.
It's also required to use HTTPS to prevent the main-in-middle attack.
To implement HTTP Basic with Angular the login service would look like this:
login (loginDetails: any): Observable<LoginResponse> { // custom class, may be empty for now
let headers = new Headers({
'Authorization': 'Basic ' + btoa(loginDetails.login + ':' + loginDetails.pass),
'X-Requested-With': 'XMLHttpRequest' // to suppress 401 browser popup
});
let options = new RequestOptions({
headers: headers
});
return this.http.post(this.loginUrl, {}, options)
.catch(e => this.handleError(e)); // handle 401 error - bad credentials
}
... then you subscribe this in the caller component:
loginNow() {
this
.loginService
.login(this.loginDetails)
.subscribe(next => {
this.router.navigateByUrl("/"); // login succeed
}, error => {
this.error = "Bad credentials"; // or extract smth from <error> object
});
}
Then you can use the loginNow() method inside component templates like (click)="loginNow().
As soon as the server will accept an authorization, JSESSIONID will be stored in your browser automatically because of Spring Security features and you won't be forced to send the credentials each time you access private resources.
Your login server method may look like this:
#PreAuthorize("hasRole('USER')")
#PostMapping("/login")
public ResponseEntity login() {
return new ResponseEntity<>(HttpStatus.OK);
}
... it would reject with 401 UNAUTHORIZED when the authorization fails or accept with 200 SUCCESS when it's not.
How to setup a server in the proper way there's a number of Spring Security demo projects present: https://github.com/spring-guides/tut-spring-security-and-angular-js
Your spring security config needs to look like this
http!!
.cors().and()
.csrf().disable()
.authorizeRequests()
.requestMatchers(object: RequestMatcher {
override fun matches(request: HttpServletRequest?): Boolean {
return CorsUtils.isCorsRequest(request)
}
}).permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().permitAll()
I had a similar issue, but I had to override the successlogout handler as mentioned here.
I'm using Grails version 2.4.3 . I am creating an application that supports RESTful APIs. Since access to these APIs should be authenticated , I tried out the Spring Security REST plugin. I checked out this example and what I could understand is , the /api/login controller is the authentication point which receives the user credentials in JSON format and after successful authentication it provides the acces token as response. I tried sending a POST request to /api/login/ with valid JSON data using the POSTMAN Rest Client. But it gives me the following error.
401 Unauthorized , Similar to 403 Forbidden, but specifically for use when authentication is possible but has failed or not yet been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.
I also tried using IntellijIDEA's REST Client but doesn't work.
Then i tried by sending AJAX Request to /api/login/ with valid JSON data
, but getting 401 on console. What is the problem here? Is this the correct login end point? How can i get authenticated using JQuery?
Try this
$.ajax({
url: " http://localhost:8080/AppName/api/login",
type: "POST",
crossDomain: true,
data: JSON.stringify({"username":"yourusername" , "password":"yourpassword"}),
contentType: 'application/json; charset=utf-8',
dataType: "json",
success: function (response) {
console.log(response);
},
error: function (xhr, status) {
alert("error");
}
}) });
You can try this code for authentication,I am sending user id and password in request header you can try as you wish :-
inject following services:-
def springSecurityService
def authenticationManager
and use following code
def login = {
final String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.startsWith("Basic")) {
boolean authResult = authenticateUser(authorization)
if (authResult) {
render response.status
} else {
render authFailed(response)
}
} else {
render authFailed(response)
}
}
protected boolean authenticateUser(String authorization) {
// Authorization: Basic base64credentials
def base64Credentials = authorization.substring("Basic".length()).trim();
byte[] credentials = base64Credentials.decodeBase64()
String actualCredential = new String(credentials)
// credentials format like username:password
final String[] values = actualCredential.split(":", 2);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(values[0], values[1]);
try {
def authentication = authenticationManager.authenticate(authRequest);
def securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
def session = request.session;
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
catch (BadCredentialsException exception) {
return false
}
return true
}
protected HttpServletResponse authFailedResponse(HttpServletResponse response) {
response.setStatus(401)
response.setHeader("WWW-Authenticate", "Basic realm=\"nmrs_m7VKmomQ2YM3:\"")
return response;
}
I need to access an asp page running on 'https' protocol.
I am facing problems at authentication part itself. The response object returns "HTTP/1.1 200 OK" but i am getting redirected to Login page itself.
Following is my code:
public FileDownloadHttpWrapper(String url,String username, String password)
{
SchemeRegistry supportedSchemes = new SchemeRegistry();
supportedSchemes.register(new Scheme("https",
SSLSocketFactory.getSocketFactory(), 443));
// prepare parameters
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "UTF-8");
HttpProtocolParams.setUseExpectContinue(params, true);
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params,supportedSchemes);
mClient = new DefaultHttpClient(ccm,params);
mClient.getCredentialsProvider().setCredentials(
new AuthScope(null,AuthScope.ANY_PORT),
new UsernamePasswordCredentials(username, password)
);
}
private Object getRequest(String url)
{
HttpGet get = new HttpGet("/EvalMuniMKT/mainmenu.asp");
HttpHost target = new HttpHost(url, 443, "https");
try
{
// execute the GET
HttpResponse resp = mClient.execute(target,get);
HttpEntity entity = resp.getEntity();
System.out.println(resp.getStatusLine());
System.out.println(EntityUtils.toString(entity));
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
// release any connection resources used by the method
}
return null;
}
I think the asp site uses form based login (session), not http basic authentication. For that you may need to do login form post and hold cookie in context and pass the context while executing the actual request.
something like :
CookieStore cookieStore = new BasicCookieStore();
// Create local HTTP context
HttpContext localContext = new BasicHttpContext();
// Bind custom cookie store to the local context
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
hc.execute(httpget, localContext);