成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Spring Security OAuth2 優(yōu)雅的集成短信驗(yàn)證碼登錄以及第三方登錄

yck / 2880人閱讀

摘要:前言基于做微服務(wù)架構(gòu)分布式系統(tǒng)時(shí),作為認(rèn)證的業(yè)內(nèi)標(biāo)準(zhǔn),也提供了全套的解決方案來(lái)支持在環(huán)境下使用,提供了開(kāi)箱即用的組件。

前言

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

圖片驗(yàn)證碼登錄

短信驗(yàn)證碼登錄

微信小程序登錄

第三方系統(tǒng)登錄

CAS單點(diǎn)登錄

在面對(duì)這些場(chǎng)景的時(shí)候,預(yù)計(jì)很多對(duì)Spring Security OAuth2不熟悉的人恐怕會(huì)無(wú)從下手?;谏鲜龅膱?chǎng)景要求,如何優(yōu)雅的集成短信驗(yàn)證碼登錄及第三方登錄,怎么樣才算是優(yōu)雅集成呢?有以下要求:

不侵入Spring Security OAuth2的原有代碼

對(duì)于不同的登錄方式不擴(kuò)展新的端點(diǎn),使用/oauth/token可以適配所有的登錄方式

可以對(duì)所有登錄方式進(jìn)行兼容,抽象一套模型只要簡(jiǎn)單的開(kāi)發(fā)就可以集成登錄

基于上述的設(shè)計(jì)要求,接下來(lái)將會(huì)在文章種詳細(xì)介紹如何開(kāi)發(fā)一套集成登錄認(rèn)證組件開(kāi)滿足上述要求。

閱讀本篇文章您需要了解OAuth2.0認(rèn)證體系、SpringBoot、SpringSecurity以及Spring Cloud等相關(guān)知識(shí)
思路

我們來(lái)看下Spring Security OAuth2的認(rèn)證流程:

這個(gè)流程當(dāng)中,切入點(diǎn)不多,集成登錄的思路如下:

在進(jìn)入流程之前先進(jìn)行攔截,設(shè)置集成認(rèn)證的類型,例如:短信驗(yàn)證碼、圖片驗(yàn)證碼等信息。

在攔截的通知進(jìn)行預(yù)處理,預(yù)處理的場(chǎng)景有很多,比如驗(yàn)證短信驗(yàn)證碼是否匹配、圖片驗(yàn)證碼是否匹配、是否是登錄IP白名單等處理

在UserDetailService.loadUserByUsername方法中,根據(jù)之前設(shè)置的集成認(rèn)證類型去獲取用戶信息,例如:通過(guò)手機(jī)號(hào)碼獲取用戶、通過(guò)微信小程序OPENID獲取用戶等等

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

實(shí)現(xiàn)

介紹完思路之后,下面通過(guò)代碼來(lái)展示如何實(shí)現(xiàn):

