欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security OAuth2集成短信驗證碼登錄以及第三方登錄

 更新時間:2018年04月20日 09:32:23   作者:李球  
這篇文章主要介紹了Spring Security OAuth2集成短信驗證碼登錄以及第三方登錄,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

基于SpringCloud做微服務架構分布式系統(tǒng)時,OAuth2.0作為認證的業(yè)內(nèi)標準,Spring Security OAuth2也提供了全套的解決方案來支持在Spring Cloud/Spring Boot環(huán)境下使用OAuth2.0,提供了開箱即用的組件。但是在開發(fā)過程中我們會發(fā)現(xiàn)由于Spring Security OAuth2的組件特別全面,這樣就導致了擴展很不方便或者說是不太容易直指定擴展的方案,例如:

  1. 圖片驗證碼登錄
  2. 短信驗證碼登錄
  3. 微信小程序登錄
  4. 第三方系統(tǒng)登錄
  5. CAS單點登錄

在面對這些場景的時候,預計很多對Spring Security OAuth2不熟悉的人恐怕會無從下手?;谏鲜龅膱鼍耙螅绾蝺?yōu)雅的集成短信驗證碼登錄及第三方登錄,怎么樣才算是優(yōu)雅集成呢?有以下要求:

  1. 不侵入Spring Security OAuth2的原有代碼
  2. 對于不同的登錄方式不擴展新的端點,使用/oauth/token可以適配所有的登錄方式
  3. 可以對所有登錄方式進行兼容,抽象一套模型只要簡單的開發(fā)就可以集成登錄

基于上述的設計要求,接下來將會在文章種詳細介紹如何開發(fā)一套集成登錄認證組件開滿足上述要求。

閱讀本篇文章您需要了解OAuth2.0認證體系、SpringBoot、SpringSecurity以及Spring Cloud等相關知識

思路

我們來看下Spring Security OAuth2的認證流程:

這個流程當中,切入點不多,集成登錄的思路如下:

  1. 在進入流程之前先進行攔截,設置集成認證的類型,例如:短信驗證碼、圖片驗證碼等信息。
  2. 在攔截的通知進行預處理,預處理的場景有很多,比如驗證短信驗證碼是否匹配、圖片驗證碼是否匹配、是否是登錄IP白名單等處理
  3. 在UserDetailService.loadUserByUsername方法中,根據(jù)之前設置的集成認證類型去獲取用戶信息,例如:通過手機號碼獲取用戶、通過微信小程序OPENID獲取用戶等等

接入這個流程之后,基本上就可以優(yōu)雅集成第三方登錄。

實現(xiàn)

介紹完思路之后,下面通過代碼來展示如何實現(xiàn):

第一步,定義攔截器攔截登錄的請求

/**
 * @author LIQIU
 * @date 2018-3-30
 **/
@Component
public class IntegrationAuthenticationFilter extends GenericFilterBean implements ApplicationContextAware {

  private static final String AUTH_TYPE_PARM_NAME = "auth_type";

  private static final String OAUTH_TOKEN_URL = "/oauth/token";

  private Collection<IntegrationAuthenticator> authenticators;

  private ApplicationContext applicationContext;

  private RequestMatcher requestMatcher;

  public IntegrationAuthenticationFilter(){
    this.requestMatcher = new OrRequestMatcher(
        new AntPathRequestMatcher(OAUTH_TOKEN_URL, "GET"),
        new AntPathRequestMatcher(OAUTH_TOKEN_URL, "POST")
    );
  }

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

