目录
    • 简化的oauth的授权改造
    • 标准的oauth授权改造
      • 自定义providersignutils

    前言

    上一篇博客简析了一下spring security oauth中生成accesstoken的源码,目的就是为了方便我们将原有的表单登录,短信登录以及社交登录的认证方法,都改造成基于accesstoken的认证方式

    基于token的表单登录

    在简析了spring security oauth的源码之后,我们发现,其实有些源码我们并不能用,至少,tokenendpoint这个组件,我们就没法用,因为这个组件只会响应/oauth/token的请求,而且spring security oauth会根据oauth协议中常用的4种授权模式去生成令牌,而我们这里是自定义的登录,自然用不上oauth协议中的授权模式,因此我们改造自定义的登录,只能借鉴其令牌生成方式。

    如果有印象,在前几篇博客中总结过自定义登录成功处理的方式,无论前面登录逻辑如何认证,我们只需要在认证成功之后,自定义生成accesstoken 即可,因此我们只需要重新处理我们自定义登录成功的处理方式即可

    那么如何处理,依旧是一个问题,这就回到了上一篇博客中的内容,构造accesstoken需要oauth2request和authentication,其中authentication是登录成功后的认证详情信息,在登录成功处理器中,会有相关参数传递进来。oauth2request由clientdeatails和tokenrequest组成,这在上一篇博客中我们已经总结过了,clientdetails根据传递参数中的clientid和clientsecret等client配置信息组成,tokenrequest则由请求中其他参数实例化而成,具体如下图所示

    相关改造代码如下

    /**
     * autor:liman
     * createtime:2021/7/10
     * comment: 自定义登录成功处理器
     */
    @component("selfauthenticationsuccesshandler")
    @slf4j
    public class selfauthenticationsuccesshandler extends simpleurlauthenticationsuccesshandler {
    
        @autowired
        private securityproperties securityproperties;
        @autowired
        private objectmapper objectmapper;
        @autowired
        private clientdetailsservice clientdetailsservice;
        @autowired
        private authorizationservertokenservices authenticationservertokenservices;
    
        @override
        public void onauthenticationsuccess(httpservletrequest request, httpservletresponse response
                , authentication authentication) throws ioexception, servletexception {
    
            log.info("自定义登录成功的处理器");
    
            string header = request.getheader("authorization");
    
            if (header == null || !header.startswith("basic ")) {
                throw new unapprovedclientauthenticationexception("请求头中没有client相关的信息");
            }
    
            string[] tokens = extractanddecodeheader(header, request);
            assert tokens.length == 2;
            string clientid = tokens[0];
            string clientsecret = tokens[1];
            //得到clientdeatils信息
            clientdetails clientdetails = clientdetailsservice.loadclientbyclientid(clientid);//得到clientdetails信息
    
            if (null == clientdetails) {
                throw new unapprovedclientauthenticationexception("clientid对应的信息不存在" + clientid);
            } else if (!stringutils.equals(clientsecret, clientdetails.getclientsecret())) {
                throw new unapprovedclientauthenticationexception("clientsecret信息不匹配" + clientsecret);
            }
    
            //构建自己的tokenrequest,由于这里不能使用oauth2中的四种授权模式,因此这里第四个参数设置为"customer"
            //同理,第一个参数主要用于组装并生成authentication,而这里的authentication已经通过参数传递进来,因此可以直接赋一个空的map
            tokenrequest tokenrequest = new tokenrequest(maputils.empty_map, clientid, clientdetails.getscope(), "customer");
    
            //构建oauth2request
            oauth2request oauth2request = tokenrequest.createoauth2request(clientdetails);
    		//构建 oauth2authentication
            oauth2authentication oauth2authentication = new oauth2authentication(oauth2request, authentication);
    		//生成accesstoken,这里依旧使用的是spring security oauth中默认的defaulttokenservice
            oauth2accesstoken accesstoken = authenticationservertokenservices.createaccesstoken(oauth2authentication);
            response.setcontenttype("application/json;charset=utf-8");
            response.getwriter().write(objectmapper.writevalueasstring(accesstoken));//将authentication作为json写到前端
    
        }
    
        /**
         * decodes the header into a username and password.
         *
         * @throws badcredentialsexception if the basic header is not present or is not valid
         *                                 base64
         */
        //todo:解码请求头中的base64编码的 appid和appsecret
        private string[] extractanddecodeheader(string header, httpservletrequest request)
                throws ioexception {
    		//格式:basic+空格+base64加密的appid和appsecret,所以这里substring(6)
            byte[] base64token = header.substring(6).getbytes("utf-8");
            byte[] decoded;
            try {
                decoded = base64.decode(base64token);
            } catch (illegalargumentexception e) {
                throw new badcredentialsexception(
                        "failed to decode basic authentication token");
            }
    
            string token = new string(decoded, "utf-8");
    
            int delim = token.indexof(":");
    
            if (delim == -1) {
                throw new badcredentialsexception("invalid basic authentication token");
            }
            return new string[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }
    

    基于token的短信验证码登录

    之前提到过,由于基于token的认证交互,其实不一定会有session会话的概念,如果我们的验证码依旧存于session中,则并不能正常校验,因此在基于token的短信验证码登录的重构中,我们唯一要做的,就是将验证码存于redis等缓存中间件中,验证码的key值为deviceid。

    方案比较简单,这里只贴出redis操作验证码的方法

    /**
     * 基于redis的验证码存取器,避免由于没有session导致无法存取验证码的问题
     */
    @component
    public class redisvalidatecoderepository implements validatecoderepository {
    
    	@autowired
    	private redistemplate<object, object> redistemplate;
    
    	/*
    	 * (non-javadoc)
    	 */
    	@override
    	public void save(servletwebrequest request, validatecode code, validatecodetype type) {
    		redistemplate.opsforvalue().set(buildkey(request, type), code, 30, timeunit.minutes);
    	}
    
    	/*
    	 * (non-javadoc)
    	 */
    	@override
    	public validatecode get(servletwebrequest request, validatecodetype type) {
    		object value = redistemplate.opsforvalue().get(buildkey(request, type));
    		if (value == null) {
    			return null;
    		}
    		return (validatecode) value;
    	}
    
    	/*
    	 * (non-javadoc)
    	 * 
    	 */
    	@override
    	public void remove(servletwebrequest request, validatecodetype type) {
    		redistemplate.delete(buildkey(request, type));
    	}
    
    	/**
    	 * @param request
    	 * @param type
    	 * @return
    	 */
    	private string buildkey(servletwebrequest request, validatecodetype type) {
    		string deviceid = request.getheader("deviceid");
    		if (stringutils.isblank(deviceid)) {
    			throw new validatecodeexception("请在请求头中携带deviceid参数");
    		}
    		return "code:" + type.tostring().tolowercase() + ":" + deviceid;
    	}
    
    }
    
    

    基于token的社交登录

    在调通微信社交登录之后,再进行总结,只是需要明确的是,这里分为两种情况,一种是简化模式,一种是标准的oauth2授权模式(这两种的区别,在qq登录和微信登录流程中有详细的体现)。

    简化的oauth的授权改造

    简化的oauth模式,oauth协议简化的认证模式,与标准最大的不同,其实就是在获取授权码的时候,顺带将openid(第三方用户id)和accesstoken(获取用户信息的令牌),在这种前后端彻底分离的架构中,前三步前端可以通过服务提供商的sdk完成openid和accesstoken的获取。但是并不能根据openid作为我们自己登录系统凭证,因此我们需要提供一个根据openid进行登录的方式这个与之前短信登录方式大同小异

    1、openidauthenticationtoken

    /**
     * autor:liman
     * createtime:2021/8/4
     * comment:openidauthenticationtoken
     */
    public class openidauthenticationtoken extends abstractauthenticationtoken {
    
        private static final long serialversionuid = springsecuritycoreversion.serial_version_uid;
    
        private final object principal;
        private string providerid;
    
    
    	/**
    		openid,和providerid作为principal
    	*/
        public openidauthenticationtoken(string openid, string providerid) {
            super(null);
            this.principal = openid;
            this.providerid = providerid;
            setauthenticated(false);
        }
    
        /**
         * this constructor should only be used by <code>authenticationmanager</code> or
         * <code>authenticationprovider</code> implementations that are satisfied with
         * producing a trusted (i.e. {@link #isauthenticated()} = <code>true</code>)
         * authentication token.
         *
         * @param principal
         * @param credentials
         * @param authorities
         */
        public openidauthenticationtoken(object principal,
                                         collection<? extends grantedauthority> authorities) {
            super(authorities);
            this.principal = principal;
            super.setauthenticated(true); // must use super, as we override
        }
    
        public object getcredentials() {
            return null;
        }
    
        public object getprincipal() {
            return this.principal;
        }
    
        public string getproviderid() {
            return providerid;
        }
    
        public void setauthenticated(boolean isauthenticated) throws illegalargumentexception {
            if (isauthenticated) {
                throw new illegalargumentexception(
                        "cannot set this token to trusted - use constructor which takes a grantedauthority list instead");
            }
    
            super.setauthenticated(false);
        }
    
        @override
        public void erasecredentials() {
            super.erasecredentials();
        }
    }
    

    2、openidauthenticationfilter

    /**
     * autor:liman
     * createtime:2021/8/4
     * comment:基于openid登录的过滤器
     */
    @slf4j
    public class openidauthenticationfilter extends abstractauthenticationprocessingfilter {
    
    
        private string openidparameter = "openid";
        private string provideridparameter = "providerid";
        private boolean postonly = true;
    
        public openidauthenticationfilter() {
            super(new antpathrequestmatcher("/authentication/openid", "post"));
        }
    
        public authentication attemptauthentication(httpservletrequest request, httpservletresponse response)
                throws authenticationexception {
            if (postonly && !request.getmethod().equals("post")) {
                throw new authenticationserviceexception("authentication method not supported: " + request.getmethod());
            }
    		//获取请求中的openid和providerid
            string openid = obtainopenid(request);
            string providerid = obtainproviderid(request);
    
            if (openid == null) {
                openid = "";
            }
            if (providerid == null) {
                providerid = "";
            }
    
            openid = openid.trim();
            providerid = providerid.trim();
    		//构造openidauthenticationtoken
            openidauthenticationtoken authrequest = new openidauthenticationtoken(openid, providerid);
    
            // allow subclasses to set the "details" property
            setdetails(request, authrequest);
    		//交给authenticationmanager进行认证
            return this.getauthenticationmanager().authenticate(authrequest);
        }
    
    
        /**
         * 获取openid
         */
        protected string obtainopenid(httpservletrequest request) {
            return request.getparameter(openidparameter);
        }
    
        /**
         * 获取提供商id
         */
        protected string obtainproviderid(httpservletrequest request) {
            return request.getparameter(provideridparameter);
        }
    
        protected void setdetails(httpservletrequest request, openidauthenticationtoken authrequest) {
            authrequest.setdetails(authenticationdetailssource.builddetails(request));
        }
    
        public void setopenidparameter(string openidparameter) {
            assert.hastext(openidparameter, "username parameter must not be empty or null");
            this.openidparameter = openidparameter;
        }
    
        public void setpostonly(boolean postonly) {
            this.postonly = postonly;
        }
    
        public final string getopenidparameter() {
            return openidparameter;
        }
    
        public string getprovideridparameter() {
            return provideridparameter;
        }
    
        public void setprovideridparameter(string provideridparameter) {
            this.provideridparameter = provideridparameter;
        }
    }
    
    

    3、openidauthenticationprovider

    /**
     * 
     */
    package com.learn.springsecurity.app.social.openid;
    /**
     * @author zhailiang
     *
     */
    public class openidauthenticationprovider implements authenticationprovider {
    
    	private socialuserdetailsservice userdetailsservice;
    
    	private usersconnectionrepository usersconnectionrepository;
    
    	/*
    	 * (non-javadoc)
    	 * 
    	 * @see org.springframework.security.authentication.authenticationprovider#
    	 * authenticate(org.springframework.security.core.authentication)
    	 */
    	@override
    	public authentication authenticate(authentication authentication) throws authenticationexception {
    
    		openidauthenticationtoken authenticationtoken = (openidauthenticationtoken) authentication;
    		
    		set<string> provideruserids = new hashset<>();
    		provideruserids.add((string) authenticationtoken.getprincipal());
    		//之前社交登录中介绍的usersconnectionrepository,从user_connection表中根据providerid和openid查询用户id
    		set<string> userids = usersconnectionrepository.finduseridsconnectedto(authenticationtoken.getproviderid(), provideruserids);
    		
    		if(collectionutils.isempty(userids) || userids.size() != 1) {
    			throw new internalauthenticationserviceexception("无法获取用户信息");
    		}
    		
    		//获取到userid了
    		string userid = userids.iterator().next();
    		
    		//利用userdetailsservice根据userid查询用户信息
    		userdetails user = userdetailsservice.loaduserbyuserid(userid);
    
    		if (user == null) {
    			throw new internalauthenticationserviceexception("无法获取用户信息");
    		}
    		
    		openidauthenticationtoken authenticationresult = new openidauthenticationtoken(user, user.getauthorities());
    		
    		authenticationresult.setdetails(authenticationtoken.getdetails());
    
    		return authenticationresult;
    	}
    
    	/*
    	 * (non-javadoc)
    	 * 
    	 * @see org.springframework.security.authentication.authenticationprovider#
    	 * supports(java.lang.class)
    	 */
    	@override
    	public boolean supports(class<?> authentication) {
    		return openidauthenticationtoken.class.isassignablefrom(authentication);
    	}
    
    	public socialuserdetailsservice getuserdetailsservice() {
    		return userdetailsservice;
    	}
    
    	public void setuserdetailsservice(socialuserdetailsservice userdetailsservice) {
    		this.userdetailsservice = userdetailsservice;
    	}
    
    	public usersconnectionrepository getusersconnectionrepository() {
    		return usersconnectionrepository;
    	}
    
    	public void setusersconnectionrepository(usersconnectionrepository usersconnectionrepository) {
    		this.usersconnectionrepository = usersconnectionrepository;
    	}
    
    }
    

    4、配置类

    /**
     * @author zhailiang
     *
     */
    @component
    public class openidauthenticationsecurityconfig extends securityconfigureradapter<defaultsecurityfilterchain, httpsecurity> {
    	
    	@autowired
    	private authenticationsuccesshandler selfauthenticationsuccesshandler;
    	
    	@autowired
    	private authenticationfailurehandler selfauthenticationfailurehandler;
    	
    	@autowired
    	private socialuserdetailsservice userdetailsservice;
    	
    	@autowired
    	private usersconnectionrepository usersconnectionrepository;
    	
    	@override
    	public void configure(httpsecurity http) throws exception {
    		
    		openidauthenticationfilter openidauthenticationfilter = new openidauthenticationfilter();
    		openidauthenticationfilter.setauthenticationmanager(http.getsharedobject(authenticationmanager.class));
    		openidauthenticationfilter.setauthenticationsuccesshandler(selfauthenticationsuccesshandler);
    		openidauthenticationfilter.setauthenticationfailurehandler(selfauthenticationfailurehandler);
    		
    		openidauthenticationprovider openidauthenticationprovider = new openidauthenticationprovider();
    		openidauthenticationprovider.setuserdetailsservice(userdetailsservice);
    		openidauthenticationprovider.setusersconnectionrepository(usersconnectionrepository);
    		
    		http.authenticationprovider(openidauthenticationprovider)
    			.addfilterafter(openidauthenticationfilter, usernamepasswordauthenticationfilter.class);
    		
    	}
    
    }
    

    测试结果

    标准的oauth授权改造

    标准的oauth模式

    针对标准的授权模式,我们并不需要做多少改动,因为在社交登录那一节中我们已经做了相关开发,只是需要说明的是,只是在spring-social的过滤器——socialauthenticationfilter中,在正常社交登录流程完成之后会默认跳转到某个页面,而这个并不适用于前后端分离的项目,因此要针对这个问题定制化解决。这需要回到之前socialauthenticationfilter加入到认证过滤器链上的代码。之前我们说过社交登录的过滤器链不需要我们手动配置,只需要初始化springsocialconfiguer的时候,会自动加入到社交登录的认证过滤器链上

    @configuration
    @enablesocial
    public class socialconfig extends socialconfigureradapter {
    	@bean
    	public springsocialconfigurer selfsocialsecurityconfig(){ 
    		springsocialconfigurer selfspringsocialconfig = new springsocialconfigurer();
    		return selfspringsocialconfig;
    	}
    }
    

    我们只需要改变socialauthenticationfilter的默认处理即可,因此我们给他加一个后置处理器,但是这个后置处理器是在springsocialconfigurer的postprocess函数中进行处理

    /**
     * autor:liman
     * createtime:2021/7/15
     * comment:自定义的springsocial配置类
     */
    public class selfspringsocialconfig extends springsocialconfigurer {
    
        private string processfilterurl;
    
        @autowired(required = false)
        private connectionsignup connectionsignup;
    
        @autowired(required = false)
        private socialauthenticationfilterpostprocessor socialauthenticationfilterpostprocessor;
    
        public selfspringsocialconfig(string processfilterurl) {
            this.processfilterurl = processfilterurl;
        }
    
        @override
        protected <t> t postprocess(t object) {
            socialauthenticationfilter socialauthenticationfilter = (socialauthenticationfilter) super.postprocess(object);
            socialauthenticationfilter.setfilterprocessesurl(processfilterurl);
            if(null!=socialauthenticationfilterpostprocessor){
                socialauthenticationfilterpostprocessor.process(socialauthenticationfilter);
            }
            return (t) socialauthenticationfilter;
        }
    
        public connectionsignup getconnectionsignup() {
            return connectionsignup;
        }
    
        public void setconnectionsignup(connectionsignup connectionsignup) {
            this.connectionsignup = connectionsignup;
        }
    
        public socialauthenticationfilterpostprocessor getsocialauthenticationfilterpostprocessor() {
            return socialauthenticationfilterpostprocessor;
        }
    
        public void setsocialauthenticationfilterpostprocessor(socialauthenticationfilterpostprocessor socialauthenticationfilterpostprocessor) {
            this.socialauthenticationfilterpostprocessor = socialauthenticationfilterpostprocessor;
        }
    }
    
    //将我们自定义的 springsocialconfigurer交给spring托管
    @configuration
    @enablesocial
    public class socialconfig extends socialconfigureradapter {
        @bean
        public springsocialconfigurer selfsocialsecurityconfig(){
            string processfilterurl = securityproperties.getsocial().getprocessfilterurl();
            selfspringsocialconfig selfspringsocialconfig = new selfspringsocialconfig(processfilterurl);
    
            //指定第三方用户信息认证不存在的注册页
            selfspringsocialconfig.signupurl(securityproperties.getbrowser().getsiguuppage());
            selfspringsocialconfig.setconnectionsignup(connectionsignup);
            selfspringsocialconfig.setsocialauthenticationfilterpostprocessor(socialauthenticationfilterpostprocessor);
            return selfspringsocialconfig;
        }
    }
    

    我们自定义的过滤器后置处理器如下

    /**
     * autor:liman
     * createtime:2021/8/7
     * comment:app社交登录认证后置处理器
     */
    @component
    public class appsocialauthenticationfilterpostprocessor implements socialauthenticationfilterpostprocessor {
    
        @autowired
        private authenticationsuccesshandler selfauthenticationsuccesshandler;
    
        @override
        public void process(socialauthenticationfilter socialauthenticationfilter) {
            socialauthenticationfilter.setauthenticationsuccesshandler(selfauthenticationsuccesshandler);
        }
    }
    

    关于用户的绑定

    这里需要总结一下之前的社交登录中用户注册绑定的操作。

    之前的社交登录绑定用户

    在之前的社交登录中,如果spring social发现用户是第一次登录,则会跳转到相关的页面,这个页面我们其实也可以自己定义并配置

    @configuration
    @enablesocial
    public class socialconfig extends socialconfigureradapter {
        @bean
        public springsocialconfigurer selfsocialsecurityconfig(){
            string processfilterurl = securityproperties.getsocial().getprocessfilterurl();
            selfspringsocialconfig selfspringsocialconfig = new selfspringsocialconfig(processfilterurl);
    
            //指定第三方用户信息认证不存在的注册页
            selfspringsocialconfig.signupurl(securityproperties.getbrowser().getsiguuppage());
            selfspringsocialconfig.setconnectionsignup(connectionsignup);
            selfspringsocialconfig.setsocialauthenticationfilterpostprocessor(socialauthenticationfilterpostprocessor);
            return selfspringsocialconfig;
        }
        
        @bean
    public providersigninutils providersigninutils(connectionfactorylocator connectionfactorylocator){
        return new providersigninutils(connectionfactorylocator,
                getusersconnectionrepository(connectionfactorylocator));
    }
    }
    

    我们配置的代码中,可以自定义页面路径,我们自定义页面如下(一个简单的登录绑定页面)

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>登录</title>
    </head>
    <body>
    	<h2>demo注册页</h2>
    	
    	<form action="user/regist" method="post">
    		<table>
    			<tr>
    				<td>用户名:</td> 
    				<td><input type="text" name="username"></td>
    			</tr>
    			<tr>
    				<td>密码:</td>
    				<td><input type="password" name="password"></td>
    			</tr>
    			<tr>
    				<td colspan="2">
    					<button type="submit" name="type" value="regist">注册</button>
    					<button type="submit" name="type" value="binding">绑定</button>
    				</td>
    			</tr>
    		</table>
    	</form>
    </body>
    </html>
    

    在用户第一次跳转到这个页面的用户选择注册,或者绑定,都会请求/user/register接口,这个接口借助providersigninutils完成会话中的用户数据更新

    @autowired
    private providersigninutils providersigninutils;
    
    @postmapping("/register")
    public void userregister(@requestbody user user, httpservletrequest request) {
    
        //利用providersigninutils,将注册之后的用户信息,关联到会话中
        providersigninutils.dopostsignup(user.getid(),new servletwebrequest(request));
    
    }
    

    在跳转之前,spring social已经帮我们将用户信息存入会话(在socialauthenticationfilter中可以看到相关代码)

    //以下代码位于:org.springframework.social.security.socialauthenticationfilter#doauthentication
    private authentication doauthentication(socialauthenticationservice<?> authservice, httpservletrequest request, socialauthenticationtoken token) {
    	try {
    		if (!authservice.getconnectioncardinality().isauthenticatepossible()) return null;
    		token.setdetails(authenticationdetailssource.builddetails(request));
    		authentication success = getauthenticationmanager().authenticate(token);
    		assert.isinstanceof(socialuserdetails.class, success.getprincipal(), "unexpected principle type");
    		updateconnections(authservice, token, success);			
    		return success;
    	} catch (badcredentialsexception e) {
    		// connection unknown, register new user?
    		if (signupurl != null) {
    			//这里就是将社交用户信息存入会话
    			// store connectiondata in session and redirect to register page
    			sessionstrategy.setattribute(new servletwebrequest(request), providersigninattempt.session_attribute, new providersigninattempt(token.getconnection()));
    			throw new socialauthenticationredirectexception(buildsignupurl(request));
    		}
    		throw e;
    	}
    }
    

    但是基于前后端分离,且并没有会话对象交互的系统,这种方式并不适用,因为并不存在会话,如何处理,需要用其他方案,其实我们可以在验证码登录的改造中受到启发,将用户数据存入会话即可,我们自定义实现一个providersigninutils将用户信息存入redis即可。

    自定义providersignutils

    1、将第三方用户数据存入redis的工具类

    /**
     * autor:liman
     * createtime:2021/8/7
     * comment:app端用户信息存入redis的工具类
     */
    @component
    public class appsignuputils {
    
        public static final string social_redis_user_prefix = "self:security:social:connectiondata";
    
        @autowired
        private redistemplate<object, object> redistemplate;
        @autowired
        private usersconnectionrepository usersconnectionrepository;
        @autowired
        private connectionfactorylocator connectionfactorylocator;
    
        public void saveconnectiondata(webrequest webrequest, connectiondata connectiondata) {
            redistemplate.opsforvalue().set(getkey(webrequest), connectiondata, 10, timeunit.minutes);
        }
    
        /**
         * 将用户与数据库中的信息进行绑定
         * @param request
         * @param userid
         */
        public void dopostsignup(webrequest request,string userid){
            string key = getkey(request);
            if(!redistemplate.haskey(key)){
                throw new runtimeexception("无法找到缓存的用户社交账号信息");
            }
            connectiondata connectiondata = (connectiondata) redistemplate.opsforvalue().get(key);
    
            //根据connectiondata实例化创建一个connection
            connection<?> connection = connectionfactorylocator.getconnectionfactory(connectiondata.getproviderid())
                    .createconnection(connectiondata);
           //将数据库中的用户与redis中的用户信息关联
            usersconnectionrepository.createconnectionrepository(userid).addconnection(connection);
        }
    
        /**
         * 获取设备id作为key
         *
         * @param webrequest
         * @return
         */
        public string getkey(webrequest webrequest) {
            string deviceid = webrequest.getheader("deviceid");
            if (stringutils.isblank(deviceid)) {
                throw new runtimeexception("设备id不能为空");
            }
            return social_redis_user_prefix + deviceid;
        }
    }
    

    2、复写掉原来的配置类

    为了避免对原有代码的侵入性处理,这里我们需要自定义一个实现beanpostprocessor接口的类

    /**
     * autor:liman
     * createtime:2021/8/7
     * comment:由于app端的社交用户绑定,不能采用跳转,也不能操作会话,需要用自定义的providersignuputils工具类
     * 因此需要定义一个后置处理器,针对springsocialconfigurer进行一些后置处理
     */
    @component
    public class appspringsocialconfigurerpostprocessor implements beanpostprocessor {
        @override
        public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception {
            return null;
        }
    
        @override
        public object postprocessafterinitialization(object bean, string beanname) throws beansexception {
    
            if(stringutils.equals(beanname,"selfsocialsecurityconfig")){
                selfspringsocialconfig configurer = (selfspringsocialconfig) bean;
                //复写掉原有的selfspringsocialconfig的signupurl
                configurer.signupurl("/app/social/signup");
                return configurer;
            }
            return bean;
        }
    }
    

    针对上述的请求路径,我们也要写一个对应路径的controller处理方法

    @restcontroller
    @slf4j
    public class appsecuritycontroller {
    
        @autowired
        private providersigninutils providersigninutils;
        @autowired
        private appsignuputils appsignuputils;
    
        @getmapping("/app/social/signup")
        @responsestatus(httpstatus.unauthorized)
        public baseresponse getsocialuserinfo(httpservletrequest request){
            baseresponse result = new baseresponse(statuscode.success);
            log.info("【app模式】开始获取会话中的第三方用户信息");
            //先从其中拿出数据,毕竟这个时候还没有完全跳转,下一个会话,就没有该数据了
            connection<?> connectionfromsession = providersigninutils.getconnectionfromsession(new servletwebrequest(request));
            socialuserinfo socialuserinfo = new socialuserinfo();
            socialuserinfo.setproviderid(connectionfromsession.getkey().getproviderid());
            socialuserinfo.setprovideruserid(connectionfromsession.getkey().getprovideruserid());
            socialuserinfo.setnickname(connectionfromsession.getdisplayname());
            socialuserinfo.setheadimg(connectionfromsession.getimageurl());
    
            //转存到自己的工具类中
            appsignuputils.saveconnectiondata(new servletwebrequest(request),connectionfromsession.createdata());
            result.setdata(socialuserinfo);
            return result;
        }
    
    }
    

    对于用户注册的接口也需要做调整

    @postmapping("/register")
    public void userregister(@requestbody user user, httpservletrequest request) {
    
        //如果是浏览器的应用利用providersigninutils,将注册之后的用户信息,关联到会话中
        providersigninutils.dopostsignup(user.getid(),new servletwebrequest(request));
    	//如果是app的应用,则利用appsignuputils 将注册之后的用户信息,关联到会话中
        appsignuputils.dopostsignup(new servletwebrequest(request),user.getid());
    
    }
    

    总结

    总结了基于token认证的三种登录方式,最为复杂的为社交登录方式

    到此这篇关于springsecurity基于token的认证方式的文章就介绍到这了,更多相关springsecurity token认证内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!