Goal
I am setting up a RESTful webservice, using RESTEasy framework. For security I use Apache Shiro. I want my api to stop accepting requests or timing out persons that login too much.
Problem
Whenever I go some URL with my browser (chrome), I can try to login infinitely many times. Seems a really bad idea to allow this. As a measure, I have made sure to remember the nr of login attempts, for which users cannot login after 3 times. However, with a brute force attack, you could still block all users from loging in. I want a more general solution.
Shiro.ini
[main]
# We store users and passwords inside the realm.
myRealm = com.myproject.shiro.DatabaseRealm
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
[urls]
/api/version = anon
/api/** = authcBasic
DatabaseRealm
public class DatabaseRealm extends AuthorizingRealm {
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// No clue what to do with this functin. I only use authentication and not authorization, so probably just nothing.
return null;
}
/**
* Check if the user inputted is valid. The user can login if holds:
* 1. Password is correct. (if not, nrOfLogonAttempts++)
* 2. LogonUser.nrOfLogonAttemps is less than 3
* 3. LogonUser.dateEndValid is null or >= today.
* #param authenticationToken Token with basic information.
* #return SimpleAuthenticationInfo
* #throws AuthenticationException Whenever the user cannot login.
*/
#SuppressWarnings("ConstantConditions")
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws UnknownAccountException, IncorrectCredentialsException, LockedAccountException, ExpiredCredentialsException {
// Connect with the database.
DbContext context = null;
try {
context = DbContextUtil.getContextFromTomcat();
// Lookup user in the database.
LogonUserMyDao logonUserMyDao = new LogonUserMyDao(context);
LogonuserPojo logonuserPojo = logonUserMyDao.fetchOneByUsername(((UsernamePasswordToken) authenticationToken).getUsername());
if (logonuserPojo == null) {
throw new UnknownAccountException("Could not find user.");
}
// Check password
String plainTextPassword = new String(((UsernamePasswordToken) authenticationToken).getPassword());
if (!BCryptUtil.checkPassword(plainTextPassword, logonuserPojo.getPassword())) {
// We will note this event.
logonuserPojo.setNroflogonattempts(logonuserPojo.getNroflogonattempts() + 1);
logonUserMyDao.update(logonuserPojo);
context.commit();
throw new IncorrectCredentialsException("Incorrect password.");
}
// Check nrOfLogonAttempts
if (logonuserPojo.getNroflogonattempts() >= 2) {
throw new LockedAccountException("Cannot login anymore.");
}
// Check date
if (logonuserPojo.getDateendvalid() != null && DateTimeUtil.isBeforeToday(logonuserPojo.getDateendvalid())) {
throw new ExpiredCredentialsException("Account is expired.");
}
// User is valid, so return some info.
return new SimpleAuthenticationInfo(logonuserPojo.getUsername(), plainTextPassword, getClass().getName());
} catch (SQLException e) {
MyLogger.logError("Could not connect to user database.", e);
throw new AuthenticationException("Could not connect to databse.");
} finally {
if (context != null) {
try {
context.getConnection().close();
} catch (SQLException e) {
MyLogger.logError("Could not close connection", e);
}
}
}
}
}
Are you looking for more general DDOS protection? There are a few options out there depending on where your app is running (for example AWS Shield).
You could also prevent connections from reaching your db with something like this: https://github.com/iTransformers/ddos-servlet-filter (but, that that would still require handling the request in your application)
On the Shiro side of things, counting your attempts is NOT a bad idea, but you need to watch out for the user management side of things (How does a user get unlocked, support request? Wait 30 minutes?) Instead of recording failures, you may just want to record/audit all attempts (excluding the actual password of course). With either option a call to support or an n minute window, this may help provide some context to support or an easy query.
Related
Hoping someone on here can help me out of a conundrum.
We are trying to remove all Admin sessions from our application, but are stuck with a few due to JCR Access Denied exceptions. Specifically, when we try to create AEM groups or users with a service user we get an Access Denied exception. Here is a piece of code written to isolate the problem:
private void testUserCreation2() {
String groupName = "TestingGroup1";
Session session = null;
ResourceResolver resourceResolver = null;
String createdGroupName = null;
try {
Map<String, Object> param = new HashMap<String, Object>();
param.put(ResourceResolverFactory.SUBSERVICE, "userManagementService");
resourceResolver = resourceResolverFactory.getServiceResourceResolver(param);
session = resourceResolver.adaptTo(Session.class);
// Create UserManager Object
final UserManager userManager = AccessControlUtil.getUserManager(session);
// Create a Group
LOGGER.info("Attempting to create group: "+groupName+" with user "+session.getUserID());
if (userManager.getAuthorizable(groupName) == null) {
Group createdGroup = userManager.createGroup(new Principal() {
#Override
public String getName() {
return groupName;
}
}, "/home/groups/testing");
createdGroupName = createdGroup.getPath();
session.save();
LOGGER.info("Group successfully created: "+createdGroupName);
} else {
LOGGER.info("Group already exists");
}
} catch (Exception e) {
LOGGER.error("Error while attempting to create group.",e);
} finally {
if (session != null && session.isLive()) {
session.logout();
}
if (resourceResolver != null)
resourceResolver.close();
}
}
Notice that I'm using a subservice name titled userManagementService, which maps to a user titled fwi-admin-user. Since fwi-admin-user is a service user, I cannot add it to the administrators group (This seems to be a design limitation on AEM). However, I have confirmed that the user has full permissions to the entire repository via the useradmin UI.
Unfortunately, I still get the following error when I invoke this code:
2020-06-22 17:46:56.017 INFO
[za.co.someplace.forms.core.servlets.IntegrationTestServlet]
Attempting to create group: TestingGroup1 with user fwi-admin-user
2020-06-22 17:46:56.025 ERROR
[za.co.someplace.forms.core.servlets.IntegrationTestServlet] Error
while attempting to create group. javax.jcr.AccessDeniedException:
OakAccess0000: Access denied at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(CommitFailedException.java:231)
at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(CommitFailedException.java:212)
at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.newRepositoryException(SessionDelegate.java:670)
at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegate.java:496)
Is this an AEM bug, or am I doing something wrong here?
Thanks in advance
So it seems the bug is actually in the old useradmin interface. It was not allowing me to add my system user into the admninistrators group, but this is possible in the new touch UI admin interface.
I'm trying to build a multi-tenant ASP.NET Core 2.1 WebApi.
I would like to chose tenant from the jwt token and not from the url or port.
So When user Request the token, I put it's tenant_id into the token.
But when I try to resolve the TenantId in the Autofac Multitenant strategy (ITenantIdentificationStrategy) like this:
public bool TryIdentifyTenant(out object tenantId)
{
_logger.LogInformation("***********************************TryIdentify");
tenantId = null;
try
{
var context = _httpContextAccessor()?.HttpContext;
if(context != null && context.Request != null)
{
var id = context.User.FindFirst("tenantId")?.Value;
if (id != null)
{
tenantId = id;
}
}
}
catch(Exception)
{
// Happens at app startup in IIS 7.0
}
return tenantId != null;
}
I see that the context.User is not jet populated and that's because the Jwt authentication didn't happen jet.
How to do it?
The short version of this is you don't get to do this for free. This is generally why it's best to use something you can trust but doesn't require additional support to use (like the host name in the request).
If you can only trust the token, then I have seen solutions that roughly do this (in pseudocode-that-looks-like-C#):
if(context.Items["tenant"] == null && context.User == null)
{
// no tenant has been identified before and
// token validation hasn't happened so manually
// get the tenant from the token knowing you are
// potentially getting an untrusted value.
context.Items["tenant"] = ManuallyLookAtToken();
}
else if(context.Items["tenant_trusted"] == null && context.User != null)
{
// a "trusted" tenant ID hasn't been read from the user
// principal so let's update.
context.Items["tenant"] = GetTenantFrom(context.User);
context.Items["tenant_trusted"] = true;
}
return context.Items["tenant"];
The risk in doing something like this is that you open yourself up to an attack where someone sends in a malformed token that only lives long enough to get past the initial part of your request pipeline. The token won't pass validation so the regular security should take care of it, but before that runs the tenant value isn't officially validated. If you have pipeline logic, that, for example... auto-provisions new tenants on first request or something like that?... then you may be in trouble. Someone could randomly generate millions of tenant names and kill your database. In cases like that you can sometimes fall back to other things like the host name.
Alternatively, you can actually manually invoke the token validation logic in that ManuallyLookAtToken() method and ensure it's valid before proceeding. It's sort of painful, but not impossible. That would mean technically you would run that twice during any given request, and it's sort of expensive, so consider the perf if you go that route and balance that with security implications.
I am using scala and play framework. I want to use play security Authorization in my app.
Previously I implemented it in project using java and play like following :
public class Secured extends Security.Authenticator {
private static String EMAIL = "Email";
private static String U_COOKIE = "ucookie";
public String getUsername(Context ctx) {
String decodedText = null;
String CHARSET = "ISO-8859-1";
Cookies cookies = play.mvc.Controller.request().cookies();
try {
Cookie emailCookie = cookies.get(EMAIL);
Cookie uCookie = cookies.get(U_COOKIE);
if (uCookie !=null && uCookie.value() != null) {
String userId = uCookie.value();
}
if (emailCookie != null && emailCookie.value() != null) {
String email = emailCookie.value();
try {
decodedText = new String(Base64.decodeBase64(email.getBytes(CHARSET)));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
Logger.error(e.getMessage());
}
return decodedText;
}
public Result onUnauthorized(Context ctx) {
String done = play.mvc.Controller.request().path();
return redirect(routes.RegController.signIn(done));
}
}
and I used above Authorization in all of my method using
#Security.Authenticated(Secured.class)
Before any of my methods throughout my application.
When I call any method #before that method gives call to secured class and authenticate user.
Now I want to implement same thing using scala. Following are my questions....
1) Is it possible to use # to inherit and call methods of secured class??
2) What is the right method to call play's security authentication??
P.S. I want to use cookies for implementation of security Authentication/Authorization.
Any help or workaround will be great favor..
If you build an application intended for production:
Don't do it
Use one of the many frameworks out there:
Deadbolt2 : https://github.com/schaloner/deadbolt-2
SecureSocial: http://www.securesocial.ws/
Silhouette : http://silhouette.mohiva.com/
They are also a great starting point to look for best practices.
If you want to do it mainly for learning and there are no real scecurity concerns go for:
https://www.playframework.com/documentation/2.3.x/ScalaActionsComposition
There look for the heading auth it gives some information how to do it.
To have the authentication kick in before any method you could use a Filter to intercept the request:
https://www.playframework.com/documentation/2.3.x/ScalaInterceptors
In servicestack OAuth implementation I only saw possibility to automatically login with eg. facebook account.
But is there abbility to support registration process with facebook login. What I wanted is to let users login to facebook app, and then load their Name, Surname and email and prefill needed text boxes for real registration on my site (since I also have to have mobile phone verification etc.) I don't want user to be authorized and authenticated when he logs in with facebook. Only credentials login should be valid one for full site access.
Edit: I found a solution.
In FacebookProvider.cs
public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName)) return false;
}
return tokens != null && session.UserName!=null && !string.IsNullOrEmpty(tokens.AccessTokenSecret);
}
The catch was the && session.UserName!=null part. So we can check if user is logged in using credentials, this will be !=null and user can use all services. If not, this will be ==null and he can only get facebook info from session.
The SocialBootstrap API project shows an example of handling the callback after a successful Authentication by overriding the OnAuthenticated() hook of its custom user session:
I've pulled out, rewrote some and highlighted some of the important bits:
public class CustomUserSession : AuthUserSession
{
public override void OnAuthenticated(IServiceBase authService,
IAuthSession session,
IOAuthTokens tokens,
Dictionary<string, string> authInfo)
{
base.OnAuthenticated(authService, session, tokens, authInfo);
//Populate matching fields from this session into your own MyUserTable
var user = session.TranslateTo<MyUserTable>();
user.Id = int.Parse(session.UserAuthId);
user.GravatarImageUrl64 = CreateGravatarUrl(session.Email, 64);
foreach (var authToken in session.ProviderOAuthAccess)
{
if (authToken.Provider == FacebookAuthProvider.Name)
{
user.FacebookName = authToken.DisplayName;
user.FacebookFirstName = authToken.FirstName;
user.FacebookLastName = authToken.LastName;
user.FacebookEmail = authToken.Email;
}
else if (authToken.Provider == TwitterAuthProvider.Name)
{
user.TwitterName = authToken.DisplayName;
}
}
//Resolve the DbFactory from the IOC and persist the user info
using (var db = authService.TryResolve<IDbConnectionFactory>().Open())
{
//Update (if exists) or insert populated data into 'MyUserTable'
db.Save(user);
}
}
//Change `IsAuthorized` to only verify users authenticated with Credentials
public override bool IsAuthorized(string provider)
{
if (provider != AuthService.CredentialsProvider) return false;
return base.IsAuthorized(provider);
}
}
Basically this user-defined custom logic (which gets fired after every successful authentication) extracts data from the UserSession and stores it in a custom 'MyUserTable'.
We've also overridden the meaning of IsAuthorized to only accept users that have authenticated with CredentialsAuth.
You can use this data to complete the rest of the registration.
Other possible customizations
ServiceStack's built-in Auth persists the AuthData and populates the Session automatically for you. If you want to add extra validation assertions you can simply use your own custom [Authentication] attribute instead containing additional custom logic. Look at the implementation of the built-in AuthenticateAttribute as a guide.
I want to store activeUser object in session scobe when someone logged in, I am using it forward for user specific operations but there is a problem accours when admin attemps to loggin because admin is not a default user so #out annotation causes an error
error code is familiar = #Out attribute requires non-null value: authenticator.activeUser
my logic code is below;
(note: admin and standart user use same login panel)
#Out(scope = ScopeType.SESSION, required = true)
private User activeUser;
public boolean authenticate() throws Exception {
// SELECT OBJECT(user) FROM User user WHERE user.name = :name and
// user.password = :pass
try {
// create a query for getting a user with specified parameters if no
// user exist throw an exception
setActiveUser((User) entityManager
.createQuery(
"select u from User u where u.userName= :uname and u.password = :pass")
.setParameter("uname", credentials.getUsername())
.setParameter("pass", credentials.getPassword())
.getSingleResult());
identity.addRole("user");
return true;
} catch (Exception e) {
// so there is no any normal user now looking for any admin entry
System.out.println("there is no any member looking for admin");
try {
Admin admin = (Admin) entityManager
.createQuery(
"select a from Admin a where a.username = :uname and a.pass = :p")
.setParameter("uname", credentials.getUsername())
.setParameter("p", credentials.getPassword())
.getSingleResult();
identity.addRole("admin");
System.err.println("found admin");
return true;
} catch (Exception e2) {
System.err.println("admin bulunamadı");
throw e2;
}
}
}
in #out required must be true because in user specific crud operations I am logging them with user id.
how can I solve this problem. setting required=false is enough?
and the other hand if no user finds the entitymanager.resultlist() method throws an exception. so i know that the #out annotataions works unless the methods throw any excepttion?