springBoot拦截器实现记住密码,单点登录,登录验证功能

# springBoot拦截拦截器实现记住密码,单点登录,登录验证功能

    • 逻辑流程图表
    • 后台拦截请求的拦截器
    • 需要注意的是拦截器执行在bean实例化前执行,所以需要在拦截器执行的时候实例化拦截器bean,在拦截器配置类里面先实例化拦截器,然后在获取

逻辑流程图表

前端 后台controlelr 拦截器 后台Controller 随意请求 先跳拦截,判断是否放行 放行,执行业务逻辑 返回不允通过 前端 后台controlelr 拦截器 后台Controller

后台拦截请求的拦截器

首先,我在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;
        }
    }

需要注意的是拦截器执行在bean实例化前执行,所以需要在拦截器执行的时候实例化拦截器bean,在拦截器配置类里面先实例化拦截器,然后在获取

你可能感兴趣的:(新的总结,微信)