I am exploring/learning Spring security modules by implementing it through REST API.
To test the impact, we are using Postman native application as a rest client.
#RestController
#RequestMapping("/auth")
public class Employee {
#GetMapping("/status")
public ResponseEntity<String> getStatus()
{
ResponseEntity<String> responseEntity = new ResponseEntity<>("Resource is fetched", HttpStatus.OK);
return responseEntity;
}
}
above is a piece of resource for sake of consumption.
and below is the code snippet to configure Authentication and authorization
#EnableWebSecurity
public class AppSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("ashish").password("{noop}admin").roles("USER")
.and().withUser("foo").password("{noop}foo").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/auth/status").hasRole("ADMIN").and()
.formLogin()
;
}
#Bean
public PasswordEncoder getPasswordEncoder()
{
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
now above authorization code is working fine when tried in browser - it uses its default spring login page.
however i am not quite able to understand how to execute/test the same through postman.
in method protected void configure(HttpSecurity http) , i tried removing formLogin() it did not work.
i added httpBasic - it also did not worked.
in postman, basic authentication is used by me.
while searching on internet, i came across some really good articles but almost all of them uses some sort of UI technology like angular or thymleaf to demonstrate the concept which i am finding hard to grasp.
I am referring below video tutorials to learn spring security.
https://www.youtube.com/watch?v=payxWrmF_0k&list=PLqq-6Pq4lTTYTEooakHchTGglSvkZAjnE&index=6&t=0s
Thanks in advance!
Ashish Parab
Do a GET request http://localhost:8080/login via postman and it will return you an html. Extract the _csrf token from the response. It will look like
<input name="_csrf" type="hidden"
value="1c470a6c-dff3-43aa-9d08-d308545dc880" />
Do a POST request as follows to http://localhost:8080/login, copying the _csrf token, username and password as form params
Take note of the JESSIONID Cookie value in the response from step two. And that is the session Id of the authenticated session.
As long as you sent the JESSIONID in subsequent requests as a cookie, spring security knows who you are. Postman will add that Cookie automatically to subsequent requests.
you can add it manually as header with that cookie header or update the postman settings to always send JESSIONID cookie
You will have to implement JWT token for the same and add it to the request header 'Authorization' in Postman. You can take a look at Java Brains Spring security videos on youtube.
Related
I need to SSO (single sign on) a user coming from an application of mine (identity provider using ASPNET Session State) and redirect them to another application of mine (service provider) that is configured to use implicit flow with IdentityServer4. I need to achieve this without requiring the user to log back in and without providing the user's password.
My initial thought was that I could use a client secret for the identity provider to redirect the user to the IdentityServer4 authentication end point with the access token as a query parameter and then use a custom validator or extension grant to issue an identity token for use with the service provider application without needing to also provide the user's password.
I've managed to issue an access token to the identity provider and then redirect the user to IdentityServer4, but issuing an identity token has proven difficult for me. I've poured over the samples and documentation and I'm confused to say the least.
I'm looking for direction on the appropriate approach to this scenario and perhaps a comprehensive example in C#. I've come to understand I can use a hybrid flow to issue an access token as well as an identity token. I think my biggest struggle is how to redirect the user and, based on the access token, issue the user an identity token (and if this is even an acceptable approach).
Simply put: I'd like to redirect the user from Application A to IdentityServer4 to Application B based on trust with the identity provider (via client secret?).
Note: I understand this could be considered an opinion-based question, but based on my research I believe there is one single best practice and that's what I'm asking for.
I managed to get this working by the following flow:
Authorize the user in Application A (Identity Provider)
Obtain Access Token from Identity Server 4 via Token Endpoint and shared secret.
Add access token as a query string parameter since headers are not preserved on redirect.
Redirect the user to an Account controller method that accepts identifying information such as username. This method is protected by a custom middleware class that checks the query string for an access token parameter. If the token exists, it is added to the authentication header; this authorizes the user to hit this controller method.
The controller method will then sign the user in and redirect them to the /connect/authorize/login endpoint.
Finally, the login endpoint sets the cookie and redirects the user to Application B (Service Provider), whose URL is specified via the redirect_uri query parameter.
Configuration for shared secret:
Add appropriate grant type, secret and new scope name to the client. The new scope will help in debugging Access token issues in your logs (especially if you have multiple applications hitting your ID4 server). Also make sure to add the Service Provider's URL to the client RedirectUris, otherwise you'll receive an "invalid redirect" error.
AllowedGrantTypes = new List<string> { GrantType.Implicit, GrantType.ClientCredentials },
ClientSecrets = new List<Secret> {
new Secret(_clientSecrets.ExternalIdpSecret.Sha256(), clientID)
},
AllowedScopes = new List<string>
{
"newScopeName"
},
RedirectUris = new List<string>
{
$"http://localhost:<portnumber>"
}
Next, add your custom middleware.
public class QueryStringOAuthBearerMiddleware
{
private readonly RequestDelegate next;
public QueryStringOAuthBearerMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
this.BeginInvoke(context);
await this.next.Invoke(context);
this.EndInvoke(context);
}
private void BeginInvoke(HttpContext context)
{
if (context.Request.Query.ContainsKey("accesstokenparametername"))
{
var accessToken = context.Request.Query.First(p => p.Key == "accesstokenparametername");
if (!string.IsNullOrEmpty(accessToken.Value))
{
context.Request.Headers.Add("Authorization", "Bearer " + accessToken.Value);
}
}
}
private void EndInvoke(HttpContext context)
{
}
}
And add the middleware to your configuration.
app.UseMiddleware<QueryStringOAuthBearerMiddleware>();
Create your login method.
[HttpGet]
[Authorize]
public async Task<IActionResult> Login2(string userName, string returnURL)
{
await _httpContextWrapper.SignInAsync(userName);
return Redirect(returnURL);
}
Configuration for Client application (IDP):
Your client side code should look like this:
var disco = await DiscoveryClient.GetAsync("http://localhost:<portnumber>");
var tokenClient = new TokenClient(disco.TokenEndpoint, "clientIdentifier", "IUsedAGuidHere");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("newScopeName");
var redirectURL = string.Format("http://localhost:2228/account/Login2?userName=<UserIDValue>&returnURL={1}&accesstokenparametername={0}",
tokenResponse.AccessToken,
Server.UrlEncode(
string.Format("/connect/authorize/login?client_id={3}&redirect_uri={2}&response_type=id_token%20token&scope=<ImplicitFlowScopes>&state={0}&nonce={1}",
CryptoRandom.CreateUniqueId(),
CryptoRandom.CreateUniqueId(),
Server.UrlEncode("http://localhost:<PortNumber>"),
"ClientIdentifier")));
Response.Redirect(redirectURL, false);
Note: Please understand you won't be able to take this code AS-IS and make it work. I've heavily modified it to protect the security of my resources.
I think I might take care of the Authentication with Application A first, then forward on to the next app...
Application A --> IdentityServer --> Application A --> Application B.
You could include some custom parameters in your returnUrl which Application A could read upon return from IdentityServer that would trigger the redirect to Application B.
I noticed yesterday that my Facebook login for my website has stopped working.
This has been working great for the last 2 months, as far as I am aware I have not changed anything. I have tried everything I can on links such as: - as well as many more...
ASP.NET MVC5 OWIN Facebook authentication suddenly not working
I have noticed that the Stack Overflow Facebook auth has also stopped working.
Has anyone else noticed this and found any solution? It's worth noting I am using azure app services to host. But this issue is also found when I am using localhost.
My current setup looks like this...
in Startup.Auth.cs
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = "xxxxxxxxxxxxx",
AppSecret = "xxxxxxxxxxxx"
};
facebookOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookOptions);
In the following method, loginInfo is null every time.
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
I also added a session "WAKEUP" from a different post suggestion, fb auth failed once before and this fixed the issue this time, but it has come back.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
Session["WAKEUP"] = "NOW!";
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
As RockSheep explained. Facebook dropped the support vor API v2.2. You need to update your OWIN nuget packages.
You can find the issue on github (from the Katanaproject).
Ensure to activate pre releases in your nuget manager, than you are able to update the nuget packages to version v3.1.0-rc1. But beware: After the update, you need to test your login carefully (maybe you also have other authentication providers like Microsoft or Google, you should test them as well).
Technical
The Api changed the version number to v2.8 and the return value from the API is now in JSON-Format and no longer escaped in the URI. The 'old' OWIN packages can not handle this changes.
[Oauth Access Token] Format - The response format of
https://www.facebook.com/v2.3/oauth/access_token returned when you
exchange a code for an access_token now return valid JSON instead of
being URL encoded. The new format of this response is {"access_token":
{TOKEN}, "token_type":{TYPE}, "expires_in":{TIME}}. We made this
update to be compliant with section 5.1 of RFC 6749.
Here you can find the code-changes on GitHub for further informations and better understanding.
A lot of people started having trouble after yesterday. This is due to Facebook dropping support for v2.2 of their API. For some reason their system still redirects auth calls that don't use a version number to the 2.2 API. A quickfix is to ensure that the API version gets sent with the API call.
Starting at v2.3 Facebook also started returning JSON objects. So make sure to change that in the code as well.
I had the same issue, found solution here Fix facebook oauth 2017
Basically, you need to extend HttpClientHandler and decode JSON response instead of body
Here is a solution for those who are using scribe java.
public Token extract(String response)
{
Preconditions.checkEmptyString(response, "Response body is incorrect. Can't extract a token from an empty string");
JSONObject obj = new JSONObject(response);
return new Token(obj.get("access_token").toString(), EMPTY_SECRET, response);
}
Create a new class and set the extractor to JSON.
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.extractors.JsonTokenExtractor;
import org.scribe.model.OAuthConfig;
public class FaceFmApi extends DefaultApi20 {
#Override
public String getAccessTokenEndpoint()
{
return "https://graph.facebook.com/oauth/access_token";
}
#Override
public AccessTokenExtractor getAccessTokenExtractor()
{
return new JsonTokenExtractor();
}
#Override
public String getAuthorizationUrl(OAuthConfig config) {
return null;
}
}
and inject your newly created class as below. Then getAccessToken() will work as expected.
public OAuthService getService() {
return new ServiceBuilder().provider(FaceFmApi.class)
.apiKey(config.getApiKey()).apiSecret(config.getApiSecret())
.callback(config.getCallback()).build();
}
What do we need : How to execute a external POST request on any page?
Why do we need : We are developing a secured intranet portal using Adobe CQ for our client. Any request for any page of Adobe CQ of intranet portal redirects to client's interface. This is an external system which generates a TOKEN and sends this token to CQ as an request parameter via HTTP request with POST method.
We set our cookies based on this token which needs to be part of every page. ( We are using page component inhertiance and setting them on root level)
Need suggestions on how this can be achieved. Let me know if more details are needed.
You can defined what method your servlet accepts, so the GET can be handled with a JSP and the POST with a SlingAllMethodsServlet.
#Component(metatype = false)
#SlingServlet(resourceTypes = "cq:Page", methods = "POST", generateComponent = false)
public class MyPOSTServlet extends SlingAllMethodsServlet {
#Override
protected void doPost(final SlingHttpServletRequest request,
final SlingHttpServletResponse response) throws ServletException,
IOException {
//your logic here
}
}
This should get triggered for all pages.
Regarding your comment below your question, never have a servlet path with /content. Either a fixed virtual path like /bin/myservlet or a resourceType.
Check the documentation from Sling: https://sling.apache.org/documentation/the-sling-engine/servlets.html
community, I was following an example of how to make a service that offers Facebook login on my web api but I can not make it work.
The link for the example. I did try the another example and still not working.
Well, in my AccountController I have the method GetExternalLogin and in the line:
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
The method return the error 401. I don't work with OWIN before, but I want in the method call the Facebook Login API. And this don't call the Facebook login page, just return 401.
I copied all the sample code and not worked. What should I do?
The code in the ChallengeResult:
public class ChallengeResult : IHttpActionResult
{
public string LoginProvider { get; set; }
public HttpRequestMessage Request { get; set; }
public ChallengeResult(string loginProvider, ApiController controller)
{
LoginProvider = loginProvider;
Request = controller.Request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
Request.GetOwinContext().Authentication.Challenge(LoginProvider);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = Request;
return Task.FromResult(response);
}
}
I don't know any thing about OWIN, sorry. I will learn
Returning 401 (Unauthorized) is correct. This is what the External Login provider (Facebook in your case) use to know that have to display the login page.
As I see, you are already following a tutorial, but maybe this one can help you to understand the authentication and authorization process with external providers. This tutorial explains how to authorize with Google and Facebook, but in your case you can skip the Google parts.
I hope this helps.
Hit the same problem, burned the same neurons. After losing enough brain mass, I found the cause in my case: In the query string, I have written Facebook with a small f. When I changed it to a capital F, it started working.
Hope this helps.
I am developing a web application using GXT, Hibernate, mysql etc. There is a login page for the application. Actually I am getting problem to set the login page when the session expires. We can set the timeout in the web.xml file but in that case we can't redirect to login page.Can you tell me how to achieve that.
You can not do a server side redirect because the application is entirely AJAX. What you can do is use the GWT Timer class and for every one of your RPC calls check/reset the timer. If the "session" expires then you do a redirect to the login page via a History token. This was the easiest way for me
Some other reading:
http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/b9eab8daaa993c83/d0192d356045e061?pli=1
http://gwt-ext.com/forum/viewtopic.php?f=9&t=1682
I have used the concept of throwing an exception in the server side when the session expires and then tried to catch the exception in the client side. I don't know whether there is any better way to do that.
On the server side, you can check if the session is expired and if so, throw a custom exception.
On the client side, on every async call you do a check for this known situation and react to it. You can create an abstract class for AsyncCallback that you will subclass for each GWT RPC call:
public abstract class SessionExpiredAwareAsyncCallback<T> implements AsyncCallback<T> {
#Override
public void onSuccess(T returnObject) {
doOnSuccess(returnObject);
}
#Override
public void onFailure(Throwable exception) {
if (exception instanceof SessionExpiredException) {
goToLoginPage();
} else {
doOnFailure(exception);
}
}
public abstract doOnSuccess(T returnObject);
public abstract doOnFailure(Throwable exception);
}
You can use gwteventservice to fire an event from the server to the client.