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
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:
- 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.
- Token ilegal ou expirado: encerra a cadeia de filtros e retorna uma mensagem de erro.
Processo de filtro personalizado:
- Herdar OncePerRequestFilter: Evite a entrada repetida da mesma solicitação no filtro.
- Substitua o método doFilterInternal.
JwtAuthenticationTokenFilter gráfico:
pergunta:
- 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.
- De onde obter informações do usuário.
solução:
-
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 复制代码
-
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:
- 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.
- 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);
}
复制代码