主頁 > 後端開發 > springboot+sercuity+oauth2+Jwt+手機號+微信+密碼 企業級認證與授權原理以及實作(完整版)

springboot+sercuity+oauth2+Jwt+手機號+微信+密碼 企業級認證與授權原理以及實作(完整版)

2020-12-08 17:02:05 後端開發

springboot+sercuity+oauth2+Jwt企業級認證與授權

  • 1.OAUTH認證流程分析
  • 2.原始碼分析
    • 2.1TokenEndpoint
    • 2.2CompositeTokenGranter
    • 2.3AbstractTokenGranter
    • 2.4 getOAuth2Authentication
    • 2.5 ResourceOwnerPasswordTokenGranter
    • 2.6 AbstractAuthenticationToken
    • 2.7 OAuth2Authentication
    • 2.8AuthenticationManager
    • 2.9ProviderManager
    • 2.10AbstractUserDetailsAuthenticationProvider
  • 3.類圖
  • 4.具體實作
    • 4.1自定義Provider implements AuthenticationProvider
    • 4.2自定義AuthenticationToken extends AbstractAuthenticationToken
    • 4.3自定義TokenGranter
    • 4.4 OauthConfig

有關什么是OAUTH2的描述

1.OAUTH認證流程分析

在這里插入圖片描述

2.原始碼分析

2.1TokenEndpoint

TokenEndpoint為令牌訪問端點
類上注解FrameworkEndpoint,這個注解和Spring的Controller注解一樣的,只是這個FrameworkEndpoint注解是給框架用的,它有處理/oauth/token的方法,分別對應get和post的請求
在這里插入圖片描述

2.2CompositeTokenGranter

ComPositeTokenGranter是多個 TokenGranter 的集合
一共有兩個方法,
1.addTokenGranter添加 TokenGranter ,
2.grant * 從 TokenGranter 串列中挨個提出來獲取準許證 (也就是我們要的 Token), 只要有個能獲取到立即回傳結果,將引數中的grant_type和集合中的進行匹配


/**
 * @author Dave Syer
 */
public class CompositeTokenGranter implements TokenGranter {
 
    private final List<TokenGranter> tokenGranters;
 
    public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
        this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
    }
    
    /**
     * 從 TokenGranter 串列中挨個提出來獲取準許證 (也就是我們要的 Token), 只要有個能獲取到立即回傳結果,
     */
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        for (TokenGranter granter : tokenGranters) {
            OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
            if (grant!=null) {
                return grant;
            }
        }
        return null;
    }
    
    /**
     * 添加 TokenGranter
     */
    public void addTokenGranter(TokenGranter tokenGranter) {
        if (tokenGranter == null) {
            throw new IllegalArgumentException("Token granter is null");
        }
        tokenGranters.add(tokenGranter);
    }
}

2.3AbstractTokenGranter

多個TokenGranter的子類
AuthorizationCodeTokenGranter 授權碼模式
ClientCredentialsTokenGranter 客戶端模式
ImplicitTokenGranter implicit 模式
RefreshTokenGranter 重繪 token 模式
ResourceOwnerPasswordTokenGranter 密碼模式
包括自定義模式 都繼承了AbstractTokenGranter
1.判斷傳入的 grantType 是否符合當前的 TokenGranter
2.根據 TokenRequest 中的 clientId 加載 Client 資訊
3.判斷 Client 資訊中的 authorizedGrantTypes (已授權的 grantType) 是否包含著傳入的 grantType
4.通過 tokenService 創建 OAuth2AccessToken 并回傳

/**
 * @author Dave Syer
 * 
 */
public abstract class AbstractTokenGranter implements TokenGranter {
	
	protected final Log logger = LogFactory.getLog(getClass());

	private final AuthorizationServerTokenServices tokenServices;

	private final ClientDetailsService clientDetailsService;
	
	private final OAuth2RequestFactory requestFactory;
	
	private final String grantType;

	protected AbstractTokenGranter(AuthorizationServerTokenServices tokenServices,
			ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
		this.clientDetailsService = clientDetailsService;
		this.grantType = grantType;
		this.tokenServices = tokenServices;
		this.requestFactory = requestFactory;
	}

