Oauth2查詢的存有客戶端(不是個人用戶,而是第三方登錄時 人-客戶端-第三方的那個客戶端)賬號密碼的表必須叫oauth_client_details
Oauth2有以下授權模式:
1.授權碼模式(Authorization Code)
2.隱式授權模式(Implicit)
3.密碼模式(Resource Owner Password Credentials)
4.客戶端模式(Client credentials)
導入day09資源里的changgou-user-oath這個模塊,其中的關鍵是這四個配置類,和引導類
1
package com.changgou.oauth.config;
import com.changgou.oauth.util.UserJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration @EnableAuthorizationServer class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { //數據源,用於從數據庫獲取數據進行認證操作,測試可以從內存中獲取 @Autowired private DataSource dataSource; //jwt令牌轉換器 @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; //SpringSecurity 用戶自定義授權認證類 @Autowired UserDetailsService userDetailsService; //授權認證管理器 @Autowired AuthenticationManager authenticationManager; //令牌持久化存儲接口 @Autowired TokenStore tokenStore; @Autowired private CustomUserAuthenticationConverter customUserAuthenticationConverter; /*** * 客戶端信息配置 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource).clients(clientDetails()); } /*** * 授權服務器端點配置 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.accessTokenConverter(jwtAccessTokenConverter) .authenticationManager(authenticationManager)//認證管理器 .tokenStore(tokenStore) //令牌存儲 .userDetailsService(userDetailsService); //用戶信息service } /*** * 授權服務器的安全配置 * @param oauthServer * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.allowFormAuthenticationForClients() .passwordEncoder(new BCryptPasswordEncoder()) .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } //讀取密鑰的配置 @Bean("keyProp") public KeyProperties keyProperties(){ return new KeyProperties(); } @Resource(name = "keyProp") private KeyProperties keyProperties; //客戶端配置 @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } @Bean @Autowired public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) { return new JwtTokenStore(jwtAccessTokenConverter); } /**** * JWT令牌轉換器 * @param customUserAuthenticationConverter * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); KeyPair keyPair = new KeyStoreKeyFactory( keyProperties.getKeyStore().getLocation(), //證書路徑 changgou.jks keyProperties.getKeyStore().getSecret().toCharArray()) //證書秘鑰 changgouapp .getKeyPair( keyProperties.getKeyStore().getAlias(), //證書別名 changgou keyProperties.getKeyStore().getPassword().toCharArray()); //證書密碼 changgou converter.setKeyPair(keyPair); //配置自定義的CustomUserAuthenticationConverter DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter(); accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter); return converter; } }
package com.changgou.oauth.config; import com.changgou.oauth.util.UserJwt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; import org.springframework.stereotype.Component; import java.util.LinkedHashMap; import java.util.Map; @Component public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter { @Autowired UserDetailsService userDetailsService; @Override public Map<String, ?> convertUserAuthentication(Authentication authentication) { LinkedHashMap response = new LinkedHashMap(); String name = authentication.getName(); response.put("username", name); Object principal = authentication.getPrincipal(); UserJwt userJwt = null; if(principal instanceof UserJwt){ userJwt = (UserJwt) principal; }else{ //refresh_token默認不去調用userdetailService獲取用戶信息,這里我們手動去調用,得到 UserJwt UserDetails userDetails = userDetailsService.loadUserByUsername(name); userJwt = (UserJwt) userDetails; } response.put("name", userJwt.getName()); response.put("id", userJwt.getId()); if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) { response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities())); } return response; } }
3
package com.changgou.oauth.config; import com.changgou.oauth.util.UserJwt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /***** * 自定義授權認證類 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired ClientDetailsService clientDetailsService; /**** * 自定義授權認證 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //取出身份,如果身份為空說明沒有認證 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); //沒有認證統一采用httpbasic認證,httpbasic中存儲了client_id和client_secret,開始認證client_id和client_secret if(authentication==null){ ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username); if(clientDetails!=null){ //秘鑰 String clientSecret = clientDetails.getClientSecret(); //靜態方式 //return new User(username,new BCryptPasswordEncoder().encode(clientSecret), AuthorityUtils.commaSeparatedStringToAuthorityList("")); //數據庫查找方式 return new User(username,clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList("")); } } if (StringUtils.isEmpty(username)) { return null; } //根據用戶名查詢用戶信息 String pwd = new BCryptPasswordEncoder().encode("itheima"); //創建User對象 String permissions = "goods_list,seckill_list"; UserJwt userDetails = new UserJwt(username,pwd,AuthorityUtils.commaSeparatedStringToAuthorityList(permissions)); return userDetails; } }
4
package com.changgou.oauth.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity @Order(-1) class WebSecurityConfig extends WebSecurityConfigurerAdapter { /*** * 忽略安全攔截的URL * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/oauth/login", "/oauth/logout"); } /*** * 創建授權管理認證對象 * @return * @throws Exception */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { AuthenticationManager manager = super.authenticationManagerBean(); return manager; } /*** * 采用BCryptPasswordEncoder對密碼進行編碼 * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /**** * * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .httpBasic() //啟用Http基本身份驗證 .and() .formLogin() //啟用表單身份驗證 .and() .authorizeRequests() //限制基於Request請求訪問 .anyRequest() .authenticated(); //其他請求都需要經過驗證 } }
授權碼授權流程(獲取jwt令牌)
1、客戶端請求第三方授權
2、用戶同意給客戶端授權
3、客戶端獲取到授權碼,請求認證服務器申請 令牌
4、認證服務器向客戶端響應令牌
5、客戶端請求資源服務器的資源,資源服務校驗令牌合法性,完成授權
6、資源服務器返回受保護資源
1 獲取授權碼
此時數據庫里的的數據為 其中changgou的密碼為加密后的changgou
在瀏覽器地址欄輸入
http://localhost:9200/oauth/authorize?client_id=changgou&response_type=code&scop=app&redirect_uri=http://localhost
這個地址是oauth2內部封裝的,不是我們自己寫的
賬號密碼都是changgou 這個賬號密碼不是用戶個人的密碼,而是第三方平台中保存的本系統的賬號和密碼
進入后點擊授權,地址欄中的http://localhost/?code=qquEOA code就是授權碼
這個授權碼生成后只能使用一次
2 獲取令牌
在postman中,編輯post請求
http://localhost:9200/oauth/token
這個地址是oauth2內部封裝的,不是我們自己寫
需要在body中攜帶三個參數(form-data)
grant_type : authorization_code 授權類型,authorization_code代表授權碼模式
code: 授權碼 上一步獲取的授權碼
redirect_uri: 申請授權碼時的跳轉url 和申請授權碼時路徑中的redirect_uri的值一致,此處為http://localhost
然后需要進行httpBasic認證,basic認證是一種認證方式,將客戶端id和客戶端密碼按照“客戶端ID:客戶端密碼”的格式拼接,並用base64編 碼,放在header中請求服務端
在postman中選擇Authorization,type選擇basicAuth,右側填入客戶端的用戶名和密碼(都是changgou)
然后就可以發送請求了
獲取到的值為
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYxMDgxOTQwNCwianRpIjoiMGM0YjA1YmItOTdhOS00ZjM2LWIzNWItOWFhMTQzYzlhNTk1IiwiY2xpZW50X2lkIjoiY2hhbmdnb3UiLCJ1c2VybmFtZSI6ImNoYW5nZ291In0.NcInwpPDeSsS24-cIh6KBFuqGBht0oy2F6mHovRwvMpJMSAHCuwpIbt3Cw3n63lKVPzqeRYhX2iOVfj2qATBIuu98DTCLkrReoEhvW6khjpW1dMJNWubdFqfPQfDmE8AcSuCGX__2pivsRUVtiew6liAxqw-GL5rD6ClTrJpTcpblo3-5nk-YMrSvAoj_xnBO4WVWh1JQJ-C_NIr0s0VuEAuKH_UZLrMGLDUyHDDk07kLiAP44gRCA1oMjBVF-OvK-9NYueetiy92T9gA8vDY01jU3SYdSOm-_Ta6xVPVY2pt6uXvzSFZ3qvCrbRK4jwnMFH-eE72vM7mhLoRPhf_g", "token_type": "bearer", "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwiYXRpIjoiMGM0YjA1YmItOTdhOS00ZjM2LWIzNWItOWFhMTQzYzlhNTk1IiwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYxMDgxOTQwNCwianRpIjoiOTNjYWZiYTgtMzE5Zi00OTI2LWEwNTMtNzg1YTVmMmQyZTEzIiwiY2xpZW50X2lkIjoiY2hhbmdnb3UiLCJ1c2VybmFtZSI6ImNoYW5nZ291In0.EgPYoKMWaBUvvSHq6Gx1SX560aUJ6N3nQg28w2EyPPACHa9k0nwn9zCbeTkVSxIfXKc7q3J6f1PqpG1eMRkSYjitOZFwvSldH91__Ij33noOf4kUbOCEfBISDtY-vcXdpb25TdkH7gTahNUMKQU6N-DnL3l3Smm22-WkDeRqpNXFY2atn36sMYX_P5Nid-0Vcf6jQKwkC5N6DD38WgEOrFsXLi7JawN7833XEDMGxLZVirAPBLWFJNLtuSk-BfIF38RsdhEiiEE1AYcQJiB0ZCVf7D9umYZ_U4wNpHTZFKODUuUurLSBDu1Fkk4ybRT06-W4aAov8xSFeaWaB61vlg", "expires_in": 43199, "scope": "app", "jti": "0c4b05bb-97a9-4f36-b35b-9aa143c9a595" }
access_token就是jwt令牌
refresh_token是刷新令牌,用於在令牌快過期時重置過期時間
expires_in 是過期時間
jti是jwt令牌的短標識,和access_token是一一對應的,我們可以把jwt令牌放進redis(jti=jwt的map),把jti放在cookie里,以節省空間
3 校驗令牌
發送get請求
http://localhost:9200/oauth/check_token?token= jwt令牌值,不帶引號
這個地址是oauth2內部封裝的,不是我們自己寫
正常解析說明校驗成功
4 刷新令牌
刷新令牌不會產生新令牌,而是重置一個令牌的死亡時間
刷新令牌不需要授權碼,不需要賬號和密碼,只需要一個refresh_token,客戶端id和客戶端密碼
在postman中,編輯post請求
http://localhost:9200/oauth/token
這個地址是oauth2內部封裝的,不是我們自己寫
需要在body中攜帶兩個參數(form-data)
grant_type : refresh_token 授權類型,refresh_token代表刷新令牌
refresh_token: 刷新令牌的值 注意不是jwt令牌,而是獲取jwt令牌時一同獲取的refresh_token
完成請求后,expires_in的值和初次獲取令牌時一致
密碼授權流程(獲取jwt令牌)
密碼模式(Resource Owner Password Credentials)與授權碼模式的區別是申請令牌不再使用授權碼
而是直接 通過用戶名和密碼即可申請令牌。
在postman中,編輯post請求
http://localhost:9200/oauth/token
這個地址是oauth2內部封裝的,不是我們自己寫
需要在body中攜帶三個參數(form-data)
grant_type: password password代表授權類型為密碼模式
username: 賬號 注意這個賬號密碼時用戶個人在本系統注冊的賬號和密碼
password: 密碼 目前賬號密碼都是itheima,這和本文中開頭導入的四個類中的第三個UserDetailsServiceImpl中的假數據有關,后期要改為從數據庫中查
然后需要進行httpBasic認證,basic認證是一種認證方式,將客戶端id和客戶端密碼按照“客戶端ID:客戶端密碼”的格式拼接,並用base64編 碼,放在header中請求服務端
在postman中選擇Authorization,type選擇basicAuth,右側填入客戶端的用戶名和密碼(都是changgou)
然后進行請求
即可獲取相關令牌
1
2
1