当前位置: 代码网 > it编程>编程语言>Java > 基于路径简单整合SpringSecurity+Gateway

基于路径简单整合SpringSecurity+Gateway

2024年08月02日 Java 我要评论
SpringSecurity是一个功能强大且高度可定制的认证和访问控制框架,提供了一系列高可配置的安全功能,但是我在整合Gateway时却发现力不从心,因为Gateway的内核是基于WebFlux的API网关,所以无法简单的将Gateway跟Springsecurity整合在一个模块之中,遂分享解决办法。实际使用下来SpringSecurity基本被架空,仅做学习参考,初学者,如有错误请一定要指出。

springsecurity是一个功能强大且高度可定制的认证和访问控制框架,提供了一系列高可配置的安全功能,但是我在整合gateway时却发现力不从心,因为gateway的内核是基于webflux的api网关,所以无法简单的将gateway跟springsecurity整合在一个模块之中,遂分享解决办法。

实际使用下来springsecurity基本被架空,仅做学习参考,初学者,如有错误请一定要指出

springsecurity大致流程

我们可以将springsecurity通过核心功能简单分为两个部分:

(1)认证:通过配置过滤器链和自己手写过滤器完成过滤拦截以及认证

(2)授权:通过实现userdetails接口并调用getauthorities方法将权限注入,在方法上使用注解@preauthorize("hasauthority('permission')")来检查是否拥有访问的权限

但是正如我所遇到的问题,不能将springsecurity跟gateway放一个模块,导致springsecurity提供的方法都不能很好的使用,所以我采用了将springsecurity的过滤拦截鉴权交由gateway处理,springsecurity仅做登录授权的方法,下面是我的实现过程

springsecurity登录授权功能的代码实现

这一段代码完成了以下几个功能:

(1)将用户的账号密码包装成usernamepasswordauthenticationtoken类型,并传给authenticationmanager

(2)authenticationmanager调用abstractuserdetailsauthenticationprovider中的daoauthenticationprovider方法

(3)daoauthenticationprovider调用userdetailsservice中的loaduserbyusername方法

@service
public class accountinfoserviceimpl extends serviceimpl<accountinfomapper, accountinfo> implements accountinfoservice {


    @autowired
    private authenticationmanager authenticationmanager;

    @autowired
    private accountrolemapper accountrolemapper;

    @autowired
    private rediscache rediscache;
    @override
    public r login(userlogindto userlogindto) {
        usernamepasswordauthenticationtoken authenticationtoken=
                new usernamepasswordauthenticationtoken(userlogindto.getaccountname(),userlogindto.getpassword());
        //传入账号密码后,会首先根据账号查询到密码,然后将查询到的密码跟传入的密码做比较
        authentication authentication = authenticationmanager.authenticate(authenticationtoken);
        //如果认证没通过,给出对应的提示
        if(objectutils.isempty(authentication)){
            return r.failed("登录失败");
        }

        //如果认证通过了,使用id生成换一个jwt jwt返回给前端
        loginuser loginuser=(loginuser)authentication.getprincipal();
        string id=loginuser.getaccountinfo().getid().tostring();

        //把完整的用户信息存入redis,id作为key
        rediscache.setcacheobject(id,loginuser,30,timeunit.minutes);

        //将过期判断交给redis,若redis数据过期则会在过滤器链中抛出token异常
        return r.success("登录成功",jwtutil.createjwt(id));
    }
}

 (4)在loaduserbyusername中根据传入的账号名来查找相关信息

 (5)如果账号存在则根据账号id来查找对应权限

 (6)将用户跟权限列表封装进loginuser

 (7)daoauthenticationprovider调用passwordencoder对比userdetails中的密码跟封装成usernamepasswordauthenticationtoken的用户传入的密码,如果相同则登陆成功

(8)登录成功后以返回的loginuser的id为key将用户信息存入redis中,并返回jwt加密后的id

