Spring boot how to store multiple sessions in a web app - java

I am attempting to make a web app where the user is required to login via Reddit. For this I am using JRAW, where the main object used is RedditClient.
I am slightly confused how I should handle tracking these multiple clients for the users who are logged in. I have a working application, but I think I'm going about storing these the wrong way.
Auth.class
public class Auth {
private static final String URL = "http://localhost:4200/";
private final UUID id = UUID.randomUUID();
private final RedditClient redditClient = getDefaultRedditClient();
private final Credentials credentials = getWebappCreds();
private final URL authUrl = getClientAuthURL();
public UUID getId(){
return id;
}
public URL getAuthUrl(){
return authUrl;
}
public AuthStatus getOAuthStatus(){
return redditClient.getOAuthHelper().getAuthStatus();
}
#JsonIgnore
public RedditClient getRedditClient(){
return redditClient;
}
#JsonIgnore
public Credentials getWebappCreds(){
return Credentials.webapp("<WEB_APP_ID>", "<WEB_APP_SECRET>", URL);
}
private URL getClientAuthURL(){
URL url = redditClient.getOAuthHelper().getAuthorizationUrl(credentials, true, "history");
return url;
}
public void auth(String state, String code) throws NetworkException, OAuthException, IllegalStateException {
System.out.println("auth - state: " + state + ", code: " + code);
String url = URL + "?state=" + state + "&code=" + code;
System.out.println("auth - url: " + url);
auth(url);
}
private void auth(String redirectURL) throws NetworkException, OAuthException, IllegalStateException {
OAuthData data = redditClient.getOAuthHelper().onUserChallenge(redirectURL, credentials);
redditClient.authenticate(data);
}
private static RedditClient getDefaultRedditClient(){
UserAgent myUserAgent = UserAgent.of("desktop", "io.rj93.reddit.search", "v0.1", "rj93");
return new RedditClient(myUserAgent);
}
}
AuthController.class
package io.rj93.reddit.search.server;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import net.dean.jraw.http.NetworkException;
import net.dean.jraw.http.oauth.OAuthException;
#RestController
#RequestMapping("api/v1")
#CrossOrigin
public class AuthController {
private static ConcurrentMap<String, Auth> auths = new ConcurrentHashMap<String, Auth>();
#RequestMapping("/auth")
public Auth getAuth(){
Auth auth = new Auth();
auths.put(auth.getId().toString(), auth);
return auth;
}
#RequestMapping(value = "/auth", method = RequestMethod.POST)
public Auth authenticate(#RequestBody Map<String, String> payload) throws NetworkException, OAuthException, IllegalStateException{
Auth auth = auths.get(payload.get("id"));
auth.auth(payload.get("state"), payload.get("code"));
return auth;
}
}
The way it works is the website requests a new auth at getAuth, which creates a new reddit client and begins the auth process. This is stored in a Map, with the key being a UUID generated, which is returned to the user and sent in all further requests to the server. The website redirects to reddit to allow the user to grant permissions, and is then redirected to my website, where the values to authenticate are sent to the server in authenticate.
How should I actually be storing this? The library doesn't allow me to instantiate a RedditClient using preset data, so I have to go through this process of redirecting to reddit, etc.

Related

MongooseIM authentication with JWT and send message (XMPP)

