在特定場景下,可能Oauth2自帶的4種認證模式可能滿足不了我們?nèi)粘5氖褂?那么今天就為大家?guī)鞳auth2自定義認證模式。
1. 什么是OAuth2.0
知道你們肯定沒耐心讀完(總結(jié)一句話就是授權(quán)用的),有耐心的可以讀完下面的內(nèi)容介紹:
首先呢在這之前我們要搞清楚什么是Oauth , OAuth 是一個開放標準,該標準允許用戶讓第三方應(yīng)用訪問該用戶在某一網(wǎng)站上存儲的私密資源(如頭像、照片、視頻等),而在這個過程中無需將用戶名和密碼提供給第三方應(yīng)用。實現(xiàn)這一功能是通過提供一個令牌(token),而不是用戶名和密碼來訪問他們存放在特定服務(wù)提供者的數(shù)據(jù)。
采用令牌(token)的方式可以讓用戶靈活的對第三方應(yīng)用授權(quán)或者收回權(quán)限。
OAuth2 是 OAuth 協(xié)議的下一版本,但不向下兼容 OAuth 1.0。
傳統(tǒng)的 Web 開發(fā)登錄認證一般都是基于 session 的,但是在前后端分離的架構(gòu)中繼續(xù)使用 session 就會有許多不便,因為移動端(Android、iOS、微信小程序等)要么不支持 cookie(微信小程序),要么使用非常不便,對于這些問題,使用 OAuth2 認證都能解決。
對于大家而言,我們在互聯(lián)網(wǎng)應(yīng)用中最常見的 OAuth2 應(yīng)該就是各種第三方登錄了,例如 QQ 授權(quán)登錄、微信授權(quán)登錄、微博授權(quán)登錄、GitHub 授權(quán)登錄等等。
2. 默認的四種驗證模式
授權(quán)碼模式:常見的第三方平臺登錄功能基本都是使用這種模式。
簡化模式:簡化模式是不需要客戶端服務(wù)器參與,直接在瀏覽器中向授權(quán)服務(wù)器申請令牌(token),一般如果網(wǎng)站是純靜態(tài)頁面則可以采用這種方式。
密碼模式:密碼模式是用戶把用戶名密碼直接告訴客戶端,客戶端使用說這些信息向授權(quán)服務(wù)器申請令牌(token)。這需要用戶對客戶端高度信任,例如客戶端應(yīng)用和服務(wù)提供商就是同一家公司,自己做前后端分離登錄就可以采用這種模式。
客戶端模式:客戶端模式是指客戶端使用自己的名義而不是用戶的名義向服務(wù)提供者申請授權(quán),嚴格來說,客戶端模式并不能算作 OAuth 協(xié)議要解決的問題的一種解決方案,但是,對于開發(fā)者而言,在一些前后端分離應(yīng)用或者為移動端提供的認證授權(quán)服務(wù)器上使用這種模式還是非常方便的。
3. 上代碼
好了有什么具體不懂的可以自行百度,費話不多說上代碼
== 首先我們需要創(chuàng)建一個類,它繼承抽象身份驗證令牌類(AbstractAuthenticationToken)==
// 我拿一個微信授權(quán)為例子
public class WechatAuthenticationToken extends AbstractAuthenticationToken {
// 這里呢相當(dāng)于實體類,用于存放數(shù)據(jù),里面的字段可以自己定義,我這里僅供參考,具體以實際業(yè)務(wù)需要為準
private static final long serialVersionUID = 550L;
//我也不知道這個是干嘛用的,反正必須要有
private final Object principal;
@Getter
private String encryptedData;
@Getter
private String iv;
@Getter
private String pEncryptedData;
@Getter
private String piv;
/**
* 賬號校驗之前的token構(gòu)造方法
*
* @param principal
*/
public WechatAuthenticationToken(Object principal, String encryptedData,String iv,String pEncryptedData, String piv) {
super(null);
this.principal = principal;
this.encryptedData = encryptedData;
this.iv=iv;
this.pEncryptedData = pEncryptedData;
this.piv = piv;
setAuthenticated(false);
}
/**
* 賬號校驗成功之后的token構(gòu)造方法
*
* @param principal
* @param authorities
*/
public WechatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
public void eraseCredentials() {
super.eraseCredentials();
}
}
這里我們創(chuàng)建一個類繼承令牌授權(quán)器(AbstractTokenGranter)
public class WechatTokenGranter extends AbstractTokenGranter {
/**
* 聲明授權(quán)者 WechatTokenGranter 支持授權(quán)模式 wechat
* 根據(jù)接口傳值 grant_type = wechat 的值匹配到此授權(quán)者
* 匹配邏輯詳見下面的兩個方法
*/
// 這里就是授權(quán)模式名
private static final String GRANT_TYPE = "wechat";
private final AuthenticationManager authenticationManager;
// 這里創(chuàng)建一個構(gòu)造函數(shù),配置的時候會用到
public WechatTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
/*
* 重寫方法
* @param client 客戶端詳細信息
*@param tokenRequest 請求參數(shù)
* */
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
// 這里將認證時的請求參數(shù)拿到
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String code = parameters.get("code");
String encryptedData = parameters.get("encryptedData");
String iv = parameters.get("iv");
String pEncryptedData = parameters.get("pEncryptedData");
String piv = parameters.get("piv");
// 移除后續(xù)無用參數(shù)
parameters.remove("code");
parameters.remove("encryptedData");
parameters.remove("iv");
parameters.remove("pEncryptedData");
parameters.remove("piv");
// 這里是我們之前創(chuàng)建的授權(quán)前的構(gòu)造函數(shù),把參數(shù)傳進去
Authentication userAuth = new WechatAuthenticationToken(code, encryptedData,iv,pEncryptedData,piv); // 未認證狀態(tài)
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
// 對參數(shù)進行認證
try {
userAuth = this.authenticationManager.authenticate(userAuth); // 認證中
} catch (Exception e) {
throw new InvalidGrantException(e.getMessage());
}
// 判斷時候認證成功
if (userAuth != null && userAuth.isAuthenticated()) { // 認證成功
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else { // 認證失敗
throw new InvalidGrantException("Could not authenticate code: " + code);
}
}
}
== 這里創(chuàng)建一個實體類實現(xiàn)AuthenticationProvider接口==
@Data
public class WechatAuthenticationProvider implements AuthenticationProvider {
// 這個事Security的用戶是如何驗證的接口,具體需要去實現(xiàn)
private UserDetailsService userDetailsService;
// 這個包是Github上的binarywang,調(diào)用微信接口的SDK
private WxMaService wxMaService;
// 由于我們的程序是Cloud,這里是跨服務(wù)調(diào)用的,可根據(jù)你們自己的業(yè)務(wù)需求來
private MemberFeignClient memberFeignClient;
/**
* 微信認證
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@SneakyThrows
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WechatAuthenticationToken authenticationToken = (WechatAuthenticationToken) authentication;
String code = (String) authenticationToken.getPrincipal();
// 以下是自定義驗證
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
String openid = sessionInfo.getOpenid();
Result<MemberAuthDTO> memberAuthResult = memberFeignClient.loadUserByOpenId(openid);
// 微信用戶不存在,注冊成為新會員
if (memberAuthResult != null && ResultCode.USER_NOT_EXIST.getCode().equals(memberAuthResult.getCode())) {
String sessionKey = sessionInfo.getSessionKey();
String encryptedData = authenticationToken.getEncryptedData();
String iv = authenticationToken.getIv();
String pEncryptedData = authenticationToken.getPEncryptedData();
String piv = authenticationToken.getPiv();
// 解密 encryptedData 獲取用戶信息
WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);
WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, pEncryptedData, piv);
MemberDTO memberDTO = new MemberDTO();
BeanUtil.copyProperties(userInfo, memberDTO);
memberDTO.setOpenid(openid);
memberDTO.setMobile(phoneNoInfo.getPhoneNumber());
memberFeignClient.addMember(memberDTO);
}
UserDetails userDetails = ((MemberUserDetailsServiceImpl) userDetailsService).loadUserByOpenId(openid);
WechatAuthenticationToken result = new WechatAuthenticationToken(userDetails, new HashSet<>());
result.setDetails(authentication.getDetails());
return result;
}
@Override
public boolean supports(Class<?> authentication) {
return WechatAuthenticationToken.class.isAssignableFrom(authentication);
}
}
最重要的一步來了,這里我們需要重寫授權(quán)服務(wù)器配置適配器(AuthorizationServerConfigurerAdapter),并且初始化一些配置文章來源:http://www.zghlxwxcb.cn/news/detail-594810.html
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final SysUserDetailsServiceImpl sysUserDetailsService;
private final MemberUserDetailsServiceImpl memberUserDetailsService;
private final StringRedisTemplate stringRedisTemplate;
private final DataSource dataSource;
private final TokenEnhancer tokenEnhancer;
/**
* OAuth2客戶端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 這里有兩種模式,一種是放在內(nèi)存,還有一種是放在數(shù)據(jù)庫,默認內(nèi)存,這里我放在數(shù)據(jù)庫,所以需要重寫
clients.withClientDetails(jdbcClientDetailsService());
}
/**
* 配置授權(quán)(authorization)以及令牌(token)的訪問端點和令牌服務(wù)(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// Token增強
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
tokenEnhancers.add(tokenEnhancer);
tokenEnhancers.add(jwtAccessTokenConverter());
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
//token存儲模式設(shè)定 默認為InMemoryTokenStore模式存儲到內(nèi)存中
endpoints.tokenStore(jwtTokenStore());
// 獲取原有默認授權(quán)模式(授權(quán)碼模式、密碼模式、客戶端模式、簡化模式)的授權(quán)者
List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
// 添加驗證碼授權(quán)模式授權(quán)者
granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(), authenticationManager, stringRedisTemplate
));
// 添加手機短信驗證碼授權(quán)模式的授權(quán)者
granterList.add(new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(), authenticationManager
));
// 添加微信授權(quán)模式的授權(quán)者 ********在這里添加我們剛才自定義的東西*******
granterList.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(), authenticationManager
));
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
.tokenEnhancer(tokenEnhancerChain)
.tokenGranter(compositeTokenGranter)
.tokenServices(tokenServices(endpoints))
;
}
/**
* 客戶端信息來源
*/
@Bean
public JdbcClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
/**
* jwt token存儲模式
*/
@Bean
public TokenStore jwtTokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 授權(quán)信息保存策略
*/
@Bean
public ApprovalStore approvalStore(){
return new JdbcApprovalStore(dataSource);
}
/**
* 授權(quán)碼模式數(shù)據(jù)來源
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
return new JdbcAuthorizationCodeServices(dataSource);
}
public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
tokenEnhancers.add(tokenEnhancer);
tokenEnhancers.add(jwtAccessTokenConverter());
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(jdbcClientDetailsService());
tokenServices.setTokenEnhancer(tokenEnhancerChain);
// 多用戶體系下,刷新token再次認證客戶端ID和 UserDetailService 的映射Map
Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();
clientUserDetailsServiceMap.put(SecurityConstants.ADMIN_CLIENT_ID, sysUserDetailsService); // 系統(tǒng)管理客戶端
clientUserDetailsServiceMap.put(SecurityConstants.APP_CLIENT_ID, memberUserDetailsService); // Android、IOS、H5 移動客戶端
clientUserDetailsServiceMap.put(SecurityConstants.WEAPP_CLIENT_ID, memberUserDetailsService); // 微信小程序客戶端
// 刷新token模式下,重寫預(yù)認證提供者替換其AuthenticationManager,可自定義根據(jù)客戶端ID和認證方式區(qū)分用戶體系獲取認證用戶信息
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap));
tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
/**
* refresh_token有兩種使用方式:重復(fù)使用(true)、非重復(fù)使用(false),默認為true
* 1 重復(fù)使用:access_token過期刷新時, refresh_token過期時間未改變,仍以初次生成的時間為準
* 2 非重復(fù)使用:access_token過期刷新時, refresh_token過期時間延續(xù),在refresh_token有效期內(nèi)刷新便永不失效達到無需再次登錄的目的
*/
tokenServices.setReuseRefreshToken(true);
return tokenServices;
}
/**
* 使用非對稱加密算法對token簽名
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyPair());
return converter;
}
/**
* 密鑰庫中獲取密鑰對(公鑰+私鑰)
*/
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "123456".toCharArray());
KeyPair keyPair = factory.getKeyPair("jwt", "123456".toCharArray());
return keyPair;
}
/**
* 自定義認證異常響應(yīng)數(shù)據(jù)
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, e) -> {
response.setStatus(HttpStatus.HTTP_OK);
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
Result result = Result.failed(ResultCode.CLIENT_AUTHENTICATION_FAILED);
response.getWriter().print(JSONUtil.toJsonStr(result));
response.getWriter().flush();
};
}
}
好了以上就是自定義驗證模式的全部內(nèi)容,有什么不懂的話可以評論下問我,我也會一 一解答,謝謝各位文章來源地址http://www.zghlxwxcb.cn/news/detail-594810.html
到了這里,關(guān)于Spring Oauth2.0 自定義認證模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!