github - createsequence/mybatis-plus-join: 基于myabtis-plus的连表查询扩展,支持字段别名、预设条件、group by ... having、数据库函数等扩展功能

public class userdetailsserviceimpl implements userdetailsservice {

    @autowired
    private accountinfomapper accountinfomapper;
    @autowired
    private accountrolemapper accountrolemapper;

    public userdetails loaduserbyusername(string accountname) throws usernamenotfoundexception{
        lambdaquerywrapper<accountinfo> lambdaquerywrapper=new lambdaquerywrapper<>();
        lambdaquerywrapper.eq(!objectutils.isempty(accountname),
                accountinfo::getaccountname,accountname)
                .eq(accountinfo::getdeleteflag,"未删除")
                .eq(accountinfo::getisenable,"启用");

        accountinfo accountinfo=this.accountinfomapper.selectone(lambdaquerywrapper);

        if(objectutils.isempty(accountinfo)){
            throw new usernamenotfoundexception("该账号不存在");
        }

        //查询对应的权限信息
        mpjlambdawrapper<accountrole> mpjlambdawrapper=new mpjlambdawrapper<>();
        mpjlambdawrapper.select(permissionlist::getpermissionname)
                .leftjoin(role.class,role::getroleid,accountrole::getroleid)
                      .leftjoin(rolepermission.class,rolepermission::getroleid,accountrole::getroleid)
                .leftjoin(permissionlist.class,permissionlist::getid,rolepermission::getpermissionid)
                .eq(accountrole::getaccountid,accountinfo.getid());

        //权限列表
        list<string> permissionnamelist = accountrolemapper.selectjoinlist(string.class, mpjlambdawrapper);

        return new loginuser(accountinfo,permissionnamelist);
    }

}

 loginuser:因为鉴权功能交给了gateway,所以实际上只需要用到accountinfo跟permissionlist

@data
@noargsconstructor
@allargsconstructor
public class loginuser implements userdetails {

    //写入自己的用户类来存储信息
    private accountinfo accountinfo;

    //用户权限列表
    private list<string> permissionlist;

    //权限列表
    @jsonfield(serialize = false)
    private list<simplegrantedauthority> authorities;

    //获取权限
    @override
    public collection<? extends grantedauthority> getauthorities() {
        if(objectutils.isempty(authorities)) {
            return permissionlist.stream()
                    .map(simplegrantedauthority::new)
                    .collect(collectors.tolist());
        }
        return authorities;
    }

    public loginuser(accountinfo accountinfo,list<string> permissionlist){
        this.permissionlist=permissionlist;
        this.accountinfo=accountinfo;
    }

    @override
    public string getpassword() {
        return accountinfo.getpassword();
    }

    @override
    public string getusername() {
        return accountinfo.getaccountname();
    }

    @override
    public boolean isaccountnonexpired() {
        return true;
    }

    @override
    public boolean isaccountnonlocked() {
        return true;
    }

    @override
    public boolean iscredentialsnonexpired() {
        return true;
    }

    @override
    public boolean isenabled() {
        return true;
    }
}

到这里,所需要的springsecurity的功能都做完了

gateway大致流程

 

 工具类

 (1)iswhitelist:读取yml文件"whitelist.yml",将请求路径与白名单路径作比较,若匹配则放行

 (2)haspermission:读取yml文件"permissionmap.yml",将用户权限以及请求路径传入,若用户权限与请求路径所需权限匹配则放行

 (3)out:返回体

public class myutil {

    //路径是否属于白名单
    public static boolean iswhitelist(string path){
        yamlpropertiesfactorybean yamlpropertiesfactorybean = new yamlpropertiesfactorybean();
        yamlpropertiesfactorybean.setresources(new classpathresource("whitelist.yml"));
        properties properties = yamlpropertiesfactorybean.getobject();
        list<string> whitelist = new arraylist<>();
        if (properties != null) {
            for (int i = 0; properties.containskey("whitelist[" + i + "]"); i++) {
                whitelist.add((string) properties.get("whitelist[" + i + "]"));
            }
        }
        //匹配
        return whitelist.stream().anymatch(s -> s.equals(path));
    }