MongooseIM has a provision to use JWT instead of username and password for authorization.
On the server-side, the docs suggest to modify the mongooseim.toml file (can be found at /etc/mongooseim/mongooseim.toml)
[auth]
methods = ["jwt"]
[auth.jwt]
secret.value = "top-secret123"
algorithm = "HS256"
username_key = "user"
But how does then one authenticate from Gajim or from Java code?
Let's first understand what is happening behind the scenes.
Instead of passing the username-password pair. We create a JWT token and send that. JWT tokens are stateless, which means if one has the secret key, one can decode and encode the token to/from the original message.
Here is a working code in Java. We generate the JWT token and send that token instead of the password. To generate the JWT token, we have used Auth0 (you will need to add this in classpath). Link to the maven project.
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import javax.net.ssl.X509TrustManager;
import java.net.InetAddress;
import java.util.Date;
public class JWTMain {
private final static String senderUsername = "jatin";
private final static String senderPassword = "abcd";
private final static String sendTo = "dad";
public static void main(String[] args) throws Exception {
Algorithm algorithm = Algorithm.HMAC256("top-secret123");
String token = JWT.create()
.withClaim("user", senderUsername) // they key needs to match with `username_key` in mongooseim.toml file
.withClaim(senderUsername, senderPassword)
.sign(algorithm);
System.out.println("Token generated: " + token);
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setSecurityMode(ConnectionConfiguration.SecurityMode.required)
.setUsernameAndPassword("jatin", token)
.setXmppDomain(JidCreate.domainBareFrom("localhost"))
.setHostAddress(InetAddress.getByName("localhost"))
.setPort(5222)
.setCustomX509TrustManager(new TrustAllManager())
.addEnabledSaslMechanism("PLAIN")
.build();
AbstractXMPPConnection connection = new XMPPTCPConnection(config);
AbstractXMPPConnection connect = connection.connect();
connection.login();
sendMessage("This message is being sent programmatically? " + new Date(), sendTo + "#localhost", connect);
}
private static void sendMessage(String body, String toJid, AbstractXMPPConnection mConnection) throws Exception {
Jid jid = JidCreate.from(toJid);
Chat chat = ChatManager.getInstanceFor(mConnection)
.chatWith(jid.asEntityBareJidIfPossible());
chat.send(body);
System.out.println("Message sent to : " + toJid);
}
}
class TrustAllManager implements X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
}
If you wish to login to Gajim with the JWT token:
The above program outputs the JWT token. You can use that token and provide the token in the password field.

Post request to API using Jersey

