Extract client X509 certificate from a secured websocket connection - authentication

I would like to create a certificate-based authentication on top of websocket communication.
So I created a websocket serverEndpoint, and set up SSL for client authentication with the help of jetty, like this:
Server server = new Server();
//Create SSL ContextFactory with appropriate attributes
SslContextFactory sslContextFactory = new SslContextFactory();
//Set up keystore path, truststore path, passwords, etc
...
sslContextFactory.setNeedClientAuth(true);
//Create the connector
ServerConnector localhostConnector = new ServerConnector(server, sslContextFactory);
localhostConnector.setHost(...);
localhostConnector.setPort(...);
server.addConnector(localhostConnector);
//Create ContextHandler
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/example");
server.setHandler(context);
// Initialize the JSR-356 layer and add custom Endpoints
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
container.addEndpoint(Endpoint1.class); //Annotated class
container.addEndpoint(Endpoint2.class);
The SSL configuration seems to be correct, since I can connect to the different endpoints with a SSL client that I wrote (a wrong certificate leads to the connection beeing terminated).
Now, I would like to extract the information contained in the client certificate. I saw I could get the certificate from a SSLSession, but the only session I have access to in the endpoint is a "normal" Session:
#OnOpen
#Override
public void open(final Session session, final EndpointConfig config)
Is there a way somehow to store the certificate or the information contained and to pass it along to the endpoints ?
Thanks for any help :)