	public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
		// 判斷引數的的 grant_type 是否符合當前的 TokenGranter,
		if (!this.grantType.equals(grantType)) {
			return null;
		}
		// 根據 TokenRequest 中的 clientId 加載 Client 資訊
		String clientId = tokenRequest.getClientId();
		// 判斷 Client 資訊中的 authorizedGrantTypes (已授權的 grant_type) 是否包含著傳入的 grant_type
		ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
		validateGrantType(grantType, client);
		/**
		  *	tokenService 創建 OAuth2AccessToken 并回傳
          *	TokenRequest 里有調介面時傳進的 parameters (包含授權根據, 例如 username, password 等), 
          *	根據 client 資訊 + tokenRequest 最終可得到 OAuth2AccessToken
          */
		if (logger.isDebugEnabled()) {
			logger.debug("Getting access token for: " + clientId);
		}

		return getAccessToken(client, tokenRequest);

	}
	protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
		return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
	}
	.........	
	

2.4 getOAuth2Authentication

在AbstractTokenGranter中,的getOAuth2Authentication()方法 會找到對應的AbstractTokenGranter實作類,進行獲取OAuth2Authentication,下面以用戶名密碼為例

2.5 ResourceOwnerPasswordTokenGranter

重寫了AbstractTokenGranter 的 getOAuth2Authentication方法做了以下幾步
1.不將密碼放入 details 中, 防止密碼泄露
2.組裝用戶密碼模式的認證資訊
3.通過 authenticationManager 認證已組裝好的資訊
4.過期、被鎖定、用戶名密碼錯誤不能用的時候拋出例外
5.從工廠創建 OAuth2Request

/**
 * @author Dave Syer
 * 
 */
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
	private static final String GRANT_TYPE = "password";
	private final AuthenticationManager authenticationManager;
	public ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager,
			AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
		this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
	}
	protected ResourceOwnerPasswordTokenGranter(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 username = parameters.get("username");
		String password = parameters.get("password");
		// Protect from downstream leaks of password
		// 洗掉密碼, 防止密碼泄露
		parameters.remove("password");
 		// 組裝用戶密碼模式的認證資訊
		Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
		((AbstractAuthenticationToken) userAuth).setDetails(parameters);
		try {
		 通過 authenticationManager 認證
			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
			//:如果用戶名/密碼是錯誤的,規范說我們應該發送400/無效的授權
			throw new InvalidGrantException(e.getMessage());
		}
		if (userAuth == null || !userAuth.isAuthenticated()) {
		//無法驗證用戶身份
			throw new InvalidGrantException("Could not authenticate user: " + username);
		}
        // 從工廠創建 OAuth2Request
		OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);		
		return new OAuth2Authentication(storedOAuth2Request, userAuth);
	}
}

2.6 AbstractAuthenticationToken

AbstractAuthenticationToken抽象類是Authentication物件的基類,可自定義AuthenticationToken繼承抽象類重寫方法

2.7 OAuth2Authentication

判斷用哪個provider
TokenGranter 類會 new 一個 AuthenticationToken 實作類,如 UsernamePasswordAuthenticationToken 傳給 ProviderManager 類,而 ProviderManager 則通過 AuthenticationToken 來判斷具體使用那個 AuthenticationProvider 實作類來處理授權,
具體的登錄邏輯由 AuthenticationProvider 實作類來實作

2.8AuthenticationManager

用于抽象建模認證管理器,用于處理一個認證請求,是認證方法的入口,接收一個Authentication物件作為引數也就是Spring Security中的Authentication認證令牌,

2.9ProviderManager

在ProviderManager的authenticate方法中,輪訓成員變數List providers,該providers中如果有一個
AuthenticationProvider的supports函式回傳true,那么就會呼叫該AuthenticationProvider的authenticate函式認證,如果認證成功則整個
認證程序結束,如果不成功,則繼續使用下一個合適的AuthenticationProvider進行認證,只要有一個認證成功則為認證成功,
在這里插入圖片描述

2.10AbstractUserDetailsAuthenticationProvider

用戶密碼校驗
實作了AuthenticationProvider,authenticate方法呼叫UserDetils,查詢用戶資訊,回傳已認證的Authentication

3.類圖

在這里插入圖片描述

4.具體實作

4.1自定義Provider implements AuthenticationProvider

手機號登錄

package com.aeotrade.provider.oauth.sms;

import com.aeotrade.base.constant.AeoConstant;
import com.aeotrade.provider.oauth.service.MoblieUserDetailsService;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Author: yewei
 * @Date: 9:08 2020/11/25
 * @Description:
 * 在AuthenticationManager認證程序中,
 * 是通過AuthenticationProvider介面的擴展來實作自定義認證方式的,
 * 定義手機和驗證碼認證提供者PhoneAndVerificationCodeAuthenticationProvider
 */