I'm very new to web-service dev and I'm trying to make a POST request to an API using Jersey. The issue is I think I'm mixing documentation and example I'm finding online between client & server. I'm pretty sure that it's simple but I can't figure out why my code is failing.
Here is my main Class :
import deliveryPayload.Payload;
import jakarta.ws.rs.*;
import jakarta.ws.rs.client.*;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;
import org.apache.commons.lang3.StringUtils;
import responsePayload.ResponsePayload;
import java.net.URI;
import java.util.*;
#Path("/hook")
public class Hook {
private static final String apiToken = "myToken";
private static final String domain = "url";
private static final String apiUrl = "https://" + domain + "/api/v1/";
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response eventHook(String body, #HeaderParam("Pass") String password) {
ObjectMapper objectMapper = new ObjectMapper();
Payload payload = new Payload();
try {
payload = objectMapper.readValue(body, Payload.class);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
EventsItem event = payload.getData().getEvents().get(0);
Actor actor = event.getActor();
Response response = ClientBuilder.newClient()
.target(getBaseURI())
.path("apps/" + "someID" + "/users")
.request(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, apiToken)
.post(Entity.entity(actor, MediaType.APPLICATION_JSON));
return response;
}
}
I'm getting this error Parse Error: The response headers can't include "Content-Length" with chunked encoding when using Postman.
Thanks for any help !

how to get claims value from JWT token authentication

I have set claims in JWT token in the token provider. now I want to get claim value through authentication when API is hit.
I have checked in Principal, details, credential, authorities but I am not getting claims in any of them.
Claims claims = Jwts.claims().setSubject(authentication.getName());
claims.put(AUTHORITIES_KEY, authorities);
claims.put("userId", userRepo.findUserIdByUsername(authentication.getName()));
return Jwts.builder()
.setSubject(authentication.getName())
.setClaims(claims)
//.claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS512, SIGNING_KEY)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
.compact();
I want to get "userId" claim from the authentication or any other way to get claims value from token.
This is how I read Claim from Token
private Claims getAllClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("Could not get all claims Token from passed token");
claims = null;
}
return claims;
}
I am using this for JWT
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
More detail here
Edit 1:
Adding Filter to get token from Request and Validate
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
public class TokenAuthenticationFilter extends OncePerRequestFilter {
protected final Log logger = LogFactory.getLog(getClass());
private TokenHelper tokenHelper;
private UserDetailsService userDetailsService;
public TokenAuthenticationFilter(TokenHelper tokenHelper, UserDetailsService userDetailsService) {
this.tokenHelper = tokenHelper;
this.userDetailsService = userDetailsService;
}
#Override
public void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws IOException, ServletException {
String username;
String authToken = tokenHelper.getToken(request);
logger.info("AuthToken: "+authToken);
if (authToken != null) {
// get username from token
username = tokenHelper.getUsernameFromToken(authToken);
logger.info("UserName: "+username);
if (username != null) {
// get user
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}else{
logger.error("Something is wrong with Token.");
}
}
chain.doFilter(request, response);
}
}
It should help.
You should be able to retrieve a claims like this within your controller
var identity = HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
// or
identity.FindFirst("ClaimName").Value;
}
If you wanted, you could write extension methods for the IPrincipal interface and retrieve claims using the code above, then retrieve them using (for example)
HttpContext.User.Identity.MethodName();
For completeness of the answer. To Decode the JWT token let's write a method to validate the token and extract the information.
public static ClaimsPrincipal ValidateToken(string jwtToken)
{
IdentityModelEventSource.ShowPII = true;
SecurityToken validatedToken;
TokenValidationParameters validationParameters = new TokenValidationParameters();
validationParameters.ValidateLifetime = true;
validationParameters.ValidAudience = _audience.ToLower();
validationParameters.ValidIssuer = _issuer.ToLower();
validationParameters.IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Secret));
ClaimsPrincipal principal = new JwtSecurityTokenHandler().ValidateToken(jwtToken, validationParameters, out validatedToken);
return principal;
}
Now we can validate and extract the Claims by using:
ValidateToken(tokenString)?.FindFirst("ClaimName")?.Value
You should note that the ValidateToken method will return null value if the validation fails.
Using Spring Security 5 you can use #AuthenticationPrincipal org.springframework.security.oauth2.jwt.Jwt token as parameter in your controller method. And then call token.getClaims()
It would be recommended to refer the blog given below. It explained how the JWT token works in spring boot
https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
List out all the claims using JWT
private void listClaimUsingJWT(String accessToken) {
try {
SignedJWT signedJWT = SignedJWT.parse(accessToken);
JWTClaimsSet claimsSet= signedJWT.getJWTClaimsSet();
Map<String,Object> myClain =claimsSet.getClaims();
String[] keySet = myClain.keySet().toArray(new String[0]);
Log.d("JWT_Claims", "loadAllOptionalClaim JWT keySetSize "+keySet.length);
for (String s : keySet) {
Log.d("JWT_Claims", "loadAllOptionalClaim JWT key ==> " + s + " ====> " + myClain.get(s));
}
} catch (Exception e) {
e.printStackTrace();
}
}
If it is in quarkus, we can get it by injecting JSONWebToken:
/**
* Injection point for the ID Token issued by the OpenID Connect Provider
*/
#Inject
#IdToken
JsonWebToken idToken;
In Java, Keys for claim in keycloak provided by JSONWebToken can be accessed via getClaimNames() method. Following is an example:
Set<String> allClaims = this.idToken.getClaimNames();
for (String claimName: allClaims) {
System.out.println("Claim name: " + claimName);
}
Ref: https://quarkus.io/guides/security-openid-connect-web-authentication

Spring Boot OAuth2: extract JWT from cookie for authentication

