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生态里有成熟的支持,而且逻辑简单清晰,步骤如下:
- 用户登录时,Android客户端把用户名和密码通过POST请求发送到Play后端的登录接口;
- 后端验证用户名密码通过后,生成JWT:Token里包含用户ID、过期时间等核心信息(称为「声明」),然后用服务器的私有密钥签名;
- 把生成的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
相关产品推荐
相关产品推荐

