授权码模式较为常见,主要流程如下:
在这次实现中,我们将使用 OAuth2 认证流程并结合 JWT 进行用户身份验证。用户通过短信验证码登录,经过验证后,生成 JWT 令牌,供前端用于后续请求。
流程如下:
在 pom.xml
中引入必要的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
在 application.yml
中配置 JWT 私钥和过期时间:
jwt:
secret: mySecretKey
expiration: 3600 # 1小时,单位秒
此接口生成验证码并缓存,实际中可以使用 Redis 实现缓存。
@RestController
@RequestMapping("/auth")
public class AuthController {
private final Map<String, String> smsCache = new HashMap<>(); // 临时缓存模拟
@GetMapping("/send-sms")
public ResponseEntity<?> sendSmsCode(@RequestParam String phoneNumber) {
String code = String.valueOf((int)((Math.random() * 9 + 1) * 1000));
smsCache.put(phoneNumber, code);
// 真实场景中需调用短信发送服务,如阿里云、Twilio
System.out.println("SMS Code for " + phoneNumber + " is " + code);
return ResponseEntity.ok("验证码已发送");
}
}
在该接口中,校验验证码是否正确,正确则生成 JWT 令牌:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@RestController
@RequestMapping("/auth")
public class AuthController {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestParam String phoneNumber, @RequestParam String code) {
if (!smsCache.containsKey(phoneNumber) || !smsCache.get(phoneNumber).equals(code)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("验证码错误");
}
String token = generateToken(phoneNumber);
return ResponseEntity.ok(Collections.singletonMap("token", token));
}
private String generateToken(String phoneNumber) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.setSubject(phoneNumber)
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
}
在 SecurityConfig
中配置 JWT 过滤器,校验请求头中的 JWT 令牌。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}
以下代码展示如何通过 HTML 和 JavaScript 调用短信验证码和登录接口,并获取 JWT。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SMS Login</title>
<script>
async function sendSms() {
const phoneNumber = document.getElementById('phone').value;
const response = await fetch(`/auth/send-sms?phoneNumber=${phoneNumber}`);
const data = await response.text();
alert(data);
}
async function login() {
const phoneNumber = document.getElementById('phone').value;
const code = document.getElementById('code').value;
const response = await fetch('/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `phoneNumber=${phoneNumber}&code=${code}`
});
const data = await response.json();
if (data.token) {
alert("登录成功");
localStorage.setItem('token', data.token);
} else {
alert("验证码错误");
}
}
</script>
</head>
<body>
<h2>短信验证码登录</h2>
<input type="text" id="phone" placeholder="手机号" required>
<button onclick="sendSms()">发送验证码</button>
<br><br>
<input type="text" id="code" placeholder="验证码" required>
<button onclick="login()">登录</button>
</body>
</html>
所有的令牌和用户数据都应通过 HTTPS 传输,以避免被中间人拦截或篡改。特别是在生产环境下,未加密的 HTTP 传输会导致敏感数据暴露。
HttpOnly
Cookie 中)以防止跨站点脚本攻击(XSS)。Authorization
头部是一种更为安全的传递方式,减少 URL 曝露令牌的风险。访问令牌应设置较短的有效期,推荐 10 分钟以内的生命周期,以减少令牌泄露后的风险。可使用刷新令牌来延长会话,以便在令牌失效后通过刷新令牌重新获取授权。
OAuth2 支持定义令牌的访问作用域(Scope)。为提高安全性,应根据用户角色和 API 需求定义精确的权限范围,避免过度授权。示例如下:
spring:
security:
oauth2:
client:
registration:
google:
scope: profile, email # 精细化权限控制
在 OAuth2 授权过程中,使用 state
参数来防止跨站请求伪造(CSRF)攻击。每次授权请求时,生成一个随机的 state
值,并在重定向后验证其一致性。
在 OAuth2 授权过程中记录关键操作日志(如授权请求、令牌交换、用户信息请求等),以便进行审计和分析。例如,可以在 Spring Boot 中使用日志工具记录请求信息和异常信息。
@RestController
public class AuthLoggingController {
private static final Logger logger = LoggerFactory.getLogger(AuthLoggingController.class);
@GetMapping("/oauth2/callback")
public String callback(OAuth2AuthenticationToken authToken) {
logger.info("User authenticated with OAuth2: {}", authToken.getPrincipal());
return "You are authenticated!";
}
}
如果在应用中配置了自定义授权服务器(如通过 Spring Authorization Server 实现),则应额外注意以下几点: