Spring Boot (v2.0.5.RELEASE)
- 程序中有些資源(接口)是需要用戶登錄才能夠使用的,或者是具有某種角色的用戶(比如普通登錄用戶,或者系統(tǒng)管理員等)才能使用,本篇文章先為大家講解如何控制使用某接口要求用戶必須登錄。
- 實(shí)現(xiàn)的思路是
- 首先定義注解
@LoginUser,該注解用于標(biāo)注哪些接口需要進(jìn)行攔截 - 定義攔截器,攔截標(biāo)注了
@LoginUser注解的接口 - 攔截之后判斷該用戶目前是不是處于登陸狀態(tài),如果是登陸狀態(tài)則放行該請求,如果未登錄則提示登陸
- 給方法或者類打上
@LoginUser注解進(jìn)行測試
- 首先定義注解
- 定義標(biāo)注注解
@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.
* 對請求標(biāo)記了LoginUser的方法進(jìn)行攔截
*/
@Component
public class LoginUserInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(LoginUserInterceptor.class);
@Resource
private ThreadLocalUtils<String> threadLocalUtils;
/**
* 在請求到達(dá)Controller之前進(jìn)行攔截并處理
*
* @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("當(dāng)前登陸用戶為:" + loginUser);
//將當(dāng)前用戶的信息存入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()的順序需要嚴(yán)格按照程序的執(zhí)行的順序
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestLogInterceptor).addPathPatterns("/**");
registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");
// "/**"和"/*"是有區(qū)別的
registry.addInterceptor(signInterceptor).addPathPatterns("/**");
}
}
- 測試(可分別將注解打在類上和方法上進(jìn)行測試)
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;
/**
* 獲取當(dāng)前的登陸的用戶信息,其實(shí)是從threadLocal中獲取
*
* @return
*/
@LoginUser
@GetMapping(path = "my")
public JSONObject my() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("當(dāng)前的登陸的用戶是:", 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 未登錄情況下調(diào)用標(biāo)記了@LoginUser的獲取當(dāng)前登陸用戶信息接口
未登錄
4.2 登錄
登錄操作
4.3 登錄之后調(diào)用調(diào)用標(biāo)記了@LoginUser的獲取當(dāng)前登陸用戶信息接口
登陸之后
稍微解釋一下上面登陸和獲取用戶信息的邏輯:
用戶請求登陸之后,會為該用戶在系統(tǒng)中生成一個HttpSession,同時在系統(tǒng)中有一個Map來存放所有的session信息,該Map的key為一個隨機(jī)字符串,value為session對象在系統(tǒng)中的堆地址,在登陸請求完成之后,系統(tǒng)會將該sesion的key值以cookie(JSESSIONID)的形式寫回瀏覽器。

設(shè)置cookie
用戶下次登陸的時候,請求中會自動帶上該
cookie,所以我們在標(biāo)記了需要登陸的@LoginUser注解的請求到達(dá)處理邏輯之前進(jìn)行攔截,就是從cookie中(JSESSIONID)取出session的key值,如果沒有該cookie,則代表用戶沒有登陸,如果有該cookie,再在存放cookie的map中取,如果沒有取到,則代表用戶的session已經(jīng)過期了,需要重新登陸,或者cookie是偽造的。拿到了登陸用戶的
session之后,我們?nèi)?code>Map中獲取對應(yīng)的值,一般是用戶的id,在通過這個用戶id,可以去數(shù)據(jù)庫查該用戶的信息,查到用戶的信息之后將用戶信息放入threadLocal中,然后就可以在任何地方get()到當(dāng)前登陸的用戶信息了,非常方便。
使用上面的基于注解的攔截器可以實(shí)現(xiàn)很多功能,比如動態(tài)的第三方接口驗(yàn)簽,和系統(tǒng)日志記錄(不需要注解)等

日志系統(tǒng)


