I want to use Shiro on my next web project but I do not know a good (if not the best) strategy to manage users ([users] in shiro.ini).
Is it best to create Shiro user for every registered member?
Or create a single Shiro user then for every member just store it to some database and acces it via that Shiro user?
If you would go for #1, how would you manage/automate it? Most of the projects I worked on opted for #2.
Thanks
Configuring users in shiro.ini is not a good option for production environment. It can be used only if you have a small number of user accounts and you don't need to create or change accounts at runtime. It is mostly used for testing.
It is better for almost all projects to use some storage to keep all user accounts. It can be database or some external authentication engine, like ldap, cas or even oauth.
You can just use Stormpath as your user/group store. Drop in the Shiro integration and boom - instant user/group data store for Shiro-enabled applications with a full management UI and Java SDK.
It even helps automate things like 'forgot password' emails and account email verification. It's free for many usages too. You can see the Shiro sample app using Stormpath as an example.
Shiro provides multiple ways to configure users. Take a look at the possible Realm configurations here.
If none of these satisfy your needs, you could even write a custom Realm for your application, that can, say, pull user info from a NoSQL database, or get info from a SAML response, or use OAuth2. It's definitely not advisable to create any user details in shiro.ini in production. To give a notion of what custom realms might look like, here's an example where I created a SAML2 based user authc and authz: shiro-saml2.
PLease do not use only one user for everyone. Avoid this option.
Much better to use one user(account) per user.
In shiro, you can have the RDMS Realm that allows you to use a simple database like mysql in order to store your user /account / permissions. :)
Clone this project, (that is not mine), and get started in 1 minute! :)
shiro/mysql GIT example
Enjoy it :)
Shiro provide implementing your own realm as per your requirement.
Create a simple realm in which you can manage details, login, permissions and roles.
You can use jdbc, Hibernate, or any other authentication manner to manage them.
Configure this realm to your ini or whatever way you using in your project.
Now Shiro will automatically invoke methods of your realm class to look for credential, permissions, roles.
For ex I have a shiro hibernate realm I used my hibernate code to manage users in my db.
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* #author Ankit
*
*/
public class PortalHibernateRealm extends AuthorizingRealm {
private static final Logger LOGGER = new Logger(
PortalHibernateRealm.class.toString());
/**
*
*/
public PortalHibernateRealm() {
super();
/*
* Set credential matcher on object creation
*/
setCredentialsMatcher(new CredentialsMatcher() {
#Override
public boolean doCredentialsMatch(AuthenticationToken arg0,
AuthenticationInfo arg1) {
UsernamePasswordToken token = (UsernamePasswordToken) arg0;
String username = token.getUsername();
String password = new String(token.getPassword());
/*
Check for credential and return true if found valid else false
*/
return false;
}
});
}
#Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principalCollection) {
Collection<String> permissionSet;
SimpleAuthorizationInfo info = null;
Long userId = (Long) principalCollection.getPrimaryPrincipal();
//Using thi principle create SimpleAuthorizationInfo and provide permissions and roles
info = new SimpleAuthorizationInfo();
return info;
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
/*using this token create a SimpleAuthenticationInfo like
User user = UserUtil.findByEmail(token.getUsername());
*/
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
primaryPrin, Password, screenName);
return authenticationInfo;
}
}
Related
i am trying to give default role as Internal/Subscriber to all users.
i made changes in we made changes in file /_system/config/apimgt/applicationdata/tenant-conf.json and added role such as to Internal/creator,Internal/everyone,apimrole
"Name": "apim:subscribe",
"Roles": "admin,Internal/creator,Internal/everyone,apimrole,Internal/subscriber"
it gives me below error
org.wso2.carbon.apimgt.api.APIManagementException: Error while adding the subscriber
laxman#gmail.com#carbon.super#carbon.super
any help appreciated
New user creation takes place in the WSO2 API Manager in two ways.
Through the management console of the API Manager
Self signup
In 1st way you can assign roles when creating users.
For self signed-up users there already exists a handler to assign Internal/subscriber role to the new users who are having Internal/selfsignup role.
To assign role: Internal/subscriber to new users or existing role not assigned users we have below two options:
Option 1
If you wish to assign subscriber role to existing role not assigned users using Management Console, then you can go to roles listing page there:
There is an option: Assign Users in Actions column in role list relevant to Internal/subscriber role.
It will list all the users who have not assigned Internal/subscriber role and there are several options to select many users at once and assign the role.
Option 2
You can write a custom user operation event listener and add it as OSGI bundle.
In this case you can refer this WSO2 IS doc and write a event listener extending AbstractIdentityUserOperationEventListener.
This sample code worked for me:
public class SampleEventListener extends AbstractIdentityUserOperationEventListener {
private static final String EVENT_LISTENER_TYPE = "org.wso2.carbon.user.core.listener.UserOperationEventListener";
private static final String SUBSCRIBER_ROLE = "Internal/subscriber";
#Override
public boolean doPreAddUser(String userName, Object credential, String[] roleList, Map<String, String> claims,
String profile, UserStoreManager userStoreManager) throws UserStoreException {
List<String> roles = new ArrayList<>(Arrays.asList(roleList));
if (!roles.isEmpty() && !roles.contains(SUBSCRIBER_ROLE)) {
userStoreManager.updateRoleListOfUser(userName, new String[]{}, new String[] { SUBSCRIBER_ROLE });
}
return true;
}
This will add Internal/subscriber role to each newly adding user, if the user doesn't have that role in the process of adding new user.
Here it has mentioned multiple interfaces with which you can implement User Store Listeners.
For OSGI bundle creation and deployment process you can find this sample GitHub project. You can copy the built jar file to the directory <APIM_HOME>/repository/components/dropins/ by following the steps have been mentioned there. (Since WSO2 API Manager is also using WSO2 IS components you can follow the same steps mentioned in README with the API Manger as well)
You can go through this blog post to get complete idea about OSGI bundling.
We are using a PostgreSQL database with AWS RDS IAM authorization feature – which means that our application needs to refresh the authorization token every 10 minutes or so (since the token is valid for 15 minutes). This token is used as a database password and I need to periodically update it. We are using the Dropwizard framework which is taking advantage of Apache Commons DBCP Component that handles connection pooling.
I was able to enhance the configuration class so that it performs an AWS API call to get the token instead of reading the password from configuration file. However this works only once, during application startup, for 15 minutes. I would like to call AWS API for the token perdiodically and handle the creation of connections as well as invalidating old ones.
import org.jooq.Configuration;
import org.jooq.impl.DefaultConfiguration;
import io.dropwizard.setup.Environment;
import org.example.myapp.ApplicationConfiguration;
// more less relevant imports...
#Override
public void run(ApplicationConfiguration configuration, Environment environment) {
Configuration postgresConfiguration = new DefaultConfiguration().set(configuration.getDbcp2Configuration()
.getDataSource())
.set(SQLDialect.POSTGRES_10)
.set(new Settings().withExecuteWithOptimisticLocking(true));
// this DSLContext object needs to be refreshed/recreated every 10 minutes with the new password!
KeysDAO.initialize(DSL.using(postgresConfiguration));
// rest of the app's config
}
How can I implement such a connection recreation mechanism? The org.jooq.ConnectionProvider looks promising, but I need some more guidance on how to inject the password on a periodic basis (and implement a custom ConnectionProvider). Any hints would be greatly appreciated.
EDIT:
This morning I was able to confirm that after a fresh deployment the database interaction is possible, and after exactly 15 minutes I'm getting first exceptions:
org.postgresql.util.PSQLException: FATAL: PAM authentication failed for user "jikg_service"
at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:514)
at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:141)
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:192)
at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:49)
at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:195)
at org.postgresql.Driver.makeConnection(Driver.java:454)
at org.postgresql.Driver.connect(Driver.java:256)
at org.apache.commons.dbcp2.DriverConnectionFactory.createConnection(DriverConnectionFactory.java:39)
at org.apache.commons.dbcp2.PoolableConnectionFactory.makeObject(PoolableConnectionFactory.java:256)
at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:868)
at org.apache.commons.pool2.impl.GenericObjectPool.ensureIdle(GenericObjectPool.java:927)
at org.apache.commons.pool2.impl.GenericObjectPool.ensureMinIdle(GenericObjectPool.java:906)
at org.apache.commons.pool2.impl.BaseGenericObjectPool$Evictor.run(BaseGenericObjectPool.java:1046)
at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
at java.base/java.util.TimerThread.run(Timer.java:506)
Suppressed: org.postgresql.util.PSQLException: FATAL: pg_hba.conf rejects connection for host "172.30.19.218", user "my_db_user", database "my_db_development", SSL off
at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:514)
at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:141)
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:201)
... 12 common frames omitted
Those exceptions are repeated every minute.
I owe you all an explanation on this one. I forgot to mention one significant detail - we're actually using a modified version of Dropwizard developed in-house that uses bundled Apache Commons DBCP (which afaik is not officially part of Dropwizard) as well as other components. I ended up dropping Apache Commons DBCP in favor of HikariCP - which made it possible to update the pool configuration at runtime. Although not officially supported, the creator of the library hinted that it might work, and in our scenario it indeed worked. Below is a sample solution.
import org.jooq.Configuration;
import org.jooq.impl.DefaultConfiguration;
import io.dropwizard.setup.Environment;
import org.example.myapp.ApplicationConfiguration;
// more less relevant imports...
#Override
public void run(ApplicationConfiguration configuration, Environment environment) {
HikariDataSource hikariDataSource = loadDatabaseConfiguration(configuration.getDatabaseConfiguration());
new DbConfigurationLoader(hikariDataSource).start();
// this DSLContext object now has the reference to DataSource object that has an always-fresh password!
KeysDAO.initialize(DSL.using(hikariDataSource, SQLDialect.POSTGRES_10, new Settings().withExecuteWithOptimisticLocking(true)));
// rest of the app's config
}
private HikariDataSource loadDatabaseConfiguration(DatabaseConfiguration configuration) {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(configuration.getJdbcUrl());
hikariDataSource.setDriverClassName(configuration.getDriverClassName());
hikariDataSource.setMinimumIdle(configuration.getMinimumIdle());
hikariDataSource.setMaximumPoolSize(configuration.getMaximumPoolSize());
hikariDataSource.setUsername(configuration.getJdbcUser());
return hikariDataSource;
}
private class DbConfigurationLoader extends Thread {
private final HikariDataSource hikariDataSource;
private final RdsTokenProvider rdsTokenProvider;
public DbConfigurationLoader(HikariDataSource hikariDataSource) {
this.rdsTokenProvider = new RdsTokenProvider();
this.hikariDataSource = hikariDataSource;
}
#Override
public void run() {
while (true) {
hikariDataSource.setPassword(rdsTokenProvider.getToken());
try {
Thread.sleep(/* token is valid for 15 minutes, so it makes sense to refresh it more often */);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
Hope this saves somebody some time in the future.
The JAVA EE #DeclareRoles annotation is a way to declare the list of possible roles of the users to match with the annotation #RolesAllowed.
But what if our roles are stored in database and if the list of the potential roles is long ?
I currently use roles to specify an atomic access to functionnalities on my website, so I have a long list of roles as some users can access functionnality-1 but not the 2, and some can on the 2 but not on the 1, etc...
I want to avoid editing the #DeclareRoles annotation every time I am creating a new role for a new functionnality, so the question is :
Is there any way to programmatically setup the #DeclareRoles annotation or to specify that it should load from a database ?
Since the introduction of the JavaEE 8 security API you have the ability to write your own identity store. This allows you to fetch users and user data from a custom location and a custom service. You asked about using a database - so here is an example using a database facade together with a custom identity store;
#ApplicationScoped
public class MyIdentityStore implements IdentityStore {
#EJB private UserFacade userFacade;
#Override
public int priority() {
return 50;
}
#Override
public Set<ValidationType> validationTypes() {
return EnumSet.of(ValidationType.PROVIDE_GROUPS, ValidationType.VALIDATE);
}
#Override
public Set<String> getCallerGroups(CredentialValidationResult validationResult) {
final String userName = validationResult.getCallerPrincipal().getName();
final User user= usersFacade.find(userName);
return user.getRoles();
}
public CredentialValidationResult validate(UsernamePasswordCredential credential) {
/* Handle validation/login of the user here */
}
}
To explain the above slightly more - the getCallerGroups() will return the roles that the user is part of - something you can then use throughout the JavaEE security API and lockdown methods such as #RolesAllowed. The validate() method handles the validation of the user when a check is requested by the container. Finally, the validationTypes() method simply specifies what this Identity store should be used for - in this case we have specified both the fetching of roles and handling of validation.
So since EE8 introduced this - it has become really flexible and easy to take advantage of the security features in the platform.
Here are some really great references on the subject;
https://www.ibm.com/developerworks/library/j-javaee8-security-api-1
https://www.ibm.com/developerworks/library/j-javaee8-security-api-2
https://www.ibm.com/developerworks/library/j-javaee8-security-api-3
https://www.baeldung.com/java-ee-8-security
I've added my own scheduler task to the TYPO3 that will, for example, create new page if necessary. The scheduler runs by a special _cli_scheduler user and if I create new pages with it, other editors may not see it.
I'm using the DataHandler (former TCE) to create new pages. The start() method accepts an optional parameter - alternative user object that will be used as a creator of the page.
Having uid of an editor user, how can I fully instantiate the \TYPO3\CMS\Core\Authentication\BackendUserAuthentication object which then I provide to the DataHandler::start()?
I was thinking of using the object manager to get new instance of the mentioned class and just set the uid on it, but the DataHandler checks some other properties of the BackendUserAuthentication object, like permissions, etc.
What is the correct way for getting BackendUserAuthentication object will all user data? Is there any factory or a repository I could use?
No one was able to help me with this, so I started digging. After some reverse engineering I have found a complete way for loading any backend user as long as you know their ID. I have created a read-only repository with the following method:
public function fetchById($userId)
{
/** #var BackendUserAuthentication $user */
$user = $this->objectManager->get(BackendUserAuthentication::class);
$user->setBeUserByUid($userId);
$user->resetUC();
$user->fetchGroupData();
$user->getFileStorages();
$user->workspaceInit();
$user->setDefaultWorkspace();
return $user;
}
It will do the following:
Load user record from database and store it internally in the $user property
Load the UC of the user
Load user/group permissions
Initialize file storage
Initialize workspace
I've dumped user created by this method and compared with the currently logged-in user and it seems that all necessary properties have been set up.
Please let me know if I missed something.
The typical scenario I am looking into is:
User1 provides proper credentials to the front-end rest client (grant type: password) and the client gets the token in return.
The client sends the token and accesses the resources owned by User1.
In my scenario, once the client has the access token for user1, I want the client to have access limited to User1's resources only.
Consider that the client accesses the URI /users/1/books. The response will contain all the books associated with User1. The main problem is that if the client accesses the URL /users/2/books with User1's token, it gets the list of all the books for User2 which shouldn't be allowed.
How can I limit the scope to the user whose credentials were used to obtain the token?
How can I map the token to a specific user in my resource server?
I am using Spring/Java. But any general theory will also help.
After a lot of debugging, I got the answer.
Spring security 1.4
Token store: InMemoryTokenStore()
In ResourceServerConfiguration, configure HttpSecurity.
#Override
public void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().
// antMatchers("/oauth/token").permitAll().
antMatchers("/api/users/{userId}").access("#webSecurity.checkUserId(authentication,#userId)")
.anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable();
// #formatter:on
}
Create a class WebSecurity and provide the implementation.
public class WebSecurity {
public boolean checkUserId(Authentication auth, int id) {
return ((UmUser)auth.getPrincipal()).getId() == id;
}
}
Resources:
http://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html#el-access-web-path-variables
http://www.baeldung.com/get-user-in-spring-security
I had to debug a lot as I was using JwtTokenStore. This returned the Principal as a String and not the instance of UserDetails as I was expecting.
Once I switched to InMemoryTokenStore, I got the expected results. This was not a problem for me as I had the choice, but I would still like to know how to achieve it with the JWT.