I'm using spring boot to build a simple auth process for my app.
I have AuthorizationServerConfig & ResourceServerConfig setup, my frontend is a SPA. When I hit /oauth/token route, I got a JWT back which I previously stored in localStorage, and when I try to hit the resource server route, I have authorization header setup with this JWT, everything works.
But now I want to do authorization with JWT stored in the cookie, how I can config it so that it works with my current authorization/resource server config? I googled for a while and the best I can find is to set up a customize token extractor, but I'm not sure how to get it right, thank you in advance.
-------------- update --------------
I have #EnableAuthorizationServer and #EnableResourceServer on, and the EnableResourceServer setup an OAuthAuthenticationProcessingFilter automatically, this filter user bearer header authentication which uses a bearer token extractor to extract from the request header, I looked at the source code, it's hardcoded into the library, how I can customize this filter to extract JWT from the cookie?
Read cookie value from the request object and parse jwt manually.
Here is sample code
public Jws<Claims> parseJWT(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, "Token cookie name");
if(cookie == null) {
throw new SecurityException("Token not found from cookies");
}
String token = cookie.getValue();
return Jwts.parser().setSigningKey("your signing Key").parseClaimsJws(token);
}
you can create request filter and check jwt.
There are many implementation for JWT. Am using this. io.jsonwebtoken
I am adding a Token Helper Class which has methods to validate, generate, refresh token. You can focus on the JWT extraction part.
Jar Dependency
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
JWT Helper Class. It contains methods to validate, refresh, generate token.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.test.dfx.common.TimeProvider;
import com.test.dfx.model.LicenseDetail;
import com.test.dfx.model.User;
#Component
public class TokenHelper {
protected final Log LOGGER = LogFactory.getLog(getClass());
#Value("${app.name}")
private String APP_NAME;
#Value("${jwt.secret}")
public String SECRET; // Secret key used to generate Key. Am getting it from propertyfile
#Value("${jwt.expires_in}")
private int EXPIRES_IN; // can specify time for token to expire.
#Value("${jwt.header}")
private String AUTH_HEADER;
#Autowired
TimeProvider timeProvider;
private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512; // JWT Algorithm for encryption
public Date getIssuedAtDateFromToken(String token) {
Date issueAt;
try {
final Claims claims = this.getAllClaimsFromToken(token);
issueAt = claims.getIssuedAt();
} catch (Exception e) {
LOGGER.error("Could not get IssuedDate from passed token");
issueAt = null;
}
return issueAt;
}
public String getAudienceFromToken(String token) {
String audience;
try {
final Claims claims = this.getAllClaimsFromToken(token);
audience = claims.getAudience();
} catch (Exception e) {
LOGGER.error("Could not get Audience from passed token");
audience = null;
}
return audience;
}
public String refreshToken(String token) {
String refreshedToken;
Date a = timeProvider.now();
try {
final Claims claims = this.getAllClaimsFromToken(token);
claims.setIssuedAt(a);
refreshedToken = Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
} catch (Exception e) {
LOGGER.error("Could not generate Refresh Token from passed token");
refreshedToken = null;
}
return refreshedToken;
}
public String generateToken(String username) {
String audience = generateAudience();
return Jwts.builder()
.setIssuer( APP_NAME )
.setSubject(username)
.setAudience(audience)
.setIssuedAt(timeProvider.now())
.setExpiration(generateExpirationDate())
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
}
private Claims getAllClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("Could not get all claims Token from passed token");
claims = null;
}
return claims;
}
private Date generateExpirationDate() {
long expiresIn = EXPIRES_IN;
return new Date(timeProvider.now().getTime() + expiresIn * 1000);
}
public int getExpiredIn() {
return EXPIRES_IN;
}
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
return (
username != null &&
username.equals(userDetails.getUsername()) &&
!isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
);
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
public String getToken( HttpServletRequest request ) {
/**
* Getting the token from Authentication header
* e.g Bearer your_token
*/
String authHeader = getAuthHeaderFromHeader( request );
if ( authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
public String getAuthHeaderFromHeader( HttpServletRequest request ) {
return request.getHeader(AUTH_HEADER);
}
}
Finally your controller class
public void validateToken(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, "TOKEN_NAME");
if(cookie == null) {
throw new SecurityException("JWT token missing");
}
String token = cookie.getValue(); // JWT Token
Claims claims = TokenHelper.getAllClaimsFromToken(token); // claims will be null if Token is invalid
}