I found a solution to get the client registered as the UserPrincipal of the session, accessible by session.getUserPrincipal().
The UserPricipal is "the authenticated user for the session". You nneed then to add an authentiation service to your ServletContextHandler, as following:
//Create SSL ContextFactory with appropriate attributes
...
//Create the connector
...
//Create ContextHandler
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/example");
//Add security contraint to the context => authentication
ConstraintSecurityHandler security = new ConstraintSecurityHandler();
Constraint constraint = new Constraint();
constraint.setName("auth");
constraint.setAuthenticate(true);
constraint.setRoles(new String[]{"user"});
Set<String> knownRoles = new HashSet<String>();
knownRoles.add("user");
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/*");
mapping.setConstraint(constraint);
security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
security.setAuthMethod("CLIENT-CERT");
LoginService loginService = new HashLoginService();
security.setLoginService(loginService);
security.setAuthenticator(new ClientCertAuthenticator());
context.setSecurityHandler(security);
This way, when a client connects to the websocket endpoint, the security handler ensures that the client must be authenticated. As I understood, the ClientCertAuthenticator will check the client request to extract information (DN of the certificate) and then pass it to the LoginService, where the client is authenticated and the UserPricipal of the session set.
The problem here is that you must have a working loginService (For instance, HashLoginService is a in-memory Loginservice working with password and usernames, JDBCLoginService works with a database). For those who, like me, just want to extract the required information from the certificate and perform authentication afterwards with this information, you can provide your own implementation of the LoginService interface.
Here is what I did:
During the definition of your security Handler:
LoginService loginService = new CustomLoginService();
loginService.setIdentityService(new DefaultIdentityService());
security.setLoginService(loginService);
CustomLoginService Class
public class CustomLoginService implements LoginService {
IdentityService identityService = null;
#Override
public String getName() {
return "";
}
#Override
public UserIdentity login(String username, Object credentials) {
//you need to return a UserIdentity, which takes as argument:
// 1. A Subjet, containing a set of principals, a set of private credentials and a set of public ones (type Object)
// 2. A Principal of this Subject
// 3. A set of roles (String)
LdapPrincipal principal = null;
try {
principal = new LdapPrincipal(username);
//you need to have a Principal. I chose LDAP because it is specifically intended for user identified with a DN.
} catch (InvalidNameException e) {
e.printStackTrace();
}
String[] roles = new String[]{"user"};
return new DefaultUserIdentity(
new Subject(false,
new HashSet<LdapPrincipal>(Arrays.asList(new LdapPrincipal[]{principal}) ),
new HashSet<Object>(Arrays.asList(new Object[]{credentials})),
new HashSet<Object>(Arrays.asList(new Object[]{credentials}))),
principal,
roles);
}
#Override
public boolean validate(UserIdentity user) {
return false;
}
#Override
public IdentityService getIdentityService() {
return identityService;
}
#Override
public void setIdentityService(IdentityService service) {
identityService = service;
}
#Override
public void logout(UserIdentity user) {
}
And that's it :)

Related

Spring security - new access token

I have following issue.
I have multitenant system (with shared Database and shared Schema). Access token that is generated when user logs in contains information about tenantId. Idea is to allow logged user to change tenat
For example: My user works for 3 tenants(hospitals). When he is logged in, he should be able to change hospital.
So the main issues is how to generate new acces token for user that will contain updated tenantId.
It would be preferable that user doesnt have to provide password again (since he is already logged in), and that request to auth-server that he triggers would contain his current token (that will confirm that he is currently authenticated) and newTenandId.
Here is some custom code:
#Service
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private MessageSource validationMessageSource;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
SpringSecurityUserWithAdditionalData user = (SpringSecurityUserWithAdditionalData) userDetailsService.loadUserByUsername(username);
return checkPassword(user, password);
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
private Authentication checkPassword(SpringSecurityUserWithAdditionalData user, String rawPassword) throws AuthenticationException {
try {
if (passwordEncoder.matches(rawPassword, user.getPassword())) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
return token;
} else {
throw new GeneralException(validationMessageSource.getMessage("security.authentication.NotValid", new Object[] {}, LocaleContextHolder.getLocaleContext().getLocale()));
}
} catch (Exception e) {
throw new BadCredentialsException(e.getMessage());
}
}
}
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration {
#Autowired
private CustomAuthenticationProvider authenticationProvider;
// #formatter:off
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
...
.and()
.logout()
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.and()
.formLogin()
.loginPage("/login")
.loginPage("/changeTenant")
.permitAll().and();
return http.build();
}
// #formatter:on
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
/**
* JWT koji je generisao authorization server sadrzi granted permissions (Spring ih naziva granted authorities) u okviru "scope" claim-a.
* Umesto njega cemo koristiti custom claim koji sam nazvao GlobalConstants.JWT_CLAIM_ROLA_LIST za specifikaciju rola koje ima authenticated korisnik.
* Spring koristi default instance JwtAuthenticationConverter koja ocekuje granted authorities u okviru "scope"/"scp" claim-a.
* Da bi koristili umesto standardno "scope" claim-a koristili claim GlobalConstants.JWT_CLAIM_ROLA_LIST override-ovan je JwtAuthenticationConverter.
*/
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName(GlobalConstants.JWT_CLAIM_ROLA_LIST); // override authorities claim-a
converter.setAuthorityPrefix(""); // eksplicitno definisemo nazive, bez podrazumevanih prefiksa (ROLE_ SCOPE_ i slicno)
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
#Bean
InitializingBean forcePostProcessor(BeanPostProcessor meterRegistryPostProcessor, MeterRegistry registry) {
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(registry, "");
}
}
If you need any additional information, please say.
I tried adding custom fields to custom login form, that will have hidden tenantId field. But i could not manage to make it work.
The authentication process should be designed to return a list of all the tenants the user has access to, typically as a list of authorities.
Separately you need a back-end call (linked to the UI) that allows the user to choose the current tenant from the list of authorities returned in authn.
The value of the current tenant must be stored in the session.
If you really want to hack this via the auth roles, you could store the real auth token and generate your own token with only the current tenant. When user changes tenant they get a new token with the new tenant (obviously after checking against the real token)

Register scoped based HttpClient in .NET Core 2

