Spring Security и Shiro интегрируют некоторые ямки в онлайн-учебник JWT!

Безопасность

Сегодня я в основном создаю новый проект.Исходный проект использовал Shiro JWT для входа в систему.Чтобы обогатить функцию контроля разрешений нового проекта, Spring Security используется для контроля разрешений. Есть надежда, что две системы смогут достичь цели единого входа через JWT. В связи с небольшим масштабом проекта и целью быстрой разработки единый сервер авторизации не используется. Однако при сборке, обращаясь к некоторым онлайн-учебникам, я обнаружил, что в обычных онлайн-учебниках есть много проблем. Большинство статей являются копиями одной и той же статьи, и проблема остается. Поэтому я решил написать эту статью для вашего ознакомления. Если есть ошибка, надеюсь, вы меня поправите.

Введение в JWT

Что такое JWT

JSON Web Token (JWT) — это открытый стандарт (RFC 7519), определяющий компактный и автономный способ безопасной передачи информации в виде объектов JSON между сторонами.

Формат структуры JWT

Веб-токены JSON состоят из трех частей, разделенных точками (.):

  1. Заголовок: информация заголовка обычно состоит из двух частей: тип токена (JWT) и используемый алгоритм подписи, например HMAC SHA256 или RSA.
{
    "alg": "HS256",//签名或摘要算法
    "typ": "JWT"//token类型
}
  1. Часть полезной нагрузки 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”}//访问主张
}
  1. Информация о подписи подписи: чтобы получить часть подписи, у вас должен быть закодированный заголовок, закодированная полезная нагрузка, секретный ключ, алгоритм подписи — тот, который указан в заголовке, и затем вы можете подписать их.
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));
    }
}

некоторые сомнения

  1. com.auth0/java-jwt 3.3.0 и io.jsonwebtoken/jjwt/0.9.0 гарантируют наличие Header и Playload в токене (даже порядок параметров одинаковый), но токен, полученный двумя пакетами, не может быть аутентифицирован. В настоящее время моя практика заключается в замене всех на com.auth0/java-jwt для использования

Знакомство с официальным сайтом JWT