JwtTokenPlugin--基于JwtToken的权限认证插件

最近做的项目都是前后端分离的,需要做一种无状态的认证机制,于是用上了jwtToken这种标准,来进行认证,介于shiro里面的东西太多,没空研究,而且需求用不到那么多特性,开发了一款基于JwtToken的一个Jfinal认证插件,以及权限,角色访问控制。

地址:https://git.oschina.net/Danjinsong/JFinal-JwtTokenPlugin.git

使用方法 :

  • 添加插件

  • @Override
    public void configPlugin(Plugins me) {
        me.add(new JwtTokenPlugin(UserService.me));                                /**配置权限拦截插件*/
    }
  • 让Model实现IJwtAble接口

/**
 * 我有故事,你有酒么?
 * JKhaled created by yunqisong@foxmail.com 2017/9/5
 * FOR : 简单实现
 */
public class User implements IJwtAble {

    private String userName;

    private String password;

    private List<String> _roles;

    private List<String> _forces;

    /**
     * 当前用户的角色
     *
     * @return
     */
    @Override
    public List<String> getRoles() {
        // 使用的时候写通过数据库查询返回给插件一个角色集合
        return get_roles();
    }

    /**
     * 当前用户的权限
     *
     * @return
     */
    @Override
    public List<String> getForces() {
        // 使用的时候写通过数据库查询返回给插件一个角色集合
        return get_forces();
    }

    /**
     * 上次修改密码时间
     *
     * @return
     */
    @Override
    public Date getLastModifyPasswordTime() {

        return new Date(System.currentTimeMillis() - 60L * 1000L * 60L * 24);
    }



    public String getUserName() {
        return userName;
    }

    public User setUserName(String userName) {
        this.userName = userName;
        return this;
    }

    public String getPassword() {
        return password;
    }

    public User setPassword(String password) {
        this.password = password;
        return this;
    }

    public User setRoles(List<String> roles) {
        this._roles = roles;
        return this;
    }

    public User setForces(List<String> forces) {
        this._forces = forces;
        return this;
    }

    public List<String> get_roles() {
        return _roles;
    }

    public List<String> get_forces() {
        return _forces;
    }
}
  • 让你的Service实现登录接口

  • /**
     * 我有故事,你有酒么?
     * JKhaled created by yunqisong@foxmail.com 2017/9/5
     * FOR : 简单实现
     */
    public class UserService implements IJwtUserService {
        public static final UserService me = new UserService();
    
        private UserService() {
        }
    
        private static Kv store = Kv.create();
    
        static {
            store.set("admin",
                    new User().setForces(Arrays.asList("登录后台", "管理用户"))
                            .setRoles(Arrays.asList("管理员", "普通用户")).setUserName("admin").setPassword("123456")
            ).set("user",
                    new User().setForces(Arrays.asList("前台登录", "发布文章"))
                            .setRoles(Arrays.asList("普通用户")).setUserName("user").setPassword("123456")
            );
        }
    
        @Override
        public IJwtAble login(String userName, String password) {
            User user = (User) store.get(userName);// 假装登录验证
            if (user != null)
                return user;
            throw new RuntimeException("找不到用户");
        }
    }
  • 通过登录拿Token

  • public void login() {
        User user = getBean(User.class, "");
        String token = JwtKit.getToken(user.getUserName(), user.getPassword());
        renderJson(Ret.by("token", token));
    }

    image.png

  • 验证==

  • 没有登录

  • image.png

  • image.png

  • 通过验证的。

  • 权限验证代码如下


  • /**
     * 我有故事,你有酒么?
     * JKhaled created by yunqisong@foxmail.com 2017/9/5
     * FOR : 简单实现
     */
    public class UserController extends Controller {
    
        public void login() {
            User user = getBean(User.class, "");
            String token = JwtKit.getToken(user.getUserName(), user.getPassword());
            renderJson(Ret.by("token", token));
        }
    
        // 登录认证
        @Before(JwtTokenInterceptor.class)
        public void testLogin() {
            renderJson(Ret.ok());
        }
    
        @Auth(hasRoles = {"管理员"})
        public void testHasRole() {
            renderJson(Ret.ok());
        }
    
        @Auth(hasForces = {"登录后台"})
        public void testHasForce() {
            renderJson(Ret.ok());
        }
    
        @Auth(withRoles = {"管理员", "普通用户"})
        public void testWithRoles() {
            renderJson(Ret.ok());
        }
    
        @Auth(withForces = {"登录后台", "管理用户"})
        public void testWithForces() {
            renderJson(Ret.ok());
        }
    
    }

    注解说明:


  • public @interface Auth {
    
        String[] hasForces() default {};               // 需要的权限 满足一个就可以访问--优先级第三
    
        String[] hasRoles() default {};                // 满足的角色 满足一个就可以访问--优先级第四
    
        String[] withForces() default {};              // 需要的权限 满足全部才可以访问--优先级第一
    
        String[] withRoles()  default {};              // 满足的橘色 满足全部才可以访问--优先级第二
    
    }
