1. 前言
这个问题网上找了各种资料,困扰了几周,终于是解决了。记住一点,不要用springsecurity官方提供的jackson序列化。序列化没问题,反序列化有大坑,会报错missing type id property ‘@class’ 。
//这个就不要用了,试了,反序列化不行。 securityjackson2modules.getmodules(this.getclass().getclassloader()) .foreach(objectmapper::registermodule);
2. redission配置
2.1 redissonproperties
@data @configurationproperties(prefix = "redisson") public class redissonproperties { /** * redis缓存key前缀 */ private string keyprefix; /** * 线程池数量,默认值 = 当前处理核数量 * 2 */ private int threads; /** * netty线程池数量,默认值 = 当前处理核数量 * 2 */ private int nettythreads; /** * 单机服务配置 */ private singleserverconfig singleserverconfig; /** * 集群服务配置 */ private clusterserversconfig clusterserversconfig; @data @noargsconstructor public static class singleserverconfig { /** * 客户端名称 */ private string clientname; /** * 最小空闲连接数 */ private int connectionminimumidlesize; /** * 连接池大小 */ private int connectionpoolsize; /** * 连接空闲超时,单位:毫秒 */ private int idleconnectiontimeout; /** * 命令等待超时,单位:毫秒 */ private int timeout; /** * 发布和订阅连接池大小 【未使用,加上启动不起】 */ private int subscriptionconnectionpoolsize; } @data @noargsconstructor public static class clusterserversconfig { /** * 客户端名称 */ private string clientname; /** * master最小空闲连接数 */ private int masterconnectionminimumidlesize; /** * master连接池大小 */ private int masterconnectionpoolsize; /** * slave最小空闲连接数 */ private int slaveconnectionminimumidlesize; /** * slave连接池大小 */ private int slaveconnectionpoolsize; /** * 连接空闲超时,单位:毫秒 */ private int idleconnectiontimeout; /** * 命令等待超时,单位:毫秒 */ private int timeout; /** * 发布和订阅连接池大小 */ private int subscriptionconnectionpoolsize; /** * 读取模式 */ private readmode readmode; /** * 订阅模式 */ private subscriptionmode subscriptionmode; } }
2.2 redissionconfiguration
注意依赖排除,引用redis包排除无用依赖.
@slf4j @enablecaching @autoconfiguration @enableconfigurationproperties(redissonproperties.class) public class redissionconfiguration implements initializingbean { @resource private redisproperties redisproperties; @resource private redissonproperties redissonproperties; private objectmapper om; @qualifier @bean("redistemplate") public redistemplate<string, object> redistemplate(redisconnectionfactory redisconnectionfactory) { redistemplate<string, object> redistemplate = new redistemplate<>(); redistemplate.setconnectionfactory(redisconnectionfactory); stringredisserializer keyserializer = new stringredisserializer(); redisserializer<object> valueserializer =new genericjackson2jsonredisserializer( om); redistemplate.setkeyserializer(keyserializer); redistemplate.setvalueserializer(valueserializer); redistemplate.sethashkeyserializer(keyserializer); redistemplate.sethashvalueserializer(valueserializer); redistemplate.afterpropertiesset(); return redistemplate; } @bean public redisson redisson() { log.info("初始化redission配置..."); config config = new config(); config.setthreads(redissonproperties.getthreads()) .setnettythreads(redissonproperties.getnettythreads()) // 缓存 lua 脚本 减少网络传输(redisson 大部分的功能都是基于 lua 脚本实现) .setusescriptcache(true) .setcodec(new jsonjacksoncodec(om)); redissonproperties.singleserverconfig singleserverconfig = redissonproperties.getsingleserverconfig(); if (objectutil.isnotnull(singleserverconfig)) { // 使用单机模式 singleserverconfig singleconfig = config.usesingleserver() .setaddress("redis://" + redisproperties.gethost() + ":"+redisproperties.getport()) .setdatabase(redisproperties.getdatabase() ) .settimeout(singleserverconfig.gettimeout()) .setclientname(singleserverconfig.getclientname()) .setidleconnectiontimeout(singleserverconfig.getidleconnectiontimeout()) .setconnectionminimumidlesize(singleserverconfig.getconnectionminimumidlesize()) .setconnectionpoolsize(singleserverconfig.getconnectionpoolsize()) //# dns监测时间间隔,单位:毫秒 .setdnsmonitoringinterval(60000l); if (objectutil.isnotempty(redisproperties.getpassword())) { singleconfig.setpassword(redisproperties.getpassword()); } } // 集群配置方式 redissonproperties.clusterserversconfig clusterserversconfig = redissonproperties.getclusterserversconfig(); if (objectutil.isnotnull(clusterserversconfig)) { clusterserversconfig serversconfig = config.useclusterservers() .settimeout(clusterserversconfig.gettimeout()) .setclientname(clusterserversconfig.getclientname()) .setidleconnectiontimeout(clusterserversconfig.getidleconnectiontimeout()) .setmasterconnectionminimumidlesize(clusterserversconfig.getmasterconnectionminimumidlesize()) .setmasterconnectionpoolsize(clusterserversconfig.getmasterconnectionpoolsize()) .setslaveconnectionminimumidlesize(clusterserversconfig.getslaveconnectionminimumidlesize()) .setslaveconnectionpoolsize(clusterserversconfig.getslaveconnectionpoolsize()) .setreadmode(clusterserversconfig.getreadmode()) .setsubscriptionmode(clusterserversconfig.getsubscriptionmode()); if (objectutil.isnotempty(redisproperties.getpassword())) { serversconfig.setpassword(redisproperties.getpassword()); } } log.info("初始化redission配置完成"); return (redisson) redisson.create(config); } @override public void afterpropertiesset() { log.info("设置objectmapper参数..."); //不影响全局objectmapper objectmapper copy = new objectmapper(); copy.setvisibility(propertyaccessor.all, jsonautodetect.visibility.any); //必须设置,否则无法将json转化为对象,会转化成map类型,jsontypeinfo.as.property序列化加@class属性 copy.activatedefaulttyping(copy.getpolymorphictypevalidator(), objectmapper.defaulttyping.non_final, jsontypeinfo.as.property); copy.registersubtypes(ajuser.class, usernamepasswordauthenticationtoken.class, sysrole.class); // 自定义objectmapper的时间处理模块 javatimemodule javatimemodule = new javatimemodule(); javatimemodule.addserializer(localdatetime.class, new localdatetimeserializer(datetimeformatter.ofpattern("yyyy-mm-dd hh:mm:ss"))); javatimemodule.adddeserializer(localdatetime.class, new localdatetimedeserializer(datetimeformatter.ofpattern("yyyy-mm-dd hh:mm:ss"))); javatimemodule.addserializer(localdate.class, new localdateserializer(datetimeformatter.ofpattern("yyyy-mm-dd"))); javatimemodule.adddeserializer(localdate.class, new localdatedeserializer(datetimeformatter.ofpattern("yyyy-mm-dd"))); javatimemodule.addserializer(localtime.class, new localtimeserializer(datetimeformatter.ofpattern("hh:mm:ss"))); javatimemodule.adddeserializer(localtime.class, new localtimedeserializer(datetimeformatter.ofpattern("hh:mm:ss"))); copy.registermodule(javatimemodule); // 启用模块 simplemodule module = new simplemodule(); module.adddeserializer(authuser.class, new authuserdeserializer()); copy.registermodule(module); copy.addmixin(authorizationgranttype.class,authorizationgranttypemixin.class); //自定义unmodifiablemapmixin copy.addmixin(collections.unmodifiablemap(new hashmap<>()).getclass(),unmodifiablemapmixin.class); //自定义unmodifiablesetmixin copy.addmixin(collections.unmodifiableset(new hashset<>()).getclass(), unmodifiablesetmixin.class); copy.addmixin(principal.class, principalmixin.class); copy.addmixin(usernamepasswordauthenticationtoken.class, usernamepasswordauthenticationtokenmixin.class); //提前加载 copy.enable(deserializationfeature.eager_deserializer_fetch); // 忽略未知属性 copy.disable(deserializationfeature.fail_on_unknown_properties); // 检查子类型 没有匹配的会报错 可调试用 //copy.disable(deserializationfeature.fail_on_invalid_subtype); // 禁用将日期序列化为时间戳的行为,解决jackson2无法反序列化localdatetime的问题 copy.disable(serializationfeature.write_dates_as_timestamps); // 忽略非法字符 \r, \n, \t copy.configure(jsonparser.feature.allow_unquoted_control_chars, true); //单独配置object赋值 om = copy; log.info("objectmapper参数设置完成..."); } }
3.自定义mixin
3.1 authorizationgranttypemixin
@jsondeserialize(using = authorizationgranttypedeserializer.class) public abstract class authorizationgranttypemixin { @jsoncreator authorizationgranttypemixin(@jsonproperty("value") string value) { } }
3.2 principalmixin
@jsontypeinfo(use = jsontypeinfo.id.class, include = jsontypeinfo.as.property, property = "@class") public abstract class principalmixin { }
3.3 unmodifiablemapmixin
@jsontypeinfo( use = jsontypeinfo.id.class ) @jsondeserialize( using = unmodifiablemapdeserializer.class ) public class unmodifiablemapmixin { @jsoncreator unmodifiablemapmixin(map<?, ?> map) { } }
3.4 unmodifiablesetmixin
@jsontypeinfo( use = jsontypeinfo.id.class, include = jsontypeinfo.as.property ) @jsondeserialize( using = unmodifiablesetdeserializer.class ) public abstract class unmodifiablesetmixin { @jsoncreator unmodifiablesetmixin(set<?> s) { } }
3.5 usernamepasswordauthenticationtokenmixin
@jsontypeinfo(use = jsontypeinfo.id.class, include = jsontypeinfo.as.property, property = "@class") @jsondeserialize( using = usernamepasswordauthenticationtokendeserializer.class ) public abstract class usernamepasswordauthenticationtokenmixin { @jsoncreator public usernamepasswordauthenticationtokenmixin( @jsonproperty("principal") object principal, @jsonproperty("credentials") object credentials, @jsonproperty("authorities") collection<? extends grantedauthority> authorities) { } }
4. 自定义deserializer
这里得注意几个问题:
1.为什么deserializer要用new objectmapper
答:用jsonparse的 jp.getcodec().readtree(jp);会报错 missing type id property ‘@class’ 。
2.为什么读取子类转化子类的时候要用jp.getcodec().treetovalue(jsonnode, clazz)
答:自定义反序列化器注册在原objectmapper里面的,new objectmapper不包含这些处理会报错 missing type id property ‘@class’ 。
3.为什么不适用springsecurity自带的序列化器,比如corejackson2module
答:同问题1,源码里面的是
objectmapper mapper = (objectmapper)jp.getcodec();
jsonnode node = (jsonnode)mapper.readtree(jp);//运行到这行会报错
换成新objectmapper则不报错
4.1 authorizationgranttypedeserializer
@slf4j public class authorizationgranttypedeserializer extends stddeserializer<authorizationgranttype> { private final objectmapper notypingmapper = new objectmapper(); public authorizationgranttypedeserializer() { super(authorizationgranttype.class); log.info(">> authorizationgranttypedeserializer 实例化完成 >>"); } @override public authorizationgranttype deserialize(jsonparser p, deserializationcontext ctxt) throws ioexception { log.info(">> using authorizationgranttypedeserializer"); jsonnode node = notypingmapper.readtree(p); // 支持两种格式:纯字符串 或 {"value": "client_credentials"} string value = node.istextual() ? node.astext() : node.get("value").astext(); return switch (value) { case "authorization_code" -> authorizationgranttype.authorization_code; case "client_credentials" -> authorizationgranttype.client_credentials; case "refresh_token" -> authorizationgranttype.refresh_token; default -> new authorizationgranttype(value); }; } }
4.2 unmodifiablemapdeserializer
@slf4j public class unmodifiablemapdeserializer extends stddeserializer<map<?, ?>> { private final objectmapper notypingmapper = new objectmapper(); public unmodifiablemapdeserializer() { super(map.class); log.info(">> unmodifiablemapdeserializer 实例化完成 >>"); } @override public map<?, ?> deserialize(jsonparser jp, deserializationcontext ctxt) throws ioexception { log.info(">> using unmodifiablemapdeserializer"); objectcodec codec = jp.getcodec(); jsonnode node = notypingmapper.readtree(jp); map<string, object> result = new linkedhashmap<>(); if (node != null && node.isobject()) { objects.requirenonnull(node); for (iterator<map.entry<string, jsonnode>> it = node.fields(); it.hasnext(); ) { map.entry<string, jsonnode> field = it.next(); jsonnode value = field.getvalue(); string key = field.getkey(); if (key.contains("principal") && value.has("@class")) { string classname = value.get("@class").astext(); try { class<?> clazz = class.forname(classname); //object val =notypingmapper.readvalue(value.traverse(notypingmapper),clazz); result.put(key,codec.treetovalue(value, clazz)); } catch (exception e) { throw new runtimeexception("无法反序列化 principal", e); } } else { // 默认处理其他字段(按 map 反序列化) result.put(key, notypingmapper.readvalue(value.traverse(notypingmapper), object.class)); } } } return collections.unmodifiablemap(result); } }
4.3 unmodifiablesetdeserializer
@slf4j public class unmodifiablesetdeserializer extends stddeserializer<set<?>> { private final objectmapper notypingmapper = new objectmapper(); public unmodifiablesetdeserializer() { super(set.class); log.info(">> unmodifiablesetdeserializer 实例化完成 >>"); } @override public set<?> deserialize(jsonparser jp, deserializationcontext ctxt) throws ioexception { log.info(">> using unmodifiablesetdeserializer"); jsonnode node = notypingmapper.readtree(jp); set<string> result = new linkedhashset<>(node.size()); if (node != null && node.isobject()) { objects.requirenonnull(node); for (jsonnode jsonnode : node) { result.add(jsonnode.astext()); } } return collections.unmodifiableset(result); } }
4.4 usernamepasswordauthenticationtokendeserializer
@slf4j public class usernamepasswordauthenticationtokendeserializer extends stddeserializer<usernamepasswordauthenticationtoken> { private final objectmapper notypingmapper = new objectmapper(); protected usernamepasswordauthenticationtokendeserializer() { super(usernamepasswordauthenticationtoken.class); log.info(">> usernamepasswordauthenticationtokendeserializer 实例化完成 >>"); } @override @sneakythrows public usernamepasswordauthenticationtoken deserialize(jsonparser jp, deserializationcontext ctxt) { log.info(">> using usernamepasswordauthenticationtokendeserializer"); jsonnode jsonnode = notypingmapper.readtree(jp); jsonnode principalnode = this.readjsonnode(jsonnode, "principal"); object principal = this.getprincipal(jp, principalnode); jsonnode credentialsnode = this.readjsonnode(jsonnode, "credentials"); jsonnode authoritiesnode = this.readjsonnode(jsonnode, "authorities"); object credentials = this.getcredentials(credentialsnode); collection<simplegrantedauthority> authorities = new arraylist<>(); if (authoritiesnode != null && authoritiesnode.isarray() && authoritiesnode.size() == 2) { //第一个是类型,第二个才是存的值 jsonnode actualauthoritiesarray = authoritiesnode.get(1); // 第二个元素是真实列表 if (actualauthoritiesarray != null && actualauthoritiesarray.isarray()) { for (jsonnode authnode : actualauthoritiesarray) { if (!authnode.has("@class")) { string role = authnode.get("authority").astext(); simplegrantedauthority authority = new simplegrantedauthority(role); authorities.add(authority); } } } } // 构造 token 对象 return new usernamepasswordauthenticationtoken(principal, credentials, authorities); } private object getcredentials(jsonnode credentialsnode) { return !credentialsnode.isnull() && !credentialsnode.ismissingnode() ? credentialsnode.astext() : null; } private object getprincipal(jsonparser jp, jsonnode principalnode) throws ioexception, classnotfoundexception { string classname = principalnode.get("@class").astext(); class<?> clazz = class.forname(classname); //使用原mapper才能使用userdeserializer return principalnode.isobject() ? jp.getcodec().treetovalue(principalnode, clazz): principalnode.astext(); } private jsonnode readjsonnode(jsonnode jsonnode, string field) { return jsonnode.has(field) ? jsonnode.get(field) : missingnode.getinstance(); } }
4.5 authuserdeserializer
@slf4j public class authuserdeserializer extends stddeserializer<authuser> { private final objectmapper notypingmapper = new objectmapper(); public authuserdeserializer() { super(authuser.class); log.info(">> ajuserdeserializer 实例化完成 >>"); } @override @sneakythrows public authuser deserialize(jsonparser p, deserializationcontext ctxt) { log.info(">> using ajuserdeserializer"); jsonnode jsonnode = notypingmapper.readtree(p); jsonnode idnode = this.readjsonnode(jsonnode, "id"); jsonnode deptidnode = this.readjsonnode(jsonnode, "deptid"); jsonnode phonenode = this.readjsonnode(jsonnode, "phone"); jsonnode usernamenode = this.readjsonnode(jsonnode, "username"); jsonnode passwordnode = this.readjsonnode(jsonnode, "password"); jsonnode accountnonlockednode = this.readjsonnode(jsonnode, "accountnonlocked"); //索引0是类型,1是数据 long id = long.parselong(idnode.get(1).astext()); long deptid = long.parselong(deptidnode.get(1).astext()); string phone = phonenode.astext(); string username= usernamenode.astext(); string password = passwordnode.astext(); boolean accountnonlocked = boolean.parseboolean(accountnonlockednode.astext()); list<simplegrantedauthority> authorities = new arraylist<>(); jsonnode authoritiesnode = this.getauthorities(jsonnode); if (authoritiesnode != null && authoritiesnode.isarray() && authoritiesnode.size() == 2) { //第一个是类型,第二个才是存的值 jsonnode actualauthoritiesarray = authoritiesnode.get(1); // 第二个元素是真实列表 if (actualauthoritiesarray != null && actualauthoritiesarray.isarray()) { for (jsonnode authnode : actualauthoritiesarray) { if (!authnode.has("@class")) { string role = authnode.get("authority").astext(); simplegrantedauthority authority = new simplegrantedauthority(role); authorities.add(authority); } } } } //取缓存不加securityconstants.bcrypt 加密的特征码 return new authuser(id, deptid,username, password, phone, (sysrole) this.getsysrole(p,this.readjsonnode(jsonnode, "sysrole")),true, true, true, accountnonlocked, authorities); } private jsonnode getauthorities(jsonnode jsonnode) { return jsonnode.has("authorities") ? jsonnode.get("authorities") : missingnode.getinstance(); } private object getsysrole(jsonparser jp, jsonnode node) throws ioexception, classnotfoundexception { string classname = node.get("@class").astext(); class<?> clazz = class.forname(classname); return node.isobject() ?jp.getcodec().treetovalue(node, clazz) : node.astext(); } private jsonnode readjsonnode(jsonnode jsonnode, string field) { return jsonnode.has(field) ? jsonnode.get(field) : missingnode.getinstance(); } }
5. 自定义扩展用户信息 authuser
@getter public class authuser extends user implements oauth2authenticatedprincipal { private static final long serialversionuid = springsecuritycoreversion.serial_version_uid; /** * 用户id */ @jsonserialize(using = tostringserializer.class) private final long id; /** * 部门id */ @jsonserialize(using = tostringserializer.class) private final long deptid; /** * 手机号 */ private final string phone; /** * 角色 */ private final sysrole sysrole; @jsoncreator(mode = jsoncreator.mode.properties) public authuser(@jsonproperty("id") long id, @jsonproperty("deptid") long deptid, @jsonproperty("username") string username, @jsonproperty("password") string password, @jsonproperty("phone") string phone, @jsonproperty("sysrole") sysrole sysrole, @jsonproperty("enabled") boolean enabled, @jsonproperty("accountnonexpired") boolean accountnonexpired, @jsonproperty("credentialsnonexpired") boolean credentialsnonexpired, @jsonproperty("accountnonlocked") boolean accountnonlocked, @jsonproperty("authorities") collection<? extends grantedauthority> authorities) { super(username, password, enabled, accountnonexpired, credentialsnonexpired, accountnonlocked, authorities); this.id = id; this.deptid = deptid; this.phone = phone; this.sysrole = sysrole; } /** * get the oauth 2.0 token attributes * @return the oauth 2.0 token attributes */ @override public map<string, object> getattributes() { return new hashmap<>(); } @override public string getname() { return this.getusername(); } }
6. springsecurity其他地方改动
这里就不再贴springsecurity其他代码了,自行实现,开源框架pig自取
6.1 认证服务器配置
@configuration @requiredargsconstructor public class authorizationserverconfiguration { private final oauth2authorizationservice authorizationservice; private final passworddecoderfilter passworddecoderfilter; private final validatecodefilter validatecodefilter; /** * authorization server 配置,仅对 /oauth2/** 的请求有效 * @param http http * @return {@link securityfilterchain } * @throws exception 异常 */ @bean @order(ordered.highest_precedence) public securityfilterchain authorizationserver(httpsecurity http) throws exception { // 配置授权服务器的安全策略,只有/oauth2/**的请求才会走如下的配置 http.securitymatcher("/oauth2/**"); oauth2authorizationserverconfigurer authorizationserverconfigurer = new oauth2authorizationserverconfigurer(); // 增加验证码过滤器 http.addfilterbefore(validatecodefilter, usernamepasswordauthenticationfilter.class); // 增加密码解密过滤器 http.addfilterbefore(passworddecoderfilter, usernamepasswordauthenticationfilter.class); http.with(authorizationserverconfigurer.tokenendpoint((tokenendpoint) -> {// 个性化认证授权端点 tokenendpoint.accesstokenrequestconverter(accesstokenrequestconverter()) // 注入自定义的授权认证converter .accesstokenresponsehandler(new ajauthenticationsuccesseventhandler()) // 登录成功处理器 .errorresponsehandler(new ajauthenticationfailureeventhandler());// 登录失败处理器 }).clientauthentication(oauth2clientauthenticationconfigurer -> // 个性化客户端认证 oauth2clientauthenticationconfigurer.errorresponsehandler(new ajauthenticationfailureeventhandler()))// 处理客户端认证异常 .authorizationendpoint(authorizationendpoint -> authorizationendpoint// 授权码端点个性化confirm页面 .consentpage(securityconstants.custom_consent_page_uri)), customizer.withdefaults()) .authorizehttprequests(authorizerequests -> authorizerequests.anyrequest().authenticated()); // 设置 token 存储的策略 http.with(authorizationserverconfigurer.authorizationservice(authorizationservice)// redis存储token的实现 .authorizationserversettings( authorizationserversettings.builder().issuer(securityconstants.project_license).build()), customizer.withdefaults()); // 设置授权码模式登录页面 http.with(new formidentityloginconfigurer(), customizer.withdefaults()); defaultsecurityfilterchain securityfilterchain = http.build(); // 注入自定义授权模式实现 addcustomoauth2grantauthenticationprovider(http); return securityfilterchain; } /** * 令牌生成规则实现 </br> * client:username:uuid * @return oauth2tokengenerator */ @bean public oauth2tokengenerator oauth2tokengenerator() { customeoauth2accesstokengenerator accesstokengenerator = new customeoauth2accesstokengenerator(); // 注入token 增加关联用户信息 accesstokengenerator.setaccesstokencustomizer(new customeoauth2tokencustomizer()); return new delegatingoauth2tokengenerator(accesstokengenerator, new oauth2refreshtokengenerator()); } /** * request -> xtoken 注入请求转换器 * @return delegatingauthenticationconverter */ @bean public authenticationconverter accesstokenrequestconverter() { return new delegatingauthenticationconverter(arrays.aslist( new oauth2resourceownerpasswordauthenticationconverter(), new oauth2resourceownersmsauthenticationconverter(), new oauth2refreshtokenauthenticationconverter(), new oauth2clientcredentialsauthenticationconverter(), new oauth2authorizationcodeauthenticationconverter(), new oauth2authorizationcoderequestauthenticationconverter())); } /** * 注入授权模式实现提供方 * <p> * 1. 密码模式 </br> * 2. 短信登录 </br> */ private void addcustomoauth2grantauthenticationprovider(httpsecurity http) { authenticationmanager authenticationmanager = http.getsharedobject(authenticationmanager.class); oauth2authorizationservice authorizationservice = http.getsharedobject(oauth2authorizationservice.class); oauth2resourceownerpasswordauthenticationprovider resourceownerpasswordauthenticationprovider = new oauth2resourceownerpasswordauthenticationprovider( authenticationmanager, authorizationservice, oauth2tokengenerator()); oauth2resourceownersmsauthenticationprovider resourceownersmsauthenticationprovider = new oauth2resourceownersmsauthenticationprovider( authenticationmanager, authorizationservice, oauth2tokengenerator()); // 处理 usernamepasswordauthenticationtoken http.authenticationprovider(new daoauthenticationprovider()); // 处理 oauth2resourceownerpasswordauthenticationtoken http.authenticationprovider(resourceownerpasswordauthenticationprovider); // 处理 oauth2resourceownersmsauthenticationtoken http.authenticationprovider(resourceownersmsauthenticationprovider); } }
到此这篇关于springsecurity整合redission序列化问题的文章就介绍到这了,更多相关springsecurity整合redission序列化内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论