public class MobileAuthenticationProvider implements AuthenticationProvider {


    /**
     * UserDetailsService
     */
    private MoblieUserDetailsService UserDetailsService;

    /**
     * redis服務
     */
    private StringRedisTemplate stringRedisTemplate;


    public MobileAuthenticationProvider(MoblieUserDetailsService UserDetailsService, StringRedisTemplate redisTemplate) {
        this.UserDetailsService = UserDetailsService;
        this.stringRedisTemplate = redisTemplate;
    }

    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        MobileAuthenticationToken phoneAndVerificationCodeAuthenticationToken = (MobileAuthenticationToken) authentication;

        Object verificationCodeObj;
        String verificationCode = Objects.nonNull(verificationCodeObj = phoneAndVerificationCodeAuthenticationToken.getCredentials()) ?
                verificationCodeObj.toString() : StringUtils.EMPTY;

        Object phoneNumberObj;
        String phoneNumber = Objects.nonNull(phoneNumberObj = phoneAndVerificationCodeAuthenticationToken.getPrincipal())
                ? phoneNumberObj.toString() : StringUtils.EMPTY;
        // 驗證用戶
        if (StringUtils.isBlank(phoneNumber)) {
            throw new InternalAuthenticationServiceException("電話號碼為空!");
        }
        // 根據電話號碼獲取用戶
        UserDetails userDetails = UserDetailsService.loadUserByMoblie(phoneNumber);
        if (Objects.isNull(userDetails)) {
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService 為空");
        }
        //校驗驗證碼
        //驗證的過期時間
        Long expire = stringRedisTemplate.opsForValue().getOperations().getExpire(AeoConstant.SMSREDIS_KEY+phoneNumber);
        System.out.println("redis過期時間回傳"+expire);
        if (  expire <= 0 ) {
            throw new InternalAuthenticationServiceException(
                    "驗證碼已過期");
        }
        String cmsCode = this.getCmsCode(phoneNumber);
        if ( cmsCode == null && StringUtils.isEmpty(cmsCode)) {
            throw new InternalAuthenticationServiceException(
                    "驗證碼為空!");
        }
        //校驗驗證碼
        if (!verificationCode.equals(cmsCode)){
            throw new InternalAuthenticationServiceException(
                    "驗證碼錯誤!");
        }
        stringRedisTemplate.delete(AeoConstant.SMSREDIS_KEY+phoneNumber);
        // 封裝需要認證的PhoneAndVerificationCodeAuthenticationToken物件
        return new MobileAuthenticationToken(userDetails.getAuthorities(), userDetails, verificationCode);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return MobileAuthenticationToken.class.isAssignableFrom(authentication);
    }
    //從redis查詢驗證碼
    public String getCmsCode(String phoneNumber) {
        //從redis中取到令牌資訊
        String value = stringRedisTemplate.opsForValue().get(AeoConstant.SMSREDIS_KEY+phoneNumber);
        //轉成物件
        return StringUtils.isEmpty(value)?"":value;
    }
}


微信登錄

package com.aeotrade.provider.oauth.wx;

import com.aeotrade.provider.oauth.service.WxUserDetailsService;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * @Author: yewei
 * @Date: 17:08 2020/11/25
 * @Description:
 */
public class OpenIdAuthenticationProvider implements AuthenticationProvider {

    private WxUserDetailsService userDetailsService;

