Filtro personalizado: separação de front-end e back-end do Spring Security + JWT + código de verificação + notas de desenvolvimento do projeto Redis

Alterar o processo de autenticação de segurança

O núcleo da implementação da autenticação personalizada: filtro personalizado + alteração da ordem da cadeia de filtros.

Cadeia de filtros de segurança

Filtro de segurança chain.png

Alterar o processo de autenticação de segurança está, na verdade, alterando a cadeia de filtros de segurança.

UsernamePasswordAuthenticationFilter é o filtro de autenticação padrão, e o login de formulário padrão do Security é implementado através deste filtro. Portanto, para personalizar a autenticação Jwt, é necessário escrever um filtro Jwt e adicioná-lo na frente de UsernamePasswordAuthenticationFilter para concluir a ação de autenticação.

JwtAuthenticationTokenFilter personalizado

efeito:

  1. Há um token válido e não expirado: Carregue as informações do usuário e passe-as para filtros downstream para processamento adicional.
  2. Token ilegal ou expirado: encerra a cadeia de filtros e retorna uma mensagem de erro.

Processo de filtro personalizado:

  1. Herdar OncePerRequestFilter: Evite a entrada repetida da mesma solicitação no filtro.
  2. Substitua o método doFilterInternal.

JwtAuthenticationTokenFilter gráfico:

JwtAuthenticationTokenFilter.drawio.png

pergunta:

  1. O tipo de retorno do método doFilterInternal é void, e a separação de front-end e back-end requer que o back-end retorne dados do tipo json.
  2. De onde obter informações do usuário.

solução:

  1. Retornar dados do tipo json em HttpServletResponse: // Definir todos os métodos na classe Tipo de retorno: private static final String CONTENT_TYPE = "application/json;charset=UTF-8";

     // 方法中设置Json返回消息体:
     response.setCharacterEncoding("UTF-8");
     response.setContentType(CONTENT_TYPE);
     JSONObject res = new JSONObject(); // 相当于一个map
    复制代码
  2. Há muitas solicitações de interface que exigem tokens, portanto, as informações do usuário devem ser armazenadas no redis após o login. Depois disso, a autenticação do token obterá as informações do usuário do redis.

Código:

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

	private static final String CONTENT_TYPE = "application/json;charset=UTF-8";

	@Autowired
	private RedisUtils redisUtils;

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ExpiredJwtException, MalformedJwtException {

		// 定义Json返回消息体
		response.setCharacterEncoding("UTF-8");
		response.setContentType(CONTENT_TYPE);
		JSONObject res = new JSONObject(); // 相当于一个map

		// 获取token: 认证后的前端请求应该在header中刷入token
		String token = request.getHeader("token");
		if (!StringUtils.hasText(token)) {
			// 放行:将请求交给后面的过滤器处理
			filterChain.doFilter(request, response);
			// 卫语句:终止此过滤器
			return;
		}

		// 校验Token
		try {
			if(JwtUtils.isTokenExpired(token)) {
				res.put("code", 404);
				res.put("msg", "token已过期");
				PrintWriter output = response.getWriter(); // 先获取getWriter后使用response会报错,所以用到writer应现用现取, 减少对后面代码使用response的影响
				output.append(res.toString());
				return; // 过期终止执行
			}
		} catch (RuntimeException e) {
			res.put("code", 400);
			res.put("msg", "token非法");
			PrintWriter output = response.getWriter();
			output.append(res.toString());
			return; // 过期终止执行
		}

		// 合法未过期的token解析
		Claims claims = JwtUtils.parseJWT(token);
		String id = claims.getSubject();

		// 从redis中获取用户信息
		User user = (User) redisUtils.get("login:" + id);
		if(Objects.isNull(user)) {
			res.put("code", 410);
			res.put("msg", "token已注销");
			PrintWriter output = response.getWriter();
			output.append(res.toString());
			return; // 终止执行
		}
		// 存入SecurityContextHolder
		UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); // 三参数的此方法传入已登录用户,方法内部将其标记为已登录
		SecurityContextHolder.getContext().setAuthentication(authenticationToken);

		// 放行
		filterChain.doFilter(request, response);
	}
}
复制代码

Passo no poço:

  1. O redator deve usar e retirar:

Tente considerar colocar o escritor primeiro no método: PrintWriter output = response.getWriter(); Descobriu-se que o último código de lançamento usa a resposta para relatar um erro.

  1. A exceção lançada quando o token expira nunca pode ser capturada e tratada:

Em vez disso, capture a exceção na classe JwtUtils em vez de no filtro (acho que ela está relacionada a ExceptionTranslationFilter) e julgue se ela expira no filtro.

Consulte Análise da solução para ExpiredJwtException quando o token expira no JWT e como realizar o processamento de negócios subsequente após a expiração . A solução fornecida, você pode retirar as declarações antes de lançar a exceção e transformar o código para analisar o token em :

public static Claims parseJWT(String token) {
		// 使用DefaultJwtParser解析
		Claims claims;
		try {
			claims = Jwts.parser()
					// 设置签名密钥
					.setSigningKey(SIGN.getBytes(StandardCharsets.UTF_8))
					// 设置被解析jwt
					.parseClaimsJws(token).getBody();
		} catch (ExpiredJwtException e) {
			claims = e.getClaims(); // 无论是否过期,都能拿到claims
		} catch (SignatureException e) {
			throw new RuntimeException("签名异常");
		}
		return claims; // 始终返回claims
	}
复制代码

Em vez de confiar na ExpiredJwtException lançada para julgar se expirou, é necessário adicionar um método para julgar se o token expirou em JwtUtils, o que é conveniente para o filtro chamar e julgar:

public static Boolean isTokenExpired(String token) {
		// 解析claims对象信息
		Claims claims = JwtUtils.parseJWT(token);
		Date expiration = claims.getExpiration();
		return new Date(System.currentTimeMillis()).after(expiration);
	}
复制代码

Acho que você gosta

Origin juejin.im/post/7080682078815125517
Recomendado
Clasificación