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;
}
}
发表评论