基于JWT与Redis的权限控制

最近处理的一个需求,让在管理平台上做一个权限控制,原本打算使用shiro完成,基于项目架构最后选择使用拦截器 配合jwt以及redis完成;

JWT:

  jwt呢,这里简单说一下,项目里主要用到的就是token传递验证身份,这里的话,不多介绍jwt使用方法,列几个网址可以了解一下;

  https://www.cnblogs.com/cjsblog/p/9277677.html

       https://www.cnblogs.com/lyzg/p/6028341.html

 

Demo目录:(没法传图片,手写一下)

  controller -> LoginController     filter -> FilterConfiguration,LoginFilter      util ->jwt,redis    jwt->JwtUtil  redis->redisUtil,redisConfig      util ->CommonUtil,Constants,StringUtil

  

LoginController   (登录接口)

@RequestMapping(value = "/fly/login")   
 public Map<String,Object> login(@RequestBody Map<String,Object> params) {
//根据params判断用户是否存在(可以利用第三方) //代码省略 String userName = params.get("userName").toString(); //定义返回map Map<String,Object> returnMap = new HashMap<>(); //定义请求路径(这一块会有很多逻辑操作,这里简化成简单的从数据库查询结果,具体使用自行封装) List<String> urls = new ArrayList<>(); urls = service.gainUrlFromDB(userName); //(这里做法是将数据库里对应到用户的权限全部查出来)      //使用JWT生成token 放入返回Map内(这一块是验证身份信息以及token过期时间用到的) Map<String, Object> map = Maps.newHashMap(); map.put("username", userName); returnMap.put("token", JwtUtil.createToken(map)); //将用户数据,权限放入redis(过期时间2小时) Map<String,Object> userRedisMsg = new HashMap<>(); userRedisMsg.put("url",urls); userRedisMsg.put("timeout",System.currentTimeMillis() + 2*60*60*1000); redisUtil.set(userName, userRedisMsg,2*60*60*1000L, TimeUnit.MILLISECONDS);
     //保存用户名信息
     request.getSession().setAttribute("userName", userName);

    return returnMap;
}

JwtUtil

public class JwtUtil {
    private static final byte[] SECRET = Constants.TOKEN_SECRET.getBytes();

    private static final JWSHeader HEADER = new JWSHeader(
            JWSAlgorithm.HS256, JOSEObjectType.JWT, null, null, null, null,
            null, null, null, null, null, null, null);

    /**
     * 生成token,该方法只在用户登录成功后调用
     * @param payload Map集合,可以存储用户id,token生成时间,token过期时间等自定义字段
     * @return token字符串,若失败则返回null
     */
    public static String createToken(Map<String, Object> payload) {
        String tokenString = null;
        // 创建一个JWS Object(第二部分)
        JWSObject jwsObject = new JWSObject(HEADER, new Payload(new JSONObject(payload)));
        try {
            // 将jwsObject进行HMAC签名,相当于加密(第三部分)
            jwsObject.sign(new MACSigner(SECRET));
            tokenString = jwsObject.serialize();
        } catch (JOSEException e) {
            log.error("签名失败: {}", e.getMessage());
            e.printStackTrace();
        }
        return tokenString;
    }