    if(requestMatcher.matches(request)){
      //設置集成登錄信息
      IntegrationAuthentication integrationAuthentication = new IntegrationAuthentication();
      integrationAuthentication.setAuthType(request.getParameter(AUTH_TYPE_PARM_NAME));
      integrationAuthentication.setAuthParameters(request.getParameterMap());
      IntegrationAuthenticationContext.set(integrationAuthentication);
      try{
        //預處理
        this.prepare(integrationAuthentication);

        filterChain.doFilter(request,response);

        //后置處理
        this.complete(integrationAuthentication);
      }finally {
        IntegrationAuthenticationContext.clear();
      }
    }else{
      filterChain.doFilter(request,response);
    }
  }

  /**
   * 進行預處理
   * @param integrationAuthentication
   */
  private void prepare(IntegrationAuthentication integrationAuthentication) {

    //延遲加載認證器
    if(this.authenticators == null){
      synchronized (this){
        Map<String,IntegrationAuthenticator> integrationAuthenticatorMap = applicationContext.getBeansOfType(IntegrationAuthenticator.class);
        if(integrationAuthenticatorMap != null){
          this.authenticators = integrationAuthenticatorMap.values();
        }
      }
    }

    if(this.authenticators == null){
      this.authenticators = new ArrayList<>();
    }

    for (IntegrationAuthenticator authenticator: authenticators) {
      if(authenticator.support(integrationAuthentication)){
        authenticator.prepare(integrationAuthentication);
      }
    }
  }

  /**
   * 后置處理
   * @param integrationAuthentication
   */
  private void complete(IntegrationAuthentication integrationAuthentication){
    for (IntegrationAuthenticator authenticator: authenticators) {
      if(authenticator.support(integrationAuthentication)){
        authenticator.complete(integrationAuthentication);
      }
    }
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}

在這個類種主要完成2部分工作:1、根據(jù)參數(shù)獲取當前的是認證類型,2、根據(jù)不同的認證類型調(diào)用不同的IntegrationAuthenticator.prepar進行預處理

第二步,將攔截器放入到攔截鏈條中

/**
 * @author LIQIU
 * @date 2018-3-7
 **/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

  @Autowired
  private RedisConnectionFactory redisConnectionFactory;

  @Autowired
  private AuthenticationManager authenticationManager;

  @Autowired
  private IntegrationUserDetailsService integrationUserDetailsService;

  @Autowired
  private WebResponseExceptionTranslator webResponseExceptionTranslator;

  @Autowired
  private IntegrationAuthenticationFilter integrationAuthenticationFilter;

  @Autowired
  private DatabaseCachableClientDetailsService redisClientDetailsService;

  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    // TODO persist clients details
    clients.withClientDetails(redisClientDetailsService);
  }

  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints
        .tokenStore(new RedisTokenStore(redisConnectionFactory))
//        .accessTokenConverter(jwtAccessTokenConverter())
        .authenticationManager(authenticationManager)
        .exceptionTranslator(webResponseExceptionTranslator)
        .reuseRefreshTokens(false)
        .userDetailsService(integrationUserDetailsService);
  }

  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.allowFormAuthenticationForClients()
        .tokenKeyAccess("isAuthenticated()")
        .checkTokenAccess("permitAll()")
        .addTokenEndpointAuthenticationFilter(integrationAuthenticationFilter);
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    jwtAccessTokenConverter.setSigningKey("cola-cloud");
    return jwtAccessTokenConverter;
  }
}

通過調(diào)用security. .addTokenEndpointAuthenticationFilter(integrationAuthenticationFilter);方法,將攔截器放入到認證鏈條中。

第三步,根據(jù)認證類型來處理用戶信息

@Service
public class IntegrationUserDetailsService implements UserDetailsService {

  @Autowired
  private UpmClient upmClient;

  private List<IntegrationAuthenticator> authenticators;

  @Autowired(required = false)
  public void setIntegrationAuthenticators(List<IntegrationAuthenticator> authenticators) {
    this.authenticators = authenticators;
  }

  @Override
  public User loadUserByUsername(String username) throws UsernameNotFoundException {
    IntegrationAuthentication integrationAuthentication = IntegrationAuthenticationContext.get();
    //判斷是否是集成登錄
    if (integrationAuthentication == null) {
      integrationAuthentication = new IntegrationAuthentication();
    }
    integrationAuthentication.setUsername(username);
    UserVO userVO = this.authenticate(integrationAuthentication);

    if(userVO == null){
      throw new UsernameNotFoundException("用戶名或密碼錯誤");
    }

    User user = new User();
    BeanUtils.copyProperties(userVO, user);
    this.setAuthorize(user);
    return user;

  }

