使用JWT控制登录鉴权的项目如何实现单个账号只允许在一处登录的功能

       使用JWT控制登录鉴权的项目如何实现单个账号只允许在一处登录的功能,也就是说在登录状态未失效的情况下,下一次登录必须踢掉上一次的登录。如果我们不做限制的话,单个账号多次登录就会产生多个token,只要未过期就都能调用接口。可能第一反应会想,用JWT实现token手动强行过期,然而JWT并未提供此功能,那么我们在项目中应该如何优雅的解决这个问题呢?<继上篇>

使用redis:

我们可以将用户每次登录后产生的token 存入redis 中,key中加入用户id来识别

//token 存入redis
redisTemplate.opsForValue().set("access_token:member_"+member.getId(), token, 30, TimeUnit.DAYS);

那么第二次同一账号执行登录操作的时候就会覆盖上一次登录存在redis中的token。

写到这里就会发现在token 未失效的情况下,上次登录产生的token已经不存在了。隐约说明了踢掉了上次登录,那么下一步该如何具体判断上一次的登录已失效呢?

其实很简单,我们虽然做不到token真正过期,但是我们能进行新旧token对比来保证旧token 无权限调用接口,从而达到限制单处登录功能。

可能会有人发现token 在未过期的时间段里无论怎么登录都是一模一样,从而无法实现新旧token对比,其实只需要在创建token的时候在 claim 里加上一个随机字符串即可。例:uuid

JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
			.claim("nickName", name)
			.claim("memId", userId)
                        .claim("uuid", "我是一个随机字符串")
			.setIssuer("francis")//发行者,自定义
                        .setAudience("client")
			.signWith(signatureAlgorithm, generalKey());

 

下一步:在过滤器中通过解析当前传入的token拿到用户id,然后从redis取出该用户登录所记录的token作对比。

        @SuppressWarnings("rawtypes")
	@Autowired
	private RedisTemplate redisTemplate;
	
	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		if("OPTIONS".equals(request.getMethod())){
			chain.doFilter(request, response);
			return;
		}
		String auth = request.getHeader("token");
		String nowToken = auth;
		if ((auth != null) && (auth.length() > 7)) {
			String HeadStr = auth.substring(0, 6).toLowerCase();
			if (HeadStr.compareTo("bearer") == 0) {
				auth = auth.substring(7, auth.length());
				if (JwtHelper.parseJWT(auth) != null) {
					//解出memId
					Integer memId = JwtHelper.getUserId(nowToken);
                                        //取出登录时保存的token
					Object oldTokenObj = redisTemplate.opsForValue().get("access_token:member_"+memId);
					System.err.println(oldTokenObj);
                                        //对比两个token,(注Validator...为feilong工具包内容,没有使用的自行修改判断即可)
					if(Validator.isNullOrEmpty(oldTokenObj) || auth.equals(oldTokenObj.toString())) {
						chain.doFilter(request, response);
						return;
					}
				}
			}
		}
		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json; charset=utf-8");
		response.setStatus(HttpServletResponse.SC_OK);
		response.getWriter().write(JsonUtil.getJsonFromObject(new Result(EnumSvrResult.ERROR_TOKEN)));
        return;
	}

写到这里那么一个新的问题又来了,filter 过滤器是属于servlet里面的,并不存在于spring的上下文中,哪怕你在filter上加@Component注解也没有作用,这点要注意了。那么redisTemplate 该如何Autowired依赖注入?

其实我们在上一篇文章中有注意到有JwtConfig类是启动加载的,在FilterRegistrationBean中将filter注册到了servlet,其实我们只需要在filter注册到servlet之前将它注册到spring 上下文中,如下

@Configuration  
public class JwtConfig {
	
 	@Bean  
    public FilterRegistrationBean basicFilterRegistrationBean(){  
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();  
        registrationBean.setFilter(getJwtFilter()); 
          
        List<String> urlPatterns = new ArrayList<>();  
        urlPatterns.add("*.auth");  
        registrationBean.setUrlPatterns(urlPatterns);  
        return registrationBean;  
    } 
 	
    //注入JwtFilter bean
    @Bean
    public JwtFilter getJwtFilter(){
        return new JwtFilter();
    }
}

那么再运行程序filter中的redis 就能成功依赖注入了。

在JWT基础上实现单个账号只允许在一处登录的功能就很简单的实现了。

 

 

—-我是francis,谨以此记录自己精彩的程序人生!!


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_38306688/article/details/88647353