springsecurity基於token的認證方式
前言
上一篇博客簡析瞭一下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認證內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- SpringSecurityOAuth2 如何自定義token信息
- 使用springcloud+oauth2攜帶token去請求其他服務
- Spring Cloud OAuth2實現自定義token返回格式
- 使用SpringSecurity 進行自定義Token校驗
- Spring Boot 2結合Spring security + JWT實現微信小程序登錄