一.案例架构
主要包括如下服务:
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的基本配置
在这里配置的,就是用户的用户名/密码/角色信息。
@Configurationpublic class SecurityConfig extendsWebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {return newBCryptPasswordEncoder();
}
@Overrideprotected void configure(AuthenticationManagerBuilder auth) throwsException {
auth.inMemoryAuthentication()
.withUser("test1")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("admin")
.and()
.withUser("test2")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("user");
}
@Overrideprotected void configure(HttpSecurity http) throwsException {
http.csrf().disable().formLogin();
}
}
配置授权服务器
@Configurationpublic classAccessTokenConfig {
//生成的 Token 要往哪里存储 可以存在 Redis 中,也可以存在内存中,也可以结合 JWT 等等
@Bean
TokenStore tokenStore() {return newInMemoryTokenStore(); //它存在内存中
}
}
// 这个类继承AuthorizationServerConfigureAdapter 对授权服务器的详细配置
@EnableAuthorizationServer //表示开启授权服务器的自动化配置。
@Configurationpublic class AuthorizationServer extendsAuthorizationServerConfigurerAdapter {
@Autowired
TokenStore tokenStore;
@Autowired
ClientDetailsService clientDetailsService;
//主要用来配置 Token 的一些基本信息
@Bean
AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services= newDefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setSupportRefreshToken(true); //是否支持刷新
services.setTokenStore(tokenStore); //token 存储的位置
services.setAccessTokenValiditySeconds(60 * 60 * 2); //token的有效期
services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); // 刷新token的有效期returnservices;
}
//用来配置令牌端点的安全约束,也就是这个端点谁能访问,谁不能访问
@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throwsException {
security.checkTokenAccess("permitAll()") //checkTokenAccess 是指一个 Token 校验的端点
.allowFormAuthenticationForClients(); //设置为可以直接访问 当资源服务器收到 Token 之后,需要去校验Token 的合法性,就会访问这个端点
}
//配置客户端的详细信息
@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throwsException {
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
}
//配置令牌的访问端点和令牌服务
@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throwsException {
endpoints.authorizationCodeServices(authorizationCodeServices()) //authorizationCodeService用来配置授权码的存储 这里我们是存在在内存中
.tokenServices(tokenServices()); //配置令牌的存储 即 access_token 的存储位置
}
@Bean
AuthorizationCodeServices authorizationCodeServices() {return newInMemoryAuthorizationCodeServices();
}
}
授权码和令牌有什么区别?授权码是用来获取令牌的,使用一次就失效,令牌则是用来获取资源的
三.资源服务器搭建
资源服务器就是用来存放用户的资源,例如你在微信上的图像、openid 等信息,用户从授权服务器上拿到 access_token 之后,接下来就可以通过 access_token 来资源服务器请求数据。
我们创建一个新的 Spring Boot 项目,叫做 user-server ,作为我们的资源服务器,创建时,添加如下依赖:
1.web
2.spring cloud security
3.spirng cloud OAuth2
配置资源服务器 (如果是小项目,资源和授权服务器可以放一起,大项目就要分开)
@Configuration
@EnableResourceServerpublic class ResourceServerConfig extendsResourceServerConfigurerAdapter {
//配置了一个 RemoteTokenServices 的实例 这是因为资源服务器和授权服务器是分开的,如果放一起就不需要配置 RemoteTokeService
@Bean
RemoteTokenServices tokenServices() {
RemoteTokenServices services= newRemoteTokenServices();
services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); //access_token 的校验地址
services.setClientId("test"); //client_id
services.setClientSecret("123"); //client_secretreturnservices;
}
//资源拦截规则
@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throwsException {
resources.resourceId("res1").tokenServices(tokenServices());
}
//请求规则
@Overridepublic void configure(HttpSecurity http) throwsException {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.anyRequest().authenticated();
}
}
测试接口
@RestControllerpublic classHelloController {
@GetMapping("/hello")publicString hello() {return "hello";
}
@GetMapping("/admin/hello")publicString admin() {return "admin";
}
}
四.第三方应用搭建
一个普通的 Spring Boot 工程,创建时加入 Thymeleaf 依赖和 Web 依赖:
在 resources/templates 目录下,创建 index.html ,内容如下:
Oauth2 第三方登录@Controllerpublic classHelloController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/index.html")publicString hello(String code, Model model) {if (code != null) {
MultiValueMap 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 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= newHttpHeaders();
headers.add("Authorization", "Bearer " +access_token);
HttpEntity httpEntity = new HttpEntity<>(headers);
ResponseEntity 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"}