Spring Boot (v2.0.5.RELEASE)
- 程序中有些資源(接口)是需要用戶登錄才能夠使用的,或者是具有某種角色的用戶(比如普通登錄用戶踩身,或者系統(tǒng)管理員等)才能使用胀茵,本篇文章先為大家講解如何控制使用某接口要求用戶必須登錄。
- 實(shí)現(xiàn)的思路是
- 首先定義注解
@LoginUser
挟阻,該注解用于標(biāo)注哪些接口需要進(jìn)行攔截 - 定義攔截器琼娘,攔截標(biāo)注了
@LoginUser
注解的接口 - 攔截之后判斷該用戶目前是不是處于登陸狀態(tài)峭弟,如果是登陸狀態(tài)則放行該請(qǐng)求,如果未登錄則提示登陸
- 給方法或者類打上
@LoginUser
注解進(jìn)行測(cè)試
- 首先定義注解
- 定義標(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.
* 對(duì)請(qǐng)求標(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;
/**
* 在請(qǐng)求到達(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();
}
}
- 注冊(cè)攔截器
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("/**");
}
}
- 測(cè)試(可分別將注解打在類上和方法上進(jìn)行測(cè)試)
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();
}
}
- 測(cè)試
4.1 未登錄情況下調(diào)用標(biāo)記了@LoginUser
的獲取當(dāng)前登陸用戶信息接口
未登錄
4.2 登錄
登錄操作
4.3 登錄之后調(diào)用調(diào)用標(biāo)記了@LoginUser
的獲取當(dāng)前登陸用戶信息接口
登陸之后
稍微解釋一下上面登陸和獲取用戶信息的邏輯:
用戶請(qǐng)求登陸之后挨务,會(huì)為該用戶在系統(tǒng)中生成一個(gè)HttpSession
击你,同時(shí)在系統(tǒng)中有一個(gè)Map
來(lái)存放所有的session
信息玉组,該Map
的key
為一個(gè)隨機(jī)字符串,value
為session
對(duì)象在系統(tǒng)中的堆地址丁侄,在登陸請(qǐng)求完成之后惯雳,系統(tǒng)會(huì)將該sesion
的key
值以cookie
(JSESSIONID)的形式寫回瀏覽器。
設(shè)置cookie
用戶下次登陸的時(shí)候鸿摇,請(qǐng)求中會(huì)自動(dòng)帶上該
cookie
石景,所以我們?cè)跇?biāo)記了需要登陸的@LoginUser
注解的請(qǐng)求到達(dá)處理邏輯之前進(jìn)行攔截,就是從cookie
中(JSESSIONID)取出session
的key
值拙吉,如果沒(méi)有該cookie
潮孽,則代表用戶沒(méi)有登陸,如果有該cookie
筷黔,再在存放cookie
的map
中取往史,如果沒(méi)有取到,則代表用戶的session
已經(jīng)過(guò)期了佛舱,需要重新登陸椎例,或者cookie
是偽造的。拿到了登陸用戶的
session
之后请祖,我們?nèi)?code>Map中獲取對(duì)應(yīng)的值订歪,一般是用戶的id
,在通過(guò)這個(gè)用戶id
肆捕,可以去數(shù)據(jù)庫(kù)查該用戶的信息刷晋,查到用戶的信息之后將用戶信息放入threadLocal
中,然后就可以在任何地方get()
到當(dāng)前登陸的用戶信息了慎陵,非常方便眼虱。
使用上面的基于注解的攔截器可以實(shí)現(xiàn)很多功能,比如動(dòng)態(tài)的第三方接口驗(yàn)簽荆姆,和系統(tǒng)日志記錄(不需要注解)等
日志系統(tǒng)