token替代session进行登录验证

在这里插入图片描述
在这里插入图片描述

LoginHandler(将登陆成功的token存入响应头发给前端)

@Controller
@RequestMapping("login")
public class LoginHandler {
    @Resource
    private LoginService loginService;

    /**
     * 获取登录验证码
     *
     * @param phone 手机号
     * @return
     */
    @GetMapping("valCode")
    public ResponseEntity getValidateCode(String phone) {
        String valCode = loginService.getValCode(phone);
        return ResponseEntity.ok(new ResponseBean(StatusEnum.OPE_SUC, valCode));
    }

    /**
     * 通过手机号和验证码验证登录
     *
     * @return
     */
    @PostMapping("doLogin")
    public ResponseEntity doLogin(@RequestBody Map<String, String> map, HttpSession session) {
        String phone = map.get("phone");
        String valCode = map.get("valCode");
        Myuser myuser = loginService.doLogin(phone, valCode);
        if (myuser == null) {
            throw new MyExceptin(StatusEnum.OPE_ERR);
        }
//        session.setAttribute("LOGIN_USER", myuser);
//        return ResponseEntity.ok(new ResponseBean(StatusEnum.LOGIN_SUC, myuser));

        // 登陆成功,生成token字符串
        Map<String, Object> token = new HashMap<>();
        token.put("phone", phone);
        String generate = JwtUtil.generate(token);
        // 将token信息放在响应头中发给客户端
        myuser.setUpass("");
        HttpHeaders headers = new HttpHeaders();
        headers.set("token", generate);
        return new ResponseEntity(new ResponseBean(StatusEnum.LOGIN_SUC, myuser), headers, HttpStatus.OK);
    }

	// 注销操作
    @GetMapping("doLogout")
    public ResponseEntity diLogout(HttpServletRequest request) {
        String token = request.getHeader("token");
        loginService.doLogout(token);
        return ResponseEntity.ok(new StatusBean(StatusEnum.OPE_SUC));
    }
}

LoginServiceImpl(登录与注销操作与redis交互)

@Service
public class LoginServiceImpl implements LoginService {

    private Logger logger = LoggerFactory.getLogger(LoginServiceImpl.class);

    @Resource
    private ShardedJedisPool jedisPool;

    @Resource
    private LoginMapper loginMapper;

    /**
     * 获取验证码
     *
     * @param phone 手机号
     * @return
     */
    @Override
    public String getValCode(String phone) {
        if (phone == null) {
            throw new MyExceptin(StatusEnum.PHONE_EMPTY);
        }
        String valCode = StringRandom.getValCode();
        // 打印日志
        logger.info("获取到了验证码,验证码为:" + valCode);

        ShardedJedis resource = jedisPool.getResource();
        // 5分钟自动过期
        resource.setex(RedisHeadInfo.CODE_HEAD + phone, 30 * 60, valCode);
        resource.close();

        return valCode;
    }

    /**
     * 验证登录信息,返回用户信息
     *
     * @param phone   登录手机号
     * @param valCode 登录验证码
     * @return
     */
    @Override
    public Myuser doLogin(String phone, String valCode) {
        if (phone == null) {
            throw new MyExceptin(StatusEnum.PHONE_EMPTY);
        }
        ShardedJedis resource = jedisPool.getResource();
        // 查看登录黑名单是否有此对象,有即删除
        if (resource.hexists(RedisHeadInfo.LOGOUT_KEY, phone)) {
            logger.info("黑名单中有此用户数据,此次操作为重新登陆,将此用户信息移除黑名单");
            resource.hdel(RedisHeadInfo.LOGOUT_KEY, phone);
        };
        resource.close();

        // 验证码验证
        boolean checked = checkValidateCode(phone, valCode);
        // 验证不通过
        if (!checked) {
            throw new MyExceptin(StatusEnum.OPE_ERR);
        }
        // 验证通过
        return getMyuserByPhone(phone);
    }

