Apache Shiro
Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management.
- Authentication: Support logins across one or more pluggable data sources (LDAP, JDBC, Kerberos, ActiveDirectory, etc).
- Authorization: Perform access control based on roles or fine-grained permissions, also using pluggable data sources.
Apache Shiro Authentication on AppEngine
We use AppEngine’s User Service to authenticate users. To integrate Apache Shiro with AppEngine User Service, we implement our own AuthenticatingFilter to control the login process
/**
* An {@link AuthenticatingFilter} which uses UserService
* to control the authentication process
*/
public class GaeAuthenticatingFilter extends AuthenticatingFilter{
public GaeAuthenticatingFilter() {
setLoginUrl(null);
}
@Override
protected AuthenticationToken createToken(
ServletRequest request, ServletResponse response) {
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
return createToken(user.getUserId(), null, request, response);
}
@Override
protected boolean isRememberMe(ServletRequest request) {
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception{
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user == null){
saveRequest(request);
String requestURI = WebUtils.getRequestUri(WebUtils.toHttp(request));
String loginUrl = this.getLoginUrl();
if (loginUrl == null){
loginUrl = userService.createLoginURL(requestURI);
WebUtils.issueRedirect(request, response, loginUrl);
}else{
request.setAttribute("requestURI", requestURI);
request.getRequestDispatcher(loginUrl).forward(request, response);
}
return false;
}else{
// Perform the internal login process
boolean result = executeLogin(request, response);
return result;
}
}
}
Apache Shiro Security Realms
Apache Shiro provides several standard Realms (JndiLdapRealm, JdbcRealm, etc) which translates this application-specific data into Shiro internal format.
AppEngine Authenticating Realm
For AppEngine our realm as below:
/**
* A {@link Realm} which uses {@link UserService} to authenicate users
*/
public class GaeAuthenticatingRealm extends AuthorizingRealm{
public GaeAuthenticatingRealm() {
}
@Override
protected void onInit() {
// We use UserService to authenticate users,
// thus dont have any credentials
setCredentialsMatcher(new AllowAllCredentialsMatcher());
}
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user == null){
throw new AccountException("Not authenticated.");
}
return new SimpleAuthenticationInfo(user, null, getName());
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// We perform Authentication only, thus return null
return null;
}
}
Put it all in web.xml
<web-app>
...
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>
org.apache.shiro.web.servlet.IniShiroFilter
</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>
[main]
authc = com.gdevelop.security.GaeAuthenticatingFilter
# Optional, used for OpenId provider selection form.
# authc.loginUrl=/WEB-INF/openIdLogin.jsp
authcRealm = com.gdevelop.security.GaeAuthenticatingRealm
[urls]
/account/* = authc
</param-value>
</init-param>
</filter>
...
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Build-in Authorizing Realm
For demo purpose, we use the build-in IniRealm. Just configure it in web.xml file.
<web-app>
...
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>
org.apache.shiro.web.servlet.IniShiroFilter
</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>
[main]
authc = com.gdevelop.security.GaeAuthenticatingFilter
# Optional, used for OpenId provider selection form.
# authc.loginUrl=/WEB-INF/openIdLogin.jsp
authcRealm = com.gdevelop.security.GaeAuthenticatingRealm
[users]
# format: username = password, role1, role2, ..., roleN
test@example.com = ,admin
test1@example.com = ,president
test2@example.com = ,darklord,schwartz
test3@example.com = ,goodguy,schwartz
[urls]
/account/* = authc
</param-value>
</init-param>
</filter>
...
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Custom Authorizing Realm
You can implement a custom Authorizing Realm. The JdbcRealm is a good starting point to do so. See also the DatastoreRealm of the http://code.google.com/p/shiro-gae project.
<web-app>
...
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>
org.apache.shiro.web.servlet.IniShiroFilter
</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>
[main]
authc = com.gdevelop.security.GaeAuthenticatingFilter
# Optional, used for OpenId provider selection form.
# authc.loginUrl=/WEB-INF/openIdLogin.jsp
authcRealm = com.gdevelop.security.GaeAuthenticatingRealm
authzRealm = <full-qualified class name>
[urls]
/account/* = authc
</param-value>
</init-param>
</filter>
...
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Secure our application
Now we are ready to perform authorization. For example, in your servlet class, we can check user permission, role as below:
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.hasRole("manager")){
...
}
if (currentUser.isPermitted("reports:generate")){
..
}
Resources
Sample code base on the Apache Shiro Quickstart
