# springboot2-oauth2-demo **Repository Path**: MissJin/springboot2-oauth2-demo ## Basic Information - **Project Name**: springboot2-oauth2-demo - **Description**: springboot2+oauth2 实现client模式的开放授权模式(oauth2),并实现了client信息配置到mysql, access_token持久化到redis或者mysql - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 15 - **Forks**: 6 - **Created**: 2019-12-24 - **Last Updated**: 2023-05-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### springboot2+oauth2的client模式的demo - 适用于无用户的两台服务器进行api调用 ### 相关的官方文档 - [https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/guides/html5/helloworld-boot.html](https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/guides/html5/helloworld-boot.html) - [https://docs.spring.io/spring-security/site/docs](https://docs.spring.io/spring-security/site/docs) - [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749) - 如果想了解 其他模式,可通过gitee.com或者github.com上面搜索其他同学写的demo [推荐:https://github.com/lexburner/oauth2-demo](https://github.com/lexburner/oauth2-demo) ### 测试流程 - 2.根据access_token请求资源 ``` # 有接口-需要client授权获取access_token后,携带access_token值进行请求 curl -i -H "Accept: application/json" -X GET http://localhost:8080/api/test?access_token=f2471a3a-9f5b-4717-96d9-426a81ef7a76 # 或 curl -i -H "Accept: application/json" -H "Authorization: Bearer f2471a3a-9f5b-4717-96d9-426a81ef7a76" -X GET http://localhost:8080/api/test ``` - 1.获取access_token ``` # oauth endpoint,根据client授权获取access_token curl http://localhost:8080/oauth/token?grant_type=client_credentials&scope=all&client_id=client-id&client_secret=client-secret # 或 curl -H "Accept: application/json" http://localhost:8080/oauth/token -d "grant_type=client_credentials&scope=all&client_id=client-id&client_secret=client-secret" # 返回值 { "access_token": "f2471a3a-9f5b-4717-96d9-426a81ef7a76", "token_type": "bearer", "expires_in": 43199, "scope": "select" } ``` - 3.根据redis中的refreush_token 获取token【client模式,没必要做refresh_token】 ``` # 获取新的access_token http://localhost:8080/oauth/token?grant_type=refresh_token&scope=all&client_id=client-id&client_secret=client-secret&refresh_token=6146a41f-89f1-436a-83ee-b230dd5ed898 # 返回值 { "access_token": "86cd32b5-2b58-4cb0-9da9-c2cc70b5283e", "token_type": "bearer", "refresh_token": "6146a41f-89f1-436a-83ee-b230dd5ed898", "expires_in": 19, "scope": "all" } ``` `注意`: > token 是存储在 服务器内存中的,重启即会丢失,如果需要持久化,可存储在redis中 ### 模拟来自客户端的请求,并演示了 client_credentials模式中的scope和authrities的用法 - 模拟客户端发起请求的代码 ```java /** * 模拟来自客户端的请求 * @return */ @RequestMapping("mockTestFromClient") public String mockTestFromClient(){ // 1. client授权获得access_token // 2. 拿access_token,请求资源 long s = System.currentTimeMillis(); String resStr = restTemplate.getForObject("http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client-id&client_secret=client-secret", String.class); JSONObject json = JSON.parseObject(resStr); long e1 = System.currentTimeMillis(); log.info("client授权获得access_token:{}, 耗时:{}", json, e1-s); // 访问 test/select, 必须满足 #oauth2.hasScope('select') and hasAuthority('oauth2') // 例如 scope=select && authorities.contain("oauth2") String result = restTemplate.getForObject(String.format("http://localhost:8080/test/select?access_token=%s", json.getString("access_token")), String.class); long e2 = System.currentTimeMillis(); log.info("拿access_token,请求资源:{}, 耗时:{}", result, e2-e1); // 访问 api/select,授权通过了就能访问 MultiValueMap map = new LinkedMultiValueMap<>(); HttpHeaders header = new HttpHeaders(); header.setContentType(MediaType.APPLICATION_JSON); header.add("Authorization", "Bearer "+ json.getString("access_token")); HttpEntity> httpEntity = new HttpEntity<>(map, header); String body = restTemplate.exchange("http://localhost:8080/api/select", HttpMethod.GET, httpEntity, String.class).getBody(); long e3 = System.currentTimeMillis(); log.info("再拿access_token,请求资源:{}, 耗时:{}", body, e3-e2); return result; } ``` - AuthorizationServerConfiguration【授权服务配置】 ```java @Configuration @EnableAuthorizationServer protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Autowired ApplicationContext applicationContext; @Autowired RedisConnectionFactory redisConnectionFactory; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client-id") .secret("client-secret") .resourceIds(DEMO_RESOURCE_ID) .authorizedGrantTypes("client_credentials", "refresh_token") .scopes("all", "select", "read", "write") .authorities("oauth2") .accessTokenValiditySeconds(20) .refreshTokenValiditySeconds(3600 * 24 * 30); //30天过期 // .and().withClient("client_2") // .resourceIds(DEMO_RESOURCE_ID) // .authorizedGrantTypes("password", "refresh_token") // .scopes("select") // .authorities("oauth2") // .secret(finalSecret); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { Set allowedTokenEndpointRequestMethods = endpoints.getAllowedTokenEndpointRequestMethods(); System.out.println("默认的提交方式:" + allowedTokenEndpointRequestMethods); endpoints .tokenStore(new RedisTokenStore(redisConnectionFactory)) //.tokenStore(new InMemoryTokenStore()) .authenticationManager(authenticationManager) .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) .reuseRefreshTokens(true); Set allowedTokenEndpointRequestMethodsAfter = endpoints.getAllowedTokenEndpointRequestMethods(); System.out.println("修改后的提交方式:" + allowedTokenEndpointRequestMethodsAfter); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) { oauthServer .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") //允许表单认证 .allowFormAuthenticationForClients(); } } ``` - ResourceServerConfiguration【资源服务配置】 ```java @Configuration @EnableResourceServer protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(DEMO_RESOURCE_ID).stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //配置api访问控制,必须认证过后才可以访问 .antMatchers("/api/**").authenticated() .and() .authorizeRequests() .antMatchers("/test/select").access("#oauth2.hasScope('select') and hasAuthority('oauth2')") .antMatchers("/test/all").access("#oauth2.hasScope('all')") .antMatchers("/test/read").access("#oauth2.hasScope('read')") .antMatchers("/test/write").access("#oauth2.hasScope('write')"); } } ``` ### 知识点 ```jshelllanguage oauth2 client credentials授权模式是搭建微服务架构的关键。 oauth2支持4种授权模式:密码模式(resource owner password credentials)、授权码模式(authorization code)、简化模式(implicit)和客户端模式(client credentials)。 • 密码模式(resource owner password credentials) 这种模式是最不推荐的,因为client可能存了用户密码;这种模式主要用来做遗留项目升级为oauth2的适配方案;当然如果client是自家的应用,也是可以;支持refresh token。 使用client_id和client_secret以及用户名密码直接获取秘钥 请求地址类似如下: http://localhost:8080/oauth/token?grant_type=password&username=lixx&password=dw123456 • 授权码模式(authorization code): 算是正宗的oauth2的授权模式,设计了auth code,通过这个code再获取token, 支持refresh token。 请求地址如下: http://localhost:8080/oauth/authorize?response_type=code&client_id=wx_takeout_client_id&redirect_uri=http://localhost:8080/login 请求流程如下: (1)使用client_id和client_secret换取授权码过程。需要跳转至登录页面,由用户手动登录。这也是微服务架构不能使用授权码模式的原因。因为程序不可能通过登录页面进行登录。 (2)通过授权码换取令牌 (3)刷新token • 简化模式(implicit) 这种模式比授权码模式少了code环节,回调url直接携带token;使用场景是基于浏览器的应用,这种模式基于安全性考虑,建议把token时效设置短一些;不支持refresh token。 请求地址类似如下: 请求: 用浏览器(此时同授权码模式,浏览器能跳转到登录页面,postman不行) http://localhost:8080/oauth/authorize?response_type=token&client_id=wx_takeout_client_id&redirect_uri=http://localhost:8080/login 与授权码模式一样,简化模式只能应用于基于浏览器的应用,需要用户手动登录。也就是说同样不适用于微服务架构。 • 客户端模式(client credentials) 这种模式直接根据client的id和密钥即可获取token,无需用户参与。 这种模式比较合适消费api的后端服务,也就是微服务架构。这正是我们需要的。 不支持refresh token,主要是没有必要。 ```