I'm implementing Shiro security and my login implementation is working but logoff seems to not log off the user. I have a log off link that points to a custom servlet the calls the Shiro logoff code. The code executes (I can step through the code and the logging output is seen in the console) however, if I press the back button in the browser and reload the original page I can get to the page and I am not asked to re-enter my logon credentials.
What do I need to do to fix this?
The servlet code is shown below. The complete example is here:
https://github.com/NACHC-CAD/web-security-example
Servlet code:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Subject subject = SecurityUtils.getSubject();
log.info("Doing log off for user: ");
if(subject != null && subject.isAuthenticated()) {
subject.logout();
log.info("Subject has been logged off");
} else {
log.info("USER NOT FOUND, NOT LOGGED OFF");
}
log.info("User has been log offed");
}
Logon code:
@Slf4j
public class MyAppRealm extends SimpleAccountRealm {
/**
* Authentication method is based on SimpleAccountRealm doGetAuthenticationInfo(AuthenticationToken token) method.
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
log.info("* * * DOING CUSTOM AUTHN * * *");
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String uid = upToken.getUsername();
String pwd = new String(upToken.getPassword());
log.info("Doing login for user: " + uid);
SimpleAccount account = null;
if(uid.equals("foo") && pwd.equals("bar")) {
account = new SimpleAccount("foo", "bar", getName());
account.setCredentials("bar");
account.addRole("ROLE_ADMIN");
} else {
account = null;
log.info("Credentials failed for user: " + uid);
}
// account.setObjectPermissions(permissions);
log.info("Done with custom authn");
return account;
}
/**
* Authorization method is based on SimpleAccountRealm doGetAuthorizationInfo(PrincipalCollection principals) method.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("* * * DOING CUSTOM AUTHZ * * *");
String username = getUsername(principals);
USERS_LOCK.readLock().lock();
try {
SimpleAccount rtn = this.users.get(username);
log.info("Done with custom authz.");
return rtn;
} finally {
USERS_LOCK.readLock().unlock();
}
}
}
web.xml
<!--
*
* shiro stuff
*
-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
shiro.ini
# ---
#
# ini file for shiro
#
# ---
[main]
myRealm = org.nachc.examples.websecurity.shiro.util.realm.MyAppRealm
securityManager.realms = $myRealm
[users]
admin = admin, ROLE_ADMIN
[roles]
ROLE_ADMIN = *
[urls]
/app/** = authcBasic
--- EDIT ---------------------------
It looks like the browser is caching the credentials and resending if the same page is accessed. Even from an incognito window in a new tab, if I access the same page the basic authentication credentials are added to the request by the browser. It looks like this is add by Chrome as part of their standard handling of basic authentication. More info here: How to clear basic authentication details in chrome (the methods given to clear the details is very manual and user dependent, not really suitable if you have a requirement to ensure a logged off user needs to resupply credentials).
--- EDIT 2 -----------------
I was able to get around this problem by implementing form based authentication using Shiro per the suggestions of the accepted answer below. Complete example is here:
This is a common problem with BASIC auth.
Do you need to support BASIC auth? If not, you could use a login form, see the example from here: https://github.com/apache/shiro/blob/main/samples/web/src/main/webapp/WEB-INF/shiro.ini#L54-L60
If you need to support both a BASIC auth and a form for web browsers, you could do something like:
/app/** = authcBasic[permissive], authc
NOTE: This is from memory, so test accordingly ;)