Сегодня я в основном создаю новый проект.Исходный проект использовал Shiro JWT для входа в систему.Чтобы обогатить функцию контроля разрешений нового проекта, Spring Security используется для контроля разрешений. Есть надежда, что две системы смогут достичь цели единого входа через JWT. В связи с небольшим масштабом проекта и целью быстрой разработки единый сервер авторизации не используется. Однако при сборке, обращаясь к некоторым онлайн-учебникам, я обнаружил, что в обычных онлайн-учебниках есть много проблем. Большинство статей являются копиями одной и той же статьи, и проблема остается. Поэтому я решил написать эту статью для вашего ознакомления. Если есть ошибка, надеюсь, вы меня поправите.
Введение в JWT
Что такое JWT
JSON Web Token (JWT) — это открытый стандарт (RFC 7519), определяющий компактный и автономный способ безопасной передачи информации в виде объектов JSON между сторонами.
Формат структуры JWT
Веб-токены JSON состоят из трех частей, разделенных точками (.):
- Заголовок: информация заголовка обычно состоит из двух частей: тип токена (JWT) и используемый алгоритм подписи, например HMAC SHA256 или RSA.
{
"alg": "HS256",//签名或摘要算法
"typ": "JWT"//token类型
}
- Часть полезной нагрузки Playload является основной частью содержимого JWT, а также объектом JSON, который содержит данные для передачи. JWT указывает семь полей по умолчанию на выбор.
{
"iss": "token-server",//签发者
"exp ": "Mon Nov 13 15:28:41 CST 2017",//过期时间
"sub ": "wangjie",//用户名
"aud": "web-server-1"//接收方,
"nbf": "Mon Nov 13 15:40:12 CST 2017",//这个时间之前token不可用
"jat": "Mon Nov 13 15:20:41 CST 2017",//签发时间
"jti": "0023",//令牌id标识
"claim": {“auth”:”ROLE_ADMIN”}//访问主张
}
- Информация о подписи подписи: чтобы получить часть подписи, у вас должен быть закодированный заголовок, закодированная полезная нагрузка, секретный ключ, алгоритм подписи — тот, который указан в заголовке, и затем вы можете подписать их.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Как работает веб-токен JSON?
Всякий раз, когда пользователь хочет получить доступ к защищенному маршруту или ресурсу, пользовательский агент должен отправить JWT, используя режим переноса, обычно в заголовке авторизации. Содержимое заголовка должно выглядеть так:
Authorization: Bearer <token>
В некоторых случаях это может быть механизм авторизации без сохранения состояния. Защищенный маршрут сервера проверит заголовок авторизации на наличие действительного JWT и, если он присутствует, разрешит пользователю доступ к защищенному ресурсу. Если JWT содержит необходимые данные, это может уменьшить потребность в запросах к базе данных для определенных операций, хотя это может быть не всегда так.
Учебное пособие Shiro по интеграции JWT
Адрес исходного поста учебника:nuggets.capable/post/684490…
исправление проблемы 1
public class JWTUtil {
问题代码
/**
* 校验token是否正确
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
修订后代码
token中已经包含Playload信息(username),此处进行校验不需要再传入username
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
Проблема 2 @RestControllerAdvice не может поймать исключение Shiro
public class JWTFilter extends BasicHttpAuthenticationFilter {
/**
* 将非法请求返回json
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setStatus(HttpServletResponse.SC_OK);
httpResponse.getWriter().write("{\"code\":401,\"msg\":\"未授权!\",\"ret\":false}");
return false;
}
}
Spring Security интегрирует JWT
Адрес исходного поста учебника:nuggets.capable/post/684490…(PS: Большинство учебников похожи на этот пост)
код проблемы
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader(this.tokenHeader);
if (authHeader != null && authHeader.startsWith(tokenHead)) {
final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer "
String username = jwtTokenUtil.getUsernameFromToken(authToken);
logger.info("checking authentication " + username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
//重点观察此处 从token中得到了用户名,再通过用户名从数据库中获取用户的详细信息
//接下来我们看一下jwtTokenUtil.validateToken()到底做了什么事
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
@Component
public class JwtTokenUtil implements Serializable {
/**
* 验证令牌
*/
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
String username = getUsernameFromToken(token);
//这里我看得一脸懵逼 不应该是校验token的合法性。
//这里居然用数据库中查到的username 和token的username比较。而数据中查询的username又是通过token的来的。
//所以这段代码有错误
return (username.equals(user.getUsername()) && !isTokenExpired(token));
}
}
некоторые сомнения
- com.auth0/java-jwt 3.3.0 и io.jsonwebtoken/jjwt/0.9.0 гарантируют наличие Header и Playload в токене (даже порядок параметров одинаковый), но токен, полученный двумя пакетами, не может быть аутентифицирован. В настоящее время моя практика заключается в замене всех на com.auth0/java-jwt для использования