验证用户是否登陆有效的几种方法
最近在阅读几个开源项目的代码,总结一下各种风格的登陆校验
方案一.使用拦截器校验session中是否包含loginUser或Token等信息
结合代码说明:
@Component
public class AdminLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
String uri = request.getRequestURI();
if (uri.startsWith("/admin") && null == request.getSession().getAttribute("loginUser")) {
request.getSession().setAttribute("errorMsg", "请重新登陆");
response.sendRedirect(request.getContextPath() + "/admin/login");
return false;
} else {
request.getSession().removeAttribute("errorMsg");
return true;
}
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
这是一个后台管理用的登陆校验器.
-
大前提:人为的规范URL格式,后台管理操作URL以"/admin"开头
-
首先从request中获取到URI链接,然后判断是否以admin开头
-
然后判断session中的User是否为空 若为空 写入错误信息errormsg到session中,并重定向到登陆界面
-
记得返回false
-
若成功,则清除session其中的errorMsg并返回true
方案二 采用切面方式进行参数校验
这段代码出自慕课网廖师兄的SpringBoot的微信点餐系统,这里的情景时实现后台卖家端的是否登陆的校验
先看看代码:
@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {
//自动注入redisTemplate操作redis
@Autowired
private StringRedisTemplate redisTemplate;
//定义一个切面,注意这里将卖家端中用户操作这块的排除了,因为这里包含用户登陆登出,不需要校验.
@Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
"&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
public void verify() {}
@Before("verify()")
public void doVerify() {
//获取request
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//查询cookie,使用封装好的工具类
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
//若为空说明没登陆
log.warn("【登录校验】Cookie中查不到token");
throw new SellerAuthorizeException();
}
//去redis里查询
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("【登录校验】Redis中查不到token");
throw new SellerAuthorizeException();
}
}
}
其中,cookie的工具包代码:
package com.imooc.utils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
public class CookieUtil {
/**
* 设置
* @param response
* @param name
* @param value
* @param maxAge
*/
public static void set(HttpServletResponse response,
String name,
String value,
int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}
/**
* 获取cookie
* @param request
* @param name
* @return
*/
public static Cookie get(HttpServletRequest request,
String name) {
Map<String, Cookie> cookieMap = readCookieMap(request);
if (cookieMap.containsKey(name)) {
return cookieMap.get(name);
}else {
return null;
}
}
/**
* 将cookie封装成Map
* @param request
* @return
*/
private static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
Map<String, Cookie> cookieMap = new HashMap<>();
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie: cookies) {
cookieMap.put(cookie.getName(), cookie);
}
}
return cookieMap;
}
}
这种方式,适用于分布式的登陆校验
方案三.token进行参数校验
这一种方案相对复杂,一步步来吧
首先定义一个注解类
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TokenToUser {
/**
* 当前用户在request中的名字
*
* @return
*/
String value() default "user";
}
然后自定义一个参数解析
public class TokenToUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Resource
private AdminUserService adminUserService;
public TokenToUserMethodArgumentResolver() {
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(TokenToUser.class)) {
return true;
}
return false;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (parameter.getParameterAnnotation(TokenToUser.class) instanceof TokenToUser) {
AdminUser user = null;
//从Request头中获取token
String token = webRequest.getHeader("token");
//然后使用token去后台数据库中校验.
if (null != token && !"".equals(token) && token.length() == 32) {
user = adminUserService.getAdminUserByToken(token);
}
//返回结果
return user;
}
//结果设置为空
return null;
}
public static byte[] getRequestPostBytes(HttpServletRequest request)
throws IOException {
int contentLength = request.getContentLength();
if (contentLength < 0) {
return null;
}
byte buffer[] = new byte[contentLength];
for (int i = 0; i < contentLength; ) {
int readlen = request.getInputStream().read(buffer, i,
contentLength - i);
if (readlen == -1) {
break;
}
i += readlen;
}
return buffer;
}
}
控制器中使用方法如下:
/**
* 保存
*/
@RequestMapping("/save")
public Result save(@RequestBody AdminUser user,@TokenToUser AdminUser loginUser) {
//此处校验参数解析器解析出来的user是否为空,空则说明登陆校验失败.
if (loginUser==null){
return ResultGenerator.genErrorResult(Constants.RESULT_CODE_NOT_LOGIN, "未登录!");
}
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
return ResultGenerator.genErrorResult(Constants.RESULT_CODE_PARAM_ERROR, "参数异常!");
}
AdminUser tempUser = adminUserService.selectByUserName(user.getUserName());
if (tempUser != null) {
return ResultGenerator.genErrorResult(Constants.RESULT_CODE_PARAM_ERROR, "用户已存在勿重复添加!");
}
if ("admin".endsWith(user.getUserName().trim())) {
return ResultGenerator.genErrorResult(Constants.RESULT_CODE_PARAM_ERROR, "不能添加admin用户!");
}
if (adminUserService.save(user) > 0) {
return ResultGenerator.genSuccessResult();
} else {
return ResultGenerator.genFailResult("添加失败");
}
}
最后,如果是SpringBoot的话记得在配置类那里注册一下:
@SpringBootApplication
public class MyBootApplication extends WebMvcConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class, args);
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
argumentResolvers.add(new TokenToUserMethodArgumentResolver());
}
}
其实,这种方法也可以改进一下,从cookie中获取token值,然后在redis中校验是否有效,以减轻mysql的压力。