    /**
     * 进行验证码校验
     *
     * @param phone
     * @param valCode
     * @return
     */
    private boolean checkValidateCode(String phone, String valCode) {
        ShardedJedis resource = jedisPool.getResource();
        String code = resource.get(RedisHeadInfo.CODE_HEAD + phone);
        if (code == null || valCode == null || !code.equals(valCode.trim())) {
            logger.info("验证码为空或验证码输入错误,操作账户为:" + phone + ",正确验证码为:" + code + ",用户输入的验证码为:" + valCode);
            resource.close();
            return false;
        }
        resource.close();
        return true;
    }

    /**
     * 检测是否是已注册用户。
     * 是的话,合法账户登录。执行登录逻辑
     * 否的话,注册新用户,并加标志表示这是一个新用户,执行登录逻辑,返回
     *
     * @param phone
     * @return
     */
    public Myuser getMyuserByPhone(String phone) {
        ShardedJedis resource = jedisPool.getResource();
        // 查看黑名单中是否有此用户数据,有即说明该用户已登出,此次访问为恶意访问
        if (resource.hexists(RedisHeadInfo.LOGOUT_KEY, phone)){
            resource.close();
            logger.info("用户已登出,存在非法访问");
            throw new MyExceptin(StatusEnum.USER_LOGOUT);
        }

        // 先查缓存中是否存有用户信息
        String userStr = resource.hget(RedisHeadInfo.LOGIN_KEY, phone);
        if (userStr != null) {
            logger.info("缓存中存有该用户信息");
            resource.close();
            return JSON.parseObject(userStr, Myuser.class);
        }
        // 缓存中没有用户信息,去数据库查找
        Myuser myuser = loginMapper.selectByPhone(phone);
        if (myuser == null) { // 说明是新用户,新插入一条数据,以默认值自动注册
            logger.info("数据库无此用户信息,自动注册");
            myuser = new Myuser();
            myuser.setUname("游客");
            myuser.setUpass("1234");
            myuser.setUphone(phone);
            loginMapper.insertMyuser(myuser);
            myuser.setNewuser("yes"); // 非数据库字段
        }
        // 将用户的登录信息存入redis
        resource.hset(RedisHeadInfo.LOGIN_KEY, phone, JSON.toJSONString(myuser));
        logger.info("将用户的登录信息存入了redis");
        resource.close();
        return myuser;
    }

    /**
     * 注销操作
     *
     * @param token
     * @return
     */
    @Override
    public void doLogout(String token) {
        Claims claim = JwtUtil.getClaim(token);
        Object phone = claim.get("phone");
        ShardedJedis resource = jedisPool.getResource();
        resource.hset(RedisHeadInfo.LOGOUT_KEY, (String) phone, "1");
        logger.info("将注销用户的信息存入了redis黑名单");
        resource.close();
    }
}

前端axios请求与响应拦截器写法

axios.defaults.baseURL = 'http://localhost:8080/0807/';
// axios.defaults.withCredentials = true; //跨域配置

//配置发送请求前的拦截器 可以设置token信息 
axios.interceptors.request.use(
	config => {
		// debugger;
		let token = localStorage.getItem("token");
		let requestUrl = config.url;
		
		if (requestUrl != "login/doLogin" && requestUrl.indexOf("login/valCode") != 0) {
			if (token != null) {
				// 向请求头加入token信息
				config.headers.token = token;
			}else{
				// 未登录,转到登录页面
				location.href = "http://127.0.0.1:8848/ssm/login.html";
			}
		}


		return config
	}, error => {
		return Promise.reject(error)
	}
)

// 配置响应拦截器 
axios.interceptors.response.use(
	res => {
		// if(res.data.code == 50001){
		// 	// 说明用户未登录	
		// 	location.href = "http://127.0.0.1:8848/ssm/login.html";
		// }
		// debugger;
			
		if(res.data.data == 50002 || res.data.data == 50003){ // token过期失效或者用户已登出的非法访问
			location.href = "http://127.0.0.1:8848/ssm/login.html";
		}
	

		// 将登陆成功后的token存入localSorage
		if ("token" in res.headers) {
			// 
			let token = res.headers.token;
			localStorage.setItem("token", token);
		}

		return Promise.resolve(res.data) // 这里直接返回data, 即接口返回的所有数据
	},
	error => {
		return Promise.reject(error);
	}
)

