一.案例架構
主要包括如下服務:
1.第三方應用
2.授權服務器
3.資源服務器
4.用戶
項目 | 端口 | 備注 |
---|---|---|
auth-server | 8080 | 授權服務器 |
user-server | 8081 | 資源服務器 |
client-app | 8082 | 第三方應用 |
首先來創建一個空的 Maven 父工程,創建好之后,里邊什么都不用加,也不用寫代碼。我們將在這個父工程中搭建這個子模塊
二.授權服務器搭建
首先我們搭建一個名為 auth-server 的授權服務,搭建的時候,選擇如下三個依賴:
1.web
2.spring cloud security
3.spirng cloud OAuth2
創建完成之后提供一個SpringSecurity的基本配置
在這里配置的,就是用戶的用戶名/密碼/角色信息。
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("test1") .password(new BCryptPasswordEncoder().encode("123456")) .roles("admin") .and() .withUser("test2") .password(new BCryptPasswordEncoder().encode("123456")) .roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().formLogin(); } }
配置授權服務器
@Configuration public class AccessTokenConfig {
//生成的 Token 要往哪里存儲 可以存在 Redis 中,也可以存在內存中,也可以結合 JWT 等等 @Bean TokenStore tokenStore() { return new InMemoryTokenStore(); //它存在內存中 } }
// 這個類繼承AuthorizationServerConfigureAdapter 對授權服務器的詳細配置 @EnableAuthorizationServer //表示開啟授權服務器的自動化配置。 @Configuration public class AuthorizationServer extends AuthorizationServerConfigurerAdapter { @Autowired TokenStore tokenStore; @Autowired ClientDetailsService clientDetailsService;
//主要用來配置 Token 的一些基本信息 @Bean AuthorizationServerTokenServices tokenServices() { DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); services.setSupportRefreshToken(true); //是否支持刷新 services.setTokenStore(tokenStore); //token 存儲的位置 services.setAccessTokenValiditySeconds(60 * 60 * 2); //token的有效期 services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); // 刷新token的有效期 return services; }
//用來配置令牌端點的安全約束,也就是這個端點誰能訪問,誰不能訪問 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.checkTokenAccess("permitAll()") //checkTokenAccess 是指一個 Token 校驗的端點 .allowFormAuthenticationForClients(); //設置為可以直接訪問 當資源服務器收到 Token 之后,需要去校驗Token 的合法性,就會訪問這個端點 }
//配置客戶端的詳細信息 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() //存儲到內存中 .withClient("test") //客戶端的id .secret(new BCryptPasswordEncoder().encode("123")) .resourceIds("rid") //資源id .authorizedGrantTypes("authorization_code","refresh_token") //授權類型 .scopes("all") //資源范圍 .redirectUris("http://localhost:8082/index.html"); //重定向url }
//配置令牌的訪問端點和令牌服務 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authorizationCodeServices(authorizationCodeServices()) //authorizationCodeService用來配置授權碼的存儲 這里我們是存在在內存中 .tokenServices(tokenServices()); //配置令牌的存儲 即 access_token 的存儲位置 } @Bean AuthorizationCodeServices authorizationCodeServices() { return new InMemoryAuthorizationCodeServices(); } }
授權碼和令牌有什么區別?授權碼是用來獲取令牌的,使用一次就失效,令牌則是用來獲取資源的
三.資源服務器搭建
資源服務器就是用來存放用戶的資源,例如你在微信上的圖像、openid 等信息,用戶從授權服務器上拿到 access_token 之后,接下來就可以通過 access_token 來資源服務器請求數據。
我們創建一個新的 Spring Boot 項目,叫做 user-server ,作為我們的資源服務器,創建時,添加如下依賴:
1.web
2.spring cloud security
3.spirng cloud OAuth2
配置資源服務器 (如果是小項目,資源和授權服務器可以放一起,大項目就要分開)
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//配置了一個 RemoteTokenServices 的實例 這是因為資源服務器和授權服務器是分開的,如果放一起就不需要配置 RemoteTokeService @Bean RemoteTokenServices tokenServices() { RemoteTokenServices services = new RemoteTokenServices(); services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); //access_token 的校驗地址 services.setClientId("test"); //client_id services.setClientSecret("123"); //client_secret return services; }
//資源攔截規則 @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("res1").tokenServices(tokenServices()); }
//請求規則 @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated(); } }
測試接口
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } @GetMapping("/admin/hello") public String admin() { return "admin"; } }
四.第三方應用搭建
一個普通的 Spring Boot 工程,創建時加入 Thymeleaf 依賴和 Web 依賴:
在 resources/templates 目錄下,創建 index.html ,內容如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Oauth2</title>
</head>
<body>
你好,test!
<a href="http://localhost:8080/oauth/authorize?client_id=test&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html">第三方登錄</a>
<h1 th:text="${msg}"></h1>
</body>
</html>
@Controller public class HelloController { @Autowired RestTemplate restTemplate; @GetMapping("/index.html") public String hello(String code, Model model) { if (code != null) { MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); map.add("code", code); map.add("client_id", "test"); map.add("client_secret", "123"); map.add("redirect_uri", "http://localhost:8082/index.html"); map.add("grant_type", "authorization_code"); Map<String,String> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class); String access_token = resp.get("access_token"); System.out.println(access_token); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Bearer " + access_token); HttpEntity<Object> httpEntity = new HttpEntity<>(headers); ResponseEntity<String> entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, httpEntity, String.class); model.addAttribute("msg", entity.getBody()); } return "index"; } }
如果 code 不為 null,也就是如果是通過授權服務器重定向到這個地址來的,那么我們做如下兩個操作:
- 根據拿到的 code,去請求
http://localhost:8080/oauth/token
地址去獲取 Token,返回的數據結構如下:
{ "access_token": "e7f223c4-7543-43c0-b5a6-5011743b5af4", //請求數據所需要的令牌 "token_type": "bearer", "refresh_token": "aafc167b-a112-456e-bbd8-58cb56d915dd", //刷新token所需要的令牌 "expires_in": 7199, //token有效期還剩多久 "scope": "all" }