    public OpenIdAuthenticationProvider( WxUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) {
        OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;
        String openId = (String) authenticationToken.getPrincipal();
        UserDetails user = userDetailsService.loadUserByOpenId(openId);
        if (user == null) {
            throw new InternalAuthenticationServiceException("openId錯誤");
        }
        OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(user, user.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public WxUserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(WxUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

4.2自定義AuthenticationToken extends AbstractAuthenticationToken

手機號登錄

package com.aeotrade.provider.oauth.sms;

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

import java.util.Collection;

/**
 * @Author: yewei
 * @Date: 9:06 2020/11/25
 * @Description:
 * 在OAuth2認證開始認證時,
 * 會提前Authentication認證資訊,
 * 然后交由AuthenticationManager認證,
 * 定義電話號碼+驗證碼的Authentication認證資訊
 */
public class MobileAuthenticationToken extends AbstractAuthenticationToken {

    /**
     * 手機號
     */
    private final Object mobile;

    /**
     * 驗證碼
     */
    private final Object code;



    public MobileAuthenticationToken(Object mobile, Object code) {
        super(null);
        this.mobile = mobile;
        this.code = code;
    }

    public MobileAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object mobile, Object code) {
        super(authorities);
        this.mobile = mobile;
        this.code = code;
        // 認證已經通過
        setAuthenticated(true);
    }

    /**
     * 用戶身份憑證(一般是密碼或者驗證碼)
     */
    @Override
    public Object getCredentials() {
        return code;
    }

    /**
     * 身份標識(一般是姓名,手機號)
     */
    @Override
    public Object getPrincipal() {
        return mobile;
    }
}

微信

package com.aeotrade.provider.oauth.wx;

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

import java.util.Collection;

/**
 * @Author: yewei
 * @Date: 17:08 2020/11/25
 * @Description:
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

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

	private final Object principal;

	// ~ Constructors
	// ===================================================================================================

	/**
	 * This constructor can be safely used by any code that wishes to create a
	 * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
	 * will return <code>false</code>.
	 *
	 */
	public OpenIdAuthenticationToken(String openId) {
		super(null);
		this.principal = openId;
		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 authorities
	 */
	public OpenIdAuthenticationToken(Object principal,
                                     Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		super.setAuthenticated(true);
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public Object getCredentials() {
		return null;
	}

	@Override
	public Object getPrincipal() {
		return this.principal;
	}

	@Override
	public void setAuthenticated(boolean isAuthenticated) {
		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();
	}
}

4.3自定義TokenGranter

手機號

package com.aeotrade.provider.oauth.sms;

import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * @Author: yewei
 * @Date: 9:10 2020/11/25
 * @Description:
 * 新增電話驗證碼型別,PhoneAndVerificationCodeTokenGranter,
 * 參考密碼型別ResourceOwnerPasswordTokenGranter的認證流程,
 * 首先進行電話號碼與驗證碼的認證,然后生成訪問授權碼
 */
public class MobileTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "cms_code";

    private final AuthenticationManager authenticationManager;

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

    @Autowired
    protected MobileTokenGranter(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<>(tokenRequest.getRequestParameters());
        // 電話號碼與驗證碼
        String phoneNumber = parameters.get("mobile");
        String verificationCode = parameters.get("cms_code");

        Authentication userAuth = new MobileAuthenticationToken(phoneNumber, verificationCode);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            // authenticationManager進行驗證
            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 phone number: " + phoneNumber);
        }
        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
}

微信

package com.aeotrade.provider.oauth.wx;

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

import java.util.Collection;

/**
 * @Author: yewei
 * @Date: 17:08 2020/11/25
 * @Description:
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

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

	private final Object principal;

	// ~ Constructors
	// ===================================================================================================

	/**
	 * This constructor can be safely used by any code that wishes to create a
	 * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
	 * will return <code>false</code>.
	 *
	 */
	public OpenIdAuthenticationToken(String openId) {
		super(null);
		this.principal = openId;
		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 authorities
	 */
	public OpenIdAuthenticationToken(Object principal,
                                     Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		super.setAuthenticated(true);
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public Object getCredentials() {
		return null;
	}

	@Override
	public Object getPrincipal() {
		return this.principal;
	}

	@Override
	public void setAuthenticated(boolean isAuthenticated) {
		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();
	}
}

4.4 OauthConfig

OauthConfig的具體配置

package com.aeotrade.provider.oauth.config;

import com.aeotrade.provider.oauth.service.AeoWebResponseExceptionTranslator;
import com.aeotrade.provider.oauth.service.AeotradeJdbcClientDetailsService;
import com.aeotrade.provider.oauth.service.RedisClientDetailsService;
import com.aeotrade.provider.oauth.service.TokenJwtEnhancer;
import com.aeotrade.provider.oauth.sms.AeotradeCompositeTokenGranter;
import com.aeotrade.provider.oauth.sms.AeotradeTokenGranter;
import com.aeotrade.provider.oauth.sms.MobileTokenGranter;
import com.aeotrade.provider.oauth.wx.OpenIdGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.util.FileCopyUtils;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableAuthorizationServer
public class OauthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AeoWebResponseExceptionTranslator exceptionTranslator;

    @Autowired
    private RedisClientDetailsService clientDetailsService;
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("permitAll()");
        security.tokenKeyAccess("permitAll()");
        security.passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        /**
         * 從資料庫讀取客戶端配置
        clients.withClientDetails(clientDetailsService()); */
        /**從redis讀取*/
        clients.withClientDetails(clientDetailsService);
        clientDetailsService.loadAllClientToCache();
    }

 /* 資料庫讀取客戶端的配置
    private ClientDetailsService clientDetailsService() {
        AeotradeJdbcClientDetailsService aeotradeJdbcClientDetailsService = new AeotradeJdbcClientDetailsService(dataSource);
        aeotradeJdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return aeotradeJdbcClientDetailsService;
    }*/

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

        // 初始化所有的TokenGranter,并且型別為CompositeTokenGranter
        List<TokenGranter> defaultTokenGranters = this.getDefaultTokenGranters(endpoints);
        //添加手機號模式
        defaultTokenGranters.add(new MobileTokenGranter(authenticationManager, endpoints.getTokenServices(),
                endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        //添加微信模式
        defaultTokenGranters.add(new OpenIdGranter(authenticationManager,endpoints.getTokenServices(),
                endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));

        endpoints.tokenGranter(new CompositeTokenGranter(defaultTokenGranters));
        endpoints
                .tokenStore(tokenStore())    /**采用JWT非對稱加密*/
                .authenticationManager(authenticationManager)
                .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET);
                //.userDetailsService(userDetailsService).exceptionTranslator(exceptionTranslator);
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancerList = new ArrayList<>();
        enhancerList.add(tokenEnhancer());
        enhancerList.add(jwtAccessTokenConverter());
        enhancerChain.setTokenEnhancers(enhancerList);
        endpoints
                .tokenEnhancer(enhancerChain)
                .accessTokenConverter(jwtAccessTokenConverter());
        new AeotradeCompositeTokenGranter(defaultTokenGranters);
    }


    private TokenEnhancer tokenEnhancer() {
        return new TokenJwtEnhancer();
    }

    @Bean
    @Primary
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return tokenServices;
    }
    /**采用JWT非對稱加密*/
    private TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("aeotrade.jks"),
                "hmtx20191001".toCharArray());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("aeotrade"));

        Resource resouce=new ClassPathResource("aeotrade.crt");
        String publicKey=null;
        try {
            publicKey=new String(FileCopyUtils.copyToByteArray(resouce.getInputStream()),"utf-8");
        }catch (Exception e){
            throw new RuntimeException(e);
        }

        //converter.setVerifierKey(publicKey);
        converter.setVerifier(new RsaVerifier(publicKey));

        return converter;
    }

    @Bean
    public ResourceOwnerPasswordTokenGranter resourceOwnerPasswordTokenGranter(@Autowired AuthenticationManager authenticationManager,@Autowired OAuth2RequestFactory oAuth2RequestFactory) {
        DefaultTokenServices defaultTokenServices = defaultTokenServices();
            defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return new ResourceOwnerPasswordTokenGranter(authenticationManager, defaultTokenServices, clientDetailsService, oAuth2RequestFactory);
    }

    @Bean
    public MobileTokenGranter mobileTokenGranter(@Autowired AuthenticationManager authenticationManager,@Autowired OAuth2RequestFactory oAuth2RequestFactory){
        DefaultTokenServices defaultTokenServices = defaultTokenServices();
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return new MobileTokenGranter(authenticationManager,defaultTokenServices, clientDetailsService, oAuth2RequestFactory);
    }
    @Bean
    public OpenIdGranter openIdGranter(@Autowired AuthenticationManager authenticationManager,@Autowired OAuth2RequestFactory oAuth2RequestFactory){
        DefaultTokenServices defaultTokenServices = defaultTokenServices();
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return new OpenIdGranter(authenticationManager,defaultTokenServices, clientDetailsService, oAuth2RequestFactory);
    }

    @Bean
    public DefaultOAuth2RequestFactory oAuth2RequestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }
    /**
     * 初始化所有的TokenGranter
     */
    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {

        ClientDetailsService clientDetails = endpoints.getClientDetailsService();
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();

        List<TokenGranter> tokenGranters = new ArrayList<>();
        // 添加授權碼模式
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,requestFactory));
        // 添加重繪令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
        ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
        // 添加影式授權模式
        tokenGranters.add(implicit);
        // 添加客戶端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
        if (authenticationManager != null) {
            // 添加密碼模式
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
                    clientDetails, requestFactory));
        }
        return tokenGranters;
    }

}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231603.html

標籤:java

上一篇:爬蟲案例:爬取小說網站 筆趣閣

下一篇:EXCEL連接的資料,要過濾表名_xlnm#_FilterDatabase,把其它的表名顯示到ComboBox

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more