    /**
     * 校验token是否合法,返回Map集合,集合中主要包含
     * code状态码
     * data鉴权成功后从token中提取的数据
     * 该方法在过滤器中调用,每次请求API时都校验
     * @param token token
     * @return Map<String, Object>
     */
    public static Map<String, Object> checkToken(String token,String url) {
        Map<String, Object> resultMap = Maps.newHashMap();
        try {
            JWSObject jwsObject = JWSObject.parse(token);
            // palload就是JWT构成的第二部分不过这里自定义的是私有声明(标准中注册的声明, 公共的声明)
            Payload payload = jwsObject.getPayload();
            JWSVerifier verifier = new MACVerifier(SECRET);
       //token校验
if(jwsObject.verify(verifier)) { JSONObject jsonObject = payload.toJSONObject(); // token检验成功(此时没有检验是否过期) resultMap.put("username", jsonObject.get("username")); RedisUtil redisUtil = (RedisUtil) SpringContextUtil.getBean("redisUtil"); long time = System.currentTimeMillis(); HashMap<String, Object> userMap = (HashMap)redisUtil.get(resultMap.get("username")); if(time > (long)userMap.get("timeout")){ resultMap.put("msg", "token过期"); }else if(!((Set)userMap.get("url")).contains(url)) { resultMap.put("msg", "用户权限不足"); }else { //这里就是权限通过,需要重置redis过期时间 resultMap.put("msg", "具有权限"); Map<String,Object> mp = new HashMap<>(); mp.put("url", userMap.get("url")); mp.put("timeout", System.currentTimeMillis() + 2*60*60*1000); redisUtil.set( resultMap.get("username"), mp, 2*60*60*1000, TimeUnit.MILLISECONDS); } resultMap.put("data", jsonObject); } else { // 检验失败 resultMap.put("msg","验证失败"); } } catch (Exception e) { e.printStackTrace(); // token格式不合法导致的异常 resultMap.clear(); resultMap.put("msg", "token格式不合法"); } return resultMap; } }

FilterConfiguration(拦截器)

@Configuration
public class FilterConfiguration {


    @Bean
    public FilterRegistrationBean loginFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setName("loginFilter");
        registration.setFilter(new LoginFilter());
        registration.addUrlPatterns("/study/*");
        registration.setOrder(Integer.MAX_VALUE);
        return registration;
    }
}

LoginFilter(自定义拦截器)     (代码里的很多文字内容  例如msg字段对应的应该做成一个常量类来封装,此处写法只为易懂)

public class LoginFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String path = request.getRequestURI();
            String reqUrl = path.substring(path.indexOf("/", 0));
            //过滤部分请求
            if (!StringUtil.matches(reqUrl, "/study/fly/login")) {
                String token = request.getHeader("Authorization");
                Map<String, Object> resultMap = JwtUtil.validToken(token, path);

                String msg = CommonUtil.toString(resultMap.get("msg"));
                switch (msg) {
                    case "具有权限":
                        // 取出payload中数据,放到request作用域中
                        request.setAttribute("jwt", resultMap.get("data"));
                        break;
                    case "用户权限不足":
                        request.setAttribute("msg", "您的权限不足");
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "权限不足");
                        return;
                    case "token格式不合法":
                    case "token过期":
                        request.setAttribute("msg", "您的token不合法或者过期了,请重新登陆");
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "token不合法或者过期了");
                        return;
                    default:
                        break;
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            e.printStackTrace();
        }
        filterChain.doFilter(request, response);
    }
}

 

 

redis的代码就不贴出来了,写demo只简单封装了下,防止误导;可以自行度娘一下,需要注意序列化;

数据库表,需要一个 菜单权限表(table_menu)角色信息表(table_role)   用户角色关联表(table_user_role)    角色权限关联表(table_role_menu)

当权限细致到接口级别(按钮级)菜单权限表需要将菜单 Id 与  请求url关联起来,也就是说,table_menu表中需要一个请求url的字段

这里对这部分代码解释一下:

首先呢,在用户登陆的时候,进行一个用户登录校验,这一块可以自己写一套验证,也可以借助第三方完成;然后,根据用户id去数据库查出当前用户所具有的角色权限(超级管理员,普通用户),然后再根据角色权限去 菜单权限表取出对应的url,将这些url以及redis过期时间一并打包放到redis里,每次用户发送接口请求,自定义拦截器会拦截请求并 依次做

token 校验、token过期校验、权限校验、redis过期时间更新操作;当完成校验,拦截器就会根据response是否抛出异常信息来判断是否具有调用接口的权限;

 

 

    

 

          

 

 

转载于:https://www.cnblogs.com/flysdream/p/11435524.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值