javascript
搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结
在搭建介紹流程之前,確保您已經(jīng)搭建了一個(gè) Eureka 注冊(cè)中心,因?yàn)闆]有注冊(cè)中心的話會(huì)報(bào)錯(cuò)(也有可能我搭建的認(rèn)證服務(wù)器是我項(xiàng)目的一個(gè)子模塊的原因):Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}
http://localhost:8761/eureka/ 是因?yàn)榕渲梦募刺峁┳?cè)中心地址,springcloud 默認(rèn)的注冊(cè)中心地址就是這個(gè)
另外:文末會(huì)提供所有代碼
Oauth2.0 有以下四種授權(quán)模式:本文介紹 授權(quán)碼認(rèn)證
1、授權(quán)碼模式(Authorization Code)[常用]
2、隱式授權(quán)模式(Implicit)[不常用]
3、密碼模式(Resource Owner Password Credentials)[常用]
4、客戶端模式(Client Credentials)[不常用]
授權(quán)碼認(rèn)證流程
- 客戶端請(qǐng)求第三方授權(quán)
- 用戶(資源擁有者)同意給客戶端授權(quán)
- 客戶端獲取到授權(quán)碼,請(qǐng)求認(rèn)證服務(wù)器申請(qǐng) 令牌
- 認(rèn)證服務(wù)器向客戶端響應(yīng)令牌
- 客戶端請(qǐng)求資源服務(wù)器的資源,資源服務(wù)校驗(yàn)令牌合法性,完成授權(quán)
- 資源服務(wù)器返回受保護(hù)資源
客戶端請(qǐng)求第三方授權(quán)
Get請(qǐng)求: http://localhost:9001/oauth/authorize?client_id=oauth&response_type=code&scop=app&redirect_uri=http://www.baidu.com 9001:是你的認(rèn)證服務(wù)器的端口 參數(shù)解釋: client_id:客戶端id,和授權(quán)配置類中設(shè)置的客戶端id一致。 response_type:授權(quán)碼模式固定為code scop:客戶端范圍,和授權(quán)配置類中設(shè)置的scop一致。 redirect_uri:跳轉(zhuǎn)uri,當(dāng)授權(quán)碼申請(qǐng)成功后會(huì)跳轉(zhuǎn)到此地址,并在后邊帶上code參數(shù)(授權(quán)碼)- 首先我們進(jìn)入的是登錄頁面,用戶名密碼則是客戶端ID,和密鑰,當(dāng)前我們配置在內(nèi)存中,后面將會(huì)采用數(shù)據(jù)庫方式
- 所以在瀏覽器中輸入的地址是:http://localhost:9001/oauth/authorize?client_id=oauth&response_type=code&scop=app&redirect_uri=http://www.baidu.com
- 在正確輸入客戶端id和密鑰之后輸入以上地址回車
- 當(dāng)我們點(diǎn)擊授權(quán)的時(shí)候,將會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到你指定的 uri 上去,也就是 www.baidu.com
- 此時(shí)我們拿到了授權(quán)碼之后就可以向認(rèn)證服務(wù)器去請(qǐng)求 token:地址為:http://localhost:9001/oauth/token POST請(qǐng)求
參數(shù)解釋:
grant_type:授權(quán)類型,填寫authorization_code,表示授權(quán)碼模式
code:授權(quán)碼,就是剛剛獲取的授權(quán)碼,注意:授權(quán)碼只使用一次就無效了,需要重新申請(qǐng)。
redirect_uri:申請(qǐng)授權(quán)碼時(shí)的跳轉(zhuǎn)url,一定和申請(qǐng)授權(quán)碼時(shí)用的redirect_uri一致。
此鏈接需要使用 http Basic認(rèn)證。 什么是http Basic認(rèn)證? http協(xié)議定義的一種認(rèn)證方式,將客戶端id和客戶端密碼按照“客戶端ID:客戶端密碼”的格式拼接,并用base64編 碼,放在header中請(qǐng)求服務(wù)端,一個(gè)例子: Authorization:Basic WGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA= 是用戶名:密碼的base64編碼。 認(rèn)證失敗服務(wù)端返回 401 Unauthorized。
返回參數(shù)解釋:
access_token:訪問令牌,攜帶此令牌訪問資源
token_type:有MAC Token與Bearer Token兩種類型,兩種的校驗(yàn)算法不同,RFC 6750建議Oauth2采用 Bearer Token(http://www.rfcreader.com/#rfc6750)。
refresh_token:刷新令牌,使用此令牌可以延長(zhǎng)訪問令牌的過期時(shí)間。
expires_in:過期時(shí)間,單位為秒。
scope:范圍,與定義的客戶端范圍一致。
jti:當(dāng)前token的唯一標(biāo)識(shí)
- 令牌校驗(yàn)地址:http://localhost:9001/oauth/check_token?token= [access_token] GET請(qǐng)求,access_token 就是剛剛申請(qǐng)的 token
- 刷新令牌地址:http://localhost:9001/oauth/token POST請(qǐng)求,參數(shù):grant_type: 固定為 refresh_token,refresh_token:刷新令牌(注意不是access_token,而是refresh_token)
以上就是授權(quán)碼授權(quán),且采用內(nèi)存配置客戶端ID和密鑰方式
下面介紹一下使用數(shù)據(jù)庫方式獲取客戶端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")// 跳轉(zhuǎn)地址 // .accessTokenValiditySeconds(3600) // token 有效時(shí)間 // .refreshTokenValiditySeconds(3600) // 刷新token有效時(shí)間 // .authorizedGrantTypes( // "authorization_code", // 根據(jù)授權(quán)碼生成令牌 // "client_credentials", // 客戶端認(rèn)證 // "refresh_token", // 刷新令牌 // "password") // 密碼方式認(rèn)證 // .scopes("app");} /*** 客戶端配置** @return ClientDetailsService*/@Beanpublic ClientDetailsService clientDetails() {return new JdbcClientDetailsService(dataSource); // dataSource 為注入的數(shù)據(jù)源}改完我們繼續(xù)之前的操作肯定會(huì)報(bào)錯(cuò),因?yàn)椴捎?JDBC 方式必定數(shù)據(jù)庫應(yīng)該有對(duì)應(yīng)的表數(shù)據(jù),所以我們查看 JdbcClientDetailsService 的實(shí)現(xiàn)方式,發(fā)現(xiàn)在 loadClientByClientId 方法中的 sql 語句,如下圖
通過看源碼,可以發(fā)現(xiàn) ClientDetailsService 的 JDBC 實(shí)現(xiàn)必須要數(shù)據(jù)庫有一張表結(jié)構(gòu)
所以我們建立數(shù)據(jù)庫表結(jié)構(gòu)
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,主要用于標(biāo)識(shí)對(duì)應(yīng)的應(yīng)用',`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 '對(duì)應(yīng)的范圍',`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '認(rèn)證模式',`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '認(rèn)證后重定向地址',`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; --- 因?yàn)橹皵?shù)據(jù)是在內(nèi)存中偽造的,所以我們也應(yīng)該偽造一個(gè)客戶端ID和密鑰的信息在數(shù)據(jù)庫中 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);再繼續(xù)前幾個(gè)步驟就一樣能夠授權(quán)成功
源碼部分
1、最重要的 AuthorizationServerConfig 認(rèn)證服務(wù)器配置類
@Configuration // 表示開啟授權(quán)服務(wù)器,擁有以下路徑可訪問 // /oauth/authorize, // /oauth/token, // /oauth/check_token, // /oauth/confirm_access, // /oauth/error // /oauth/login // /oauth/logout @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {/*** 數(shù)據(jù)源,用于從數(shù)據(jù)庫獲取數(shù)據(jù)進(jìn)行認(rèn)證操作,測(cè)試可以從內(nèi)存中獲取*/@Autowiredprivate DataSource dataSource;/*** jwt令牌轉(zhuǎn)換器*/@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;/*** SpringSecurity 用戶自定義授權(quán)認(rèn)證類*/@Autowiredprivate UserDetailsService userDetailsService;/*** 授權(quán)認(rèn)證管理器*/@Autowiredprivate AuthenticationManager authenticationManager;/*** 令牌持久化存儲(chǔ)接口*/@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")// 跳轉(zhuǎn)地址 // .accessTokenValiditySeconds(3600) // token 有效時(shí)間 // .refreshTokenValiditySeconds(3600) // 刷新token有效時(shí)間 // .authorizedGrantTypes( // "authorization_code", // 根據(jù)授權(quán)碼生成令牌 // "client_credentials", // 客戶端認(rèn)證 // "refresh_token", // 刷新令牌 // "password") // 密碼方式認(rèn)證 // .scopes("app");}/*** 授權(quán)服務(wù)器端點(diǎn)配置** @param endpoints endpoints*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.accessTokenConverter(jwtAccessTokenConverter).authenticationManager(authenticationManager) //認(rèn)證管理器.tokenStore(tokenStore) //令牌存儲(chǔ).userDetailsService(userDetailsService); //用戶信息service}/*** 授權(quán)服務(wù)器的安全配置** @param oauthServer oauthServer*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer oauthServer) {oauthServer.allowFormAuthenticationForClients().passwordEncoder(new BCryptPasswordEncoder()) // 密碼加密器.tokenKeyAccess("permitAll()") // 允許所有人請(qǐng)求令牌.checkTokenAccess("isAuthenticated()"); // 已驗(yàn)證的客戶才能請(qǐng)求 check_token 接口驗(yàn)證 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令牌轉(zhuǎn)換器** @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
/*** 自定義授權(quán)認(rèn)證類*/ @Slf4j @Service // 標(biāo)識(shí)為主要實(shí)現(xiàn)類,自動(dòng)注入會(huì)優(yōu)先選擇此實(shí)現(xiàn)類 @Primary public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate ClientDetailsService clientDetailsService;/*** 自定義授權(quán)認(rèn)證** @param username username* @return UserDetails* @throws UsernameNotFoundException e*/@Overridepublic UserDetails loadUserByUsername(@NonNull String username) throws UsernameNotFoundException {// 取出身份,如果身份為空說明沒有認(rèn)證Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 沒有認(rèn)證統(tǒng)一采用httpBasic認(rèn)證,httpBasic中存儲(chǔ)了client_id 和 client_secret,開始認(rèn)證 client_id 和 client_secretif (authentication == null) {// AuthorizationServerConfig.java 配置了 ClientDetails 的實(shí)現(xiàn)方式為 JdbcClientDetailsService// 查看 JdbcClientDetailsService 源碼可知其提供了客戶端憑證的 增刪改查 方法,這里主要使用了 根據(jù)用戶名查找的方式try {ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);if (clientDetails != null) {//秘鑰String clientSecret = clientDetails.getClientSecret();// 通過 客戶端id和密鑰訪問系統(tǒng):靜態(tài)方式就是密鑰未 BCryptPasswordEncoder() 加密編碼//靜態(tài)方式: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: 值越小,越優(yōu)先被加載*/ @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");}/*** 創(chuàng)建授權(quán)管理認(rèn)證對(duì)象** @return AuthenticationManager* @throws Exception e*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 采用BCryptPasswordEncoder對(duì)密碼進(jìn)行編碼** @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基本身份驗(yàn)證.and().formLogin() //啟用表單身份驗(yàn)證.and().authorizeRequests() //限制基于Request請(qǐng)求訪問.anyRequest().authenticated(); //其他請(qǐng)求都需要經(jīng)過驗(yàn)證} }下一篇:密碼授權(quán)流程快速通道:密碼授權(quán)流程
總結(jié)
以上是生活随笔為你收集整理的搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于 MySQL5.7.log 版本导出
- 下一篇: 搭建认证服务器 - Spring Sec