如何实现JAX-RS基础认证与授权及后续请求验证?
解决JAX-RS后续请求身份验证的问题
嘿,我看你已经搞定了登录时的Basic认证和令牌生成,但后续请求拿不到Authorization头的问题,核心原因应该是客户端没在后续请求里带上你返回的令牌。咱们一步步来解决这个问题:
1. 先确保客户端正确携带令牌
登录成功后,你返回的令牌得让客户端存起来(比如浏览器的localStorage、Cookie,或者移动端的本地存储),之后每次请求都要在Authorization头里以Bearer <你的令牌>的格式带上。举个例子,客户端的请求头应该是这样的:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJvbmthciIsImV4cCI6MTY5MzUzNzYwMH0.SK8sJ9r7...
如果客户端没这么做,服务器自然拿不到这个头,所以先确认客户端的请求逻辑是否正确。
2. 服务器端统一处理令牌验证
别在每个资源方法里手动取令牌验证,用JAX-RS的ContainerRequestFilter来统一拦截需要授权的请求,代码更优雅也更易维护:
第一步:写一个令牌验证过滤器
@Provider @Priority(Priorities.AUTHENTICATION) public class TokenAuthFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { // 从请求头里拿Authorization String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); // 先检查头是否存在,格式是否正确 if (authHeader == null || !authHeader.startsWith("Bearer ")) { // 没带令牌或者格式不对,直接返回401 requestContext.abortWith( Response.status(Response.Status.UNAUTHORIZED) .header(HttpHeaders.WWW_AUTHENTICATE, "Bearer") .build() ); return; } // 提取出令牌部分 String token = authHeader.substring("Bearer ".length()).trim(); try { // 这里写你的令牌验证逻辑:比如解析JWT、查数据库看令牌是否有效、是否过期 validateToken(token); // 验证通过后,可以把用户信息放到请求上下文里,后面的资源方法直接用 String username = getUsernameFromToken(token); requestContext.setProperty("loggedInUser", username); } catch (InvalidTokenException e) { // 令牌无效,返回401 requestContext.abortWith( Response.status(Response.Status.UNAUTHORIZED) .entity("令牌无效或已过期") .build() ); } } // 自定义验证令牌的方法,你可以根据自己的令牌类型实现 private void validateToken(String token) throws InvalidTokenException { // 比如如果是JWT,用JJWT库验证签名和过期时间 // Jwts.parser().setSigningKey("你的密钥").parseClaimsJws(token); } // 从令牌里提取用户名的方法 private String getUsernameFromToken(String token) { // 比如JWT里的subject就是用户名 // return Jwts.parser().setSigningKey("你的密钥").parseClaimsJws(token).getBody().getSubject(); return ""; } }
第二步:标记需要授权的接口
可以自定义一个@Secured注解,用来标记哪些接口需要验证令牌:
@NameBinding @Retention(RUNTIME) @Target({TYPE, METHOD}) public @interface Secured {}
然后给过滤器加上这个注解:
@Provider @Priority(Priorities.AUTHENTICATION) @Secured // 加上这个注解 public class TokenAuthFilter implements ContainerRequestFilter { // ... 上面的逻辑不变 }
之后在需要授权的资源方法上标注@Secured就行:
@GET @Path("/user/info") @Secured // 这个接口会被过滤器拦截验证令牌 public Response getUserInfo(@Context ContainerRequestContext requestContext) { String username = (String) requestContext.getProperty("loggedInUser"); return Response.ok("当前登录用户:" + username).build(); }
第三步:优化登录接口的返回
登录成功后,除了返回令牌,最好在响应头里明确告诉客户端令牌的格式,比如:
@GET @Path("/login") public Response login(@HeaderParam("Authorization") String authString) { // 解码Basic Auth的用户名密码 String[] credentials = decodeBasicAuth(authString); String username = credentials[0]; String password = credentials[1]; if (checkCredentials(username, password)) { // 生成令牌,这里用JWT举例子 String token = generateJwtToken(username); // 返回令牌,同时在响应头里带上Bearer格式的令牌,方便客户端直接用 return Response.ok("登录成功") .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .build(); } else { return Response.status(Response.Status.UNAUTHORIZED).entity("用户名或密码错误").build(); } } // 生成JWT令牌的示例方法 private String generateJwtToken(String username) { long expireTime = 3600000; // 1小时过期 return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + expireTime)) .signWith(SignatureAlgorithm.HS256, "你的专属密钥,别泄露!") .compact(); }
3. 排查常见坑点
如果还是出现Authorization头为空,先检查这几点:
- 客户端是否真的在请求里带上了Authorization头?可以用Postman或者浏览器的开发者工具看请求头
- 如果是浏览器端,有没有跨域问题?跨域请求需要服务器配置CORS,允许
Authorization头被携带 - 令牌是否过期?如果令牌过期了,客户端应该重新登录获取新令牌
内容的提问来源于stack exchange,提问作者OnkarG
相关产品推荐
相关产品推荐

