I am trying get a JWT access token from WSO2 IS. I followed instructions from msf4j Oauth2 Security Sample, and managed to get a JWT acces token by resource owner password grant type.
but I have problem authenticating the token externally.
it seems that the token had not been signed by the default "wso2carbon.jks".
also, my claim configurations in the "service providers" was not reflected in jwt content
so my questions: how to config the JWT signing certificate in WSO2IS?
and also:
How to manipulate the claims in the JWT?
I do not want to turn to the "introspect" endpoint out of performance concern, and my strategy is to just trust the IS, only to make sure(locally) of the authenticity of the JWT token
please advise
thanks
You can follow [1] to get JWT Access Tokens(Self contained access tokens) using WSO2 Identity Server
[1] https://medium.com/#hasinthaindrajee/self-contained-access-tokens-with-wso2-identity-server-82111631d5b6
well, it seems to be my own fault.
I had been using the jose4j JWT package, and kept getting verification failed message.
after further checking into the msf4j implementation, I switched over to nimbus-jose-jwt JWT package, and got it done,
below are my implementation.
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class JwtParser {
private static final String KEYSTORE = System.getProperty("javax.net.ssl.trustStore");
private static final String KEYSTORE_PASSWORD = System.getProperty("javax.net.ssl.trustStorePassword");
private static Map<String, JWSVerifier> verifiers = getVerifiers();
public static JWTClaimsSet verify(String jwt) throws Exception {
SignedJWT signedJWT = SignedJWT.parse(jwt);
if (!new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) {
new Exception("token has expired");
}
boolean notYet = true;
for(Iterator<JWSVerifier> it = verifiers.values().iterator(); notYet && it.hasNext();){
JWSVerifier verifier = it.next();
notYet = !signedJWT.verify(verifier);
}
if(notYet){
throw new Exception("token verification failed");
}
JWTClaimsSet claims = signedJWT.getJWTClaimsSet();
if (claims == null) {
// Do something with claims
throw new Exception("non valid payload in token, failed");
}
return claims;
}
private static Map<String, JWSVerifier> getVerifiers(){
Map<String, JWSVerifier> verifiers = new HashMap<>();
try (InputStream inputStream = new FileInputStream(KEYSTORE)) {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(inputStream, KEYSTORE_PASSWORD.toCharArray());
Enumeration<String> aliases = keystore.aliases();
while(aliases.hasMoreElements()){
String alias = aliases.nextElement();
if(!keystore.isCertificateEntry(alias)){
continue;
}
Certificate cert = keystore.getCertificate(alias);
if(cert == null){
continue;
}
PublicKey key = cert.getPublicKey();
verifiers.put(alias, new RSASSAVerifier((RSAPublicKey)key));
}
}catch(KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e){
//TODO: report the exception
}
return verifiers;
}
}
Related
I have a .netcore 3 project (WorkerService Template) which sends JSON data to a REST endpoint. The requests are sent via a HttpClient and configured to use a client certificate which the server requires. The server response is always 200 and HTML characters. According to the server managers the request is redirected to the home page of the web server, because the client machine is being correctly handled with a specific user but no certificate is available. I am using the following code:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("client").ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
X509Certificate2 certificate = GetCertificate(Configuration.CertificateSubjectKeyIdentifier);
handler.ClientCertificates.Add(certificate);
return handler;
}
}
GetCertificate retrieves the certificate from the Certificate Store:
private X509Certificate2 GetCertificate(string subjectIdentifier)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var collection = store.Certificates;
var certificates = collection.Find(X509FindType.FindBySubjectKeyIdentifier, subjectIdentifier, true);
foreach (var certificate in certificates)
{
if (DateTime.Compare(DateTime.Parse(certificate.GetExpirationDateString()), DateTime.Now) >= 0)
{
Logger.LogInformation($"Loaded X.509 certificate {certificate.Subject} issued by {certificate.Issuer}, valid from {certificate.GetEffectiveDateString()} to {certificate.GetExpirationDateString()}");
return certificate;
}
}
Logger.LogError($"X.509 certificate not loaded: No valid certificate could be found.");
return null;
}
Code which sends a request:
public async Task<ResponseData> PostAsync<T>(string url, T dataToSend)
{
ResponseData result = null;
HttpResponseMessage httpResponseMessage = null;
try
{
var errorHttp = false;
HttpClient httpClient;
using (httpClient = HttpClientFactory.CreateClient("client)) // IHttpClientFactory initialized in ctor
{
HttpContent httpContent;
using (httpContent = CreateJsonHttpContent(dataToSend, MediaType.ApplicationJson)) //build JSON from data
{
httpResponseMessage = await httpClient.PostAsync(url, httpContent).ConfigureAwait(false);
result = BuildResponseData(httpResponseMessage); //writes response data in a class
if (httpResponseMessage?.IsSuccessStatusCode == true)
{
result.Content = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else
{
errorHttp = true;
}
if (errorHttp)
{
var httpRequestException = new HttpRequestException($"The http request to {url} was not successful.");
Logger.LogError($"{httpRequestException.Message} : {httpRequestException.InnerException}");
Logger.LogError(httpRequestException.StackTrace);
}
}
}
}
catch (SocketException socketException)
{
Logger.LogError($"{socketException.Message} : {socketException.InnerException}");
result = new ResponseData(socketException);
}
catch (WebException wex)
{
Logger.LogError($"{wex.Message} : {wex.InnerException}");
if (wex.Status == WebExceptionStatus.ConnectFailure || wex.Status == WebExceptionStatus.Timeout)
{
Logger.LogError($"Cannot connect to the rest service : {WebExceptionStatus.Timeout}");
}
}
catch (Exception ex)
{
LogException(ref ex);
result = new ResponseData(ex);
}
finally
{
httpResponseMessage?.Dispose();
}
return result;
}
The class which uses the PostAsync method is also registered in the ServiceCollection. Any ideas what could be wrong here? Could it also be that the certificate is not being handled correctly on the server side?
My strong suspection is the misconfiguration on client (your) end. Your application reads for certificate from LocalMachine store. By default, only local administrator and SYSTEM account can read/use private keys for certificates installed in LocalMachine store.
Either, install the certificate in CurrentUser store of a user account under which the client application is running, or explicitly grant private key permissions to user account under which the client application is running. To do this:
Open Certificates MMC snap-in under LocalMachine context (certlm.msc)
Expand Personal\Certificates
Select desired certificate, right-click and then Manage Private Keys menu item.
Grant Read permissions to user account under which the client application is running.
In this case, you don't need to modify your code or move certificate between stores.
I am using Keycloak admin API to create a new user.
How can I check that the given password indeed fits the realm password policy before creating the user?
I am using the following code:
Response response = usersResource.create(userRepresentation);
String userId = response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1");
UserResource userResource = usersResource.get(userId);
CredentialRepresentation passwordCred = new CredentialRepresentation();
passwordCred.setTemporary(false);
passwordCred.setType(CredentialRepresentation.PASSWORD);
passwordCred.setValue(somePassword);
userResource.resetPassword(passwordCred);
The problem with the above code is that the method "resetPassword" fails if the given password does not fit the password policy, but at this point the user has already been created in keycloak, and I have to delete it, since I have no way to "rollback".
The other option is to check is the password is OK before creating the user. But how can I do it?
You will get validation failure message as JSON Object some thing like this
{"error":"invalidPasswordMinLengthMessage","error_description":"Invalid password: minimum length 8."}
I used following code to read the validation failure message from the ClientErrorException
public void resetUserInvalidPassword() {
String userId = createUser("user1", "user1#localhost");
try {
CredentialRepresentation cred = new CredentialRepresentation();
cred.setType(CredentialRepresentation.PASSWORD);
cred.setValue(" ");
cred.setTemporary(false);
realm.users().get(userId).resetPassword(cred);
} catch (ClientErrorException e) {
Response response = e.getResponse();
System.out.println(getErrorMessage(response));
response.close();
}
}
private String getErrorMessage(Response response) {
Object entity = response.getEntity();
String errorMessage = "(none)";
if (entity instanceof ErrorRepresentation)
errorMessage = ((ErrorRepresentation) entity).getErrorMessage();
else if (entity instanceof InputStream)
errorMessage = new BufferedReader(new InputStreamReader((InputStream)entity)).lines().collect(Collectors.joining("\n"));
else if (entity != null)
errorMessage = entity.toString();
return errorMessage;
}
UserResource has the getUsers() method to find a user by username and check its attributes.
If the resetPassword failed, you could find the user, apply checks if needed, and resetPassword again.
I have designed a web application which uses very simple implementation of JWT token's to provide Authentication/Authorization.
My Implementation :
There are two types of urls's public and secure.
Public urls are to generate token with username/password.
I have added filter on secure url to check for the Authorization Header and JWT Token.
#Bean
public FilterRegistrationBean jwtFilter()
{
final FilterRegistrationBean registrationBean = new
FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/secure/*");
return registrationBean;
}
Filter will validate the token. I haven't added expiration date yet.
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final String authHeader = request.getHeader("authorization");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(req, res);
} else {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header");
}
final String token = authHeader.substring(7);
try {
final Claims claims = Jwts.parser().setSigningKey(secretKey.toString).parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
} catch (final SignatureException e) {
throw new ServletException("Invalid token");
}
chain.doFilter(req, res);
}
This is providing authentication and also its is immune to CSRF.No one can create valid token without secret Key.
Are there other attacks possible on token base authentication service which i have missed?
I want to create an authorization server using Spring Oauth which is able to issue it's own JWT tokens. The authorization server must delegate the authentication to Google. I have been following this tutorial which does almost everything I want: https://spring.io/guides/tutorials/spring-boot-oauth2/
I was able to add Google as an authentication provider, but I'm struggling with the JWT part.
Here's my authorization server configuration:
#SpringBootApplication
#EnableOAuth2Client
#EnableAuthorizationServer
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class MsAuthorizationGmailApplication extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/gmail")).and().logout()
.logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
#Bean
#ConfigurationProperties("gmail")
public ClientResources gmail() {
return new ClientResources();
}
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(gmail(), "/login/gmail"));
filter.setFilters(filters);
return filter;
}
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
filter.setTokenServices(JwtConfig.tokenServices());
return filter;
}
public static void main(String[] args) {
SpringApplication.run(MsAuthorizationGmailApplication.class, args);
}
}
In the JWT config I'm not trying to do anything fancy, just trying to make it pass for now:
public final class JwtConfig {
private static final String KEY = "123";
private JwtConfig() {
}
private static JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(KEY);
return converter;
}
private static TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
public static DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
}
I get the following exception:
org.springframework.security.authentication.BadCredentialsException: Could not obtain user details from token
at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:122) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:112) [spring-web-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:73) [spring-web-4.3.6.RELEASE.jar:4.3.6.RELEASE]
....
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.11.jar:8.5.11]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
Caused by: org.springframework.security.oauth2.common.exceptions.InvalidTokenException: Cannot convert access token to JSON
at org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter.decode(JwtAccessTokenConverter.java:287) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtTokenStore.convertAccessToken(JwtTokenStore.java:88) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtTokenStore.readAccessToken(JwtTokenStore.java:80) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.loadAuthentication(DefaultTokenServices.java:229) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:112) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
... 62 common frames omitted
Caused by: java.lang.IllegalArgumentException: JWT must have 3 tokens
at org.springframework.security.jwt.JwtHelper.decode(JwtHelper.java:49) ~[spring-security-jwt-1.0.0.RELEASE.jar:na]
at org.springframework.security.jwt.JwtHelper.decodeAndVerify(JwtHelper.java:74) ~[spring-security-jwt-1.0.0.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter.decode(JwtAccessTokenConverter.java:277) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
... 66 common frames omitted
How I understand this: It looks like that when Google issues an access token, the authorization server (being a client of Google OAuth) tries to decode the access token as a JWT, and throws an exception because Google's token is not a valid JWT (it's just an access token).
I would like to create a JWT containing the access token (which will be used to access Google APIs) and some additional information about the user. I would also like to be able to refresh the JWT token when the access token expires. Is there any way to achieve this?
Am not sure about GMail but for your own Authorization server, you can add one token enhancer JwtAccessTokenConverter which will convert you token into JWT.
For sample, pls refer oauth2-spring-boot-mongo-jwt-sample
Generally, normal token payload is of below type
{
"access_token": "bc9c021f-b5ae-43af-9746-737b533f9bc5",
"token_type": "bearer",
"refresh_token": "fee7a2a1-eff9-4757-8dd3-5392ee225bea",
"expires_in": 43199,
"scope": "read-foo" }
whereas, JWT looks something like this
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJyZWFkLWZvbyJdLCJleHAiOjE1MTQ3ODMwNTIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiJlMjM4MDg1YS0xZjFjLTQ5ZWQtODNiMC1iN2Q1MjI5OWUwZjYiLCJjbGllbnRfaWQiOiJ3ZWItY2xpZW50In0.-OSw1Vr4o1dnAQL3n7QFGG6UOXr4itc0Kp8dugyT4zU",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJyZWFkLWZvbyJdLCJhdGkiOiJlMjM4MDg1YS0xZjFjLTQ5ZWQtODNiMC1iN2Q1MjI5OWUwZjYiLCJleHAiOjE1MTczMzE4NTIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiIzYTA2OTZmMy1mYzg1LTQ2YTEtYjVlMC01NmQ2OGVmYTJhMmUiLCJjbGllbnRfaWQiOiJ3ZWItY2xpZW50In0.jSBriPfM-rSgHHLyifIuBHwrwCkyb5I2u2AKa8kQUUU",
"expires_in": 43199,
"scope": "read-foo",
"jti": "e238085a-1f1c-49ed-83b0-b7d52299e0f6"
}
Am new to web services. Am trying to generate unique session id for every login that a user does, in web services.
What I thought of doing is,
Write a java file which has the login and logout method.
Generate WSDL file for it.
Then generate web service client(using Eclipse IDE), with the WSDl file which I generate.
Use the generated package(client stub) and call the methods.
Please let me know if there are any flaws in my way of implementation.
1. Java file with the needed methods
public String login(String userID, String password) {
if (userID.equalsIgnoreCase("sadmin")
&& password.equalsIgnoreCase("sadmin")) {
System.out.println("Valid user");
sid = generateUUID(userID);
} else {
System.out.println("Auth failed");
}
return sid;
}
private String generateUUID(String userID) {
UUID uuID = UUID.randomUUID();
sid = uuID.toString();
userSessionHashMap = new HashMap<String, String>();
userSessionHashMap.put(userID, sid);
return sid;
}
public void logout(String userID) {
Set<String> userIDSet = userSessionHashMap.keySet();
Iterator<String> iterator = userIDSet.iterator();
if (iterator.equals(userID)) {
userSessionHashMap.remove(userID);
}
}
2. Generated WSDL file
Developed the web service client from the wsdl.
4. Using the developed client stub.
public static void main(String[] args) throws Exception {
ClientWebServiceLogin objClientWebServiceLogin = new ClientWebServiceLogin();
objClientWebServiceLogin.invokeLogin();
}
public void invokeLogin() throws Exception {
String endpoint = "http://schemas.xmlsoap.org/wsdl/";
String username = "sadmin";
String password = "sadmin";
String targetNamespace = "http://WebServiceLogin";
try {
WebServiceLoginLocator objWebServiceLoginLocator = new WebServiceLoginLocator();
java.net.URL url = new java.net.URL(endpoint);
Iterator ports = objWebServiceLoginLocator.getPorts();
while (ports.hasNext())
System.out.println("ports Iterator size-->" + ports.next());
WebServiceLoginPortType objWebServiceLoginPortType = objWebServiceLoginLocator
.getWebServiceLoginHttpSoap11Endpoint();
String sid = objWebServiceLoginPortType.login(username, password);
System.out.println("sid--->" + sid);
} catch (Exception exception) {
System.out.println("AxisFault at creating objWebServiceLoginStub"
+ exception);
exception.printStackTrace();
}
}
On running the this file, I get the following error.
AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
faultSubcode:
faultString: java.net.ConnectException: Connection refused: connect
faultActor:
faultNode:
faultDetail:
{http://xml.apache.org/axis/}stackTrace:java.net.ConnectException: Connection refused: connect
Can anyone suggest an alternate way of handling this task ? And what could probably be the reason for this error.
Web services are supposed to be stateless, so having "login" and "logout" web service methods doesn't make much sense.
If you want to secure web services calls unfortunately you have to code security into every call. In your case, this means passing the userId and password to every method.
Or consider adding a custom handler for security. Read more about handlers here.