面试题答案
一键面试认证流程设计思路
- 用户注册与登录:
- 用户在客户端填写注册信息,发送到服务器。服务器验证信息合法性(如用户名是否唯一,密码强度等),然后将用户信息(加密后的密码等)存储到数据库。
- 登录时,用户提供用户名和密码,服务器从数据库中检索对应记录,对密码进行验证(使用哈希匹配等方式)。若验证成功,生成一个认证令牌(如JWT)。
- 认证令牌生成:
- 使用JWT(JSON Web Token)较为合适,它包含用户身份信息(如用户ID、用户名等)和一些元数据(如过期时间)。可以使用Kotlin的相关JWT库(如
io.jsonwebtoken:jjwt - kotlin
)来生成和验证JWT。 - 在生成JWT时,使用一个服务器端的密钥(Secret Key)进行签名,确保令牌的完整性和不可伪造性。例如:
import io.jsonwebtoken.Claims import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm import java.util.Date fun generateToken(userId: String, username: String): String { val now = Date() val expiration = Date(now.time + 3600000) // 1小时过期 val claims: Claims = Jwts.claims().apply { setSubject(userId) put("username", username) } return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS256, "your - secret - key") .compact() }
- 使用JWT(JSON Web Token)较为合适,它包含用户身份信息(如用户ID、用户名等)和一些元数据(如过期时间)。可以使用Kotlin的相关JWT库(如
- 客户端存储与发送:
- 客户端在登录成功后,将认证令牌存储在本地(如
localStorage
或SharedPreferences
)。每次向API发送请求时,将令牌包含在请求头(如Authorization: Bearer <token>
)中。
- 客户端在登录成功后,将认证令牌存储在本地(如
- 服务器验证:
- 在Ktor的服务器端,通过一个拦截器(Intercepter)来验证请求中的令牌。例如:
import io.ktor.application.* import io.ktor.auth.* import io.ktor.http.* import io.jsonwebtoken.Claims import io.jsonwebtoken.Jwts import io.jsonwebtoken.security.Keys val jwtRealm = "JWT Realm" val jwtSecretKey = Keys.hmacShaKeyFor("your - secret - key".toByteArray()) fun Application.configureSecurity() { install(Authentication) { jwt(jwtRealm) { verifier(Jwts.parserBuilder() .setSigningKey(jwtSecretKey) .build()) validate { credential -> val claims = credential.payload.getClaims() as Claims if (claims.getSubject()!= null) { JWTPrincipal(claims) } else { null } } } } }
授权策略实施思路
- 用户角色定义:
- 在数据库中为每个用户定义一个角色字段,如
admin
、user
、guest
等。
- 在数据库中为每个用户定义一个角色字段,如
- 权限映射:
- 为不同的API端点定义不同的权限要求。例如,一个获取用户基本信息的端点可能允许所有用户角色访问,而一个删除用户的端点可能只允许
admin
角色访问。 - 可以使用一个配置文件或数据库表来存储这种权限映射关系。例如:
{ "/api/user/info": ["admin", "user", "guest"], "/api/user/delete": ["admin"] }
- 为不同的API端点定义不同的权限要求。例如,一个获取用户基本信息的端点可能允许所有用户角色访问,而一个删除用户的端点可能只允许
- 授权检查:
- 在Ktor中,通过一个授权拦截器来检查用户的角色是否有权限访问当前请求的端点。例如:
import io.ktor.application.* import io.ktor.auth.* import io.ktor.http.* import io.ktor.response.* import io.ktor.routing.* fun Application.configureAuthorization() { routing { authenticate(jwtRealm) { intercept(ApplicationCallPipeline.Features) { val principal = call.principal<JWTPrincipal>() val claims = principal?.payload?.getClaims() as Claims val role = claims["role"] as String val requestedPath = call.request.uri val allowedRoles = getAllowedRoles(requestedPath) if (!allowedRoles.contains(role)) { call.respond(HttpStatusCode.Forbidden, "You do not have permission to access this resource.") return@intercept } proceed() } } } } fun getAllowedRoles(path: String): List<String> { // 从配置文件或数据库中获取允许的角色列表 // 示例返回 return when (path) { "/api/user/info" -> listOf("admin", "user", "guest") "/api/user/delete" -> listOf("admin") else -> emptyList() } }
防止重放攻击
- 使用一次性令牌(Nonce):
- 在认证流程中,服务器在发送认证挑战(如登录请求响应)时,包含一个一次性随机数(Nonce)。客户端在后续的认证请求(如发送登录密码和Nonce)中,将该Nonce回传给服务器。服务器验证Nonce的有效性(只接受一次,且在一定时间内有效),并在验证成功后将其标记为已使用。
- 时间戳验证:
- 在认证令牌(如JWT)中包含一个时间戳字段。服务器在验证令牌时,检查时间戳是否在合理的时间范围内(如当前时间前后几分钟内)。如果时间戳超出范围,则拒绝该请求,认为可能是重放攻击。例如,在JWT生成时添加时间戳:
val claims: Claims = Jwts.claims().apply { setSubject(userId) put("username", username) put("timestamp", System.currentTimeMillis()) }
- 在验证时检查时间戳:
val claims = credential.payload.getClaims() as Claims val timestamp = claims["timestamp"] as Long val currentTime = System.currentTimeMillis() if (currentTime - timestamp > 300000) { // 5分钟有效期 return null }
- 令牌过期机制:
- 设置较短的认证令牌过期时间,如1小时。这样即使令牌被窃取,攻击者也只有较短的时间窗口来进行重放攻击,降低攻击成功的可能性。同时,提供刷新令牌机制,允许用户在令牌过期时获取新的有效令牌而无需重新登录。