首先,我在java后台编写了一个拦截器,代码如下
注意:其中
@IgnoreAuth自定义注解
AuthTokenException自定义异常,用来抛出前端可判断的信息,不明之处可查看我博客文档链接
import com.jeesite.common.lang.StringUtils;
import com.jeesite.modules.wx.annotation.IgnoreAuth;
import com.jeesite.modules.wx.config.ApiRRException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 拦截器,拦截配置中设定的所属路径下的请求
* 此拦截器作用方式:
* 1.判断请求头里面是否携带token,从而验证是否登录
* 2.存在token,以token为key查询redis是否有用户信息,从而验证是否过期
* 3.此前可判断请求里面的cookie是否有cookieId,有说明用户点击了记住密码,无论请求头是否token,直接从redis中
* 读取用户的账号密码,进行登录,重新生成token更新redis以及存入请求头(redis里之所以有是因为该用户上次登录
* 点击记住密码,所以在登录时往redis存信息时不会设置失效,永久保存)
* @author yangzeng
* @email [email protected]
*/
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
//此为用以读取redis中以token为key,用户信息为value的数据
@Autowired
private RedisTemplate<String,Object> redisTemplate;
public static final String LOGIN_USER_KEY = "LOGIN_USER_KEY";
public static final String LOGIN_TOKEN_KEY = "X-Nideshop-Token";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//支持跨域请求
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,X-Nideshop-Token,X-URL-PATH");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
//这块涉及到一个自定义注解,忽略验证
IgnoreAuth annotation;
if (handler instanceof HandlerMethod) {
annotation = ((HandlerMethod) handler).getMethodAnnotation(IgnoreAuth.class);
} else {
return true;
}
//如果有@IgnoreAuth注解,则不验证token
if (annotation != null) {
return true;
}
//先判断cookie中是否有cookieId,有则代表记住密码,无需判断请求头中有无token直接去redis中查询用户信息
String cookieId=getTokenFromCookie(request);
if(cookieId!=null){
//说明客户上次登录记住了密码,
//根据cookieId在redis中查询,看是否有用户信息以及是否在有效期内
WebUser userInfo=(WebUser) redisTemplate.opsForValue().get(cookieId);
if (userInfo!=null){
//更新redis中用户登录有效期
request.setAttribute(LOGIN_USER_KEY, userInfo.getId());
request.setAttribute(LOGIN_TOKEN_KEY ,cookieId);//将token携带会页面,存储本地
}else{
//虽然记住了密码,浏览器留存了用户登录凭证,但是redis找不到,还是要重新登录
throw new AuthTokenException("请先登录", 401);
}
}else{
//用户没有记住过密码,既浏览器没有存储用户登录信息
//从header中获取token
String token = request.getHeader(LOGIN_TOKEN_KEY);
//如果header中不存在token,则从参数中获取token
if (StringUtils.isBlank(token)) {
token = request.getParameter(LOGIN_TOKEN_KEY);
//也可从session中获取token,此项目中咱不用
//token=request.getSession().getAttribute(LOGIN_TOKEN_KEY).toString();
}
//token为空
if (StringUtils.isBlank(token)) {
throw new AuthTokenException("请先登录", 401);
}
//根据token在redis中查询,看是否有用户信息以及是否在有效期内
WebUser userInfo=(WebUser) redisTemplate.opsForValue().get(token);
if (userInfo!=null){
//更新redis中用户登录有效期
request.setAttribute(LOGIN_USER_KEY, userInfo.getId());
redisTemplate.expire(token,20,TimeUnit.MINUTES);//延长20分钟
}else {
//有token,但是失效了
throw new AuthTokenException("登录失效", 401);
}
}
return true;
}
//从request中读取cookie中存入的对应项目value
public String getTokenFromCookie(HttpServletRequest request){
Cookie[] cookies=request.getCookies();
for (Cookie cookie:cookies
) {
if (cookie.getName().equals("项目名key")){
//说明项目中存入了该项目的用户登陆tokenID,唯一标识
return cookie.getValue();
}
}
return null;
}
}
其次,我编写一个拦截器配置类,将拦截器注入到spring容器中
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
public class AuthorizationConfig implements WebMvcConfigurer {
/**
* 此处获取拦截器实例化对象
* @return
*/
@Bean
public AuthorizationInterceptor getAuthorizationInterceptor(){
return new AuthorizationInterceptor();
}
/**
* 设置拦截器作用范围
* 讲解:下面调用getAuthorizationInterceptor方法是为了在执行拦截器前实例化拦截器的对象,以免拦截器中的redisTemplate为空
* 也可new一个拦截器实例对象,如果拦击器中没有service层逻辑的话
* 拦截所有web开头的请求
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//可以有多个拦截器的注入,拦截方式这两也有两种
//第一种是运用了自定义注解,在拦截中判断是否放行
registry.addInterceptor(getAuthorizationInterceptor())
.addPathPatterns("/web/**");
//第二种是在拦截器注入时候直接排除不拦截的路径,与上一个不同的是拦截器对象的声明也用了两种方式
registry.addInterceptor(new CeShiInterceptor()).addPathPatterns("/wx/**").excludePathPatterns("/wx/index");
}
}
最后是登录方法,我们当在登录方法中登录成功是生成token值,连同用户信息存入redis中,以及session中或者有记住密码需求的还要存入cookie中,返回给前端,供他们之后请求要么放在参数要么放在session要么放在header中用以通过我们的拦截器
/**
* 登录接口
*/
@RequestMapping(value = "/login")
@ResponseBody
@IgnoreAuth
public Object studentLogin(String loginName, String passWord, HttpServletRequest request, HttpServletResponse response){
try {
if (StringUtils.isEmpty(loginName)||StringUtils.isEmpty(passWord)){
//账号密码未空
return toResponsFail("账号密码未空");
}else {
WebUser webUser=new WebUser();
webUser.setLoginName(loginName);
webUser.setPassWord(passWord);
Map<String,Object> map=new HashMap<>();//用以在login方法中装载生成的token
Boolean flag=webUserService.login(webUser,map);
if (flag){
//如果需要记住密码,在response中cookie存入我们的项目标识和对应的token
Cookie cookie=new Cookie("项目标识作为key",map.get("token").toString());
response.addCookie(cookie);
request.getSession().setAttribute("token",map.get("token"));//此处暂且不用
return toResponsSuccess(map);//携带用户信息登录成功返回
}else {
//登录失败
return toResponsFail("登录失败");
}
}
}catch (Exception e){
e.printStackTrace();
return toResponsFail("系统错误");
}
}
/**
* 根据账号密码登录方法
* @param webUser
* @return
*/
@Override
public Boolean login(WebUser webUser,Map<String,Object> map) {
List<WebUser> info=webUserMapper.selectWebUserList(webUser);
if (info.size()==1){
/**
* 自定义token,使用当前用户的信息加上时间戳生成md5字符串
* 加时间戳保证每次登陆的token不同
*/
String token="loginName="+webUser.getLoginName()+System.currentTimeMillis();
String tokenMd5=DigestUtils.md5DigestAsHex(token.getBytes());
redisTemplate.opsForValue().set(tokenMd5,info.get(0),30,TimeUnit.MINUTES);
map.put("token",tokenMd5);
map.put("userInfo",info.get(0));
return null;
}else {
return false;
}
}