申請QQ、微信相關AppId和AppSecret,這些你們本身到QQ互聯和微信開發平臺 去申請吧
還有java後臺要引入相關的jar包,以下:html
<dependencies> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--<dependency>--> <!--<groupId>org.springframework.cloud</groupId>--> <!--<artifactId>spring-cloud-starter-security</artifactId>--> <!--</dependency>--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-config</artifactId> <version>1.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-core</artifactId> <version>1.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-security</artifactId> <version>1.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-web</artifactId> <version>1.1.6.RELEASE</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.2</version> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>2.0.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version>2.0.4.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.6</version> </dependency>
而後在application.properties裏面設置相關配置,如redis、mysql等設置,以下:前端
spring.datasource.url= spring.datasource.username= spring.datasource.password= spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.redis.host=127.0.0.1 spring.redis.password=your_pwd spring.redis.port=6379 spring.redis.timeout=30000 ssb.security.social.register-url=/social/signUp ssb.security.social.filter-processes-url=/social-login ssb.security.social.bind-url=https://website/social-bind/qq ssb.security.social.callback-url=https://website/social-login ssb.security.social.connect-url=https://website/social-connect //QQ受權 ssb.security.social.qq.app-id= ssb.security.social.qq.app-secret= ssb.security.social.qq.provider-id=qq //WeChat受權 ssb.security.social.wechat.app-id= ssb.security.social.wechat.app-secret= ssb.security.social.wechat.provider-id=wechat
準備工做作好以後,如今咱們開始分析社交綁定,其實spring-social框架裏已經自帶了spring-social-web,這個jar包裏面有個ConnectController.java類,這個類已經幫咱們實現了相關綁定與解綁實現方法,問題在於它是基於Session的,因此若是是先後端分離項目使用Session固然應有問題,因此咱們要結合Redis來使用,把相關變量都存在Redis中,因此咱們上面已經配置好了Redis,咱們再來看看Redis配置代碼:vue
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ return new RestTemplate(factory); } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory(){ SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(50000);//單位爲ms factory.setConnectTimeout(50000);//單位爲ms return factory; } }
設置好以後,咱們來分析一下spring-social-web這個jar包獲取社交帳號綁定狀況,它的請求地址是/connect,代碼以下:java
@Controller @RequestMapping({"/connect"}) public class ConnectController implements InitializingBean { private static final Log logger = LogFactory.getLog(ConnectController.class); private final ConnectionFactoryLocator connectionFactoryLocator; private final ConnectionRepository connectionRepository; private final MultiValueMap<Class<?>, ConnectInterceptor<?>> connectInterceptors = new LinkedMultiValueMap(); private final MultiValueMap<Class<?>, DisconnectInterceptor<?>> disconnectInterceptors = new LinkedMultiValueMap(); private ConnectSupport connectSupport; private final UrlPathHelper urlPathHelper = new UrlPathHelper(); private String viewPath = "connect/"; private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); private String applicationUrl = null; protected static final String DUPLICATE_CONNECTION_ATTRIBUTE = "social_addConnection_duplicate"; protected static final String PROVIDER_ERROR_ATTRIBUTE = "social_provider_error"; protected static final String AUTHORIZATION_ERROR_ATTRIBUTE = "social_authorization_error"; @Inject public ConnectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) { this.connectionFactoryLocator = connectionFactoryLocator; this.connectionRepository = connectionRepository; } /** @deprecated */ @Deprecated public void setInterceptors(List<ConnectInterceptor<?>> interceptors) { this.setConnectInterceptors(interceptors); } public void setConnectInterceptors(List<ConnectInterceptor<?>> interceptors) { Iterator var2 = interceptors.iterator(); while(var2.hasNext()) { ConnectInterceptor<?> interceptor = (ConnectInterceptor)var2.next(); this.addInterceptor(interceptor); } } public void setDisconnectInterceptors(List<DisconnectInterceptor<?>> interceptors) { Iterator var2 = interceptors.iterator(); while(var2.hasNext()) { DisconnectInterceptor<?> interceptor = (DisconnectInterceptor)var2.next(); this.addDisconnectInterceptor(interceptor); } } public void setApplicationUrl(String applicationUrl) { this.applicationUrl = applicationUrl; } public void setViewPath(String viewPath) { this.viewPath = viewPath; } public void setSessionStrategy(SessionStrategy sessionStrategy) { this.sessionStrategy = sessionStrategy; } public void addInterceptor(ConnectInterceptor<?> interceptor) { Class<?> serviceApiType = GenericTypeResolver.resolveTypeArgument(interceptor.getClass(), ConnectInterceptor.class); this.connectInterceptors.add(serviceApiType, interceptor); } public void addDisconnectInterceptor(DisconnectInterceptor<?> interceptor) { Class<?> serviceApiType = GenericTypeResolver.resolveTypeArgument(interceptor.getClass(), DisconnectInterceptor.class); this.disconnectInterceptors.add(serviceApiType, interceptor); } @RequestMapping( method = {RequestMethod.GET} ) public String connectionStatus(NativeWebRequest request, Model model) { this.setNoCache(request); this.processFlash(request, model); Map<String, List<Connection<?>>> connections = this.connectionRepository.findAllConnections(); model.addAttribute("providerIds", this.connectionFactoryLocator.registeredProviderIds()); model.addAttribute("connectionMap", connections); return this.connectView(); } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.GET} ) public String connectionStatus(@PathVariable String providerId, NativeWebRequest request, Model model) { this.setNoCache(request); this.processFlash(request, model); List<Connection<?>> connections = this.connectionRepository.findConnections(providerId); this.setNoCache(request); if(connections.isEmpty()) { return this.connectView(providerId); } else { model.addAttribute("connections", connections); return this.connectedView(providerId); } } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.POST} ) public RedirectView connect(@PathVariable String providerId, NativeWebRequest request) { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); MultiValueMap<String, String> parameters = new LinkedMultiValueMap(); this.preConnect(connectionFactory, parameters, request); try { return new RedirectView(this.connectSupport.buildOAuthUrl(connectionFactory, request, parameters)); } catch (Exception var6) { this.sessionStrategy.setAttribute(request, "social_provider_error", var6); return this.connectionStatusRedirect(providerId, request); } } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.GET}, params = {"oauth_token"} ) public RedirectView oauth1Callback(@PathVariable String providerId, NativeWebRequest request) { try { OAuth1ConnectionFactory<?> connectionFactory = (OAuth1ConnectionFactory)this.connectionFactoryLocator.getConnectionFactory(providerId); Connection<?> connection = this.connectSupport.completeConnection(connectionFactory, request); this.addConnection(connection, connectionFactory, request); } catch (Exception var5) { this.sessionStrategy.setAttribute(request, "social_provider_error", var5); logger.warn("Exception while handling OAuth1 callback (" + var5.getMessage() + "). Redirecting to " + providerId + " connection status page."); } return this.connectionStatusRedirect(providerId, request); } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.GET}, params = {"code"} ) public RedirectView oauth2Callback(@PathVariable String providerId, NativeWebRequest request) { try { OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory)this.connectionFactoryLocator.getConnectionFactory(providerId); Connection<?> connection = this.connectSupport.completeConnection(connectionFactory, request); this.addConnection(connection, connectionFactory, request); } catch (Exception var5) { this.sessionStrategy.setAttribute(request, "social_provider_error", var5); logger.warn("Exception while handling OAuth2 callback (" + var5.getMessage() + "). Redirecting to " + providerId + " connection status page."); } return this.connectionStatusRedirect(providerId, request); } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.GET}, params = {"error"} ) public RedirectView oauth2ErrorCallback(@PathVariable String providerId, @RequestParam("error") String error, @RequestParam(value = "error_description",required = false) String errorDescription, @RequestParam(value = "error_uri",required = false) String errorUri, NativeWebRequest request) { Map<String, String> errorMap = new HashMap(); errorMap.put("error", error); if(errorDescription != null) { errorMap.put("errorDescription", errorDescription); } if(errorUri != null) { errorMap.put("errorUri", errorUri); } this.sessionStrategy.setAttribute(request, "social_authorization_error", errorMap); return this.connectionStatusRedirect(providerId, request); } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.DELETE} ) public RedirectView removeConnections(@PathVariable String providerId, NativeWebRequest request) { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); this.preDisconnect(connectionFactory, request); this.connectionRepository.removeConnections(providerId); this.postDisconnect(connectionFactory, request); return this.connectionStatusRedirect(providerId, request); } @RequestMapping( value = {"/{providerId}/{providerUserId}"}, method = {RequestMethod.DELETE} ) public RedirectView removeConnection(@PathVariable String providerId, @PathVariable String providerUserId, NativeWebRequest request) { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); this.preDisconnect(connectionFactory, request); this.connectionRepository.removeConnection(new ConnectionKey(providerId, providerUserId)); this.postDisconnect(connectionFactory, request); return this.connectionStatusRedirect(providerId, request); } protected String connectView() { return this.getViewPath() + "status"; } protected String connectView(String providerId) { return this.getViewPath() + providerId + "Connect"; } protected String connectedView(String providerId) { return this.getViewPath() + providerId + "Connected"; } protected RedirectView connectionStatusRedirect(String providerId, NativeWebRequest request) { HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class); String path = "/connect/" + providerId + this.getPathExtension(servletRequest); if(this.prependServletPath(servletRequest)) { path = servletRequest.getServletPath() + path; } return new RedirectView(path, true); } public void afterPropertiesSet() throws Exception { this.connectSupport = new ConnectSupport(this.sessionStrategy); if(this.applicationUrl != null) { this.connectSupport.setApplicationUrl(this.applicationUrl); } } private boolean prependServletPath(HttpServletRequest request) { return !this.urlPathHelper.getPathWithinServletMapping(request).equals(""); } private String getPathExtension(HttpServletRequest request) { String fileName = this.extractFullFilenameFromUrlPath(request.getRequestURI()); String extension = StringUtils.getFilenameExtension(fileName); return extension != null?"." + extension:""; } private String extractFullFilenameFromUrlPath(String urlPath) { int end = urlPath.indexOf(63); if(end == -1) { end = urlPath.indexOf(35); if(end == -1) { end = urlPath.length(); } } int begin = urlPath.lastIndexOf(47, end) + 1; int paramIndex = urlPath.indexOf(59, begin); end = paramIndex != -1 && paramIndex < end?paramIndex:end; return urlPath.substring(begin, end); } private String getViewPath() { return this.viewPath; } private void addConnection(Connection<?> connection, ConnectionFactory<?> connectionFactory, WebRequest request) { try { this.connectionRepository.addConnection(connection); this.postConnect(connectionFactory, connection, request); } catch (DuplicateConnectionException var5) { this.sessionStrategy.setAttribute(request, "social_addConnection_duplicate", var5); } } private void preConnect(ConnectionFactory<?> connectionFactory, MultiValueMap<String, String> parameters, WebRequest request) { Iterator var4 = this.interceptingConnectionsTo(connectionFactory).iterator(); while(var4.hasNext()) { ConnectInterceptor interceptor = (ConnectInterceptor)var4.next(); interceptor.preConnect(connectionFactory, parameters, request); } } private void postConnect(ConnectionFactory<?> connectionFactory, Connection<?> connection, WebRequest request) { Iterator var4 = this.interceptingConnectionsTo(connectionFactory).iterator(); while(var4.hasNext()) { ConnectInterceptor interceptor = (ConnectInterceptor)var4.next(); interceptor.postConnect(connection, request); } } private void preDisconnect(ConnectionFactory<?> connectionFactory, WebRequest request) { Iterator var3 = this.interceptingDisconnectionsTo(connectionFactory).iterator(); while(var3.hasNext()) { DisconnectInterceptor interceptor = (DisconnectInterceptor)var3.next(); interceptor.preDisconnect(connectionFactory, request); } } private void postDisconnect(ConnectionFactory<?> connectionFactory, WebRequest request) { Iterator var3 = this.interceptingDisconnectionsTo(connectionFactory).iterator(); while(var3.hasNext()) { DisconnectInterceptor interceptor = (DisconnectInterceptor)var3.next(); interceptor.postDisconnect(connectionFactory, request); } } private List<ConnectInterceptor<?>> interceptingConnectionsTo(ConnectionFactory<?> connectionFactory) { Class<?> serviceType = GenericTypeResolver.resolveTypeArgument(connectionFactory.getClass(), ConnectionFactory.class); List<ConnectInterceptor<?>> typedInterceptors = (List)this.connectInterceptors.get(serviceType); if(typedInterceptors == null) { typedInterceptors = Collections.emptyList(); } return typedInterceptors; } private List<DisconnectInterceptor<?>> interceptingDisconnectionsTo(ConnectionFactory<?> connectionFactory) { Class<?> serviceType = GenericTypeResolver.resolveTypeArgument(connectionFactory.getClass(), ConnectionFactory.class); List<DisconnectInterceptor<?>> typedInterceptors = (List)this.disconnectInterceptors.get(serviceType); if(typedInterceptors == null) { typedInterceptors = Collections.emptyList(); } return typedInterceptors; } private void processFlash(WebRequest request, Model model) { this.convertSessionAttributeToModelAttribute("social_addConnection_duplicate", request, model); this.convertSessionAttributeToModelAttribute("social_provider_error", request, model); model.addAttribute("social_authorization_error", this.sessionStrategy.getAttribute(request, "social_authorization_error")); this.sessionStrategy.removeAttribute(request, "social_authorization_error"); } private void convertSessionAttributeToModelAttribute(String attributeName, WebRequest request, Model model) { if(this.sessionStrategy.getAttribute(request, attributeName) != null) { model.addAttribute(attributeName, Boolean.TRUE); this.sessionStrategy.removeAttribute(request, attributeName); } } private void setNoCache(NativeWebRequest request) { HttpServletResponse response = (HttpServletResponse)request.getNativeResponse(HttpServletResponse.class); if(response != null) { response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 1L); response.setHeader("Cache-Control", "no-cache"); response.addHeader("Cache-Control", "no-store"); } } }
上面就是ConnectController的源碼了,咱們如今分析一下獲取當前用戶社交綁定狀況的方法:mysql
@RequestMapping( method = {RequestMethod.GET} ) public String connectionStatus(NativeWebRequest request, Model model) { this.setNoCache(request); this.processFlash(request, model); Map<String, List<Connection<?>>> connections = this.connectionRepository.findAllConnections(); model.addAttribute("providerIds", this.connectionFactoryLocator.registeredProviderIds()); model.addAttribute("connectionMap", connections); return this.connectView(); } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.GET} ) public String connectionStatus(@PathVariable String providerId, NativeWebRequest request, Model model) { this.setNoCache(request); this.processFlash(request, model); List<Connection<?>> connections = this.connectionRepository.findConnections(providerId); this.setNoCache(request); if(connections.isEmpty()) { return this.connectView(providerId); } else { model.addAttribute("connections", connections); return this.connectedView(providerId); } }
對了,就是這兩個方法,前面第一個方法請求的地址是:/connect(須要用戶登陸) 這個地址是獲取當前用戶全部社交帳號綁定狀況,第二個方法請求的地址是:/connect/{providerId}(須要用戶登陸) 這個地址是獲取某個社交帳號綁定狀況,如/connect/qq,因此咱們要獲取當前用戶綁定的全部社交帳號綁定狀況,使用的是第一個方法,可是如今有個問題,獲取完以後 它是直接跳轉頁面到/connect/status,固然這不是咱們想要的,咱們要修改這個類,好比地址換成/socialConnect,這個換成本身的就好,而後咱們來改下這個方法,以下:git
@RequestMapping( method = {RequestMethod.GET} ) public ResponseEntity<?> connectionStatus(NativeWebRequest request, Model model) throws JsonProcessingException { this.setNoCache(request); this.processFlash(request, model); Map<String, List<Connection<?>>> connections = this.connectionRepository.findAllConnections(); model.addAttribute("providerIds", this.connectionFactoryLocator.registeredProviderIds()); model.addAttribute("connectionMap", connections); Map<String,Boolean> result = new HashMap<String, Boolean>(); for (String key : connections.keySet()){ result.put(key, org.apache.commons.collections.CollectionUtils.isNotEmpty(connections.get(key))); } return ResponseEntity.ok(objectMapper.writeValueAsString(result)); }
改好的代碼直接返回Json數據給前端,而不是跳轉頁面,完美解決了先後端分離項目問題,好了,咱們使用postman發送請求測試看看:github
如圖所示,咱們成功獲取當前登陸用戶全部社交帳號綁定狀況了(爲何這裏只有qq和微信?社交帳號的類型是你application.proterties裏面配置的)。web
好了,咱們來看看綁定社交帳號的方法:redis
@RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.POST} ) public RedirectView connect(@PathVariable String providerId, NativeWebRequest request) { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); MultiValueMap<String, String> parameters = new LinkedMultiValueMap(); this.preConnect(connectionFactory, parameters, request); try { return new RedirectView(this.connectSupport.buildOAuthUrl(connectionFactory, request, parameters)); } catch (Exception var6) { this.sessionStrategy.setAttribute(request, "social_provider_error", var6); return this.connectionStatusRedirect(providerId, request); } } @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.GET}, params = {"code"} ) public RedirectView oauth2Callback(@PathVariable String providerId, NativeWebRequest request) { try { OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory)this.connectionFactoryLocator.getConnectionFactory(providerId); Connection<?> connection = this.connectSupport.completeConnection(connectionFactory, request); this.addConnection(connection, connectionFactory, request); } catch (Exception var5) { this.sessionStrategy.setAttribute(request, "social_provider_error", var5); logger.warn("Exception while handling OAuth2 callback (" + var5.getMessage() + "). Redirecting to " + providerId + " connection status page."); } return this.connectionStatusRedirect(providerId, request); }
如今來分析 下這兩個 方法的做用,第一個方法請求的地址是:POST /connect/{providerId}(須要登陸) ,第二個方法請求地址是:GET /connect/{providerId}?code=&state=(須要登陸),第一個方法是獲取社交受權鏈接地址(這個是你本身社交登陸時候封裝好的,這裏我不打算詳細講解,後面課程再放出來吧)好比qq的受權地址:https://graph.qq.com/oauth2.0...,這樣當你受權成功以後就回調到了第二個方法裏面,順便把code和state原樣返回過來,這一套綁定機制都是基於session的,下面咱們來分析看下他是如何實現的。spring
咱們以微信爲例,首先咱們發送一個POST請求/connect/wechat,由於你已經登陸了,因此後臺能夠獲取當前user是誰,而後就獲取到請求的連接:https://open.weixin.qq.com/co...,最後就是跳轉到這個連接上面去。這是第一個方法的做用,接下來咱們分析第二個方法。
請求上面的連接以後就是跳轉到微信掃碼的頁面,以下所示:
掃完以後立馬就跳到上面連接redirect_uri地址上面去,也就是如今的第二個方法上面,並且是帶着state和code兩個參數,這時候後臺開始驗證你回傳過來的state值是否是匹配的,不匹配就報錯而且跳轉到出錯頁面,匹配的話就往下走,而且經過code獲取SpringSecurity OAuth相關社交用戶信息並保存到數據庫中,這就是code和state的做用,驗證和獲取完以後就能夠,這樣你就綁定成功了,最後跳轉到/connected/wechat頁面了,這樣就結束了綁定功能了。
那麼咱們先後端分離項目要使用這套機制,咱們必須改一下他的源碼了。
首先第一個方法,咱們要把userId保存到以state的redis鍵值對中,也就是:{state:userId},而後以JSON的格式返回社交受權的連接給前臺,這是第一個方法要修改的思路。
而後第二個方法,是社交受權連接返回回來的,由於先後端分離項目session就沒法使用了,因此要獲取用戶信息必須經過上面redis保存的{state:userId},來獲取用戶id。再一個咱們經過code獲取社交用戶信息,兩個數據都獲取了,這個時候咱們就能夠安心的把社交用戶信息保存到數據庫中(這裏的經過state從redis中獲取userId,其實也是一種驗證state的方式,你想一想但是呢!),最後就跳轉到你想要的頁面就行了,下面就是修改後的代碼了,能夠看看:
@RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.POST} ) public ResponseEntity<?> connect(@PathVariable String providerId, NativeWebRequest request) { HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class); Principal user = nativeRequest.getUserPrincipal(); ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); MultiValueMap<String, String> parameters = new LinkedMultiValueMap(); this.preConnect(connectionFactory, parameters, request); try { String social_connect_url = this.connectSupport.buildOAuthUrl(connectionFactory, request, parameters); String state = (String) this.sessionStrategy.getAttribute(request, "oauth2State"); this.sessionStrategy.removeAttribute(request, "oauth2State"); //把userId以state爲key的形式保存到redis中 socialRedisHelper.saveStateUserId(state, user.getName()); //返回社交連接地址 return ResponseEntity.ok(social_connect_url); } catch (Exception var6) { this.sessionStrategy.setAttribute(request, "social_provider_error", var6); logger.info(var6.getMessage()); return null; } } //輔助方法1 protected String callbackUrl(NativeWebRequest request) { if (this.callbackUrl != null) { return this.callbackUrl; } else { HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class); return this.applicationUrl != null ? this.applicationUrl + this.connectPath(nativeRequest) : nativeRequest.getRequestURL().toString(); } } //輔助方法2 private String connectPath(HttpServletRequest request) { String pathInfo = request.getPathInfo(); return request.getServletPath() + (pathInfo != null ? pathInfo : ""); } //回調方法 @RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.GET}, params = {"code"} ) public void oauth2Callback(@PathVariable String providerId, NativeWebRequest request, HttpServletResponse response) { try { //ConnectController是先保存在session裏面,而後回調從session裏面取出來校驗 //我如今是經過redis保存state 的 userId,這樣就至關於校驗了state String state = request.getParameter("state"); String code = request.getParameter("code"); OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory) this.connectionFactoryLocator.getConnectionFactory(providerId); AccessGrant accessGrant = connectionFactory.getOAuthOperations().exchangeForAccess(code, this.callbackUrl(request), null); Connection<?> connection = connectionFactory.createConnection(accessGrant); //從redis中獲取userid String userId = socialRedisHelper.getStateUserId(state); //保存到數據庫中 jdbcConnectionRepository.createConnectionRepository(userId).addConnection(connection); //跳轉頁面到前臺任何你想設置的地址 response.sendRedirect(connectUrl); } catch (Exception ex) { logger.info(ex.getMessage()); } }
這樣你就完成了後臺綁定相關工做,那麼我把前端相關代碼也放出來你們看下吧:
gotoBind(type){ let url = `${this.$url}/socialConnect/${type}`; this.$post(url) .then(res=>{ if(res.code == 0){ this.openWindow(res.data.redirect_uri) } }) }, openWindow(url){ let sf_H = 550; let sf_W = 720; var iTop = (window.screen.height-30 -sf_H)/2; //得到窗口的垂直位置; var iLeft = (window.screen.width-10 -sf_W)/2; //得到窗口的水平位置; let s = window.open(url,"social_bind_form",'height='+sf_H+ ', width='+sf_W+',top='+iTop+',left='+iLeft+'toolbar=no, menubar=no, scrollbars=no, status=no, location=yes, resizable=yes'); },
上面是獲取社交綁定地址並跳轉,下面是回調成功以後關閉對話框並刷新的頁面代碼。
<template> <section> <!--社交綁定成功處理頁面--> </section> </template> <script> import {mapActions,mapState} from 'vuex' export default { data(){ return{ } }, created(){ window.close(); opener.location.reload(); }, methods:{ } } </script>
咱們來演示一下:
綁定社交帳號已經成功了,如今咱們來看一下如何解綁社交帳號吧,咱們先看下源碼是如何實現的,以下
@RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.DELETE} ) public RedirectView removeConnections(@PathVariable String providerId, NativeWebRequest request) { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); this.preDisconnect(connectionFactory, request); this.connectionRepository.removeConnections(providerId); this.postDisconnect(connectionFactory, request); return this.connectionStatusRedirect(providerId, request); } @RequestMapping( value = {"/{providerId}/{providerUserId}"}, method = {RequestMethod.DELETE} ) public RedirectView removeConnection(@PathVariable String providerId, @PathVariable String providerUserId, NativeWebRequest request) { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); this.preDisconnect(connectionFactory, request); this.connectionRepository.removeConnection(new ConnectionKey(providerId, providerUserId)); this.postDisconnect(connectionFactory, request); return this.connectionStatusRedirect(providerId, request); }
第一個方法請求地址是:Delete /connect/{providerId}(需登陸),第二個方法請求地址是:Delete /connect/{providerId}/{providerUserId}(需登陸),注意這裏的providerUserId其實就是社交用戶id,好比微信的openId,第一個方法是根據登陸的userId和providerId來刪除數據庫中綁定的社交用戶數據,第二個方法是根據登陸的userId和providerId還有providerUserId來刪除數據庫中綁定的社交用戶數據,這兩個 方法都有相同的一點就是跳轉到刪除以後的頁面,因此咱們只要把跳轉頁面以JSON的形式返回給前端就好,下面就是修改後的代碼:
@RequestMapping( value = {"/{providerId}"}, method = {RequestMethod.DELETE} ) public ResponseEntity<?> removeConnections(@PathVariable String providerId, NativeWebRequest request) { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); this.preDisconnect(connectionFactory, request); this.connectionRepository.removeConnections(providerId); this.postDisconnect(connectionFactory, request); return ResponseEntity.ok("success"); } @RequestMapping( value = {"/{providerId}/{providerUserId}"}, method = {RequestMethod.DELETE} ) public ResponseEntity<?> removeConnection(@PathVariable String providerId, @PathVariable String providerUserId, NativeWebRequest request) throws IOException { try { ConnectionFactory<?> connectionFactory = this.connectionFactoryLocator.getConnectionFactory(providerId); this.preDisconnect(connectionFactory, request); this.connectionRepository.removeConnection(new ConnectionKey(providerId, providerUserId)); this.postDisconnect(connectionFactory, request); } catch (Exception ex) { logger.info(ex.getMessage()); } return ResponseEntity.ok("success"); }
咱們再把前端代碼貼出來:
gotoUnBind(type){ let url = `${this.$url}/socialConnect/${type}`; this.$delete(url) .then(res=>{ if(res.code == 0){ this.$Message.success('解綁成功!') location.reload(); } }) },
一、只要把思路理清楚了,其實修改爲本身想要的代碼就不難
二、注意ConnectController代碼是基於Session的,因此你必需要登陸的狀況下才能使用
三、redis的使用在這裏發揮到了必定做用,因此說先後端分離項目離不開redis
qq互聯文檔
Spring Security Oauth2.0 實現短信驗證碼登陸
深刻了解 Spring Security
SpringBoot+Spring Security基本配置
spring-social-mongodb
微信的redirect_uri參數錯誤解決辦法
網頁微信第三方登陸-redirect_uri參數錯誤
Java實現QQ、微信、新浪微博第三方登陸
如何從零開始對接第三方登陸(Java版):QQ登陸和微博登陸
QQ受權登陸改
Spring Security QQ 登錄
第三方APP實現QQ登錄
2 Apache Shiro 身份認證(登陸)
Spring Security 實戰:QQ登陸實現
基於Spring的QQ第三方登陸實現
Spring Security源碼分析三:Spring Social實現QQ社交登陸
微信受權登陸-先後端分離
從零開始的Spring Security Oauth2(一)
Spring Boot and OAuth2
Spring security OAuth2 深刻解析
spring boot 入門之security oauth2 jwt完美整合例子-java編程
jojozhai/security
window.open打開的窗口關閉後刷新父頁面的子頁面
Spring Security 實戰:QQ登陸實現jwt