I have NET Core 2 Web API application. During the process i have to invoke Client A's API to get some data. So i am using HttpClient to invoke it. Client A also requires me to pass userid and password in header.
So instead of directly injecting HttpClient i have wrapper around HttpClient something like below
public class ClientA : IClientA
{
private readonly HttpClient _httpClient;
public ClientA(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetData()
{
return await _httpClient.HttpGetAsync("someurl");
}
}
Then use ClientA in Service
public class MyService :IMyService
{
private readonly IClientA _clientA
public MyService(IClientA clientA)
{
_clientA= clientA
}
public void DoSomethig()
{
_clientA.GetData();
}
}
Then i am registering everything in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyService, MyService>();
services.AddScoped(factory =>
{
Func<Task<IClientA>> provider = async () =>
{
using (var dbContext = factory.GetService<MyDBContext>())
{
// get userid and password from database here
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("UserId",userid);
httpClient.DefaultRequestHeaders.Add("Password",password);
return new ClientA(httpClient);
}
};
return provider;
});
}
However i am getting error
System.InvalidOperationException: Unable to resolve service for type
'System.Net.Http.HttpClient' while attempting to activate
'XXXXXXXXX.ClientA'. at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type
serviceType, Type implementationType, ISet1 callSiteChain,
ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type
serviceType, Type implementationType, ISet1 callSiteChain)
remaining exception removed for brevity
Notice that during registration i am newing-up instance of HttpClient and passing it to ClientA class because i have to set userid and password.
To get rid the above error I can register HttpClient with UserID and Password with DI framework and i guess that would work.
However, in that case, if have another client, ClientB, that takes HttpClient then DI framework will inject same httpclient that has userid and password. and that will create security issue because ClientB would see ClientA's credentials in request headers.
public class ClientB(HttpClient client)
{
private readonly _httpClient;
public class ClientB(HttpClient client)
{
_httpClient = client;
}
public string CallClientB(string url)
{
// here ClientB will receive ClientA's credentials
return await _httpClient.HttpGetAsync(url);
}
}
You don't want to be instantiating httpclient in a scoped context, that is creating an instance of httpclient per request which is not the recommended usage pattern for that class. (won't scale well). https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Create a singleton with a separate interface per customer (assuming a small number of customers) - possibly with a code access security demand in its implementation, depending on your setup (identity impersonation enabled?)
That will a) scale well b) only run once per customer per application instance/startup and c) enforce an access check for usage.
Also, this answer is connected and relevant to your header requirements - HttpClient single instance with different authentication headers
resolved my issue
services.AddScoped<IClientA>(factory =>
{
var dbContext = factory.GetService<MyDBContext>();
// get userid and password from database here
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("UserId",userid);
httpClient.DefaultRequestHeaders.Add("Password",password);
return new ClientA(httpClient);
});

How can I configure claim authentication in a separate asp.net mvc authentication website with Thinktecture 2 with friendly user login page?

Can anybody tell me what is going wrong with this approach for single sing on?
I have a website A with the authentication logic inside. The user can select the role to access the portal that he wants to go. The problem is how can I configure properly those websites to redirect correctly, because when I redirect, I lose the token(redirect is a GET), I never had the cookie on the portal that I want to go. Do I am missing something in my implementation? Maybe a configuration on the portal that I want to redirect? I am not using the audienceUri on the webconfig, Is this related with the problem? I am using a service that gives me a token if the user is authenticated. Then with that token, I want to redirect the page to the corresponding portal.
Said that I will show you the Login Method in AccountController
[HttpPost]
public async Task<ActionResult> Login(string ddlRoles, string ddlUrls, LoginModel user)
{
var service = new AuthenticationServiceAgent(user.Username, user.Password);
var securityService = new SecurityServiceAgent(service.GetToken());
...processing the claims
//AT THIS POINT THE USER IS AUTHENTICATED
FederatedAuthentication.WSFederationAuthenticationModule.SetPrincipalAndWriteSessionToken(token, true);
.... get the url to redirect
Response.Redirect(urlToRedirect, false);
}
My Proxy class looks like this
public class UserNameTokenServiceProxy : TokenServiceProxy
{
#region Properties
public SecurityCredential Credential { get; set; }
#endregion
#region Methods
public override SecurityToken GetToken(ISecurityCredential credential = null)
{
if (null == credential)
{
throw new ArgumentNullException("credential");
}
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(StsEndPoint)
);
factory.TrustVersion = TrustVersion.WSTrust13;
if (null != factory.Credentials)
{
factory.Credentials.UserName.UserName = credential.UserName;
factory.Credentials.UserName.Password = credential.Password;
}
var rst = new RequestSecurityToken()
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
TokenType = TokenTypes.Saml11TokenProfile11,
***********RelyingPartyUri comes from a config file*********
AppliesTo = new EndpointReference(RelyingPartyUri)
};
try
{
var channel = factory.CreateChannel();
var sToken = channel.Issue(rst);
return sToken;
}
catch (Exception ex)
{
return null;
}
}
#endregion
}
}
And the base class of that one is the following:
public abstract class TokenServiceProxy
{
#region Fields
protected string StsEndPoint;
protected string RelyingPartyUri;
#endregion
#region Constructors
protected TokenServiceProxy()
{
StsEndPoint = ConfigurationManager.AppSettings["stsEndpoint"];
if (null == StsEndPoint)
{
throw new Exception("STSEndPoint cannot be null");
}
RelyingPartyUri = ConfigurationManager.AppSettings["relyingPartyUri"];
if (null == RelyingPartyUri)
{
throw new Exception("RelyingPartyUri cannot be null");
}
//StsEndPoint = <add key="stsEndpoint" value="https://sso.dev.MyCompany.com/idsrv/issue/wstrust/mixed/username"/>;
//RelyingPartyUri = #"value="https://dev.MyCompany.com/MyCompanyPortal"/>";
}
#endregion
#region Abstract Methods
public abstract SecurityToken GetToken(ISecurityCredential credential = null);
#endregion
}
Basically we are not using the default configuration for WS with the audienceUri and the federationConfiguration section, the equivalent would be:
<system.identityModel.services>
<federationConfiguration>
<cookieHandler mode="Default" requireSsl="false" />
<wsFederation passiveRedirectEnabled="true" issuer="https://sso.dev.MyCompany.com/idsrv/issue/wstrust/mixed/username" realm="https://dev.MyCompany.com/MyCompanyPortal" requireHttps="false" />
</federationConfiguration>
</system.identityModel.services>
I'm not quite sure what are all the components in your solution, but in any case you seem to try to do too much on your own.
SSO or not - each integrated application needs to maintain its own authentication state (with authentication cookie), and to do so it needs to get the security token from Identity Provider.
In a most common scenario for WS-Federation (Relying Party initiated SSO), Relying Party application redirects user to Identity Provider, Identity Provider checks user credentials and POSTS security token back to Relying Party application, where it is handled by WS-Federation module to create authentication cookie.
In your case I guess that you are trying to achieve "Identity Provider initiated" SSO (assuming, that your login page is part of Identity Provider).
If this is the case, you need to POST created security token to selected Relying Party (as far as I know WS-Federation does not support sending tokens in GET query string).
Of course you need to have proper WS-Federation configuration on your Relying Party side to accept this token and create authentication cookie - having that the authentication is done automatically by WS-Federation module.

How are bearer tokens stored server-side in Web API 2?

