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
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;
    }
}
 
             我要评论
我要评论 
                                            ![[ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹](https://images.3wcode.com/3wcode/20240802/s_0_202408020028483641.png) 
                                            
发表评论