案例架构
项目 |
端口 |
备注 |
auth-server |
8080 |
授权服务器 |
resource-server |
8081 |
资源服务器 |
client-app |
8082 |
第三方应用 |
授权服务器
创建SpringBoot应用并加入三个依赖:
- web
- spring cloud security
- spirng cloud OAuth2
1.对Spring Security做一些基本配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("atomsk") .password(new BCryptPasswordEncoder().encode("123")) .roles("admin") .and() .withUser("guest") .password(new BCryptPasswordEncoder().encode("123")) .roles("user"); }
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().formLogin(); } }
|
2.用Redis存储token
1 2 3 4 5 6 7 8 9 10
| @Configuration public class AccessTokenConfig { @Autowired RedisConnectionFactory redisConnectionFactory;
@Bean TokenStore tokenStore(){ return new RedisTokenStore(redisConnectionFactory); } }
|
3.配置授权服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @Configuration @EnableAuthorizationServer public class AuthorizationServer extends AuthorizationServerConfigurerAdapter { @Autowired TokenStore tokenStore; @Autowired ClientDetailsService clientDetailsService;
@Bean AuthorizationCodeServices authorizationCodeServices(){
return new InMemoryAuthorizationCodeServices(); }
@Bean AuthorizationServerTokenServices tokenServices() { DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); services.setSupportRefreshToken(true); services.setTokenStore(tokenStore); services.setAccessTokenValiditySeconds(60 * 60 * 2); services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); return services; }
@Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.checkTokenAccess("permitAll()") .allowFormAuthenticationForClients(); }
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("atomsk") .secret(new BCryptPasswordEncoder().encode("321")) .resourceIds("res1") .authorizedGrantTypes("authorization_code","refresh_token") .scopes("all") .redirectUris("http://localhost:8082/index.html"); }
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authorizationCodeServices(authorizationCodeServices()) .tokenServices(tokenServices()); } }
|
资源服务器
tip:小项目授权服务器和资源服务器可以放在一起。
和创建授权服务器一样,创建SpringBoot应用并加入三个依赖:
- web
- spring cloud security
- spirng cloud OAuth2
1.加入如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Bean RemoteTokenServices tokenServices(){ RemoteTokenServices services=new RemoteTokenServices(); services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); services.setClientId("atomsk"); services.setClientSecret("321"); 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/**").hasAnyRole("admin") .anyRequest().authenticated(); } }
|
2.添加两个配置接口
1 2 3 4 5 6 7 8 9 10 11
| @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } @GetMapping("/admin/hello") public String admin() { return "admin"; } }
|
第三方应用
第三方应用就是一个普通的 Spring Boot 工程,创建时加入 Thymeleaf 依赖和 Web 依赖即可
1.在templates创建一个index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Tittle</title> </head> <body> Hello,Oauth2 <a href="http://localhost:8080/oauth/authorize?client_id=atomsk&response_type=code">第三方登录</a>
<h1 th:text="${msg}"></h1> </body> </html>
|
超链接里的参数:
- client_id 客户端 ID,根据我们在授权服务器中的实际配置填写。
- response_type 表示响应类型,这里是 code 表示响应一个授权码。
2.定一个Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @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", "atomsk"); map.add("client_secret", "321"); 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,返回的数据结构如下:
1 2 3 4 5 6 7
| { "access_token": "e7f223c4-7543-43c0-b5a6-5011743b5af4", "token_type": "bearer", "refresh_token": "aafc167b-a112-456e-bbd8-58cb56d915dd", "expires_in": 7199, "scope": "all" }
|
接下来,根据我们拿到的 access_token,去请求资源服务器,注意 access_token 通过请求头传递,最后将资源服务器返回的数据放到 model 中
本博文完全参照江南一点雨的Oauth2的授权码模式案例写成,感兴趣的点击下面链接访问原文:
http://www.javaboy.org/2020/0414/oauth2_authorization_code.html