Auth注解适用于方法和类,注意:方法上的注解会覆盖掉类上的权限注解。欢迎大家测试,使用

评论区

cs88du

2017-09-12 14:29

前后端分离,这个太有用了,感谢

djs19960601

2017-09-12 17:33

@cs88du 多谢,欢迎反馈问题。

要输就输给追求

2017-09-14 11:45

是不是应该加一个刷新有效时间?这样子只要认证成功,我把token保存了,岂不是随时都可以拿这个token来换新的token?

要输就输给追求

2017-09-14 14:21

密码不能存token里面。存token里面的用户名和密码是没有加密的。我把你项目里面拿到https://jwt.io/去解析就能直接解析出用户名和密码。

djs19960601

2017-09-15 13:28

@要输就输给追求 插件里面token的默认有效时间是7天,构造函数可以修改任意时间

djs19960601

2017-09-15 13:32

@要输就输给追求 token是可以存密码的,这个涉及到服务器缓存清空后(比如重启服务器)去自动登录,你可以解析出是因为现在加密用的私钥是官方默认的default,插件的构造函数可以修改私钥,你改成自己的私钥,就解析不出了

要输就输给追求

2017-09-15 17:07

@djs19960601 不不不,你要不信,你可以拿一个token去https://jwt.io/试试,看能不能把你的密码解析出来

djs19960601

2017-09-17 13:45

@要输就输给追求 我这个测试用例的配置是可以解析出来,我插件还有其他构造方法,你修改掉秘钥配置你看他还能解析?加盐不同他还能识别?

要输就输给追求

2017-09-17 17:14

@djs19960601 你没理解到那个密钥的作用,那个密钥是用来加密签名信息的,不是用来加密前面两个部分的。不信你可以给我一个token,我把密码给你

djs19960601

2017-09-19 09:05

@要输就输给追求 嗯,那这是个问题,以后修复对这个用户名密码进行二次加密再放入token,谢谢

要输就输给追求

2017-09-19 10:37

@djs19960601 我现在没有把密码放里面,我token的有效期只有30分钟,失效前3分钟刷新

djs19960601

2017-09-19 11:59

@要输就输给追求 那你服务端怎么缓存这个token的,开发过程中重启服务器是不是要重新登录?

djs19960601

2017-09-19 12:03

@要输就输给追求 我打算下阶段不把密码放在token,服务器端缓存如果没有,会存文件,做序列化反序列化,这样重启服务器token也不会失效,我之所以把密码存token就是为了开发过程中,频繁重启服务器的场景不需要再进行登陆认证,可以凭借这个密码直接去数据库认证

要输就输给追求

2017-09-20 09:38

@djs19960601 我token有效期短啊。30分钟。如果服务器重启了,我就只验证用户名就可以了,不验证密码

jerlowliu

2017-10-08 15:39

仔细看了一下源码,发现你的token没有用算法加密,同时还把password一起保存在token中了。这样要有问题的。 @djs19960601

djs19960601

2017-10-09 08:40

@jerlowliu 我之所以把密码存token就是为了开发过程中,频繁重启服务器的场景不需要再进行登陆认证,可以凭借这个密码直接去数据库认证, 我打算下阶段不把密码放在token,token存文件,做序列化反序列化,这样重启服务器token也不会失效。token加密不加密有个配置salt的地方,你可以修改默认的salt

jerlowliu

2017-10-09 13:05

@djs19960601 加salt的地方看到了。你是加密了的,我搞错了。但是还有一个问题,我发现你把状态保存在服务器上,不知道是不是故意这样设计的,还是怎么说。给出的token,客户端访问的时候只要,验证token的正确性就可以了。不需要根服务器匹配。

djs19960601

2017-10-09 13:30

@jerlowliu 服务端需要做权限认证的情况下,需要知道当前token所带信息是谁的,有没有权限访问这个请求,我不可能吧权限角色这么多信息一股脑全部写进token吧,只能在服务器端储存这些东西然后通过用户名映射到对应的权限角色,这个设计是参考spring security的做法。所以这个做法就是让服务器准确获取到当前用户,方便后台操作有关信息,权限控制只是一个方面,实际业务中比如生成订单,等等业务都是需要后台明确知道当前用户是谁的,这里我设计的是在controller里面直接getAttr("me")就可以获取用户所有信息

djs19960601

2017-10-09 13:36

@jerlowliu 更新了token生成策略,现在不会储存密码这些危险信息了

giscafer

2017-11-21 18:29

不错

leomj

2017-12-15 10:11

顶一个

djs19960601

2017-12-15 11:49

@leomj 灰常谢谢!

liuzy666

2017-12-18 10:38

@djs19960601 你这里用的post在线测试用的什么啊,127.0.0.1都不行啊

liuzy666

2017-12-18 10:42

@djs19960601 找到了https://bbs.125.la/forum.php?mod=viewthread&tid=13933172&extra=page=1&typeid=426

djs19960601

2017-12-21 08:54

@liuzy666 postman 你百度下这个软件

热门分享

扫码入社