第一步,定義攔截器攔截登錄的請(qǐng)求
/**
 * @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 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)){
            //設(shè)置集成登錄信息
            IntegrationAuthentication integrationAuthentication = new IntegrationAuthentication();
            integrationAuthentication.setAuthType(request.getParameter(AUTH_TYPE_PARM_NAME));
            integrationAuthentication.setAuthParameters(request.getParameterMap());
            IntegrationAuthenticationContext.set(integrationAuthentication);
            try{
                //預(yù)處理
                this.prepare(integrationAuthentication);

                filterChain.doFilter(request,response);

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

    /**
     * 進(jìn)行預(yù)處理
     * @param integrationAuthentication
     */
    private void prepare(IntegrationAuthentication integrationAuthentication) {

        //延遲加載認(rèn)證器
        if(this.authenticators == null){
            synchronized (this){
                Map 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;
    }
}

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

第二步,將攔截器放入到攔截鏈條中
/**
 * @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;
    }
}

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

第三步,根據(jù)認(rèn)證類型來(lái)處理用戶信息
@Service
public class IntegrationUserDetailsService implements UserDetailsService {

    @Autowired
    private UpmClient upmClient;

    private List authenticators;

    @Autowired(required = false)
    public void setIntegrationAuthenticators(List 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("用戶名或密碼錯(cuò)誤");
        }

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

    }

    /**
     * 設(shè)置授權(quán)信息
     *
     * @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;
    }
}

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

@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只會(huì)處理沒(méi)有指定的認(rèn)證類型即是默認(rèn)的認(rèn)證類型,這個(gè)類中主要是通過(guò)用戶名獲取密碼。接下來(lái)來(lái)看下圖片驗(yàn)證碼登錄如何處理的:

/**
 * 集成驗(yàn)證碼認(rèn)證
 * @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");
        //驗(yàn)證驗(yàn)證碼
        Result result = vccClient.validate(vcToken, vcCode, null);
        if (!result.getData()) {
            throw new OAuth2Exception("驗(yàn)證碼錯(cuò)誤");
        }
    }

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

VerificationCodeIntegrationAuthenticator繼承UsernamePasswordAuthenticator,因?yàn)槠渲皇切枰趐repare方法中驗(yàn)證驗(yàn)證碼是否正確,獲取用戶還是用過(guò)用戶名密碼的方式獲取。但是需要認(rèn)證類型為"vc"才會(huì)處理
接下來(lái)來(lái)看下短信驗(yàn)證碼登錄是如何處理的:

@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) {

        //獲取密碼,實(shí)際值是驗(yàn)證碼
        String password = integrationAuthentication.getAuthParameter("password");
        //獲取用戶名,實(shí)際值是手機(jī)號(hào)
        String username = integrationAuthentication.getUsername();
        //發(fā)布事件,可以監(jiān)聽(tīng)事件進(jìn)行自動(dòng)注冊(cè)用戶
        this.applicationEventPublisher.publishEvent(new SmsAuthenticateBeforeEvent(integrationAuthentication));
        //通過(guò)手機(jī)號(hào)碼查詢用戶
        UserVO userVo = this.ucClient.findUserByPhoneNumber(username);
        if (userVo != null) {
            //將密碼設(shè)置為驗(yàn)證碼
            userVo.setPassword(passwordEncoder.encode(password));
            //發(fā)布事件,可以監(jiān)聽(tīng)事件進(jì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 result = vccClient.validate(smsToken, smsCode, username);
        if (!result.getData()) {
            throw new OAuth2Exception("驗(yàn)證碼錯(cuò)誤或已過(guò)期");
        }
    }

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

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

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

總結(jié)

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

項(xiàng)目地址:https://gitee.com/leecho/cola...
大家有好的建議和想法可以一起溝通交流。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69060.html

相關(guān)文章

  • Spring Security Oauth2.0 實(shí)現(xiàn)短信驗(yàn)證登錄

    摘要:驗(yàn)證碼的發(fā)放校驗(yàn)邏輯比較簡(jiǎn)單,方法后通過(guò)全局判斷請(qǐng)求中是否和手機(jī)號(hào)匹配集合,重點(diǎn)邏輯是令牌的參數(shù) spring security oauth2 登錄過(guò)程詳解 ? showImg(https://segmentfault.com/img/remote/1460000012811024); ? 定義手機(jī)號(hào)登錄令牌 /** * @author lengleng * @date 2018/...

    陸斌 評(píng)論0 收藏0
  • 前后端分離項(xiàng)目 — SpringSocial 綁定與解綁社交賬號(hào)如微信、QQ

    摘要:我們以微信為例,首先我們發(fā)送一個(gè)請(qǐng)求,因?yàn)槟阋呀?jīng)登錄了,所以后臺(tái)可以獲取當(dāng)前是誰(shuí),然后就獲取到請(qǐng)求的鏈接,最后就是跳轉(zhuǎn)到這個(gè)鏈接上面去。 1、準(zhǔn)備工作 申請(qǐng)QQ、微信相關(guān)AppId和AppSecret,這些大家自己到QQ互聯(lián)和微信開(kāi)發(fā)平臺(tái) 去申請(qǐng)吧 還有java后臺(tái)要引入相關(guān)的jar包,如下: org.springframework.security....

    tigerZH 評(píng)論0 收藏0
  • 前后端分離項(xiàng)目 — 基于SpringSecurity OAuth2.0用戶認(rèn)證

    摘要:前言現(xiàn)在的好多項(xiàng)目都是基于移動(dòng)端以及前后端分離的項(xiàng)目,之前基于的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線,尤其是當(dāng)基于的微服務(wù)架構(gòu)以及單頁(yè)面應(yīng)用流行起來(lái)后,情況更甚。使用生成是什么請(qǐng)自行百度。 1、前言 現(xiàn)在的好多項(xiàng)目都是基于APP移動(dòng)端以及前后端分離的項(xiàng)目,之前基于Session的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線,尤其是當(dāng)基于SpringCloud的微服務(wù)架構(gòu)以及...

    QLQ 評(píng)論0 收藏0
  • Spring Security

    摘要:框架具有輕便,開(kāi)源的優(yōu)點(diǎn),所以本譯見(jiàn)構(gòu)建用戶管理微服務(wù)五使用令牌和來(lái)實(shí)現(xiàn)身份驗(yàn)證往期譯見(jiàn)系列文章在賬號(hào)分享中持續(xù)連載,敬請(qǐng)查看在往期譯見(jiàn)系列的文章中,我們已經(jīng)建立了業(yè)務(wù)邏輯數(shù)據(jù)訪問(wèn)層和前端控制器但是忽略了對(duì)身份進(jìn)行驗(yàn)證。 重拾后端之Spring Boot(四):使用JWT和Spring Security保護(hù)REST API 重拾后端之Spring Boot(一):REST API的搭建...

    keelii 評(píng)論0 收藏0
  • Spring 指南(目錄)

    摘要:指南無(wú)論你正在構(gòu)建什么,這些指南都旨在讓你盡快提高工作效率使用團(tuán)隊(duì)推薦的最新項(xiàng)目版本和技術(shù)。使用進(jìn)行消息傳遞了解如何將用作消息代理。安全架構(gòu)的主題指南,這些位如何組合以及它們?nèi)绾闻c交互。使用的主題指南以及如何為應(yīng)用程序創(chuàng)建容器鏡像。 Spring 指南 無(wú)論你正在構(gòu)建什么,這些指南都旨在讓你盡快提高工作效率 — 使用Spring團(tuán)隊(duì)推薦的最新Spring項(xiàng)目版本和技術(shù)。 入門指南 這些...

    only_do 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<