Hi All,
for anybody interested in using MongoDB as a Datastore for his realm, here
are the relevant code snippets for doing so:
shiro.ini:
[main]
mongoRealm = de.versatec.mongo.shiro.MongoRealm
-----------------------
MongoRealm.java:
package de.versatec.mongo.shiro;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.query.Query;
import com.mongodb.MongoClient;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class MongoRealm extends AuthorizingRealm {
private MongoProvider mongoProvider; // a singleton for accessing
MongoDB
private MongoClient mongoClient = null;
private Datastore ds = null;
public MongoRealm() { // I do this to get a singleton Mongo client
(mongo java driver does connection pooling on its own)
try {
InitialContext initialContext = new InitialContext();
BeanManager bm = (BeanManager)
initialContext.lookup("java:comp/BeanManager");
Bean<MongoProvider> bean = (Bean<MongoProvider>)
bm.getBeans(MongoProvider.class).iterator().next();
CreationalContext<MongoProvider> ctx =
bm.createCreationalContext(bean);
mongoProvider = (MongoProvider) bm.getReference(bean,
MongoProvider.class, ctx);
if (mongoClient == null && mongoProvider != null) {
mongoClient = mongoProvider.getMongoClient();
}
if (ds == null && mongoProvider != null) {
ds = mongoProvider.getDs();
}
} catch (NamingException ex) {
Logger.getLogger(MongoRealm.class.getName()).log(Level.SEVERE,
null, ex);
}
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection
pc) {
Set<String> roles = new HashSet<>();
Set<Permission> permissions = new HashSet<>();
for (Object tmp : pc.fromRealm(getClass().getName())) {
User user =
ds.find(User.class).field("name").equal(tmp.toString()).get();
if (user != null) {
roles.addAll(user.getRoles());
for (String temp : roles) { // in a multi-tenant environment
different tenants may use the same role names, therefore we have to reduce
the list of roles to those for the specific tenant
Query<Role> q = ds.createQuery(Role.class);
q.field("name").equal(temp);
q.field("tenantId.identifier").equal(user.getTenantId().getIdentifier());
Role role = q.get();
if (role != null) {
permissions.addAll(role.getPermissions());
}
}
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setObjectPermissions(permissions);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken
at) throws AuthenticationException {
User user =
ds.find(User.class).field("name").equal(at.getPrincipal().toString()).get();
if (user != null) {
return new SimpleAuthenticationInfo(user.getName(),
user.getPassword(), getClass().getName());
}
throw new AuthenticationException();
}
}
--------------------
MongoProvider.java:
package de.versatec.mongo.shiro;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.Morphia;
import com.mongodb.MongoClient;
import java.net.UnknownHostException;
import java.util.Properties;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.enterprise.context.ApplicationScoped;
import javax.faces.context.FacesContext;
@ApplicationScoped
public class MongoProvider {
// this resource is configured in glassfish 3.1.2 as JNDI -> Custom
Resources -> 'nosql/qmmongo', type: java.util.Properties
// this way I need not change source code or configuration file when
database connection parameters change
// *but you may just as well hard code your database connection parameters
instead*
@Resource(name = "nosql/qmmongo")
private Properties mongoConnection = null;
private MongoClient mongoClient = null;
private Morphia morphia = null;
private Datastore ds = null;
public MongoProvider() {
}
@PostConstruct
public void init() {
String options = mongoConnection.getProperty("options");
if (mongoClient == null) {
try {
mongoClient = new
MongoClient(mongoConnection.getProperty("host:port"));
} catch (UnknownHostException ex) {
ex.printStackTrace();
}
}
if (morphia == null && mongoClient != null) {
morphia = new Morphia();
morphia.mapPackage("de.versatec.mongobase");
}
if (ds == null && morphia != null) {
ds = morphia.createDatastore(mongoClient, "frameDB");
}
}
... // getters and setters
}
--------------------
User.java:
package de.versatec.mongo.shiro;
import com.google.code.morphia.annotations.Embedded;
import com.google.code.morphia.annotations.Entity;
import com.google.code.morphia.annotations.Id;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import org.bson.types.ObjectId;
@Entity
public class User implements Serializable {
@Id
private ObjectId id;
private String name;
private String password;
@Embedded
private TenantId tenantId;
private Set<String> roles = new HashSet<>();
... // getters and setters
}
-----------------------
Role.java:
package de.versatec.mongo.shiro;
import com.google.code.morphia.annotations.Embedded;
import com.google.code.morphia.annotations.Entity;
import com.google.code.morphia.annotations.Id;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authz.Permission;
import org.bson.types.ObjectId;
@Entity
public class Role {
@Id
private ObjectId id;
private String name;
private Set<Permission> permissions = new HashSet<>();
@Embedded
private TenantId tenantId;
public Role(String name) {
this.name = name;
}
public Role() {
}
... // getters and setters
}
-------------------------
TenantId.java (yes we use a multi-tenant approach, but you do *not *have to
do the same):
package de.versatec.mongo.shiro;
import com.google.code.morphia.annotations.Embedded;
@Embedded
public class TenantId {
private String identifier;
public TenantId(String identifier) {
this.identifier = identifier;
}
public TenantId() {
}
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
}
--
View this message in context:
http://shiro-user.582556.n2.nabble.com/Example-MongoDB-Realm-tp7579029.html
Sent from the Shiro User mailing list archive at Nabble.com.