前端页面登出

islogout() {
this.$confirm('此操作将退出系统, 是否继续?', '提示', {
			confirmButtonText: '确定',
			cancelButtonText: '取消',
			type: 'warning'
		}).then(() => {
			axios.get('login/doLogout')
				.then(response => {
					console.log(response);
					if (response.code == 20000) {
						localStorage.removeItem("token");
						location.href = "http://127.0.0.1:8848/ssm/login.html";

					}

				}).catch(function(error) {
					console.log(error);
				});

			// location.href = "/secondStage/login?method=logout";
		});
	}

LoginInterceptor(spring注册登录拦截器)

跨域设置开启暴露响应头设置
    <mvc:cors>
        <mvc:mapping path="/**" exposed-headers="token" allow-credentials="true"/>
    </mvc:cors>
配置登陆拦截器
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/login/doLogin"></mvc:exclude-mapping>
            <mvc:exclude-mapping path="/login/valCode"></mvc:exclude-mapping>
            <mvc:exclude-mapping path="/login/doLogout"></mvc:exclude-mapping>
            <bean class="com.javasm.common.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private CurrentLoginUser loginUser;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 对预检请求放行
        String method = request.getMethod();
        if (method.equals("OPTIONS")) {
            return true;
        }

        String token = request.getHeader("token");
        if (token != null) {
            Claims claim = JwtUtil.getClaim(token);
            if(claim!=null){
                // 可以设置当前登录用户信息,共享至其他接口
                String phone = (String)claim.get("phone");
                loginUser.setLoginUser(phone);

                //刷新token
                String s = refreshToken(claim);
                if(s!=null){
                    response.addHeader("token",s);
                }
                return true;
            }else{
                //token已过期
                throw new MyExceptin(StatusEnum.TOKEN_EXPIR);
            }
        } else {
            throw new MyExceptin(StatusEnum.OPE_ERR);
        }
    }

    private String refreshToken(Claims claim) {
        Date nowDate = new Date();
        Date expiration = claim.getExpiration();
        //如果剩余有效时间仅剩3分钟的话,刷新token
        if ((expiration.getTime() - nowDate.getTime()) <= 3 * 60 * 1000) {
            //要刷新token
            Object phone = claim.get("phone");
            Map<String, Object> map = new HashMap<>();
            map.put("phone", phone);
            String token = JwtUtil.generate(map);
            return token;
        }
        return null;
    }
}

CurrentLoginUser(共享当前登录用户信息)

  • 将登录用户信息放入threadLocal维护,使得其他接口也能获得当前登录的用户信息
@Component
public class CurrentLoginUser {
    @Resource
    private LoginService loginService;

    private static final ThreadLocal<Myuser> MYUSER_THREAD_LOCAL = new ThreadLocal<>();

    // 通过手机号查询已登录用户信息
    public Myuser getLoginUser(){
        return MYUSER_THREAD_LOCAL.get();
    }


    public void setLoginUser(String phone){
        Myuser loginUser = loginService.getMyuserByPhone(phone);
        MYUSER_THREAD_LOCAL.set(loginUser);
    }
    
}

JwtUtil(生成与解析token)

  • 依赖jjwt-0.9.1.jar
public class JwtUtil {
    public static final String UID = "uid";
    private static final String SECRET = "6A50A18D70FA63636645C65459F1D78A";
    private static final long EXPIRE = 5 * 60 * 1000;//有效时间,5分钟

    /**
     * 生成token
     *
     * @param claims:字符串中要保存的用户信息
     * @return
     */
    public static String generate(Map<String, Object> claims) {
        Date nowDate = new Date();
        //过期时间
        Date expireDate = new Date(nowDate.getTime() + EXPIRE);
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
    }

    /**
     * 解析token
     *
     * @param token
     * @return
     */
    public static Claims getClaim(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();

        return claims;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值