Spring Boot (v2.0.5.RELEASE)
- 程序中有些资源(接口)是需要用户登录才能够使用的,或者是具有某种角色的用户(比如普通登录用户,或者系统管理员等)才能使用,本篇文章先为大家讲解如何控制使用某接口要求用户必须登录。
- 实现的思路是
- 首先定义注解
@LoginUser
,该注解用于标注哪些接口需要进行拦截 - 定义拦截器,拦截标注了
@LoginUser
注解的接口 - 拦截之后判断该用户目前是不是处于登陆状态,如果是登陆状态则放行该请求,如果未登录则提示登陆
- 给方法或者类打上
@LoginUser
注解进行测试
- 定义标注注解
@LoginUser
package com.futao.springmvcdemo.annotation; import com.futao.springmvcdemo.model.enums.Role; import java.lang.annotation.*; /** * @author futao * Created on 2018/9/19-14:39. * 登陆用户,用户角色 */ @Target(value = { ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LoginUser { /** * 要求的用户角色 * * @return */ Role role() default Role.Normal; }
2。 定义拦截器LoginUserInterceptor
package com.futao.springmvcdemo.annotation.impl; import com.alibaba.fastjson.JSON; import com.futao.springmvcdemo.annotation.LoginUser; import com.futao.springmvcdemo.model.entity.constvar.ErrorMessage; import com.futao.springmvcdemo.model.system.RestResult; import com.futao.springmvcdemo.model.system.SystemConfig; import com.futao.springmvcdemo.utils.ThreadLocalUtils; import org.apache.commons.lang3.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @author futao * Created on 2018/9/19-14:44. * 对请求标记了LoginUser的方法进行拦截 */ @Component public class LoginUserInterceptor extends HandlerInterceptorAdapter { private static final Logger logger = LoggerFactory.getLogger(LoginUserInterceptor.class); @Resource private ThreadLocalUtils<String> threadLocalUtils; /** * 在请求到达Controller之前进行拦截并处理 * * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { //注解在方法上 LoginUser loginUserAnnotation = ((HandlerMethod) handler).getMethodAnnotation(LoginUser.class); //注解在类上 LoginUser classLoginUserAnnotation = ((HandlerMethod) handler).getMethod().getDeclaringClass().getAnnotation(LoginUser.class); if (ObjectUtils.anyNotNull(loginUserAnnotation, classLoginUserAnnotation)) { HttpSession session = request.getSession(false); //session不为空 if (ObjectUtils.allNotNull(session)) { String loginUser = (String) session.getAttribute(SystemConfig.LOGIN_USER_SESSION_KEY); if (ObjectUtils.allNotNull(loginUser)) { System.out.println("当前登陆用户为:" + loginUser); //将当前用户的信息存入threadLocal中 threadLocalUtils.set(loginUser); } else { System.out.println("用户不存在"); return false; } } else {//session为空,用户未登录 RestResult restResult = new RestResult(false, "-1", ErrorMessage.NOT_LOGIN, ErrorMessage.NOT_LOGIN.substring(6)); response.getWriter().append(JSON.toJSONString(restResult)); return false; } } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //释放threadLocal资源 threadLocalUtils.remove(); } }
- 注册拦截器
package com.futao.springmvcdemo.annotation; import com.futao.springmvcdemo.annotation.impl.LoginUserInterceptor; import com.futao.springmvcdemo.annotation.impl.RequestLogInterceptor; import com.futao.springmvcdemo.annotation.impl.SignInterceptor; import org.springframework.boot.SpringBootConfiguration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /** * @author futao * Created on 2018/9/18-15:15. */ @SpringBootConfiguration public class WebMvcConfiguration implements WebMvcConfigurer { @Resource private SignInterceptor signInterceptor; @Resource private LoginUserInterceptor loginUserInterceptor; @Resource private RequestLogInterceptor requestLogInterceptor; /** * addInterceptor()的顺序需要严格按照程序的执行的顺序 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestLogInterceptor).addPathPatterns("/**"); registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**"); // "/**"和"/*"是有区别的 registry.addInterceptor(signInterceptor).addPathPatterns("/**"); } }
- 测试(可分别将注解打在类上和方法上进行测试)
package com.futao.springmvcdemo.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.futao.springmvcdemo.annotation.LoginUser; import com.futao.springmvcdemo.model.entity.User; import com.futao.springmvcdemo.model.system.SystemConfig; import com.futao.springmvcdemo.service.UserService; import org.apache.commons.lang3.ObjectUtils; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.List; import java.util.UUID; /** * @author futao * Created on 2018/9/19-15:05. */ @RequestMapping(path = "User", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @RestController public class UserController { @Resource private UserService userService; /** * 获取当前的登陆的用户信息,其实是从threadLocal中获取 * * @return */ @LoginUser @GetMapping(path = "my") public JSONObject my() { JSONObject jsonObject = new JSONObject(); jsonObject.put("当前的登陆的用户是:", userService.currentUser()); return jsonObject; } /** * 模拟登陆接口 * * @param mobile * @param request * @return */ @PostMapping(path = "login") public JSONObject login( @RequestParam("mobile") String mobile, HttpServletRequest request ) { HttpSession session = request.getSession(); session.setAttribute(SystemConfig.LOGIN_USER_SESSION_KEY, String.valueOf(UUID.randomUUID())); session.setMaxInactiveInterval(SystemConfig.SESSION_INVALIDATE_SECOND); return new JSONObject(); } }
- 测试
4.1 未登录情况下调用标记了@LoginUser
的获取当前登陆用户信息接口
4.2 登录
4.3 登录之后调用调用标记了@LoginUser的获取当前登陆用户信息接口
稍微解释一下上面登陆和获取用户信息的逻辑:
用户请求登陆之后,会为该用户在系统中生成一个HttpSession
,同时在系统中有一个Map
来存放所有的session
信息,该Map
的key
为一个随机字符串,value
为session
对象在系统中的堆地址,在登陆请求完成之后,系统会将该sesion
的key
值以cookie
(JSESSIONID)的形式写回浏览器。
用户下次登陆的时候,请求中会自动带上该cookie
,所以我们在标记了需要登陆的@LoginUser
注解的请求到达处理逻辑之前进行拦截,就是从cookie
中(JSESSIONID)取出session
的key
值,如果没有该cookie
,则代表用户没有登陆,如果有该cookie
,再在存放cookie
的map
中取,如果没有取到,则代表用户的session
已经过期了,需要重新登陆,或者cookie
是伪造的。
拿到了登陆用户的session
之后,我们去Map
中获取对应的值,一般是用户的id
,在通过这个用户id
,可以去数据库查该用户的信息,查到用户的信息之后将用户信息放入threadLocal
中,然后就可以在任何地方get()
到当前登陆的用户信息了,非常方便。
使用上面的基于注解的拦截器可以实现很多功能,比如动态的第三方接口验签,和系统日志记录(不需要注解)等