= spring-boot-demo-oauth-authorization-server Doc Writer v1.0, 2019-01-07 :toc: spring boot oauth2 授权服务器, - 授权码模式、密码模式、刷新令牌 - 自定义 UserDetailService - 自定义 ClientDetailService - jwt 非对称加密 - 自定义登录授权页面 > SQL 语句 > > - DDL: `src/test/resources/schema.sql` > - DML: `src/test/resources/import.sql` 测试用例使用 h2 数据库,测试数据如下: .测试客户端 |=== |客户端 id |客户端密钥 |资源服务器名称 |授权类型 | scopes| 回调地址 |oauth2 |oauth2 |oauth2 |authorization_code,password,refresh_token |READ,WRITE |http://example.com |test |oauth2 |oauth2 |authorization_code,password,refresh_token |READ |http://example.com |error |oauth2 |test |authorization_code,password,refresh_token |READ |http://example.com |=== .测试用户 |=== |用户名 |密码 |角色 |admin |123456 |ROLE_ADMIN |test |123456 |ROLE_TEST |=== == 授权码模式 > 测试用例:`com.xkcoding.oauth.oauth.AuthorizationCodeGrantTests` === 获取授权码 - 请求地址: http://localhost:8080/oauth/authorize?response_type=code&client_id=oauth2&redirect_uri=http://example.com&scope=READ - 用户名:admin - 密码:123456 image::image/Login.png[login] === 确认授权 登录成功以后,进入确认授权页面。已经确认过的用户,不会再次要求确认。 image::image/Confirm.png[confirm] 确认授权后,获取授权码 image::image/Code.png[code] === 请求 token 使用以下代码可以直接请求 token [shell] ---- curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'code=GgX6QD' \ --data-urlencode 'redirect_uri=http://example.com' \ --data-urlencode 'client_id=oauth2' \ --data-urlencode 'scope=READ WRITE' ---- 得到 token [token] ---- { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiUkVBRCJdLCJleHAiOjE1NzgzODY4MTYsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZjAyMDhiNTUtYTJjYS00NjI4LTg5YjEtNzI5MzY4MzAxOWNhIiwiY2xpZW50X2lkIjoib2F1dGgyIn0.RqJpsin6bMnwI57cGpODTplLeW_gtNWHo_l4SimyRLsnxpCWm5oY1EOb4qVHpXvCbhNsUj69D462P7le13OOmexysZIQhaoGZ_CbIlEp63XsCnr5nSKeX3dgQlyTUDjOUL0WUtY2lKqLCGMeX_rpVhfmSh3b7MC0Ntxq5ao-943QMXGRIeRvJgSkvfY2HBN6-zx1H6rE0wxnUfBC1M08kUkFYlSmsFchiz-E_oTzJvE2D8lA9g-eEFU6cZ_els4Q77Vvc_O6SXUZ7o65vFyLyUjLvh9QF1825SGIUUdXTUYSZjnSAXChhRIAT5pLRHK-gthIzpOaWrgj6ebUoG02Eg", "token_type": "bearer", "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiUkVBRCJdLCJhdGkiOiJmMDIwOGI1NS1hMmNhLTQ2MjgtODliMS03MjkzNjgzMDE5Y2EiLCJleHAiOjE1NzgzODY4MTYsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiMGViNTU2MTQtYjgxYS00MTFmLTg1MTAtZThkMjZmODJmMjJhIiwiY2xpZW50X2lkIjoib2F1dGgyIn0.CBGcjirkf-3187SgbZr0ikauiCS8U9YLaoR4sNlRQjd-gaIeF5PChnIs_yAmG_VpqPFlPRdSl8DA05S2QnFpT3TkRjyP-LPDZgsVAPfczMAdVywU1zOKYZeq-gM6p9bmGEabbZoBlIxOImsjeyFSCui6UtRTZjNlj3AhGIzvs52T8bDqC796iHPDZvJ97MMgsEiRyu-mxDm1o1LMuBX9RHCx9rAkBVf52q36bqWMcYAlDOu1wYjpmhalSLZyWcmraQvClEitXGJI4eTFapTnuXQuWFIL-973V_5Shw98-bk65zZQOEheazHrUf-n4h-sYT4akehnYSVxX2UIg9XsCw", "expires_in": 5999, "scope": "READ", "jti": "f0208b55-a2ca-4628-89b1-7293683019ca" } ---- == 密码模式 > 测试用例:`com.xkcoding.oauth.oauth.ResourceOwnerPasswordGrantTests` `test` 用户进行授权 [source] ---- curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \ --data-urlencode 'password=123456' \ --data-urlencode 'username=test' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'scope=READ WRITE' ---- == 刷新令牌 携带 `refresh_token` 去请求 [source] ---- curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \ --data-urlencode 'grant_type=refresh_token' \ --data-urlencode 'refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiUkVBRCJdLCJhdGkiOiJmMDIwOGI1NS1hMmNhLTQ2MjgtODliMS03MjkzNjgzMDE5Y2EiLCJleHAiOjE1NzgzODY4MTYsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiMGViNTU2MTQtYjgxYS00MTFmLTg1MTAtZThkMjZmODJmMjJhIiwiY2xpZW50X2lkIjoib2F1dGgyIn0.CBGcjirkf-3187SgbZr0ikauiCS8U9YLaoR4sNlRQjd-gaIeF5PChnIs_yAmG_VpqPFlPRdSl8DA05S2QnFpT3TkRjyP-LPDZgsVAPfczMAdVywU1zOKYZeq-gM6p9bmGEabbZoBlIxOImsjeyFSCui6UtRTZjNlj3AhGIzvs52T8bDqC796iHPDZvJ97MMgsEiRyu-mxDm1o1LMuBX9RHCx9rAkBVf52q36bqWMcYAlDOu1wYjpmhalSLZyWcmraQvClEitXGJI4eTFapTnuXQuWFIL-973V_5Shw98-bk65zZQOEheazHrUf-n4h-sYT4akehnYSVxX2UIg9XsCw' ---- == 解析令牌 携带令牌解析 [source] ---- curl --location --request POST 'http://127.0.0.1:8080/oauth/check_token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \ --data-urlencode 'token=' ---- 解析结果 [source] ---- { "aud": [ "oauth2" ], "user_name": "admin", "scope": [ "READ", "WRITE" ], "active": true, "exp": 1578389936, "authorities": [ "ROLE_ADMIN" ], "jti": "fe59fce9-6764-435e-8fa7-7320e11af811", "client_id": "oauth2" } ---- == 退出登录 授权码模式登陆是在授权服务器上登录的,所以退出也要在授权服务器上退出。 携带回调地址进行退出,退出完成后跳转到回调地址: image::image/Logout.png[logout] 退出以后自动跳转到回调地址(要加 `http` 或 `https`) == 获取公钥 通过访问 '/oauth/token_key' 获取 JWT 公钥 [source] ---- curl --location --request GET 'http://127.0.0.1:8080/oauth/token_key' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' ---- 获取后 [source] ---- { "alg": "SHA256withRSA", "value": "-----BEGIN PUBLIC KEY-----\n......\n-----END PUBLIC KEY-----" } ---- == 核心配置 === 授权服务器配置 [Oauth2AuthorizationServerConfig] ---- @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.authenticationManager(authenticationManager) // 自定义用户 .userDetailsService(sysUserService) // 内存存储 .tokenStore(tokenStore) // jwt 令牌转换 .accessTokenConverter(jwtAccessTokenConverter); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 从数据库读取我们自定义的客户端信息 clients.withClientDetails(sysClientDetailsService); } @Override public void configure(AuthorizationServerSecurityConfigurer security) { security // 获取 token key 需要进行 basic 认证客户端信息 .tokenKeyAccess("isAuthenticated()") // 获取 token 信息同样需要 basic 认证客户端信息 .checkTokenAccess("isAuthenticated()"); } ---- === 安全配置 [WebSecurityConfig] ---- @Override protected void configure(HttpSecurity http) throws Exception { http // 开启表单登录,授权码模式的时候进行登录 .formLogin() // 路径等 .loginPage("/oauth/login") .loginProcessingUrl("/authorization/form") // 失败以后携带错误信息进行再次跳转登录页面 .failureHandler(clientLoginFailureHandler) .and() // 退出登录相关 .logout() .logoutUrl("/oauth/logout") .logoutSuccessHandler(clientLogoutSuccessHandler) .and() // 授权服务器安全配置 .authorizeRequests() .antMatchers("/oauth/**").permitAll() .anyRequest() .authenticated(); } ---- == 参考 - https://echocow.cn/articles/2019/07/14/1563096109754.html[Spring Security Oauth2 从零到一完整实践(三)授权服务器 ]