I am setting up bearer token authentication in Web API 2, and I don't understand how (or where) the bearer token is being stored server-side. Here is the relevant code:
Startup:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
}
}
WebApiConfig:
public class WebApiConfig
{
public static void ConfigureWebApi()
{
Register(GlobalConfiguration.Configuration);
}
public static void Register(HttpConfiguration http)
{
AuthUtil.ConfigureWebApiToUseOnlyBearerTokenAuthentication(http);
http.Routes.MapHttpRoute("ActionApi", "api/{controller}/{action}", new {action = Actions.Default});
}
}
AuthUtil:
public class AuthUtil
{
public static string Token(string email)
{
var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, email));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
return token;
}
public static void ConfigureWebApiToUseOnlyBearerTokenAuthentication(HttpConfiguration http)
{
http.SuppressDefaultHostAuthentication();
http.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
}
}
LoginController:
public class LoginController : ApiController
{
...
public HttpResponseMessage Post([FromBody] LoginJson loginJson)
{
HttpResponseMessage loginResponse;
if (/* is valid login */)
{
var accessToken = AuthUtil.Token(loginJson.email);
loginResponse = /* HTTP response including accessToken */;
}
else
{
loginResponse = /* HTTP response with error */;
}
return loginResponse;
}
}
Using the above code, I'm able to login and store the bearer token client-side in a cookie, and then make calls to controllers marked with [Authorize] and it lets me in.
My questions are:
Where / how is the bearer token being stored server-side? It seems like this is hapenning through one of the OWIN calls but I can't tell where.
Is it possible to persist the bearer tokens to a database server-side so that they can remain in place after a Web API server restart?
If the answer to #2 is no, is there anyway for a client to maintain its bearer token and re-use it even after the Web API goes down and comes back up? While this may be rare in Production, it can happen quite often doing local testing.
They're not stored server side -- they're issued to the client and the client presents them on each call. They're verified because they're signed by the owin host's protection key. In SystemWeb hosting, that protection key is the machineKey setting from web.config.
That's unnecessary, as long as the protection key the owin host uses doesn't change across server restarts.
A client can hold onto a token for as long as the token is valid.
For those who are looking for how to set web.config, here is a sample
<system.web>
<machineKey validation="HMACSHA256" validationKey="64-hex"
decryption="AES" decryptionKey="another-64-hex"/>
</system.web>
You need both validationKey and decriptionkey to make it work.
And here is how to generate keys
https://msdn.microsoft.com/en-us/library/ms998288.aspx
To add to this, the token can be persisted server side using the SessionStore property of of CookieAuthenticationOptions. I wouldn't advocate doing this but it's there if your tokens become excessively large.
This is an IAuthenticationSessionStore so you could implement your own storage medium.
By default the token is not stored by the server. Only your client has it and is sending it through the authorization header to the server.
If you used the default template provided by Visual Studio, in the Startup ConfigureAuth method the following IAppBuilder extension is called: app.UseOAuthBearerTokens(OAuthOptions).

Spring MVC 3.1 How to access HttpSession in Custom Authentication Provider (which implements AuthenticationProvider)

My application calls a web service during the Authentication process (as shown in code below).
How can I save some information in HttpSession during this process?
This information like customer-account-number will be used in various other places in the application after the user is logged in.
Is it possible to pass HttpSession parameter to the MyServiceManager's static login method?
public class MyAuthenticationManager implements AuthenticationProvider {
#Override
public boolean supports(Class<? extends Object> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
#Override
public Authentication authenticate(Authentication authentication) {
//MyServiceManager.login - makes a call to web service
if(MyServiceManager.login(authentication.getName(), authentication.getCredentials().toString(), XXX_HTTP_SESSION_XXX))
{
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority> ();
authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
authorities.add(new GrantedAuthorityImpl("ROLE_SUPERVISOR"));
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(),authorities);
}
else
{
return null;
}
}
}
After breaking a lot of head on this issue, I was able to achive the objective using following work around.
Getting hold of session is really not feasible in following method
public Authentication authenticate(Authentication authentication)
I created a class
import java.security.Principal;
public class UserInfo implements Principal{
private String customerId;
private String accountNumber;
private String name;
}
The information which I wanted to store in session (like customerId, accountNumber etc), I saved it in userInfo object.
and this object was passed to UsernamePasswordAuthenticationToken
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
authorities.add(new GrantedAuthorityImpl("ROLE_SUPERVISOR"));
return new UsernamePasswordAuthenticationToken(**userInfo**, authentication.getCredentials(),authorities);
This information is readily available in the user's session using
(UserInfo)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
I home this is a good enough way to tackle the problem.
We can do this by:
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session= attr.getRequest().getSession(false);
I recommend false as it is assumed that no one without valid session should be inside this method.