I trying to understand the codes of Full-stack web-application at https://github.com/callicoder/spring-security-react-ant-design-polls-app
but I do not understand how does spring-boot know which current user is logging in.
this is ReactJS (front-end) code that calls the api.
export function getUserCreatedPolls(username, page, size) {
page = page || 0;
size = size || POLL_LIST_SIZE;
return request({
url: API_BASE_URL + "/users/" + username + "/polls?page=" + page + "&size=" + size,
method: 'GET'
});
}
And, this is spring-boot(back-end) code that receives variables from front-end
#GetMapping("/users/{username}/polls")
public PagedResponse<PollResponse> getPollsCreatedBy(#PathVariable(value = "username") String username,
#CurrentUser UserPrincipal currentUser,
#RequestParam(value = "page", defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) int page,
#RequestParam(value = "size", defaultValue = AppConstants.DEFAULT_PAGE_SIZE) int size) {
return pollService.getPollsCreatedBy(username, currentUser, page, size);
}
how does spring-boot get {UserPrincipal currentUser} from front-end?
how does ReactJs sent {UserPrincipal currentUser} to back-end?
It's a spring boot oauth jwt provider + resource server and ReactJs as the consumer
ReactJs can consume the server resources ( rest api ) by sending and HTTP request, but it should first get an authorization for that (Token)
The server will send JWT token after a success login
then when reacteJs send an HTTP request, it actually inject extra information to the HTTP request which is the authorization token
when the server get this request and before it reach the controller, the request pass throw a chain of filter ( spring security filter chain ) , look at this filter class method in the code link , after a success user authentication calling the SecurityContextHolder class to fill the security context with the current authenticated user ( User Principle ), and finally when the request reach the controller, our security context is filled up
#CurrentUser UserPrincipal currentUser , when you added UserPrincipal currentUser parameter to spring Controller methods, it will fill the object from the context automatically, you can do it by your self by calling the SecurityContextHolder class and get the current authenticated User
...
// Get The Jwt token from the HTTP Request
String jwt = getJwtFromRequest(request);
// Check The validation of JWT - if true the user is trusted
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
/*
Note that you could also encode the user's username and roles inside JWT claims
and create the UserDetails object by parsing those claims from the JWT.
That would avoid the following database hit. It's completely up to you.
*/
// Get the user object
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// Fill the security context with this user
SecurityContextHolder.getContext().setAuthentication(authentication);
...
Related
I developed a Java servlet that allows 2-Factor authentication.
First, I set up an endpoint (Web/authenticate/credentials) that accepts user credentials, checks if they exist in the database, in which case it returns the corresponding user challenge in the response, and set the user account ID in the session attribute.
Then, when the user receives the challenge, they provide a secret and send a request to a different endpoint (Web/authenticate/secret), which first checks if the requests is associated with any session, and then retrieves the user account ID from the session attribute to complete the authentication.
All this works well, when testing with Postman, but when I write some unit tests with MockMvc, a new session is always created as I send the secret to the second endpoint (Web/authenticate/secret), even when I explicitly set the SESSION cookie header.
Below is a snippet of the unit test that fails:
MvcResult preAuthMvcResult = preAuthenticateUser(deviceAlias, userPin);//This calls the first endpoint
// Get session cookie to send in next request header
String sessionCookie = preAuthMvcResult.getResponse().getHeader(HttpHeaders.SET_COOKIE);
JSONObject userCredentialsJson = new JSONObject();
userCredentialsJson.put("secret", secret);
// HTTP request
HttpHeaders httpHeader = new HttpHeaders();
httpHeader.add("x-api-key", apiKey);
httpHeader.add(PRE_AUTH_HEADER, accountIdHash);
httpHeader.add(HttpHeaders.COOKIE, sessionCookie);
// Act
MvcResult mvcResult = mockMvc
.perform(post("/Web/authenticate/secret").contentType(MediaType.APPLICATION_JSON_VALUE)
.headers(httpHeader).content(userCredentialsJson.toString()))
.andReturn();
// Assert
int responseStatus = mvcResult.getResponse().getStatus();
// This fails the test because a new session is created, and the authication cannot proceed, as the server lost track of the authenticating user
assertThat(responseStatus == HttpStatus.OK.value()).isTrue();
I am developing a social Twitter app for which I am using Spring Boot 2.2.2. I am following the instructions here https://careydevelopment.us/2017/05/24/implement-twitter-login-solution-spring-boot/.
I have just modified my code to be more REST oriented instead of RedirectView(as done in above link)
#PostMapping("/getToken")
public ResponseEntity<TwitterTokenResponse> getToken(HttpServletRequest request) throws Exception {
Twitter twitter = getTwitter();
String callbackUrl = "http://localhost:3000/settings";
RequestToken requestToken = twitter.getOAuthRequestToken(callbackUrl);
request.getSession().setAttribute("requestToken", requestToken);
*//setting twitter attribute in session*
request.getSession().setAttribute("twitter", twitter);
String twitterUrl = requestToken.getAuthorizationURL();
TwitterTokenResponse twitterTokenResponse = new TwitterTokenResponse(requestToken.getToken(), requestToken.getTokenSecret(), true, 0L, null);
log.info("Authorization url is " + twitterUrl);
log.info("Request token is " + requestToken);
return ResponseEntity.ok(twitterTokenResponse);
}
#PostMapping("/twitterCallback")
public ResponseEntity<TwitterTokenResponse> twitterCallback(#RequestParam(value = "oauth_verifier", required = false) String oauthVerifier, #RequestParam(value = "denied", required = false) String denied, HttpServletRequest request) throws Exception {
if (denied != null) {
log.error("Could not get token from Twitter! Access denied");
return null;
}
*//Getting twitter attribute back from session but it is null*
Twitter twitter = (Twitter) request.getSession().getAttribute("twitter");
RequestToken requestToken = (RequestToken) request.getSession().getAttribute("requestToken");
AccessToken token = twitter.getOAuthAccessToken(requestToken, oauthVerifier);
request.getSession().removeAttribute("requestToken");
TwitterTokenResponse twitterTokenResponse = new TwitterTokenResponse(token.getToken(), token.getTokenSecret(), true, token.getUserId(), token.getScreenName());
log.info("Access token is " + token);
return ResponseEntity.ok(twitterTokenResponse);
}
As can be seen in getToken() method I am setting the twitter variable in session and trying to get the same variable from session in twitterCallback() method. the twitter attribute I am getting from the session is always coming null. Any reasons why it is like that and what can be done to solve this?
Thank you
First - please check your sessionid in both controller methods, they must be the same. If they are not, so obviously you have a new session that means there is no "twitter" attribute on it.
Second - There may be a time period between your requests (between the first request to get token and second to fetch it), that can cause the session to kill and generate a new one that doesn't have "twitter" attribute anymore, So Please check your session timeout also.
everyone!
I'm new to Oauth2 and I've had different approaches with it.
I have a doubt. I'm actually building a Provider Server with Spring Security and I have an external access token provider (Google and AWS Cognito).
I know the process to get the access token following the code grant flow (Which is the one I want to implement). I built an Android app that gets the code and changes it for the access token.
My question is:
How do I validate that the token I'm sending to the Provider Server is a valid one using Spring Security to also access the protected resources that the server has?
Thank you in advance.
I think there are two alternatives either u get the public key and verify the token urself or maybe they have an endpoint where you can send the token and know if its a valid one or not.
Something like this
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
// Specify the CLIENT_ID of the app that accesses the backend:
.setAudience(Collections.singletonList(CLIENT_ID))
// Or, if multiple clients access the backend:
//.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
.build();
// (Receive idTokenString by HTTPS POST)
GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
Payload payload = idToken.getPayload();
// Print user identifier
String userId = payload.getSubject();
System.out.println("User ID: " + userId);
// Get profile information from payload
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
// Use or store profile information
// ...
} else {
System.out.println("Invalid ID token.");
}
Link: https://developers.google.com/identity/sign-in/web/backend-auth
What I want to do: I want to test my endpoint using RestAssured. The key is that the endpoint is available only for users who are logged in. For logging in I'm using spring security default endpoint with custom successHandler in which I'm setting some random token, saving it to database and returning in header "User-Token". I'm not creating a session on the back end. When I want to access a secured endpoint, front-end makes a call to it, with "User-Token" header. Then I'm using the token for checking in the database. Each token is different and random. Also I don't use any spring-security things for token. Now I want to test this behavior.
Technologies: React & Redux, Spring Boot, RestAssured, JUnit, Tomcat
What's not working: First of all, I'm not really sure how to obtain the token. I mean I can force it by hand to database to some test user, but AFAIK it's a bad bad practice. I read the documentation and come across part about auth().form. But below it was mentioned that it's not the best approach as have to made to the server in order to retrieve the webpage with the login details and it's not possible - webpage is totally separated from backend. I did try the approach nevertheless but it didn't work.
#Before
public void LogInUser(){
String loginUrl = "http://localhost:8080/login";
userToken =
given().auth().form("username","password").
when().get(loginUrl).getHeader("User-Token");
System.out.println(userToken);
}
So then I thought that maybe I don't need auth() at all - I don't need session, so calling the endpoint itself with data should be enough. I checked how data is passed from front-end to back-end and did this:
Form Data: username=something&password=something
#Before
public void LogInUser(){
String loginUrl = "http://localhost:8080/login";
userToken =
given().parameter("username=oliwka&password=jakies")
.when().get(loginUrl).getHeader("User-Token");
System.out.println(userToken);
}
And while it's passing, userToken is null. It's declared as class variable not method variable and it's String.
How can I obtain token for user and test my endpoint for which I need a token?
You can use below procedure to get the access token.
Step 1 : Create a method that will accept a json string and parse the data and return the access token. below is the method. You can use your preferable json parser library.
public String getAccessToken(String jsonStr) {
JSONParser parser = new JSONParser();
Object obj = null;
try {
obj = parser.parse(jsonStr);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject jsonObject = (JSONObject) obj;
String accessToken = (String) jsonObject.get("access_token");
System.out.println("access_token : " + accessToken);
return accessToken;
}
Step 2 : Now call your login api with username and password like below
String loginUrl = "http://localhost:8080/login";
Response res = null;
String returnValue = "";
response = given().param("username", "yourUserName")
.param("password", "yourpassword")
.param("client_id", "If Any otherwise skip it")
.param("grant_type", "If Any otherwise skip it")
.param("clear_all", "true")
.post(loginUrl);
returnValue = response.body().asString();
String accessToken = getAccessToken(returnValue);
Please let me know if you can get your desired access token.
I have an ASP.NET Web API project and on initial user login, the username and password are sent in an http header over SSL and validated by the server.
The server creates a database record with the UserId, a randmon 64 character string (UserToken), expiration date and the client IP address.
The UserToken is then sent back to the client and then be stored in a cookie.
All subsequent requests send the UserToken in an http header and that is validated using the calling IP address by the server.
This way, the username and password are only sent once, and all calls using the UserToken are logged.
I have created two custom DelegatingHandlers - LoginAuthenticationHandler, and TokenAuthenticationHandler - which process the http headers and send an appropriate 200 or 400 http response.
////////////////
Seems my only problem is that I want the LoginAuthenticationHandler to also return the UserToken to the client, so it can store the cookie.
Sorry for the verbosity :-\
Also - I'm new to Web API - so maybe this is not the best place for this to be done - but it would be very convenient if the UserToken can be passed back to the LoginController in this way.
Thanks for any input :-)
Some related SO posts:
DelegatingHandler for response in WebApi
Is it possible to pass data from DelegatingHandler to Controller in ASP.NET Web API?
////////////////
public class LoginAuthenticationHandler : DelegatingHandler
{
public const string BasicScheme = "Basic";
public const string ChallengeAuthenticationHeaderName = "WWW-Authenticate";
public const char AuthorizationHeaderSeparator = ':';
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Get Authorization Http Header
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// Check if Basic Authentication
if (authHeader.Scheme != BasicScheme)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// Decode UserName + Password from Http Header
var encodedCredentials = authHeader.Parameter;
var credentialBytes = Convert.FromBase64String(encodedCredentials);
var credentials = Encoding.ASCII.GetString(credentialBytes);
var credentialParts = credentials.Split(AuthorizationHeaderSeparator);
if (credentialParts.Length != 2)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
var username = credentialParts[0].Trim();
var password = credentialParts[1].Trim();
// Authenticate Username + Password and Return UserToken
var userId = new Users().GetUserIdFromUserNamePassword(username, password);
if (userId == 0)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// User is Authorized - Create New UserToken
var ipAddress = HttpContext.Current.Request.UserHostAddress;
var userToken = new Users().CreateUserToken(ipAddress, userId);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
//======================================================
// Return UserToken to Login Controller to be Stored as Cookie on the Client
// response.Content = userToken ??
// maybe set header for userToken ??
// HttpRequestMessage Properties ??
return response;
//======================================================
});
}
private static Task<HttpResponseMessage> CreateUnauthorizedResponse()
{
// Send Back Http Unauthorized if Authentication Fails
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.Add(ChallengeAuthenticationHeaderName, BasicScheme);
var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
taskCompletionSource.SetResult(response);
return taskCompletionSource.Task;
}
}
}
Generally, HTTP services are stateless and the concept of login does not apply. LoginController is for the MVC controllers and not web API. What you are trying to do is not a good practice, even though it is technically possible to achieve.
If you really want to do what you are trying to do, do not think along the lines of sending the session data (what you call the user token) to LoginController. You can write the cookie into the response from your message handler itself. See this. You must only store encrypted data into a cookie in that case. Instead of creating your own cookie and all that, you can use Forms Authentication and create a cookie with FA ticket. See this.
BTW, it is possible and easy to spoof client IP addresses.
Perhaps you could login using the controller without using a DelegatingHandler: you could return the token to the client to be added to the header of future API calls, or add it to the header in the controller using the Request.Headers.Add function.
Then you would not need two custom DelegatingHandlers, the TokenAuthenticationHandler would be sufficient. But you would want to specify that all requests other than the initial login are funneled through the TokenAuthenticationHandler.
To do that, you will need to customize the WebAPI routes. In the default Web API projects, this is currently done in the WebApiConfig.Register method in WebApiConfig.cs (called from Global.asax.cs). First, have all your API calls route through your TokenAuthenticationHandler; then add the login route plainly such that it does not funnel through your TokenAuthenticationHandler:
//this message handler chain is used to put TokenAuthenticationHandleron all API requests and not Login
DelegatingHandler[] handlers = new DelegatingHandler[] {
new TokenAuthenticationHandler()
};
var routeHandlers = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(config), handlers);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}",
defaults: null,
constraints: null,
handler: routeHandlers
);
//login route
config.Routes.MapHttpRoute(
name: "Login",
routeTemplate: "login/{action}",
defaults: new { Controller = "Login" }
);
Now, you can validate the token in the TokenAuthenticationHandler using request.Headers.TryGetValues to get it:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
//token validation
IEnumerable<string> foundValues = null;
if (request.Headers.TryGetValues("AuthenticationToken", out foundValues))
{
if (foundValues.Count() == 1)
{
string token = foundValues.Single();
AuthenticationDAO dao = new AuthenticationDAO();
if (dao.AuthenticateUser(token))
{
//add values to request.Properties for use in Web API controllers
request.Properties.Add(new KeyValuePair<string, object>("SomeValue", 4));
//Engage!
return base.SendAsync(request, cancellationToken);
}
}
}
//fail if token not present or not valid
var tcs = new TaskCompletionSource<HttpResponseMessage>();
tcs.SetResult(new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent("Missing or invalid authorization token.")
});
return tcs.Task;
}
As per your original question of passing values from the DelegatingHandler to the Controller, that is easily possible using the request.Properties.Add function as demonstrated above.
Some additional considerations:
I am not sure that sending the login credentials in the header is any
more secure than just as content in the request, since it is all over
SSL.
You should consider implementing an AntiForgeryToken. This
article is a good starter, and this SO post points out how
you could use DelegatingHandler to also only check for it on web
requests (allowing your api to be accessed from native apps).
You can easily add a DelegatingHandler that applies to all requests
that enforces HTTPS.
Hope that helps. What I've outlined is the way I'm doing it, so I hope for some comments if it's wrong.