使用AOP实现登录校验的完整指南
一、AOP基础概念
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于将横切关注点(如日志、事务、安全等)与业务逻辑分离。核心概念包括:
概念 说明 对应代码示例
切面(Aspect) 封装横切逻辑的模块 GlobalOperationAspect类
连接点(JoinPoint) 程序执行中的特定点 interceptorDo方法的JoinPoint参数
通知(Advice) 切面在特定连接点的动作 @Before注解的方法
切点(Pointcut) 匹配连接点的表达式 @annotation(com.live.web.annotation.GlobalInterceptor)
引入(Introduction) 向现有类添加新方法/属性 本例未使用
目标对象(Target) 被通知的对象 如loadAllVideo方法所在的Controller
二、登录校验实现详解
1. 定义注解(标记需要拦截的方法)
@Target({ElementType.METHOD, ElementType.TYPE}) // 可作用于方法和类
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface GlobalInterceptor {
boolean checkLogin() default false; // 是否检查登录状态
}
注解作用:
• 作为元数据标记需要拦截的方法
• 通过checkLogin
属性灵活控制是否进行登录校验
2. 实现切面逻辑
@Aspect
@Component
@Slf4j
public class GlobalOperationAspect {
@Resource
private RedisUtils redisUtils;
// 定义切点:拦截带有GlobalInterceptor注解的方法
@Before("@annotation(com.easylive.web.annotation.GlobalInterceptor)")
public void interceptorDo(JoinPoint point) {
// 1. 获取方法签名和注解
Method method = ((MethodSignature) point.getSignature()).getMethod();
GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class);
if (interceptor != null && interceptor.checkLogin()) {
checkLogin(); // 2. 执行登录校验
}
}
private void checkLogin() {
// 3. 获取请求对象
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
// 4. 从Header获取token
String token = request.getHeader(Constants.TOKEN_WEB);
if (StringTools.isEmpty(token)) {
throw new BusinessException(ResponseCodeEnum.CODE_901); // 5. token为空抛出异常
}
// 6. 验证token有效性
TokenUserInfoDto userInfo = (TokenUserInfoDto) redisUtils.get(
Constants.REDIS_KEY_TOKEN_WEB + token);
if (userInfo == null) {
throw new BusinessException(ResponseCodeEnum.CODE_901); // 7. token无效抛出异常
}
}
}
3. 业务方法应用
@GlobalInterceptor(checkLogin = true) // 启用登录校验
public ResponseVO loadAllVideo() {
// 已通过校验的用户可直接获取信息
TokenUserInfoDto userInfo = getTokenUserInfoDto();
// ...业务逻辑
}
三、执行流程分析
四、设计优势分析
解耦性:将安全校验与业务逻辑分离
灵活性:
• 通过注解开关控制校验
• 可轻松扩展其他校验规则(如权限校验)一致性:所有Controller方法统一处理方式
可维护性:校验逻辑集中管理,修改方便
五、扩展应用场景
1. 添加权限校验
public @interface GlobalInterceptor {
boolean checkLogin() default false;
String[] roles() default {}; // 新增角色校验
}
// 在切面中补充校验
if (interceptor.roles().length > 0) {
checkRoles(interceptor.roles());
}
2. 参数校验
private void validateParams(JoinPoint point) {
Object[] args = point.getArgs();
// 实现参数校验逻辑
}
3. 操作日志记录
@AfterReturning(pointcut="@annotation(interceptor)", returning="result")
public void logOperation(JoinPoint point, Object result) {
// 记录方法调用日志
}
六、性能优化建议
缓存注解解析结果:
private static ConcurrentMap<Method, GlobalInterceptor> cache = new ConcurrentHashMap<>();
GlobalInterceptor interceptor = cache.computeIfAbsent(method, m ->
m.getAnnotation(GlobalInterceptor.class));
减少重复查询:
// 将验证后的用户信息存入Request属性
request.setAttribute(Constants.USER_INFO_KEY, userInfo);
异步日志记录:
@Async
public void asyncLogOperation(...) {
// 异步记录日志
}
七、常见问题解决方案
问题1:内部方法调用不走AOP?
• 原因:Spring AOP基于代理,内部调用不经过代理
• 解决:从ApplicationContext获取代理对象调用
问题2:多切面执行顺序?
• 使用@Order注解控制顺序
• 默认按切面名称字母顺序
问题3:异常处理?
• 使用@AfterThrowing处理特定异常
• 结合全局异常处理器统一处理
这种基于注解和AOP的登录校验实现,是Spring生态中典型的权限控制方案,既保持了代码的简洁性,又提供了足够的灵活性和扩展能力。
登录校验的多种实现方式
除了上面介绍的AOP注解方式,还有多种实现登录校验的方法,各有优缺点和适用场景。以下是常见的几种方案:
1. 拦截器(Interceptor)实现
实现原理:
基于Spring MVC的拦截器机制,在控制器方法执行前后进行拦截
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
// 校验token逻辑
if(!checkToken(token)) {
response.setStatus(401);
return false;
}
return true;
}
}
// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}
优点:
• 配置简单,基于URL模式匹配
• 执行时机早于AOP,性能稍好
缺点:
• 不够灵活,无法基于方法粒度控制
• 难以获取方法上的元数据信息
2. 过滤器(Filter)实现
实现原理:
基于Servlet规范的过滤器,在最外层对请求进行过滤
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("Authorization");
if(!checkToken(token)) {
((HttpServletResponse)response).setStatus(401);
return;
}
chain.doFilter(request, response);
}
}
// 注册过滤器
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<AuthFilter> authFilter() {
FilterRegistrationBean<AuthFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(new AuthFilter());
reg.addUrlPatterns("/api/*");
return reg;
}
}
优点:
• 执行时机最早,性能最好
• 可处理静态资源等非Spring管理的请求
缺点:
• 无法获取Spring上下文信息
• 配置粒度较粗
3. Spring Security框架
实现原理:
Spring官方安全框架,提供完整认证授权解决方案
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthFilter(authenticationManager()));
}
}
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
// JWT校验逻辑
}
}
优点:
• 功能全面,支持多种认证方式
• 内置CSRF防护等安全功能
• 社区支持好
缺点:
• 学习曲线较陡峭
• 配置复杂,可能引入不需要的功能
4. 网关层统一校验
实现原理:
在API网关(如Spring Cloud Gateway)层统一处理认证
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if(!checkToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
}
优点:
• 统一入口,避免每个服务重复实现
• 性能损耗最小化
缺点:
• 需要额外维护网关服务
• 微服务架构才需要
5. 方法参数解析器
实现原理:
通过自定义参数解析器自动注入用户信息
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String token = request.getHeader("Authorization");
return getUserFromToken(token);
}
}
// 使用注解
@GetMapping("/userinfo")
public UserInfo getUserInfo(@CurrentUser User user) {
return userService.getInfo(user.getId());
}
优点:
• 使用简洁,直接获取用户信息
• 与业务代码自然集成
缺点:
• 只适用于获取当前用户场景
• 需要配合其他校验方式使用
6. 对比总结
方式 适用场景 优点 缺点
AOP注解 方法粒度的灵活控制 灵活,可扩展性强 性能稍差
拦截器 URL模式的统一校验 配置简单,性能较好 粒度较粗
过滤器 最外层请求过滤 性能最好 无法使用Spring特性
Spring Security 需要完整安全解决方案 功能全面 复杂,学习成本高
API网关 微服务架构 统一管理,性能好 需要额外基础设施
参数解析器 需要自动注入用户信息 使用简洁 需配合其他方式使用
7. 选择建议
单体应用:AOP注解 + 拦截器组合
微服务架构:网关统一认证 + 服务内AOP补充
高安全性要求:Spring Security
高性能要求:过滤器 + 参数解析器
快速开发:AOP注解方式最简单直接
实际项目中,通常会组合使用多种方式,比如用过滤器做基础校验,AOP做细粒度控制,参数解析器方便获取用户信息。
JWT实现登录校验的完整方案
JWT(JSON Web Token)是一种流行的无状态认证机制,特别适合分布式系统的登录校验。下面我将详细介绍基于JWT的登录校验实现方案。
一、JWT基础概念
1. JWT组成结构
header.payload.signature
• Header:包含令牌类型和签名算法
• Payload:存放用户信息和其他数据
• Signature:防止令牌被篡改的签名
2. 工作流程
二、Spring Boot实现方案
1. 添加依赖
<!-- pom.xml -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
2. JWT工具类
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
// 生成令牌
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
// 验证令牌
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
// 从令牌获取用户名
public String getUsernameFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
}
3. 登录接口实现
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
// 1. 验证用户名密码
boolean authenticated = authenticate(loginRequest.getUsername(),
loginRequest.getPassword());
if (!authenticated) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 2. 生成JWT
String token = jwtTokenUtil.generateToken(loginRequest.getUsername());
// 3. 返回响应
Map<String, String> response = new HashMap<>();
response.put("token", token);
return ResponseEntity.ok(response);
}
private boolean authenticate(String username, String password) {
// 实际项目中这里应该查询数据库验证
return "admin".equals(username) && "123456".equals(password);
}
}
4. JWT校验过滤器
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. 从请求头获取token
String token = request.getHeader("Authorization");
// 2. 检查token是否存在和有效
if (token != null && token.startsWith("Bearer ")) {
String jwt = token.substring(7);
if (jwtTokenUtil.validateToken(jwt)) {
// 3. 设置认证信息到SecurityContext
String username = jwtTokenUtil.getUsernameFromToken(jwt);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
5. 安全配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthFilter jwtAuthFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
}
}
三、客户端使用方式
1. 登录获取token
POST /auth/login
Content-Type: application/json
{
"username": "admin",
"password": "123456"
}
响应示例:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTY1..."
}
2. 访问受保护API
GET /api/protected
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
四、高级功能实现
1. 令牌刷新机制
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(HttpServletRequest request) {
String oldToken = request.getHeader("Authorization");
if (oldToken != null && oldToken.startsWith("Bearer ")) {
String username = jwtTokenUtil.getUsernameFromToken(oldToken.substring(7));
String newToken = jwtTokenUtil.generateToken(username);
// 返回新token...
}
return ResponseEntity.badRequest().build();
}
2. 黑名单实现(注销功能)
// 在JwtTokenUtil中添加
private Set<String> tokenBlacklist = new HashSet<>();
public void invalidateToken(String token) {
tokenBlacklist.add(token);
}
public boolean isTokenValid(String token) {
return !tokenBlacklist.contains(token) && validateToken(token);
}
3. 多设备登录管理
// 用户-设备token映射
private Map<String, Set<String>> userDeviceTokens = new ConcurrentHashMap<>();
public String generateToken(String username, String deviceId) {
// ...生成token
userDeviceTokens.computeIfAbsent(username, k -> new HashSet<>()).add(deviceId);
return token;
}
public void logoutDevice(String username, String deviceId) {
userDeviceTokens.getOrDefault(username, Collections.emptySet()).remove(deviceId);
}
五、安全最佳实践
使用HTTPS:防止令牌被截获
设置合理过期时间:通常2小时-7天
不要存储敏感信息:JWT内容可以被解码
使用强密钥:至少256位的密钥
实现令牌刷新:减少长期有效的风险
防范CSRF:即使JWT不易受CSRF影响也应防范
日志监控:记录异常令牌尝试
六、与其他方案的对比
特性 JWT 传统Session OAuth2
状态 无状态 有状态 可无状态
性能 高 中等 中等
扩展性 强 弱 强
适用场景 API/微服务 传统Web应用 第三方登录
实现复杂度 简单 简单 复杂
JWT特别适合现代前后端分离架构和微服务场景,它的无状态特性可以显著减轻服务器压力,简化横向扩展。但对于需要即时撤销令牌的场景,需要额外实现黑名单机制。