    //权限列表是否拥有权限访问路径的权限
    public static boolean haspermission(list<string> permissionlist,string path){
        yamlpropertiesfactorybean yamlpropertiesfactorybean = new yamlpropertiesfactorybean();
        yamlpropertiesfactorybean.setresources(new classpathresource("permissionmap.yml"));
        properties properties = yamlpropertiesfactorybean.getobject();
        map<string, string> permissionmap = new hashmap<>();
        if (properties != null) {
            for (int i = 0; properties.containskey("permissionmap[" + i + "].url"); i++) {
                permissionmap.put
                        ((string)properties.get("permissionmap[" + i + "].url"),
                                (string)properties.get("permissionmap[" + i + "].permission") );
            }
        }
        //匹配
        return permissionlist.stream().anymatch(s -> s.equals(permissionmap.get(path)));
    }

    //返回体
    public static mono<void> out(serverhttpresponse response, string data) {
        jsonobject message = new jsonobject();
        message.addproperty("success", false);
        message.addproperty("code", 28004);
        message.addproperty("data", data);
        byte[] bits = message.tostring().getbytes(standardcharsets.utf_8);
        databuffer buffer = response.bufferfactory().wrap(bits);
        response.getheaders().add("content-type", "application/json;charset=utf-8");
        return response.writewith(mono.just(buffer));
    }

}

permissionmap.yml 文件格式如下

whitelist.yml 文件格式如下

gateway过滤拦截鉴权功能的代码实现

大致流程跟流程图基本一致

public class myglobalfilter implements globalfilter, ordered {

    @autowired
    private rediscache rediscache;

    @override
    public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
        serverhttprequest request = exchange.getrequest();
        string path = request.geturi().getpath();
        //获取token
        string token = optional.ofnullable(request.getheaders().get("token"))
                .filter(tokenlist -> !tokenlist.isempty())
                .map(tokens -> tokens.get(0))
                .orelse(null);
        //如果token为空
        if (token == null) {
            //如果路径属于白名单则放行
            if (myutil.iswhitelist(path)) {
                return chain.filter(exchange);
            }
            //如果token为空且路径不在白名单则返回
            else {
                return myutil.out(exchange.getresponse(), "token为空");
            }
        }
        //如果token不为空
        else {
            try {
                //将token转成id并获取对象
                loginuser loginuser = rediscache.getcacheobject(jwtutil.parsejwt(token).getsubject());
                //将loginuser放入exchange中
                exchange.getattributes().put("loginuser", loginuser);
                //如果不能成功获取对象则说明该token非法
                if (objectutils.isempty(loginuser)) {
                    return myutil.out(exchange.getresponse(), "token非法");
                }
            } catch (exception e) {
                return myutil.out(exchange.getresponse(), "token非法");
            }
        }
        return chain.filter(exchange);
    }

    //表示执行顺序,数字越小优先级越高
    @override
    public int getorder() {
        return 0;
    }

}
public class mypermissionfilter implements globalfilter, ordered {

    @override
    public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
        //获取路径
        string path = optional.ofnullable(exchange.getrequest().geturi().getpath())
                .orelse(" illegal path");

        //如果路径属于白名单则放行
        if(myutil.iswhitelist(path)){
            return chain.filter(exchange);
        }

        //获取权限列表
        list<string> permissionlist= optional.ofnullable((loginuser) exchange.getattributes().get("loginuser"))
                .map(loginuser::getpermissionlist)
                .orelse(null);

        //如果拥有权限则放行
        if(myutil.haspermission(permissionlist,path)){
            return chain.filter(exchange);
        }

        //如果不存在则拦截
        return myutil.out(exchange.getresponse(),"权限不足或路径不存在");
    }

    @override
    public int getorder() {
        return 1;
    }


}

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com