Springboot实现登录拦截的三种方式 您所在的位置:网站首页 Java实现登录 Springboot实现登录拦截的三种方式

Springboot实现登录拦截的三种方式

2023-10-06 10:34| 来源: 网络整理| 查看: 265

文章目录 1. 登录认证1.1 介绍1.2 方式1.3 扩展 2. 实现2.1 项目结构以及前置准备2.2 过滤器实现登录拦截2.3 拦截器实现登录拦截2.4 AOP+自定义注解实现2.5 顺序分析 3. 扩展3.1 ThreadLocal存放登录用户3.2 springMVC的参数解析器

1. 登录认证 1.1 介绍

在现在的前后端项目中,在不使用框架的情况下,登录成功之后,会生产Token发送到前端,每次请求通过cookie或者请求头携带到后台,后台在执行业务代码之前,先校验用户是否登录,根据登录状态获取是否有该接口的权限。这个操作希望是跟业务代码分离的,实现非侵入式的登录拦截和权限控制。

1.2 方式

spring提供下面三种方式实现非侵入式的登录和权限校验,下面一一说明

Java Web中提供的FilterSpringMvc中提供的拦截器InterceptorSpring提供的AOP技术+自定义注解 1.3 扩展

在使用上述三种方式实现登录登录拦截之后,为登录会直接响应JSON的错误数据。但是如果在方法中要使用到登录用户存储的登录信息,那么就得重新获取了。推荐两种比较简单的方式

在拦截器中判断登录状态之后,存储到线程池对象ThreadLocal对象中。但是如果不是在一个线程中,比较麻烦。使用SpringMvc提供的自定义参数解析器,结合自定义参数注解,完成对标注注解的参数进行自动注入。比较简单,推荐使用 2. 实现

本文对应源码地址: 01-spring-boot-auth-filter · master · csdn / spring-boot-csdn · GitLab (sea-clouds.cn)

pom.xml

org.springframework.boot spring-boot-dependencies 2.5.2 pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-configuration-processor org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 javax.servlet javax.servlet-api org.projectlombok lombok org.apache.commons commons-lang3 cn.hutool hutool-all 5.6.0 2.1 项目结构以及前置准备

image-20210807185738246

前置实现,登录逻辑,这通过UserController中提供了三个接口,登录,查询用户,测试接口

登录接口登录成功之后,生成token,使用UUID,此处不使用加密算法。把token和登录信息对应关系存入redis,失效时间半个小时。

image-20210807185958557

测试

此处使用PostMan进行接口测试

login登录接口

post /user/login 请求成功,返回token

image-20210807190603996

findAllUser查询接口

get /user 返回用户列表

image-20210807190812133

2.2 过滤器实现登录拦截

LoginFilter登录过滤器