2-legged OAuth and the Gmail atom feed

We're trying to get 2-legged OAuth to work with the Gmail atom feed. We're using the Java library contributed by John Kristian, Praveen Alavilli and Dirk Balfanz. [http://oauth.net/code/] instead of the GData library.
We know we have the correct CONSUMER_KEY and CONSUMER_SECRET, etc. becuase it works with the Contacts feed (http://www.google.com/m8/feeds/contacts/default/full) and have no problems. However with Gmail atom feed it always returns: HTTP/1.1 401 Unauthorized
Any ideas? Should we try a different OAuth framework or does the problem lie on the Google side?
We think we got it working with the OAuth libraries but not with the GData library.
Snippet of code is:
import static net.oauth.OAuth.HMAC_SHA1;
import static net.oauth.OAuth.OAUTH_SIGNATURE_METHOD;
import java.net.URL;
import java.util.List;
import java.util.Map;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
import net.oauth.ParameterStyle;
import net.oauth.SimpleOAuthValidator;
import net.oauth.client.OAuthClient;
import net.oauth.client.httpclient4.HttpClient4;
/**
* Sample application demonstrating how to do 2-Legged OAuth in the Google Data
* Java Client. See the comments below to learn about the details.
*
*/
public class GmailAtomFeed2LeggedOauth {
public static String CONSUMER_KEY = "test-1001.com";
public static String CONSUMER_SECRET = "zN0ttehR3#lSecr3+";
public static String SCOPE = "https://mail.google.com/mail/feed/atom";
public static String RESOURCE_URL = "https://mail.google.com/mail/feed/atom";
public static String SERVICE_NAME = "mail";
public static String username = "username";
public static boolean debug = true;
public static void main(String[] args) throws Exception {
// This should be passed in as a parameter
String user = username + "#" + CONSUMER_KEY;
OAuthConsumer consumer = new OAuthConsumer(null, CONSUMER_KEY, CONSUMER_SECRET, null);
OAuthAccessor accessor = new OAuthAccessor(consumer);
// HMAC uses the access token secret as a factor,
// and it's a little less compute-intensive than RSA.
accessor.consumer.setProperty(OAUTH_SIGNATURE_METHOD, HMAC_SHA1);
// Gmail only supports an atom feed
URL atomFeedUrl = new URL(SCOPE +"?xoauth_requestor_id=" + user);
System.out.println("=====================================================");
System.out.println("Building new request message...");
OAuthMessage request = accessor.newRequestMessage(OAuthMessage.GET, atomFeedUrl.toString(),null);
if (debug) {
List<Map.Entry<String, String>> params = request.getParameters();
for (Map.Entry<String, String> p : params) {
System.out.println("'" + p.getKey() + "' = <" + p.getValue() + ">");
}
System.out.println("Validating message...");
SimpleOAuthValidator validator=new SimpleOAuthValidator();
validator.validateMessage(request,accessor);
}
OAuthClient client = new OAuthClient(new HttpClient4());
System.out.println("Client invoking request message...");
System.out.println(" request: " + request);
OAuthMessage message = client.invoke(request, ParameterStyle.AUTHORIZATION_HEADER);
System.out.println("=====================================================");
System.out.println(" message: " + message.readBodyAsString());
System.out.println("=====================================================");
}
}
Put the OAuth data in the Authorization header, not on the URI.

Categories

Resources