You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Play Framework移动应用REST API认证实现技术咨询

嘿,兄弟!作为一名从Android转后端的开发者,你的困惑我太能共情了——毕竟刚接触后端时,很多概念容易被误解,咱们一步步把这些问题掰明白:

先澄清一个关键误解:OAuth 2.0不止是第三方登录

你之前以为OAuth 1/2只用来做Facebook、Google这类第三方登录,这是个很常见的误区!实际上,OAuth 2.0是一套通用的令牌式身份验证框架,完全可以用于你自己的Android客户端和Play服务端的会话管理,它的无状态特性特别适配RESTful API的场景,非常适合你的需求。

一、Play 2后端如何生成Access Token

最推荐的方案是使用JWT(JSON Web Token),它在Play生态里有成熟的支持,而且逻辑简单清晰,步骤如下:

  1. 用户登录时,Android客户端把用户名和密码通过POST请求发送到Play后端的登录接口;
  2. 后端验证用户名密码通过后,生成JWT:Token里包含用户ID、过期时间等核心信息(称为「声明」),然后用服务器的私有密钥签名;
  3. 把生成的JWT返回给客户端,客户端存储到SharedPreferences里(记得后续请求都要带上这个Token)。

给你一个Play 2(Java版)的简单代码示例,用到JJWT库(先在build.sbt里添加依赖):

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import play.Configuration;

// 从配置文件读取密钥,不要硬编码!
private final String JWT_SECRET = Configuration.root().getString("jwt.secret");

// 生成Access Token的方法
public String generateAccessToken(Long userId) {
    // 设置Token有效期,比如1小时
    long expirationMillis = 3600000;
    return Jwts.builder()
            .setSubject(userId.toString()) // 把用户ID作为核心标识
            .setExpiration(new Date(System.currentTimeMillis() + expirationMillis))
            .signWith(SignatureAlgorithm.HS256, JWT_SECRET) // 用HS256算法签名
            .compact();
}

// 登录接口示例
public Result login() {
    JsonNode requestBody = request().body().asJson();
    String username = requestBody.get("username").asText();
    String password = requestBody.get("password").asText();
    
    // 这里替换成你实际的用户验证逻辑
    User user = userService.validateUser(username, password);
    if (user != null) {
        String accessToken = generateAccessToken(user.getId());
        // 可选:同时生成Refresh Token(后面讲超时处理会用到)
        String refreshToken = generateRefreshToken(user.getId());
        return ok(Json.obj("access_token" -> accessToken, "refresh_token" -> refreshToken));
    } else {
        return unauthorized("Invalid username or password");
    }
}

为什么选JWT?因为它是「自包含」的Token——后端不需要存储Token本身,只要验证签名和过期时间就可以确认身份,完美契合RESTful API的无状态要求。

二、服务端如何处理Token超时

Token超时是会话管理的核心问题,这里给你两种关键机制:

1. 过期Token的即时验证

每次客户端请求API时,需要在请求头里带上Token(比如Authorization: Bearer <你的Token>),后端通过拦截器统一验证Token的有效性:

  • 如果Token签名无效:返回401 Unauthorized;
  • 如果Token已过期:同样返回401,提示客户端重新登录或刷新Token。

用Play的Action Composition实现全局拦截的示例:

import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CompletableFuture;

public class AuthInterceptor extends Action.Simple {
    private final String JWT_SECRET = Configuration.root().getString("jwt.secret");

    @Override
    public CompletionStage<Result> call(Http.Context ctx) {
        String authHeader = ctx.request().getHeader("Authorization");
        // 检查请求头是否合法
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return CompletableFuture.completedFuture(unauthorized("Missing or invalid authorization header"));
        }
        String token = authHeader.substring(7);
        try {
            // 验证Token的签名和过期时间
            Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token);
            // 把用户ID存入请求上下文,方便后续接口使用
            String userId = Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody().getSubject();
            ctx.args.put("userId", userId);
            // 验证通过,继续执行原请求
            return delegate.call(ctx);
        } catch (Exception e) {
            // Token无效或过期
            return CompletableFuture.completedFuture(unauthorized("Invalid or expired access token"));
        }
    }
}

// 在需要验证的Controller方法上添加注解
@With(AuthInterceptor.class)
public Result getUserProfile() {
    Long userId = Long.parseLong((String) ctx().args.get("userId"));
    UserProfile profile = userService.getProfile(userId);
    return ok(Json.toJson(profile));
}

2. Refresh Token机制(避免用户频繁登录)

如果Access Token有效期很短(比如1小时),用户频繁登录体验很差,这时可以搭配Refresh Token:

  • Access Token:短有效期(1小时),用于日常API请求;
  • Refresh Token:长有效期(比如7天),专门用来刷新Access Token。

实现思路:

  • 用户登录时,后端同时生成Access Token和Refresh Token,Refresh Token要存储到数据库(关联用户ID和过期时间);
  • 当Access Token过期时,客户端用Refresh Token请求后端的刷新接口;
  • 后端验证Refresh Token的有效性(是否存在、未过期、属于当前用户),验证通过后生成新的Access Token返回;
  • 如果Refresh Token也过期了,就要求用户重新登录。

代码示例(Refresh Token相关逻辑):

import java.util.UUID;
import java.util.Date;

// 生成Refresh Token并存储到数据库
public String generateRefreshToken(Long userId) {
    String refreshToken = UUID.randomUUID().toString();
    // 保存到数据库:user_id, refresh_token, expires_at
    refreshTokenRepository.save(new RefreshTokenEntity(userId, refreshToken, new Date(System.currentTimeMillis() + 7*24*3600000)));
    return refreshToken;
}

// 刷新Access Token的接口
public Result refreshAccessToken() {
    JsonNode requestBody = request().body().asJson();
    String refreshToken = requestBody.get("refresh_token").asText();
    
    RefreshTokenEntity tokenEntity = refreshTokenRepository.findByToken(refreshToken);
    if (tokenEntity == null || tokenEntity.getExpiresAt().before(new Date())) {
        return unauthorized("Invalid or expired refresh token");
    }
    // 生成新的Access Token
    String newAccessToken = generateAccessToken(tokenEntity.getUserId());
    return ok(Json.obj("access_token" -> newAccessToken));
}
三、额外的安全建议
  • 密钥保密:服务器的JWT密钥绝对不能硬编码,要存在Play的application.conf配置文件里,并且不要提交到版本控制系统;
  • 客户端存储安全:Android端的Token不要直接存在普通的SharedPreferences,建议用EncryptedSharedPreferences加密存储;
  • 强制HTTPS:所有API请求必须用HTTPS,避免Token在网络传输中被明文窃取;
  • Token权限控制:可以在JWT里添加用户角色、权限等声明,后端验证Token时顺便做授权检查,比如判断用户是否有权限访问某个接口。

内容的提问来源于stack exchange,提问作者Jim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:35:42