  /**
   * 設置授權信息
   *
   * @param user
   */
  public void setAuthorize(User user) {
    Authorize authorize = this.upmClient.getAuthorize(user.getId());
    user.setRoles(authorize.getRoles());
    user.setResources(authorize.getResources());
  }

  private UserVO authenticate(IntegrationAuthentication integrationAuthentication) {
    if (this.authenticators != null) {
      for (IntegrationAuthenticator authenticator : authenticators) {
        if (authenticator.support(integrationAuthentication)) {
          return authenticator.authenticate(integrationAuthentication);
        }
      }
    }
    return null;
  }
}

這里實現(xiàn)了一個IntegrationUserDetailsService ,在loadUserByUsername方法中會調(diào)用authenticate方法,在authenticate方法中會當前上下文種的認證類型調(diào)用不同的IntegrationAuthenticator 來獲取用戶信息,接下來來看下默認的用戶名密碼是如何處理的:

@Component
@Primary
public class UsernamePasswordAuthenticator extends AbstractPreparableIntegrationAuthenticator {

  @Autowired
  private UcClient ucClient;

  @Override
  public UserVO authenticate(IntegrationAuthentication integrationAuthentication) {
    return ucClient.findUserByUsername(integrationAuthentication.getUsername());
  }

  @Override
  public void prepare(IntegrationAuthentication integrationAuthentication) {

  }

  @Override
  public boolean support(IntegrationAuthentication integrationAuthentication) {
    return StringUtils.isEmpty(integrationAuthentication.getAuthType());
  }
}

UsernamePasswordAuthenticator只會處理沒有指定的認證類型即是默認的認證類型,這個類中主要是通過用戶名獲取密碼。接下來來看下圖片驗證碼登錄如何處理的:

/**
 * 集成驗證碼認證
 * @author LIQIU
 * @date 2018-3-31
 **/
@Component
public class VerificationCodeIntegrationAuthenticator extends UsernamePasswordAuthenticator {

  private final static String VERIFICATION_CODE_AUTH_TYPE = "vc";

  @Autowired
  private VccClient vccClient;

  @Override
  public void prepare(IntegrationAuthentication integrationAuthentication) {
    String vcToken = integrationAuthentication.getAuthParameter("vc_token");
    String vcCode = integrationAuthentication.getAuthParameter("vc_code");
    //驗證驗證碼
    Result<Boolean> result = vccClient.validate(vcToken, vcCode, null);
    if (!result.getData()) {
      throw new OAuth2Exception("驗證碼錯誤");
    }
  }

  @Override
  public boolean support(IntegrationAuthentication integrationAuthentication) {
    return VERIFICATION_CODE_AUTH_TYPE.equals(integrationAuthentication.getAuthType());
  }
}

VerificationCodeIntegrationAuthenticator繼承UsernamePasswordAuthenticator,因為其只是需要在prepare方法中驗證驗證碼是否正確,獲取用戶還是用過用戶名密碼的方式獲取。但是需要認證類型為"vc"才會處理
接下來來看下短信驗證碼登錄是如何處理的:

@Component
public class SmsIntegrationAuthenticator extends AbstractPreparableIntegrationAuthenticator implements ApplicationEventPublisherAware {

  @Autowired
  private UcClient ucClient;

  @Autowired
  private VccClient vccClient;

  @Autowired
  private PasswordEncoder passwordEncoder;

  private ApplicationEventPublisher applicationEventPublisher;

  private final static String SMS_AUTH_TYPE = "sms";

  @Override
  public UserVO authenticate(IntegrationAuthentication integrationAuthentication) {

    //獲取密碼,實際值是驗證碼
    String password = integrationAuthentication.getAuthParameter("password");
    //獲取用戶名,實際值是手機號
    String username = integrationAuthentication.getUsername();
    //發(fā)布事件,可以監(jiān)聽事件進行自動注冊用戶
    this.applicationEventPublisher.publishEvent(new SmsAuthenticateBeforeEvent(integrationAuthentication));
    //通過手機號碼查詢用戶
    UserVO userVo = this.ucClient.findUserByPhoneNumber(username);
    if (userVo != null) {
      //將密碼設置為驗證碼
      userVo.setPassword(passwordEncoder.encode(password));
      //發(fā)布事件,可以監(jiān)聽事件進行消息通知
      this.applicationEventPublisher.publishEvent(new SmsAuthenticateSuccessEvent(integrationAuthentication));
    }
    return userVo;
  }

