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
上一篇:爬蟲案例:爬取小說網站 筆趣閣
