javascript
搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结
在搭建介紹流程之前,確保您已經搭建了一個 Eureka 注冊中心,因為沒有注冊中心的話會報錯(也有可能我搭建的認證服務器是我項目的一個子模塊的原因):Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}
http://localhost:8761/eureka/ 是因為配置文件未提供注冊中心地址,springcloud 默認的注冊中心地址就是這個
另外:文末會提供所有代碼
Oauth2.0 有以下四種授權模式:本文介紹 授權碼認證
1、授權碼模式(Authorization Code)[常用]
2、隱式授權模式(Implicit)[不常用]
3、密碼模式(Resource Owner Password Credentials)[常用]
4、客戶端模式(Client Credentials)[不常用]
授權碼認證流程
- 客戶端請求第三方授權
- 用戶(資源擁有者)同意給客戶端授權
- 客戶端獲取到授權碼,請求認證服務器申請 令牌
- 認證服務器向客戶端響應令牌
- 客戶端請求資源服務器的資源,資源服務校驗令牌合法性,完成授權
- 資源服務器返回受保護資源
客戶端請求第三方授權
Get請求: http://localhost:9001/oauth/authorize?client_id=oauth&response_type=code&scop=app&redirect_uri=http://www.baidu.com 9001:是你的認證服務器的端口 參數解釋: client_id:客戶端id,和授權配置類中設置的客戶端id一致。 response_type:授權碼模式固定為code scop:客戶端范圍,和授權配置類中設置的scop一致。 redirect_uri:跳轉uri,當授權碼申請成功后會跳轉到此地址,并在后邊帶上code參數(授權碼)- 首先我們進入的是登錄頁面,用戶名密碼則是客戶端ID,和密鑰,當前我們配置在內存中,后面將會采用數據庫方式
- 所以在瀏覽器中輸入的地址是:http://localhost:9001/oauth/authorize?client_id=oauth&response_type=code&scop=app&redirect_uri=http://www.baidu.com
- 在正確輸入客戶端id和密鑰之后輸入以上地址回車
- 當我們點擊授權的時候,將會將請求轉發到你指定的 uri 上去,也就是 www.baidu.com
- 此時我們拿到了授權碼之后就可以向認證服務器去請求 token:地址為:http://localhost:9001/oauth/token POST請求
參數解釋:
grant_type:授權類型,填寫authorization_code,表示授權碼模式
code:授權碼,就是剛剛獲取的授權碼,注意:授權碼只使用一次就無效了,需要重新申請。
redirect_uri:申請授權碼時的跳轉url,一定和申請授權碼時用的redirect_uri一致。
此鏈接需要使用 http Basic認證。 什么是http Basic認證? http協議定義的一種認證方式,將客戶端id和客戶端密碼按照“客戶端ID:客戶端密碼”的格式拼接,并用base64編 碼,放在header中請求服務端,一個例子: Authorization:Basic WGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA= 是用戶名:密碼的base64編碼。 認證失敗服務端返回 401 Unauthorized。
返回參數解釋:
access_token:訪問令牌,攜帶此令牌訪問資源
token_type:有MAC Token與Bearer Token兩種類型,兩種的校驗算法不同,RFC 6750建議Oauth2采用 Bearer Token(http://www.rfcreader.com/#rfc6750)。
refresh_token:刷新令牌,使用此令牌可以延長訪問令牌的過期時間。
expires_in:過期時間,單位為秒。
scope:范圍,與定義的客戶端范圍一致。
jti:當前token的唯一標識
- 令牌校驗地址:http://localhost:9001/oauth/check_token?token= [access_token] GET請求,access_token 就是剛剛申請的 token
- 刷新令牌地址:http://localhost:9001/oauth/token POST請求,參數:grant_type: 固定為 refresh_token,refresh_token:刷新令牌(注意不是access_token,而是refresh_token)
以上就是授權碼授權,且采用內存配置客戶端ID和密鑰方式
下面介紹一下使用數據庫方式獲取客戶端ID和密鑰方式
首先更改配置類 AuthorizationServerConfig
@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.jdbc(dataSource).clients(clientDetails());//使用 JDBC 模式 // clients.inMemory() // .withClient("oauth") // 客戶端ID // .secret("oauth") // 客戶端密鑰 // .redirectUris("http://www.baidu.com")// 跳轉地址 // .accessTokenValiditySeconds(3600) // token 有效時間 // .refreshTokenValiditySeconds(3600) // 刷新token有效時間 // .authorizedGrantTypes( // "authorization_code", // 根據授權碼生成令牌 // "client_credentials", // 客戶端認證 // "refresh_token", // 刷新令牌 // "password") // 密碼方式認證 // .scopes("app");} /*** 客戶端配置** @return ClientDetailsService*/@Beanpublic ClientDetailsService clientDetails() {return new JdbcClientDetailsService(dataSource); // dataSource 為注入的數據源}改完我們繼續之前的操作肯定會報錯,因為采用 JDBC 方式必定數據庫應該有對應的表數據,所以我們查看 JdbcClientDetailsService 的實現方式,發現在 loadClientByClientId 方法中的 sql 語句,如下圖
通過看源碼,可以發現 ClientDetailsService 的 JDBC 實現必須要數據庫有一張表結構
所以我們建立數據庫表結構
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for oauth_client_details -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` (`client_id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客戶端ID,主要用于標識對應的應用',`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客戶端秘鑰,BCryptPasswordEncoder加密算法加密',`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '對應的范圍',`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '認證模式',`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '認證后重定向地址',`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`access_token_validity` int(11) NULL DEFAULT NULL COMMENT '令牌有效期',`refresh_token_validity` int(11) NULL DEFAULT NULL COMMENT '令牌刷新周期',`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`client_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;SET FOREIGN_KEY_CHECKS = 1; --- 因為之前數據是在內存中偽造的,所以我們也應該偽造一個客戶端ID和密鑰的信息在數據庫中 INSERT INTO `oauth_client_details` VALUES ('oauth', NULL, '$2a$10$1z1vevmlMKwlw2YxbQxc0e1IY7ZME1nW35T123O1lzYfEk5YrJe4O', 'app', 'authorization_code,password,refresh_token,client_credentials', 'http://www.baidu.com', NULL, 432000000, 432000000, NULL, NULL);再繼續前幾個步驟就一樣能夠授權成功
源碼部分
1、最重要的 AuthorizationServerConfig 認證服務器配置類
@Configuration // 表示開啟授權服務器,擁有以下路徑可訪問 // /oauth/authorize, // /oauth/token, // /oauth/check_token, // /oauth/confirm_access, // /oauth/error // /oauth/login // /oauth/logout @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {/*** 數據源,用于從數據庫獲取數據進行認證操作,測試可以從內存中獲取*/@Autowiredprivate DataSource dataSource;/*** jwt令牌轉換器*/@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;/*** SpringSecurity 用戶自定義授權認證類*/@Autowiredprivate UserDetailsService userDetailsService;/*** 授權認證管理器*/@Autowiredprivate AuthenticationManager authenticationManager;/*** 令牌持久化存儲接口*/@Autowiredprivate TokenStore tokenStore;@Resource(name = "keyProp")private KeyProperties keyProperties;/*** 客戶端信息配置** @param clients 客戶端* @throws Exception exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.jdbc(dataSource).clients(clientDetails()); // clients.inMemory() // .withClient("oauth") // 客戶端ID // .secret("oauth") // 客戶端密鑰 // .redirectUris("http://www.baidu.com")// 跳轉地址 // .accessTokenValiditySeconds(3600) // token 有效時間 // .refreshTokenValiditySeconds(3600) // 刷新token有效時間 // .authorizedGrantTypes( // "authorization_code", // 根據授權碼生成令牌 // "client_credentials", // 客戶端認證 // "refresh_token", // 刷新令牌 // "password") // 密碼方式認證 // .scopes("app");}/*** 授權服務器端點配置** @param endpoints endpoints*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.accessTokenConverter(jwtAccessTokenConverter).authenticationManager(authenticationManager) //認證管理器.tokenStore(tokenStore) //令牌存儲.userDetailsService(userDetailsService); //用戶信息service}/*** 授權服務器的安全配置** @param oauthServer oauthServer*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer oauthServer) {oauthServer.allowFormAuthenticationForClients().passwordEncoder(new BCryptPasswordEncoder()) // 密碼加密器.tokenKeyAccess("permitAll()") // 允許所有人請求令牌.checkTokenAccess("isAuthenticated()"); // 已驗證的客戶才能請求 check_token 接口驗證 token 有效性}/*** 讀取密鑰的配置** @return KeyProperties*/@Bean("keyProp")public KeyProperties keyProperties() {return new KeyProperties();}/*** 客戶端配置** @return ClientDetailsService*/@Beanpublic ClientDetailsService clientDetails() {return new JdbcClientDetailsService(dataSource);}@Bean@Autowiredpublic TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {return new JwtTokenStore(jwtAccessTokenConverter);}/*** JWT令牌轉換器** @param customUserAuthenticationConverter customUserAuthenticationConverter* @return JwtAccessTokenConverter*/@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();KeyPair keyPair = new KeyStoreKeyFactory(keyProperties.getKeyStore().getLocation(), //證書路徑 changgou.jkskeyProperties.getKeyStore().getSecret().toCharArray()) //證書秘鑰 changgou.getKeyPair(keyProperties.getKeyStore().getAlias(), //證書別名 changgoukeyProperties.getKeyStore().getPassword().toCharArray()); //證書密碼 changgouconverter.setKeyPair(keyPair);//配置自定義的 CustomUserAuthenticationConverterDefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);return converter;} }2、UserDetailsServiceImpl
/*** 自定義授權認證類*/ @Slf4j @Service // 標識為主要實現類,自動注入會優先選擇此實現類 @Primary public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate ClientDetailsService clientDetailsService;/*** 自定義授權認證** @param username username* @return UserDetails* @throws UsernameNotFoundException e*/@Overridepublic UserDetails loadUserByUsername(@NonNull String username) throws UsernameNotFoundException {// 取出身份,如果身份為空說明沒有認證Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 沒有認證統一采用httpBasic認證,httpBasic中存儲了client_id 和 client_secret,開始認證 client_id 和 client_secretif (authentication == null) {// AuthorizationServerConfig.java 配置了 ClientDetails 的實現方式為 JdbcClientDetailsService// 查看 JdbcClientDetailsService 源碼可知其提供了客戶端憑證的 增刪改查 方法,這里主要使用了 根據用戶名查找的方式try {ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);if (clientDetails != null) {//秘鑰String clientSecret = clientDetails.getClientSecret();// 通過 客戶端id和密鑰訪問系統:靜態方式就是密鑰未 BCryptPasswordEncoder() 加密編碼//靜態方式:return new User(username,clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList(""));return new User(username, clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList(""));}} catch (ClientRegistrationException e) {log.error(String.format("客戶端ID %s 不存在!", username));throw new UsernameNotFoundException(String.format("客戶端ID %s 不存在!", username));}}if(username == null){return null;} }3、WebSecurityConfig
/*** EnableWebSecurity: 開啟 SpringSecurity* Order: 值越小,越優先被加載*/ @Configuration @EnableWebSecurity @Order(-1) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 忽略安全攔截的URL** @param web web*/@Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers("/oauth/login", "/oauth/logout");}/*** 創建授權管理認證對象** @return AuthenticationManager* @throws Exception e*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 采用BCryptPasswordEncoder對密碼進行編碼** @return PasswordEncoder*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** @param http http* @throws Exception e*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable() //.httpBasic() //啟用Http基本身份驗證.and().formLogin() //啟用表單身份驗證.and().authorizeRequests() //限制基于Request請求訪問.anyRequest().authenticated(); //其他請求都需要經過驗證} }下一篇:密碼授權流程快速通道:密碼授權流程
總結
以上是生活随笔為你收集整理的搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于 MySQL5.7.log 版本导出
- 下一篇: 搭建认证服务器 - Spring Sec