Spring如何实现仅在Session Cookie有效时允许访问,避免重复校验
问题描述
我希望给API的所有功能添加认证保护,目前已经实现了Session Cookie的发送,但不想在每个接口函数里重复校验Cookie的有效性,有没有更优的实现方案?
注:以下代码仅用于测试,暂未处理明文密码的问题。
登录代码
@RestController @EnableSpringHttpSession public class OnlineMapping { @Autowired private UserRepository userRepository; @PostMapping(path = "/online") public ResponseEntity<?> onlineRequest(@RequestBody OnlineRequest onlineRequest, HttpSession session) { User user; user = userRepository.findUserByUsernameAndPassword(onlineRequest.username, onlineRequest.password); if (user!=null){ user.setLatestTimeStamp(System.currentTimeMillis()); return new ResponseEntity<>("You are now online, Enjoy!", HttpStatus.OK); } else { session.setAttribute("valid", false); session.invalidate(); return new ResponseEntity<>("Invalid login", HttpStatus.valueOf(401)); } } }
示例接口函数
public ResponseEntity<?> createMessage(@RequestBody MessageCreateRequest messageCreateRequest, HttpSession session) { if (session.getAttribute("valid").equals(true)){ // 不想写这段重复代码 Message m = new Message(); m.setContent(messageCreateRequest.content); m.setSenderID(messageCreateRequest.senderID); m.setChannelID(messageCreateRequest.channelID); m.setTimeStamp(System.currentTimeMillis()); messageRepository.save(m); return new ResponseEntity<>("Message created!", HttpStatus.OK); }// return new ResponseEntity<>("Invalid Session", HttpStatus.UNAUTHORIZED); }
解决方案
在Spring生态里,有三种主流方式可以避免重复编写Session校验逻辑:
1. 用Spring Security实现自动Session认证
Spring Security原生支持基于Session的认证,配置后会自动拦截需要保护的接口,无需手动写校验代码:
配置步骤:
- 引入Spring Security依赖(若未添加)
- 编写Security配置类:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 配置免认证路径(比如登录接口) .authorizeHttpRequests(auth -> auth .requestMatchers("/online").permitAll() .anyRequest().authenticated() ) // 开启Session管理,配置失效处理 .sessionManagement(session -> session .invalidSessionStrategy((req, res) -> { res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.getWriter().write("Invalid Session"); }) ) // 前后端分离API可禁用CSRF(根据实际场景调整) .csrf(csrf -> csrf.disable()); return http.build(); } }
- 改造登录接口,复用Spring Security的认证逻辑:
@RestController public class OnlineMapping { @Autowired private AuthenticationManager authenticationManager; @PostMapping("/online") public ResponseEntity<?> login(@RequestBody OnlineRequest request) { try { // 触发Spring Security认证流程 Authentication auth = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.username, request.password) ); SecurityContextHolder.getContext().setAuthentication(auth); // 认证成功后Spring自动创建并维护Session return new ResponseEntity<>("You are now online, Enjoy!", HttpStatus.OK); } catch (AuthenticationException e) { return new ResponseEntity<>("Invalid login", HttpStatus.UNAUTHORIZED); } } }
配置完成后,除了/online之外的所有接口都会自动校验Session有效性,无需在业务代码中重复判断。
2. 自定义拦截器统一校验Session
如果不想引入Spring Security,可通过自定义拦截器实现全局Session校验:
实现步骤:
- 编写拦截器类:
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; public class SessionAuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 排除登录接口 if ("/online".equals(request.getRequestURI())) { return true; } // 校验Session有效性 Boolean valid = (Boolean) request.getSession().getAttribute("valid"); if (valid == null || !valid) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Invalid Session"); return false; } return true; } }
- 注册拦截器到Spring容器:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SessionAuthInterceptor()) .addPathPatterns("/**") // 拦截所有路径 .excludePathPatterns("/online"); // 排除登录接口 } }
这样所有接口请求都会被拦截器统一校验Session,业务接口无需再写重复代码。
3. 自定义注解+AOP实现灵活认证控制
如果需要更细粒度的认证控制(比如部分接口需要认证,部分不需要),可以用自定义注解配合AOP实现:
实现步骤:
- 定义认证注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequireAuth { }
- 编写AOP切面处理校验逻辑:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import jakarta.servlet.http.HttpSession; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @Aspect @Component public class AuthAspect { @Around("@annotation(RequireAuth)") public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable { // 获取当前请求的Session ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpSession session = attributes.getRequest().getSession(); Boolean valid = (Boolean) session.getAttribute("valid"); if (valid == null || !valid) { return new ResponseEntity<>("Invalid Session", HttpStatus.UNAUTHORIZED); } // 校验通过,执行原业务方法 return joinPoint.proceed(); } }
- 在需要认证的接口上添加注解:
@RequireAuth public ResponseEntity<?> createMessage(@RequestBody MessageCreateRequest messageCreateRequest) { // 直接编写业务逻辑,无需处理Session校验 Message m = new Message(); m.setContent(messageCreateRequest.content); m.setSenderID(messageCreateRequest.senderID); m.setChannelID(messageCreateRequest.channelID); m.setTimeStamp(System.currentTimeMillis()); messageRepository.save(m); return new ResponseEntity<>("Message created!", HttpStatus.OK); }
这种方式可以精准控制哪些接口需要认证,灵活性更高。
内容的提问来源于stack exchange,提问作者Alex_X1
相关产品推荐
相关产品推荐

