I am implementing a service in CapnProto. The service follows these steps (roughly):
authenticate on the server
do operations through a Service interface (object-capability) once authenticated.
I want to achieve something like the following:
interface Authorization {
login #0 (userName :User) -> (result :Service);
}
interface Service {
# doOperation needs userName in order to operate, as an implicit
# parameter, when the RPC arrives. I do not want to use an explicit
# userName parameter. Otherwise, a user could claim to
# be someone else in a function parameter. To achieve this I need
# to know who is the userName that holds this capability from inside
# doOperation. I want to avoid token authentication
# for each operation.
# Thus, the RPC I am implementing is stateful.
doOperation #0 (param1 :A);
#...
}
What I want is, that from doOperation, I can identify the user that is using that capability (I want to know her userName). Namely:
What I have solved is that the user using the Service capability is known to have that permission (since the Service is the result of calling login)
The problem is that I have many of those users, and, for each of them, I want to do the matching between the user of the Service capability and her login in the first step.
It turns out that this was very simple.
When creating Service interface in code, just pass the authentication information and save it in the Service object, like this:
class ServiceImpl : public Service::Server {
string userId_;
public:
explicit ServiceImpl(string userId) : userId_(move(userId)) {}
protected:
kj::Promise<void> doOperatoration(DoOperationContext ctx) override {
//use userId_ here
}
};
class AuthorizationImpl : public Authorization::Server {
protected:
kj::Promise<void> login(LoginContext ctx) override {
std::string user = ctx.getParams().getUserName();
//Here I forward the authentication state to the service
ctx.getResults().setService(kj::heap<ServiceImpl>(user);
//..
}
};
Related
I have have read other posts on the same topic, but I haven't really gotten a clear picture of how to best solve this:
I have a webservice, that is "stateless" when it comes to the authentication/session, meaning that the client will send two strings with every request (in the HTTP header), AuthToken and DeviceUUID.
These two strings are then compared to the storage, and if found, we know which user it is.
1)
Id like to use the [Authenticate] attribute for each service that I want to protect, and then a method should be executed where I check the two strings.
2)
If I add the [RequiredRole], a method should also be executed, where I have access to the HTTP headers (the two strings), so I can do my lookup.
I am unsure of how to do this in the easiest and cleanest manner possible. I do not want to create ServiceStack Session objects etc, I just want a method that, for each decorated services, runs a method to check authenticated state.
If you want to execute something else in addition when the [Authenticate] and [RequiredRole] attributes are used then it sounds like you want a custom [MyAuthenticate] Request Filter attribute which does both, i.e. validates that the request is Authenticated and executes your custom functionality, e.g:
public class MyAuthenticateAttribute : AuthenticateAttribute
{
public override async Task ExecuteAsync(IRequest req, IResponse res, object dto)
{
await base.ExecuteAsync(req, res, requestDto);
var authenticated = !res.IsClosed;
if (authenticated)
{
//...
}
}
}
Then use it instead of [Authenticate] in places where you need that extra functionality:
[MyAuthenticate]
public class MyServices { ... }
But I'd personally keep the logic in the attributes separated:
public class MyLogicPostAuthAttribute : RequestFilterAsyncAttribute
{
public override async Task ExecuteAsync(IRequest req, IResponse res, object dto)
{
//...
}
}
So they're explicit and can be layered independently of the [Authenticate] attribute, e.g:
[Authenticate]
[MyLogicPostAuth]
public class MyServices { ... }
Which can also be combined like:
[Authenticate, MyLogicPostAuth]
public class MyServices { ... }
Through the Websphere Console I've setup a Policy Set and a Policy Set Binding in order to support UsernameToken authentication on a webservice. As expected, it is rejecting web service calls without correct usernames and passwords. However, it is now accepting every user in the connected LDAP.
I would like to be able to only allow access to users in a specific LDAP group. I have the feeling that I need to create a custom JAAS Login in the Caller settings, but I'm not completely sure.
Does anybody have a solution for this, or a direction where I should be looking?
Edit: I'm doing this to expose an IBM BPM web service.
Create your web service based on EJB not a POJO, and then use #RolesAllowed annotation to specify roles which are allowed to invoke particular method from your service. Use adminconsole, scirpt or binding file to map defined role to user or groups from the LDAP server.
This is probably much easier than fighting with Login module and more flexible.
You can create a custom JAAS login module to use when consuming the username token. You can use a JAAS config that first calls the built-in token consumer, then your custom consumer. Doing it this way means that you can use the built-in consumer to parse the token and do timestamp and nonce processing and you only have to do the username/password validation in your own login module.
The instructions can be found here: http://www14.software.ibm.com/webapp/wsbroker/redirect?version=phil&product=was-nd-dist&topic=twbs_replace_authmethod_usernametoken
(Please forgive the formatting. I'm doing the best I can with what I have available here.)
Replacing the authentication method of the UsernameToken consumer using a stacked JAAS login module
By default, the Web services security UsernameToken consumer, UNTConsumeLoginModule, always validates the username and password that are contained within the token against the WebSphere registry. You can use the SPIs that GenericSecurityTokenFactory provides to bypass this authentication method.
About this task
If you want to replace the authentication method that UNTConsumeLoginModule uses, you must provide your own custom JAAS login module to do the authentication. The custom login module is stacked under UNTConsumeLoginModule in a custom JAAS configuration. The UNTConsumeLoginModule consumes and validates the token XML. The validation of the values provided for username and password is deferred to the custom stacked login module.
Because the use of UNTConsumeLoginModule carries with it the assumption that the username and password will be authenticated, more requirements are put on a stacked login module that intends to perform this function than are put on login modules that are only intended to provide dynamic token functionality.
To indicate to UNTConsumeLoginModule that it should not authenticate the username and password, you must set the following property on the configured callback handler:
com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true
Like most WS-Security login modles, UNTConsumeLoginModule always puts the consumed token in the shared state map to which all login modules in the stack have access. When authDeferred=true is specified, in the commit phase, UNTConsumeLoginModule ensures that the same UsernameToken object that had originally been put on the shared state has been put in another location in the shared state. If this UsernameToken object cannot be found, a LoginException occurs. Therefore, you cannot just set authDeferred=true on the callback handler without having an accompanying login module return the token to the shared state.
Procedure
Develop a JAAS login module to do the authentication and make it available to your application code. This new login module stacks under the com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule.
This login module must:
Use the following method to get the UsernameToken that UNTConsumeLoginModule consumes.
UsernameToken unt = UsernameToken)factory.getConsumerTokenFromSharedState(sharedState,UsernameToken.ValueType);
In this code example, factory is an instance of com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory.
Check the username and password in the manner that you choose.
You can call unt.getUsername() and unt.getPassword() to get the username and password.
Your login module should throw a LoginException if there is an authentication error.
Put the UsernameToken, that was obtained in the previous substep, back on the shared state.
Use the following method to put the UsernameToken back on the shared state.
factory.putAuthenticatedTokenToSharedState(sharedState, unt);
Following is an example login module:
package test.tokens;
import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
import com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import com.ibm.websphere.wssecurity.wssapi.token.UsernameToken;
import java.util.ArrayList;
import com.ibm.wsspi.security.registry.RegistryHelper;
import com.ibm.websphere.security.UserRegistry;
public class MyUntAuthenticator implements LoginModule {
private Map _sharedState;
private Map _options;
private CallbackHandler _handler;
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this._handler = callbackHandler;
this._sharedState = sharedState;
this._options = options;
}
public boolean login() throws LoginException {
//For the sake of readability, this login module does not
//protect against all NPE's
GenericSecurityTokenFactory factory = null;
WSSUtilFactory utilFactory = null;
try {
factory = GenericSecurityTokenFactory.getInstance();
utilFactory = WSSUtilFactory.getInstance();
} catch (Exception e) {
throw new LoginException(e.toString());
}
if (factory == null) {
throw new LoginException("GenericSecurityTokenFactory.getInstance() returned null");
}
UsernameToken unt = (UsernameToken)factory.getConsumerTokenFromSharedState(this._sharedState,UsernameToken.ValueType);
String username = unt.getUsername();
char [] password = unt.getPassword();
//authenticate the username and password
//to validate a PasswordDigest password (fixpack 8.5.5.8 and later)
//String pw = yourCodeToLookUpPasswordForUsername(username);
//boolean match = utilFactory.verifyDigestedPassword(unt, pw.toCharArray());
//if (!match) throw new LoginException("Digested passwords do not match");
//Example:
try {
simpleUserGroupCheck(username, password, "cn=group1,o=ibm,c=us");
} catch (Exception e) {
LoginException le = new LoginException(e.getMessage());
le.initCause(e);
throw le;
}
//Put the authenticated token to the shared state
factory.putAuthenticatedTokenToSharedState(this._sharedState, unt);
return true;
}
private boolean simpleUserGroupCheck(String username, char [] password, String group) throws Exception {
String allowedGroup = null;
//get the default user registry
UserRegistry user_reg = RegistryHelper.getUserRegistry(null);
//authenticate the user against the user registry
user_reg.checkPassword(username, new String(password));
//get the list of groups that the user belongs to
java.util.List<String> groupList = user_reg.getGroupsForUser(username);
//you can either use a hard-coded group
allowedGroup = group;
//or get the value from your own custom property on the callback handler
//WSSUtilFactory util = WSSUtilFactory.getInstance();
//Map map = util.getCallbackHandlerProperties(this._handler);
//allowedGroup = (String) map.get("MY_ALLOWED_GROUP_1");
//check if the user belongs to an allowed group
if (!groupList.contains(allowedGroup)) {
throw new LoginException("user ["+username+"] is not in allowed group ["+allowedGroup+"]");
}
return true;
}
//implement the rest of the methods required by the
//LoginModule interface
}
Create a new JAAS login configuration.
In the administrative console, select Security > Global security.
Under Authentication, select Java Authentication and Authorization Service.
Select System logins.
Click New, and then specify Alias = test.consume.unt.
Click New, and then specify Module class name = com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule
Click OK.
Click New, and then specify Module class name = test.tokens.MyUntAuthenticator
Select Use login module proxy.
Click OK, and then click SAVE.
Configure your UsernameToken token consumer to use the new JAAS configuration.
Open your bindings configuration that you want to change.
In the administrative console, select WS-Security > Authentication and protection.
Under Authentication tokens, select the UsernameToken inbound token that you want to change.
Select JAAS login = test.consume.unt.
Set the required property on the callback handler that is configured for the UsernameToken consumer.
Click Callback handler.
Add the com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true custom property.
Click OK.
Click SAVE.
Restart the application server to apply the JAAS configuration changes.
Test your service.
Hi I am new to working with Servicestack and have downloaded their very comprehensive bootstrapapi example and am working with it, but am still having some issues. The issue is with security, what is happening is I am getting 405 errors when trying to access the protected services. Using the authenticate service it appears that I am authenticating correctly. Please help and explain. Here is the code:
public class Hello
{
public string Name { get; set; }
}
public class AuthHello
{
public string Name { get; set; }
}
public class RoleHello
{
public string Name { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
The Services:
public class HelloService : ServiceBase<Hello>
{
//Get's called by all HTTP Verbs (GET,POST,PUT,DELETE,etc) and endpoints JSON,XMl,JSV,etc
protected override object Run(Hello request)
{
return new HelloResponse { Result = "Hello, Olle är en ÖL ål " + request.Name };
}
}
[Authenticate()]
public class AuthHelloService : RestServiceBase<AuthHello>
{
public object Execute(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
[RequiredRole("Test")]
public class RoleHelloService : RestServiceBase<RoleHello>
{
public object Execute(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
Here is the AppHost:
public class HelloAppHost : AppHostBase
{
//Tell Service Stack the name of your application and where to find your web services
public HelloAppHost() : base("Hello Web Services", typeof(HelloService).Assembly) { }
public override void Configure(Container container)
{
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {new CustomCredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
container.Register<ICacheClient>(new MemoryCacheClient() { FlushOnDispose = false });
//register user-defined REST-ful urls
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name}")
.Add<AuthHello>("/AuthHello")
.Add<RoleHello>("/RoleHello");
}
}
UPDATE
Everything works as expect if you replace : RestServiceBase with : ISevice so now the question is why.
Check the wiki documentation first
I would first go through the documentation in ServiceStack's Authentication Wiki to get a better idea about how ServiceStack's Authentication works. There's a lot of documentation in the wiki, so if you're unsure of something you should refer to that first. It's a community wiki so feel free to expand whats there if you think it can help others.
Refer to the implementation in the source code if behavior is not clear
If you're unsure of what something does you should refer to the RequiredRole source code as the master authority as how it works. RequiredRole is just a Request Filter Attribute which gets run before every service that has the attribute.
The RequiredRole attribute just calls your session.HasRole() method as seen here:
public bool HasAllRoles(IAuthSession session)
{
return this.RequiredRoles
.All(requiredRole => session != null
&& session.HasRole(requiredRole));
}
Because it just calls your session you can override the implementation of session.HasRole() if you have a custom session.
Registering and Implementing a CustomUserSession
The Social BootstrapApi project does implement its own CustomSession that it registers here but does not override the HasRole() implementation so it uses the built-in implementation in the base AuthUserSession.HasRole() which simply looks like the Roles collection to see if the user has the specified role in their Session POCO:
public virtual bool HasRole(string role)
{
return this.Roles != null && this.Roles.Contains(role);
}
Session properties populated by AuthUserRepository
The Roles property (as well as most other properties on a users Session) is populated by the AuthUserRepository that you have specified e.g. if you're using the OrmLiteAuthRepository like SocialBootstrapApi does here than the Roles attribute is persisted in the Roles column in the UserAuth RDBMS table. Depending on the AuthUserRepository you use the UserAuth / UserOAuthProvider POCOs get stored as RDBMS tables in OrmLite or as text blobs in Redis, etc.
Manage roles and permissions with AssignRoles / UnAssignRoles services
So for a user to have the required role (and authorization to pass), it should have this Role added to its UserAuth db row entry. ServiceStack's AuthFeature includes 2 services for managing users permissions and roles:
/assignroles
/unassignroles
How to initially give someone the Admin Role
These services does require a user with the Admin Role to be already authenticated.
You can do this by manually changing a specific users UserAuth.Role column to include the value "Admin". The Social Bootstrap API project instead does this by handling the OnAuthenticated() event on its CustomUserSession that simply checks to see if the authenticated username is declared in the Web.Config and if it is, calls the AssignRoles service giving that authenticated user the Admin Role:
if (AppHost.Config.AdminUserNames.Contains(session.UserAuthName)
&& !session.HasRole(RoleNames.Admin))
{
var assignRoles = authService.ResolveService<AssignRolesService>();
assignRoles.Execute(new AssignRoles {
UserName = session.UserAuthName,
Roles = { RoleNames.Admin }
});
}
Good time.
Suppose there are 8 web-services in the one application. 5 of them require authorization (a client must to provide a JSESSIONID cookie and a corresponding session must not be invalidated), other 3 can be called without the jsessionid cookie. My naive solution is to write a servlet filter which intercepts requests and retrieve their pathInfos (all the services have the same url structure: /service/serviceSuffix). There is a enum which contains the serviceSuffix of each web service that requires authorization. When the request is retrieved the pathInfo is collected; if this pathInfo is contained in the enum and there is the corresponding valid session the request is sent ahead to the filter chain. Otherwise, an error is sent back to a client. After a while I've realized that it is needed to add the possibility to retrieve the wsdl and xsds for the concrete service. So that, two more check were added.
public class SecurityFilter implements Filter {
public static final String WSDL = "wsdl";
public static final String XSD = "xsd=";
/**
* Wittingly left empty
*/
public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse)response;
String pathInfo = servletRequest.getPathInfo();
String queryString = servletRequest.getQueryString();
if (pathInfo != null && SecureWebServices.contains(pathInfo)) {
if (queryString != null && (queryString.equals(WSDL) || queryString.startsWith(XSD))) {
// wsdl or xsd is requested
chain.doFilter(request, response);
} else {
// a web service's method is called
HttpSession requestSession = servletRequest.getSession(false);
if (requestSession != null) { // the session is valid
chain.doFilter(request, response);
} else {
servletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
} else {
chain.doFilter(request, response);
}
}
/**
* Wittingly left empty
*/
public void destroy() {}
}
It seems that it is not very secure, because if the request's pathInfo is not in the enum, this request is passed on (just in case of some unexpected system calls).
Could you, please, suggest what to do, how to increase the security level. I want to build a configurable system (that is why I have the enum. It is possible just to add a path there to secure the web service and it is not required to duplicate the security code in the each web service). How to increase
Maybe I do not understand but.
jsessionid has nothink to do with security. you simply just get it.
Next I am not sure if you want authentication or authorization. The code as provided will not provide you with security features.
I suppose you are interested in authentication anyway. Security logic can be provided with standard web container features. Just send in authentication data in the header of request and you are done. web container can be configured to secure only selected resources (urls)
In this hypothetical scenario there is an ASP.NET 4 web application that simultaneously aggregates data from multiple web services. The web services are all of the same implementation, but are separate instances and are not aware of each other.
In the web application a user provides credentials for each web service he wants access to, and the authentication process iterates through all of his user name/password combos coupled with the URL for each web service. (The clunky UI is for illustration only....)
Assume the web application uses the ValidateUser method in a custom MembershipProvider class for authentication, and the MembershipProvider is configured in web.config as per usual.
Assume also that the custom MembershipProvider class has a Url property that changes with each authentication call to the different web services.
Assuming all of that, how do you handle the scenario where User 1 and User 2 are authenticating at the same time, but User 1 has access to Web Service A, B, and C, and User 2 has access to Web Service X, Y, and Z?
Will the credentials and URLs potentially get mixed up and User 1 might see User 2's data and vice-versa?
If you are going to implement a custom membership provider you will see lots of headaches down the road. The reason is that in your app model, the authorization scheme is based on whatever membership the user has (for a specific service).
I would advise to have your own membership (for your own site) and extend the profile model so that you can retrieve credentials for each service that the user has access to straight out of the user's profile.
The profile information can be used in conjunction with your own authorization based on your own membership and role providers (specific for your site). In that case you can assign each user a role specific to each service.
To successfully achieve that, for each service, write a wrapper, encapsulating service calls with your own methods (which call the service). This will allow you to mark your own methods with the [PrincipalPermissison] attribute... and achieve seemless authorization.
So if your user has access to the Amazon web service and there are credentials for that service stored in his/her profile you can have the following:
User Role: "AmazonAccessor"
public AmazonServiceWrapper
{
[PrincipalPermission(SecurityAction.Demand, Role = "AmazonAccessor")]
public void DoSomething()
{
UserProfile profile = UserProfile.Get();
ServiceCredential credential = (ServiceCredential)(from c in profile.ServiceCredentials where c.ServiceName = "Amazon" select c).Take(1);
if( credential == null )
return;
AmazonService amazon = new AmazonService();
amazon.ClientCredentials.UserName.UserName = credential.Username; //coming from profile
amazon.ClientCredentials.UserName.Password = credential.Password; //coming from profile
try{
amazon.DoSomething(); //wrap the amazon call.
}
catch(Exception ex)
{
}
}
}
This will prevent you from having to juggle membership and all sorts of other headaches.
Now to create your own profile you can do something like this:
[Serializable]
public class ServiceCredential
{
public string ServiceName { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string ServiceUrl { get; set; }
}
public class UserProfile : ProfileBase
{
public static UserProfile Get(string username)
{
return (UserProfile)Create(username);
}
public static UserProfile Get()
{
return (UserProfile)Create(Membership.GetUser().UserName);
}
[SettingsAllowAnonymous(false)]
public List<ServiceCredential> ServiceCredentials
{
get
{
try
{
return base.GetPropertyValue("ServiceCredentials") as List<ServiceCredential>;
}
catch
{
return new List<ServiceCredential>();
}
}
set
{
base.SetPropertyValue("ServiceCredentials", value);
}
}
}
And of course the Web config:
<system.Web>
<profile
inherits="MyApplication.UserProfile"
defaultProvider="AspNetSqlProfileProvider">
<providers>
<add
name="MyProfileProvider"
type="System.Web.Profile.SqlProfileProvider"
connectionStringName="MyConnectionString"
applicationName="MyApplication" />
</providers>
</profile>
<system.Web>