spring cloud oauth2(三) 自定義授權類型 手機號+短信驗證碼


第一部分:關於授權類型 grant_type 的解析

  1. 每種 grant_type 都會有一個對應的 TokenGranter 實現類。
  2. 所有 TokenGranter 實現類都通過 CompositeTokenGranter 中的 tokenGranters 集合存起來。
  3. 然后通過判斷 grantType 參數來定位具體使用那個 TokenGranter 實現類來處理授權。

第二部分:關於授權登錄邏輯

  1. 每種 授權方式 都會有一個對應的 AuthenticationProvider 實現類來實現。
  2. 所有 AuthenticationProvider 實現類都通過 ProviderManager 中的 providers 集合存起來。
  3. TokenGranter 類會 new 一個 AuthenticationToken 實現類,如 UsernamePasswordAuthenticationToken 傳給 ProviderManager 類。
  4. ProviderManager 則通過 AuthenticationToken 來判斷具體使用那個 AuthenticationProvider 實現類來處理授權。
  5. 具體的登錄邏輯由 AuthenticationProvider 實現類來實現,如 DaoAuthenticationProvider

所有的授權類型都會繼承 AbstractTokenGranter

自定義grant_type步驟

  • 自定義token,繼承自 **AbstractAuthenticationToken* * ,用於到登錄的時候傳入認證信息
  • 自定義模式的提供者,繼承自 *implements AuthenticationProvider, MessageSourceAware* ;作用是如果當前的token為該提供的類型,會調用authenticate方法進行登錄操作
  • 自定義TokenGranter,在getOAuth2Authentication 方法中傳入自定義的token進行驗證

自定義token類型,直接復制 UsernamePasswordAuthenticationToken

package com.Lonni.oauth.token;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

/**
 * 繼承自AbstractAuthenticationToken 用於自定義的token
 *
 */
public class MobileCodeAuthenticationToken extends AbstractAuthenticationToken {


    // ~ Instance fields
    // ================================================================================================

    /**
     * 認證之前  存的是手機號
     * 認證之后 存的是用戶信息
     */

    private final Object principal;
    private Object credentials;


    public MobileCodeAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        //設置沒有認證
        setAuthenticated(false);
    }


    public MobileCodeAuthenticationToken(Object principal, Object credentials,
                                               Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        // must use super, as we override
        //設置已經認證
        super.setAuthenticated(true);
    }

@Override
    public Object getCredentials() {
        return this.credentials;
    }
    @Override
    public Object getPrincipal() {
        return this.principal;
    }
    @Override
    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();
        credentials = null;
    }
}

自定義模式提供者

package com.Lonni.oauth.provider;


import com.Lonni.common.constant.AuthConstant;
import com.Lonni.core.Constants.AppConstans;
import com.Lonni.oauth.service.UserDetailsServiceImpl;
import com.Lonni.oauth.token.MobileCodeAuthenticationToken;
import org.apache.commons.lang.NullArgumentException;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;

/**
 * 自定義手機模式的提供者
 * 判斷token類型是否為MobileCodeAuthenticationToken,如果是則會使用此provider
 */

public class MobileCodeAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {

    private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    /**
     * userDetailsService的實現類 不需要自動注入 直接傳入
     */
    private UserDetailsServiceImpl userDetailsService;

    private RedisTemplate redisTemplate;

    /**
     * 業務處理的方法
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //未認證之前是手機號 認證之后是客戶信息
        String principal =(String) authentication.getPrincipal();
        if (StringUtils.isEmpty(principal)){
            throw  new BadCredentialsException("手機號不能為空");
        }

        // 這里的Credentials是先通過AbstractTokenGranter組裝  new MobileCodeAuthenticationToken()傳入的
        String code = (String) authentication.getCredentials();
        if (code == null) {
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "手機驗證碼不能為空"));
        }
        //正式環境放開注釋

//        String key=AuthConstant.AUTH_SMS_CACHE_KEY+principal+code;
//        Object cacheCode = redisTemplate.opsForValue().get(key);
//        if (cacheCode == null || !cacheCode.toString().equals(code)) {
//            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "驗證碼無效"));
//        }
        //清除redis中的短信驗證碼
        //stringRedisTemplate.delete(RedisConstant.SMS_CODE_PREFIX + mobile);
        UserDetails user;
        try {
            user = userDetailsService.loadUserByPhone(principal);
        } catch (UsernameNotFoundException var6) {
           throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "查詢用戶信息失敗!"));
        }
        check(user);
        MobileCodeAuthenticationToken authenticationToken = new MobileCodeAuthenticationToken(user, code, user.getAuthorities());
        authenticationToken.setDetails(authenticationToken.getDetails());
        return authenticationToken;
    }

    /**
     * 賬號禁用、鎖定、超時校驗
     *
     * @param user
     */
    private void check(UserDetails user) {
        if (user==null){
            throw  new NullArgumentException(this.messages.getMessage("未查詢到用戶", "未查詢到用戶"));
        }
        if (!user.isAccountNonLocked()) {
            throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
        } else if (!user.isEnabled()) {
            throw new DisabledException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
        } else if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
        }
    }

    /**
     * 判斷是否為MobileCodeAuthenticationToken類型  如果是 直接調用 切斷過濾器鏈
     * 如果不是 繼續查找
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return MobileCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }


     public void  setUserDetailsService(UserDetailsServiceImpl userDetailsService){
        this.userDetailsService=userDetailsService;
    }
    public void  setRedisTemplate( RedisTemplate redisTemplate){
        this.redisTemplate=redisTemplate;
    }
}

自定義 MobileCodeTokenGranter

package com.Lonni.oauth.granter;

import com.Lonni.oauth.token.MobileCodeAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import java.util.LinkedHashMap;
import java.util.Map;

public class MobileCodeTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "mobile";

    private final AuthenticationManager authenticationManager;

    public MobileCodeTokenGranter(AuthenticationManager authenticationManager,
                                  AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    private MobileCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                                   ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String mobile = parameters.get("mobile");
        String code = parameters.get("code");
        //調用自定義的token擴展 實現用戶認證 
        Authentication userAuth = new MobileCodeAuthenticationToken(mobile,code);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        }
        catch (AccountStatusException ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }
        catch (BadCredentialsException e) {
            // If the username/password are wrong the spec says we should send 400/invalid grant
            throw new InvalidGrantException(e.getMessage());
        }
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate mobile: " + mobile);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
}

將自定義的TokenGanter加入到security中

復制上一節的 TokenGranterExt擴展類 加入手機驗證碼模式

        //添加手機驗證碼
        //granters.add(new SmsCodeTokenGranter(authenticationManager, userDetailsService, redisTemplate, endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));

        granters.add(new MobileCodeTokenGranter(authenticationManager,  endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM