登錄實現
拿瀏覽器網頁登錄舉例:
基於 OAuth2.0-密碼模式 實現網頁登錄的本質就是瀏覽器通過 /oauth/token
接口將 用戶名 和 密碼 等信息傳給後臺, 然後後臺驗證通過後返回一個有效的 token
給瀏覽器.
通過 curl
命令發送請求
-
請求頭
Authorization
存放的是clientId
和secret
經過Base64
編碼後的結果 -
請求參數包括用戶名(
username
)、密碼(password
)、授權模式(grant_type
).
curl --location --request
POST 'http://localhost:8101/oauth/token?username=zhangsan&password=123456&grant_type=password \
--header 'Authorization: Basic bmltbzE6MTIzNDU2'
響應內容
{
"scope": "[all, read, write]",
"code": 0,
"access_token": "7e1d19dd-5cef-4993-a1c3-c35aa53d9b29",
"token_type": "bearer",
"refresh_token": "992518eb-4357-4283-8673-a9ca96ad2a9e",
"expires_in": 7199
}
問題
如果我們想把登錄接口命名爲 /login
, 該怎麼辦?
方法一
在 AuthorizationServerConfigurerAdapter
配置一個 pathMapping
, 把原有的路徑給 覆蓋 掉.
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.pathMapping("/oauth/token","/login"); }
}
方法二
根據上文 源碼分析 - Spring Security OAuth2 生成 token 的執行流程 講的 API , 實現 TokenEndpoint#postAccessToken()方法
的核心邏輯, 重新定義一個 /login
接口.
回顧上文中的一張圖:
核心代碼如下:
@PostMapping(value = "/login")
@ResponseBody
public String doLogin(
HttpServletRequest request,
String username,
String password) {
// 自定義響應對象
LoginRes res = new LoginRes();
try {
// 對請求頭進行 base64 解碼, 獲取 client id 和 client secret
String[] tokens = CryptUtils.decodeBasicHeader(request.getHeader("Authorization"));
String clientId = tokens[0];
String clientSecret = tokens[1];
// 通過 clientId 獲取客戶端詳情
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
// 校驗 ClientDetails
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("Unknown client id : " + clientId);
}
if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
throw new UnapprovedClientAuthenticationException("Invalid client secret for client id : " + clientId);
}
// 通過 username 和 password 構建一個 Authentication 對象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(req.getUsername(),
req.getPassword());
// 驗證用戶信息
Authentication auth = authenticationManager.authenticate(authRequest);
// 放入 Secirty 的上下文
SecurityContextHolder.getContext().setAuthentication(auth);
// 通過 Client 信息和 請求參數, 獲取一個 TokenRequest 對象
TokenRequest tokenRequest = new TokenRequest(new HashMap<String, String>(), clientId,
clientDetails.getScope(), "password");
// 通過 TokenRequest 和 ClientDetails 構建 OAuthRequest
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
// 通過 OAuth2Request 和 Authentication 構建OAuth2Authentication
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, auth);
// 通過 OAuth2Authentication 構建 OAuth2AccessToken
OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
// 把 token 信息封裝到 自定義的響應對象中
res.setAccessToken(token.getValue());
res.setTokenType(token.getTokenType());
res.setRefreshToken(token.getRefreshToken().getValue());
res.setExpiresIn(token.getExpiresIn());
res.setScope(token.getScope().toString());
} catch (Exception e) {
log.warn("Fail to login of user {} for {}", req.getUsername(), e.getMessage());
}
return JsonUtil.toJsonString(res);
}