  @Override
  public void prepare(IntegrationAuthentication integrationAuthentication) {
    String smsToken = integrationAuthentication.getAuthParameter("sms_token");
    String smsCode = integrationAuthentication.getAuthParameter("password");
    String username = integrationAuthentication.getAuthParameter("username");
    Result<Boolean> result = vccClient.validate(smsToken, smsCode, username);
    if (!result.getData()) {
      throw new OAuth2Exception("驗證碼錯誤或已過期");
    }
  }

  @Override
  public boolean support(IntegrationAuthentication integrationAuthentication) {
    return SMS_AUTH_TYPE.equals(integrationAuthentication.getAuthType());
  }

  @Override
  public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    this.applicationEventPublisher = applicationEventPublisher;
  }
}

SmsIntegrationAuthenticator會對登錄的短信驗證碼進行預處理,判斷其是否非法,如果是非法的則直接中斷登錄。如果通過預處理則在獲取用戶信息的時候通過手機號去獲取用戶信息,并將密碼重置,以通過后續(xù)的密碼校驗。

總結

在這個解決方案中,主要是使用責任鏈和適配器的設計模式來解決集成登錄的問題,提高了可擴展性,并對spring的源碼無污染。如果還要繼承其他的登錄,只需要實現(xiàn)自定義的IntegrationAuthenticator就可以。

項目地址:https://gitee.com/leecho/cola-cloud

本地下載:cola-cloud_jb51.rar

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • SpringBoot設置默認主頁的方法步驟

    SpringBoot設置默認主頁的方法步驟

    這篇文章主要介紹了SpringBoot設置默認主頁的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • Mybatis之collection標簽中javaType和ofType屬性的區(qū)別說明

    Mybatis之collection標簽中javaType和ofType屬性的區(qū)別說明

    這篇文章主要介紹了Mybatis之collection標簽中javaType和ofType屬性的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java實現(xiàn)矩陣乘法以及優(yōu)化的方法實例

    Java實現(xiàn)矩陣乘法以及優(yōu)化的方法實例

    這篇文章主要給大家介紹了關于Java實現(xiàn)矩陣乘法以及優(yōu)化的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-02-02
  • 為什么說要慎用SpringBoot @ComponentScan

    為什么說要慎用SpringBoot @ComponentScan

    本文主要介紹了為什么說要慎用SpringBoot @ComponentScan,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧
    2021-07-07
  • 簡單了解Spring中BeanFactory與FactoryBean的區(qū)別

    簡單了解Spring中BeanFactory與FactoryBean的區(qū)別

    這篇文章主要介紹了簡單了解Spring中BeanFactory與FactoryBean的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12
  • java實現(xiàn)2048小游戲(含注釋)

    java實現(xiàn)2048小游戲(含注釋)

    這篇文章主要為大家介紹了java實現(xiàn)2048小游戲,含詳細注釋,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • Springboot項目使用攔截器方法詳解

    Springboot項目使用攔截器方法詳解

    這篇文章主要介紹了Springboot項目使用攔截器方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-09-09
  • Maven版本沖突的三種解決方法

    Maven版本沖突的三種解決方法

    在Maven項目中,依賴傳遞可能導致Jar包版本沖突,常見的解決策略包括依賴排除、版本鎖定和使用maven-shade-plugin插件,本文就來介紹一下這三種解決方法,感興趣的可以了解一下
    2024-10-10
  • Java程序命令行參數(shù)用法總結

    Java程序命令行參數(shù)用法總結

    這篇文章主要介紹了Java程序命令行參數(shù)用法總結,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • SpringCloud?Ribbon負載均衡原理

    SpringCloud?Ribbon負載均衡原理

    這篇文章主要介紹了SpringCloud?Ribbon負載均衡原理,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,感興趣的朋友可以參考一下
    2022-09-09

最新評論