Я работаю над приложением, в котором реализован Java Restful Backend Sercvice с аутентификацией Apache Shiro. Теперь я могу заставить пользователя зарегистрироваться и успешно войти в систему, используя пароль и солидную базу данных. Теперь я хочу улучшить это, добавив аутентификацию JWT.
Сценарий будет:
- Пользователь пытается войти в систему, используя имя пользователя и пароль
- После успешной отправки учетных данных и входа пользователя в систему shiro серверная часть создает токен jwt и отправляет его обратно клиенту
- В каждом новом запросе клиент отправляет токен jwt, полученный на предыдущем шаге, на сервер для аутентификации.
- Shiro Filter проверяет токен, содержащийся в запросе. Если действительный процесс продолжается, в противном случае верните сообщение об ошибке.
Для реализации этой функции я следовал:
Веб-токен JSON с Apache Shiro
Вся работа выполняется JWTVerifyingFilter:
public class JWTVerifyingFilter extends AccessControlFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse arg1, Object arg2) throws Exception {
boolean accessAllowed = false;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String jwt = httpRequest.getHeader("Authorization");
if (jwt == null || !jwt.startsWith("Bearer ")) {
return accessAllowed;
}
jwt = jwt.substring(jwt.indexOf(" "));
String username = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary("secret"))
.parseClaimsJws(jwt).getBody().getSubject();
String subjectName = (String) SecurityUtils.getSubject().getPrincipal();
if (username.equals(subjectName)) {
accessAllowed = true;
}
return accessAllowed;
}
@Override
protected boolean onAccessDenied(ServletRequest arg0, ServletResponse arg1) throws Exception {
HttpServletResponse response = (HttpServletResponse) arg1;
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
Однако я сталкиваюсь со следующими проблемами:
Как вы можете видеть, секрет, используемый для генерации подписи jwt, является одинаковым 'secret' для всех пользователей. В каждом примере, который я нашел относительно генерации подписи токена jwt и аутентификации, они используют один и тот же секрет для всех пользователей при генерации подписи jwt.
В моей реализации я хотел бы использовать соль, хранящуюся на пользователя в базе данных, чтобы сгенерировать подпись jwt, а затем проверить ее. Поэтому при создании jwt для пользователя я использую следующую функцию, которая использует соль пользователя для создания подписи jwt.
public class JWTProvider {
private JWTProvider() {
}
public static String getJWTToken(User user) {
System.out.println("JWT Provider FIRED");
SignatureAlgorithm sigAlg = SignatureAlgorithm.HS512;
byte[] apiKeySecretBytes = Base64.decode(user.getSalt());
Key signingKey = new SecretKeySpec(apiKeySecretBytes, sigAlg.getJcaName());
Date date = new Date();
JwtBuilder builder = Jwts.builder()
.setSubject(user.getUsername())
.claim("FirstName", user.getFirstName())
.claim("LastName", user.getLastName())
.setIssuedAt(date)
.setExpiration(new Date(date.getTime() + 24 * 60 * 60 * 1000)) //Expires in One Days
.signWith(sigAlg, signingKey);
System.out.println("Generated JWT: " + builder.compact());
return builder.compact();
}
}
В моем случае, для проверки подписи jwt мне нужно получить соль для каждого пользователя. Итак, я изменил реализацию JWTVerifyingFilter следующим образом:
public class JWTVerifyingFilter extends AccessControlFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
System.out.println("JWT Verifier Fired.....");
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String jwt = httpRequest.getHeader("Authorization");
if (jwt == null || !jwt.startsWith("Bearer ")) {
System.out.println("DEn e brika prama: ");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
System.out.println("Found Something");
jwt = jwt.substring(jwt.indexOf(" "));
System.out.println("JWT: " + jwt);
User user = (User) SecurityUtils.getSubject().getPrincipal();
System.out.println("MMMMMMMMMMMMMM " + user.getUsername() + "jhhhhhhhhhhhhhh" + user.getSalt());
String subjectName = ((User) SecurityUtils.getSubject()).getPrincipal();
System.out.println("Subject: " + subjectName);
String username = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary("secret"))
.parseClaimsJws(jwt).getBody().getSubject();
System.out.println("UserNAeme: " + username);
System.out.println("Subject: " + subjectName);
if (username.equals(subjectName)) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
Однако при вызове Apache Shiro SecurityUtils.getSubject()
возвращает объект null . Я попытался реализовать класс Singleton, чтобы всегда получать один и тот же объект из SecurityUtils (если есть один объект, вернуть его, иначе вызвать SecurityUtils.getSubject ()), но безуспешно. В этой более поздней реализации только один пользователь может войти в систему. Используя другой браузер, пользователь, о котором сообщалось в бэкэнде, уже вошел в систему с учетными данными пользователя, ранее зарегистрированного в другом браузере.
Вопросы:
- Можно ли использовать один и тот же секрет в аутентификации jwt для всех пользователей ??
- Где apache shiro хранит информацию о вошедших в систему пользователях и как я могу получить к ним доступ из java-сервиса restful backend ??
- Какой-нибудь полный пример использования Apache Shiro различных секретных фраз jwt для каждого пользователя Каков наилучший способ реализовать это.
- Нужно ли мне хранить в памяти, например, redis, чтобы аутентифицированные пользователи сохраняли свои соли при входе в систему и получали их оттуда при последующих запросах ??
Заранее спасибо за любые ответы.