public class LoginFilter implements Filter { private final RedisTemplate redisTemplate; private final LoginProperties loginProperties; public LoginFilter(RedisTemplate redisTemplate, LoginProperties loginProperties) { this.redisTemplate = redisTemplate; this.loginProperties = loginProperties; } @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 过滤路径 String requestURI = httpServletRequest.getRequestURI(); if (!loginProperties.getFilterExcludeUrl().contains(requestURI)) { // 获取token String token = httpServletRequest.getHeader(Constant.TOKEN_HEADER_NAME); if (StringUtils.isBlank(token)) { returnNoLogin(response); return; } // 从redis中拿token对应user User user = (User) redisTemplate.opsForValue().get(Constant.REDIS_USER_PREFIX + token); if (user == null) { returnNoLogin(response); return; } // token续期 redisTemplate.expire(Constant.REDIS_USER_PREFIX + token, 30, TimeUnit.MINUTES); } chain.doFilter(request, response); } /** * 返回未登录的错误信息 * @param response ServletResponse */ private void returnNoLogin(ServletResponse response) throws IOException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; ServletOutputStream outputStream = httpServletResponse.getOutputStream(); // 设置返回401 和响应编码 httpServletResponse.setStatus(401); httpServletResponse.setContentType("Application/json;charset=utf-8"); // 构造返回响应体 Result result = Result.builder() .code(HttpStatus.UNAUTHORIZED.value()) .errorMsg("未登陆,请先登陆") .build(); String resultString = JSONUtil.toJsonStr(result); outputStream.write(resultString.getBytes(StandardCharsets.UTF_8)); } @Override public void destroy() { } }

WebMvcConfig配置拦截器

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Resource private LoginProperties loginProperties; @Resource private RedisTemplate redisTemplate; /** * 添加登录过滤器 */ @Bean public FilterRegistrationBean loginFilterRegistration() { // 注册LoginFilter FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new LoginFilter(redisTemplate, loginProperties)); // 设置名称 registrationBean.setName("loginFilter"); // 设置拦截路径 registrationBean.addUrlPatterns(loginProperties.getFilterIncludeUrl().toArray(new String[0])); // 指定顺序,数字越小越靠前 registrationBean.setOrder(-1); return registrationBean; } }

测试

未登录访问查询接口,会报错401

image-20210807191702407

登录之后正常访问

image-20210807191731430

2.3 拦截器实现登录拦截

LoginInterception登录拦截器

@Component public class LoginInterception implements HandlerInterceptor { @Resource private RedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取token String token = request.getHeader(Constant.TOKEN_HEADER_NAME); if (StringUtils.isBlank(token)) { returnNoLogin(response); return false; } // 从redis中拿token对应user User user = (User) redisTemplate.opsForValue().get(Constant.REDIS_USER_PREFIX + token); if (user == null) { returnNoLogin(response); return false; } // token续期 redisTemplate.expire(Constant.REDIS_USER_PREFIX + token, 30, TimeUnit.MINUTES); // 放行 return true; } /** * 返回未登录的错误信息 * @param response ServletResponse */ private void returnNoLogin(HttpServletResponse response) throws IOException { ServletOutputStream outputStream = response.getOutputStream(); // 设置返回401 和响应编码 response.setStatus(401); response.setContentType("Application/json;charset=utf-8"); // 构造返回响应体 Result result = Result.builder() .code(HttpStatus.UNAUTHORIZED.value()) .errorMsg("未登陆,请先登陆") .build(); String resultString = JSONUtil.toJsonStr(result); outputStream.write(resultString.getBytes(StandardCharsets.UTF_8)); } }

WebMvcConfig配置拦截器

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Resource private LoginProperties loginProperties; @Resource private LoginInterception loginInterception; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterception) .addPathPatterns(loginProperties.getInterceptorIncludeUrl()) .excludePathPatterns(loginProperties.getInterceptorExcludeUrl()); } }

测试

未登录访问接口,正常拦截

image-20210807192437682

登录访问接口,正常通行

image-20210807191731430

2.4 AOP+自定义注解实现

LoginValidator自定义注解

/** * @description 登录校验注解,用户aop校验 * @author HLH * @email [email protected] * @date Created in 2021/8/1 下午9:35 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LoginValidator { boolean validated() default true; }

LoginAspect登录AOP类

@Component @Aspect public class LoginAspect { @Resource private RedisTemplate redisTemplate; /** * 切点,方法上有注解或者类上有注解 * 拦截类或者是方法上标注注解的方法 */ @Pointcut(value = "@annotation(xyz.hlh.annotition.LoginValidator) || @within(xyz.hlh.annotition.LoginValidator)") public void pointCut() {} @Around("pointCut()") public Object before(ProceedingJoinPoint joinpoint) throws Throwable { // 获取方法方法上的LoginValidator注解 MethodSignature methodSignature = (MethodSignature)joinpoint.getSignature(); Method method = methodSignature.getMethod(); LoginValidator loginValidator = method.getAnnotation(LoginValidator.class); // 如果有,并且值为false,则不校验 if (loginValidator != null && !loginValidator.validated()) { return joinpoint.proceed(joinpoint.getArgs()); } // 正常校验 获取request和response ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (requestAttributes == null || requestAttributes.getResponse() == null) { // 如果不是从前段过来的,没有request,则直接放行 return joinpoint.proceed(joinpoint.getArgs()); } HttpServletRequest request = requestAttributes.getRequest(); HttpServletResponse response = requestAttributes.getResponse(); // 获取token String token = request.getHeader(Constant.TOKEN_HEADER_NAME); if (StringUtils.isBlank(token)) { returnNoLogin(response); return null; } // 从redis中拿token对应user User user = (User) redisTemplate.opsForValue().get(Constant.REDIS_USER_PREFIX + token); if (user == null) { returnNoLogin(response); return null; } // token续期 redisTemplate.expire(Constant.REDIS_USER_PREFIX + token, 30, TimeUnit.MINUTES); // 放行 return joinpoint.proceed(joinpoint.getArgs()); } /** * 返回未登录的错误信息 * @param response ServletResponse */ private void returnNoLogin(HttpServletResponse response) throws IOException { ServletOutputStream outputStream = response.getOutputStream(); // 设置返回401 和响应编码 response.setStatus(401); response.setContentType("Application/json;charset=utf-8"); // 构造返回响应体 Result result = Result.builder() .code(HttpStatus.UNAUTHORIZED.value()) .errorMsg("未登陆,请先登陆") .build(); String resultString = JSONUtil.toJsonStr(result); outputStream.write(resultString.getBytes(StandardCharsets.UTF_8)); } }

Controller标注注解

image-20210807193016813

测试

未登录访问接口,正常拦截

image-20210807192437682

登录访问接口,正常通行

image-20210807191731430

2.5 顺序分析

如果Filter Interceptor AOP都有的话,顺序如下

FilterInterceptorAOP 3. 扩展 3.1 ThreadLocal存放登录用户

LoginUserThread线程对象

public class LoginUserThread { /** 线程池变量 */ private static final ThreadLocal LOGIN_USER = new ThreadLocal(); private LoginUserThread() {} public static User get() { return LOGIN_USER.get(); } public void put(User user) { LOGIN_USER.set(user); } public void remove() { LOGIN_USER.remove(); } }

LoginInterceptor改造在前置方法中放入线程对象,在after中清空前置对象

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取token String token = request.getHeader(Constant.TOKEN_HEADER_NAME); if (StringUtils.isBlank(token)) { returnNoLogin(response); return false; } // 从redis中拿token对应user User user = (User) redisTemplate.opsForValue().get(Constant.REDIS_USER_PREFIX + token); if (user == null) { returnNoLogin(response); return false; } // 存放如ThreadLocal LoginUserThread.put(user); // 放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 存放如ThreadLocal LoginUserThread.remove(); }

测试

方法修改如下

@GetMapping public ResponseEntity findAllUser() { System.out.println(LoginUserThread.get()); return success(PRE_USER_LIST); }

访问,查看控制台打印结果

image-20210807195052054

3.2 springMVC的参数解析器

LoginUser自定义注解

/** * @description 登录参数注解,通过spring参数解析器解析 * @author HLH * @email [email protected] * @date Created in 2021/8/1 下午9:35 */ @Target(ElementType.PARAMETER) // 作用于参数 @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LoginUser { }

LoginUserResolver参数解析器

/** * @description 登录参数注入,通过spring参数解析器解析 * @author HLH * @email [email protected] * @date Created in 2021/8/1 下午9:35 */ @Component public class LoginUserResolver implements HandlerMethodArgumentResolver { @Resource private RedisTemplate redisTemplate; /** * 是否进行拦截 * @param parameter 参数对象 * @return true,拦截。false,不拦截 */ @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(LoginUser.class); } /** * 拦截之后执行的方法 */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 从request中获取token,此处只做参数解析,不做登录校验 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (requestAttributes == null) { return null; } HttpServletRequest request = requestAttributes.getRequest(); // 获取token String token = request.getHeader(Constant.TOKEN_HEADER_NAME); if (StringUtils.isBlank(token)) { return null; } // 从redis中拿token对应user return (User) redisTemplate.opsForValue().get(Constant.REDIS_USER_PREFIX + token); } }

WebMvcConfig添加参数解析器

@Resource private LoginUserResolver loginUserResolver; @Override public void addArgumentResolvers(List resolvers) { resolvers.add(loginUserResolver); }

测试

controller方法改造

@GetMapping("/test") public String test(@LoginUser User user) { System.out.println(user); return "测试编码"; }

访问查看控制台结果

image-20210807200128831



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有