java - Apache Shiro Stateless - 无会话 JWT 令牌认证问题
问题描述
我正在使用带有 Apache Shiro 的 Java Restful Jersey 实现在线平台进行身份验证授权。我的安全实现基于文章JSON Web Token with Apache Shiro。以下是我的 shiro.ini 和实现的类。
shiro.ini
[main]
jwtg = gr.histopath.platform.lib.JWTGuard
jwtv = gr.histopath.platform.lib.JWTVerifyingFilter
ds = com.mysql.cj.jdbc.MysqlDataSource
ds.serverName = 127.0.0.1
ds.port = 3306
ds.user = histopathUser
ds.password = H1s+0p@+h.U$er
ds.databaseName = histopath
jdbcRealm = gr.histopath.platform.lib.MyRealm
jdbcRealm.dataSource = $ds
credentialsMatcher = org.apache.shiro.authc.credential.Sha512CredentialsMatcher
credentialsMatcher.hashIterations = 50000
credentialsMatcher.hashSalted = true
credentialsMatcher.storedCredentialsHexEncoded = false
jdbcRealm.credentialsMatcher = $credentialsMatcher
jdbcRealm.permissionsLookupEnabled = false
shiro.loginUrl = /authentication/login
#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.globalSessionTimeout = 172800000
# ssl.enabled = false
securityManager.realms = $jdbcRealm
[users]
[roles]
[urls]
/authentication/login = authc
# /authentication/logout = logout
/search/* = noSessionCreation, jwtv
/statistics/* = noSessionCreation, jwtv
/clinics/* = noSessionCreation, jwtv
/patients/* = noSessionCreation, jwtv
/incidents/* = noSessionCreation, jwtv
/doctors/* = noSessionCreation, jwtv
/users/new = noSessionCreation, anon
/users/details/* = noSessionCreation, anon
/users/* = noSessionCreation, jwtv
/* = anon
MyRealm.java
package gr.histopath.platform.lib;
import gr.histopath.platform.model.DAO.UserDAO;
import gr.histopath.platform.model.TransferObjects.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;
public class MyRealm extends JdbcRealm {
private UserDAO userDAO;
private User user;
private String password;
private ByteSource salt;
public MyRealm() {
this.userDAO = new UserDAO();
setSaltStyle(SaltStyle.COLUMN);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// identify account to log to
UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
String username = userPassToken.getUsername();
System.out.println("GMOTO: " + userPassToken.getUsername());
if (username.equals(null)) {
System.out.println("Username is null.");
return null;
}
// read password hash and salt from db
// System.out.println("Username: " + username);
if(!userDAO.isOpen()){
userDAO = new UserDAO();
}
this.user = userDAO.getByUsername(username);
this.userDAO.closeEntityManager();
System.out.println("user's email: " + this.user.getUsername());
if (this.user == null) {
System.out.println("No account found for user [" + username + "]");
return null;
}
this.password = this.user.getPassword();
this.salt = ByteSource.Util.bytes(Base64.decode(this.user.getSalt()));
SaltedAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, salt, getName());
return info;
}
}
MY JWT 验证过滤器:
package gr.histopath.platform.lib;
import gr.histopath.platform.model.TransferObjects.User;
import io.jsonwebtoken.*;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
public class JWTVerifyingFilter extends AccessControlFilter {
private static final Logger logger = LoggerFactory.getLogger(JWTVerifyingFilter.class);
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
logger.debug("Verifying Filter Execution");
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
String jwt = httpRequest.getHeader("Authorization");
logger.debug("JWT Found");
if (jwt == null || !jwt.startsWith("Bearer ")) {
// System.out.println("DEn Brika Tipota: ");
logger.debug("No Token Found...");
// servletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
jwt = jwt.substring(jwt.indexOf(" "));
Subject subject = SecurityUtils.getSubject();
// System.out.println("Token Found");
// System.out.println("JWT: " + jwt);
// System.out.println("Authenticated? " + subject.isAuthenticated());
// System.out.println(" session " + subject.getSession().getId());
// System.out.println(" salt " + ((User) subject.getPrincipal()).getSalt());
// System.out.println(" who-is " + ((User) subject.getPrincipal()).getUsername());
User user = null;
if (subject.isAuthenticated()) {
user = (User) subject.getPrincipal();
String username = null;
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(user.getSalt()))
.parseClaimsJws(jwt);
// System.out.println("Claims: " + claimsJws);
logger.debug("Expiration: " + claimsJws.getBody().getExpiration());
username = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(user.getSalt()))
.parseClaimsJws(jwt).getBody().getSubject();
} catch (ExpiredJwtException expiredException) {
logger.debug("Token Is Expired....");
logger.debug(expiredException.getMessage(), expiredException);
// System.out.println("Token IS Expired.....");
// expiredException.printStackTrace();
logger.debug("Logging out the user...");
// System.out.println("Logging out the user...");
SecurityUtils.getSubject().logout();
// System.out.println("mmmnnnnn: " + SecurityUtils.getSubject().isAuthenticated());
return false;
// throw expiredException;
} catch (SignatureException signatureException) {
logger.debug(signatureException.getMessage(), signatureException);
// signatureException.printStackTrace();
return false;
} catch (Exception e) {
logger.debug(e.getMessage(), e);
// e.printStackTrace();
return false;
}
// System.out.println("Subject: " + user.getUsername());
return username.equals(user.getUsername());
}
// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
和智威汤逊卫士
package gr.histopath.platform.lib;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class JWTGuard extends AuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
// System.out.println("JWT GUARD FIRED!!!!!");
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
一切工作几乎都很好,除了随机/偶尔,尽管用户登录,会话超时发生并且系统注销了用户,尽管事实上令牌有 7 天的有效期。
所以我决定尝试在没有任何会话的情况下使系统无状态。为此,我使用了以下命令:
securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
但是,现在我根本无法登录。我明白了
java.lang.NullPointerException at gr.histopath.platform.lib.MyRealm.doGetAuthenticationInfo(MyRealm.java:31)
即字符串用户名 = userPassToken.getUsername(); //这是空的
现在我的 shiri.ini 如下所示:
更改了 shiro.ini
[main]
jwtg = gr.histopath.platform.lib.JWTGuard
jwtv = gr.histopath.platform.lib.JWTVerifyingFilter
ds = com.mysql.cj.jdbc.MysqlDataSource
ds.serverName = 127.0.0.1
ds.port = 3306
ds.user = histopathUser
ds.password = H1s+0p@+h.U$er
ds.databaseName = histopath
jdbcRealm = gr.histopath.platform.lib.MyRealm
jdbcRealm.dataSource = $ds
credentialsMatcher = org.apache.shiro.authc.credential.Sha512CredentialsMatcher
credentialsMatcher.hashIterations = 50000
credentialsMatcher.hashSalted = true
credentialsMatcher.storedCredentialsHexEncoded = false
jdbcRealm.credentialsMatcher = $credentialsMatcher
jdbcRealm.permissionsLookupEnabled = false
shiro.loginUrl = /authentication/login
#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager
#sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
#securityManager.sessionManager = $sessionManager
#securityManager.sessionManager.globalSessionTimeout = 172800000
securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
# ssl.enabled = false
securityManager.realms = $jdbcRealm
[users]
[roles]
[urls]
/authentication/login = authc
# /authentication/logout = logout
/search/* = noSessionCreation, jwtv
/statistics/* = noSessionCreation, jwtv
/clinics/* = noSessionCreation, jwtv
/patients/* = noSessionCreation, jwtv
/incidents/* = noSessionCreation, jwtv
/doctors/* = noSessionCreation, jwtv
/users/new = noSessionCreation, anon
/users/details/* = noSessionCreation, anon
/users/* = noSessionCreation, jwtv
/* = anon
我还没有找到任何 sessionless shiro 的完整示例。有什么建议可以让我的代码正常工作吗?我一定错过了什么,但我不知道是什么。
- 为什么禁用会话后 MyRealm 无法从 UsernamePasswordToken 读取用户名?
- 为什么在我的第一个实现中偶尔会发生会话超时。有什么想法吗?
解决方案
你试过noSessionCreation
吗?
您是否有任何请求会话的代码(或调用任何代码)?
推荐阅读
- android - 即使在基于紧急系统的应用程序的打盹和应用程序待机模式下也需要推送通知支持
- angular - 我无法使用 ```http.post``` 发送数据
- flutter - cubit 的静态返回值不会发出新的状态
- javascript - 一次性下载所有 Google 表格图表?
- javascript - 如何从 django 视图中的 HTML 用户输入中获取价值?
- google-assistant-sdk - Google 智能助理 - 一直要求打开个人搜索结果,即使它已打开
- amazon-web-services - 如何在 AWS Redshift 中正确存储和分析空间数据
- python - 科学计数法转换错误的字符串
- javascript - FCC API 项目。我不理解带有承诺的代码流程
- python - 使用“not F()”从查询集中切换布尔字段