oauth2.0協議是一種對外開放式協議,主要用於第三方登陸受權。redis
例如:在豆瓣官網點擊用qq登陸算法
以及微信的受權都是基於oauth2.0協議作的。數據庫
oauth2.0的認證流程api
(A)用戶打開客戶端,客戶端要求用戶給予受權。瀏覽器
(B)用戶贊成給予客戶端受權。服務器
(C)客戶端使用上一步得到的受權(通常是Code),向認證服務器申請令牌TOKEN。微信
(D)認證服務器對客戶端進行認證之後,確認無誤,贊成發放令牌。app
(E)客戶端使用令牌,向資源服務器申請獲取資源(用戶信息等)。框架
(F)資源服務器確認令牌無誤,贊成向客戶端開放資源。
less
主要分爲
1:Authorization serve:受權服務器。
2:client 客戶端
3:Resource server:資源服務器,即服務提供商存放用戶生成的資源的服務器。它與認證服務器,能夠是同一臺服務器,也能夠是不一樣的服務器。
oauth2.0的五種協議:
受權碼模式(authorization code)
簡化模式(implicit)
密碼模式(resource owner password credentials)
客戶端模式(client credentials)
擴展模式(Extension)
1 受權碼模式
2 簡化模式
不經過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,跳過"受權碼"這個步驟。
全部步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不須要認證
步驟以下:
(A)客戶端將用戶導向認證服務器。
(B)用戶決定是否給於客戶端受權。
(C)若用戶受權,認證服務器將用戶導向客戶端指定的"重定向URI",並在URI的Hash部分包含了訪問令牌。
(D)瀏覽器向資源服務器發出請求,其中不包括上一步收到的Hash值。
(E)資源服務器返回一個網頁,其中包含的代碼能夠獲取Hash值中的令牌。
(F)瀏覽器執行上一步得到的腳本,提取出令牌。
(G)瀏覽器將令牌發給客戶端。
3 密碼模式
(A)用戶向客戶端提供用戶名和密碼。
(B)客戶端將用戶名和密碼發給認證服務器,向後者請求令牌。
(C)認證服務器確認無誤後,向客戶端提供訪問令牌。
4客戶端模式
指客戶端以本身的名義,而不以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式並不屬於OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端註冊,客戶端以本身的名義要求"服務提供商"提供服務,其實不存在受權問題。
(A)客戶端向認證服務器進行身份認證,並要求一個訪問令牌。
(B)認證服務器確認無誤後,向客戶端提供訪問令牌。
jwt介紹
jwt 主要由3個部分組成
3.1 JWT頭
JWT頭部分是一個描述JWT元數據的JSON對象,一般以下所示。
{
"alg": "HS256",
"typ": "JWT"
}
3.2 有效載荷
有效載荷部分,是JWT的主體內容部分,也是一個JSON對象,包含須要傳遞的數據。 JWT指定七個默認字段供選擇。
iss:發行人
exp:到期時間
sub:主題
aud:用戶
nbf:在此以前不可用
iat:發佈時間
jti:JWT ID用於標識該JWT
除以上默認字段外,咱們還能夠自定義私有字段,以下例:
{
"sub": "1234567890",
"name": "chongchong",
"admin": true
}
請注意,默認狀況下JWT是未加密的,任何人均可以解讀其內容,所以不要構建隱私信息字段,存放保密信息,以防止信息泄露。
JSON對象也使用Base64 URL算法轉換爲字符串保存。
3.3簽名哈希
簽名哈希部分是對上面兩部分數據簽名,經過指定的算法生成哈希,以確保數據不會被篡改。
首先,須要指定一個密碼(secret)。該密碼僅僅爲保存在服務器中,而且不能向用戶公開。而後,使用標頭中指定的簽名算法(默認狀況下爲HMAC SHA256)根據如下公式生成簽名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)
在計算出簽名哈希後,JWT頭,有效載荷和簽名哈希的三個部分組合成一個字符串,每一個部分用"."分隔,就構成整個JWT對象。
jwt和普通token的區別,主要是普通token的加密解密都是由咱們本身定義,可是jwt是基於一種加密標準,統一了token的各自加密,你們遵照的一種標準。
oauth2.0和jwt實現 登陸驗證
1 :資源服務器的配置
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends
ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated().and()
.requestMatchers().antMatchers("/api/**");
}
}
application.properties配置
server.port=8081
security.oauth2.resource.jwt.key-value=123456
2 :受權服務器的配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter{
@Autowired
private AuthenticationManager authenticationManager;
@Value("${security.oauth2.jwt.signingKey}")
private String signingKey;
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter=new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(signingKey);
return jwtAccessTokenConverter;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception{
authorizationServerEndpointsConfigurer
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(ClientDetailsServiceConfigurer clients)throws Exception{
clients.inMemory().withClient("clientapp")
.secret("112233")
.scopes("read_userinfo")
.authorizedGrantTypes(
"password",
"authorization_code",
"refresh_token");
}
}
application.properties配置
security.user.name=zhu
security.user.password=xiang
#jwt的密鑰
security.oauth2.jwt.signingKey=123456
這樣就配置好了。
獲取token,訪問受權服務器
http://localhost:8080/oauth/token?grant_type=password&username=zhu&password=xiang&scope=read_userinfo
1 進入 /oauth/token ,到
TokenEndpoint
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
//獲取客戶端信息 InMemoryClientDetailsService和JdbcClientDetailsService 是根據啓動時候加載受權服務的時候clients.inMemory().withClient("clientapp") 決定加載哪一個,我這邊明顯就是從內存獲取那個
ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
} else {
if (authenticatedClient != null) {
this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
} else if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
} else {
if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
this.logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.emptySet());
}
if (this.isRefreshTokenRequest(parameters)) {
tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
}
//校驗帳號密碼以及生成token,咱們這邊因爲是從內存中校驗,因此就不須要重寫userdetailService,不然是加載數據庫的話 須要重寫
//在DefaultTokenServices OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken); 生成token
//存儲token this.tokenStore.storeAccessToken(accessToken, authentication); 分爲四種模式 1 redis,2 jdbc 3 內存 4 jwt 其實jwt並不須要存儲的地方,由於它自己就是一種算法加密來的,經過再次加密就能夠獲取到,因此說它的tokenstore是爲空的
OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
} else {
return this.getResponse(token);
}
}
}
}
獲取到token 後
訪問 http://localhost:8081/api/userinfo
校驗token
OAuth2AuthenticationProcessingFilter 會攔截url請求
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
boolean debug = logger.isDebugEnabled();
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
Authentication authentication = this.tokenExtractor.extract(request);
if (authentication == null) {
if (this.stateless && this.isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
} else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
//檢查token
Authentication authResult = this.authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
this.eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
} catch (OAuth2Exception var9) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + var9);
}
this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));
return;
}
chain.doFilter(request, response);
}
OAuth2AuthenticationManager類
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
} else {
String token = (String)authentication.getPrincipal();
。 //查找token驗證,
OAuth2Authentication auth = this.tokenServices.loadAuthentication(token);
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
} else {
Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (this.resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(this.resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + this.resourceId + ")");
} else {
this.checkClientDetails(auth);
if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
if (!details.equals(auth.getDetails())) {
details.setDecodedDetails(auth.getDetails());
}
}
auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
return auth;
}
}
}
}
DefaultTokenServices類
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException {
OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
} else if (accessToken.isExpired()) {
this.tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
} else {
//這邊從以前儲存的地方獲取,也有4種,本次是從jwt中獲取
OAuth2Authentication result = this.tokenStore.readAuthentication(accessToken); if (result == null) { throw new InvalidTokenException("Invalid access token: " + accessTokenValue); } else { if (this.clientDetailsService != null) { String clientId = result.getOAuth2Request().getClientId(); try { this.clientDetailsService.loadClientByClientId(clientId); } catch (ClientRegistrationException var6) { throw new InvalidTokenException("Client not valid: " + clientId, var6); } } return result; } }}