前文传送地址:
《Spring Security实现OAuth2.0——授权服务》
《Spring Security实现OAuth2.0——资源服务》
前面两篇文章已经介绍了授权服务器和资源服务器的实例,其实我们整个Spring Security实现OAuth2.0的核心部分就已经完成了,但是为了案例的完整性,我们本文再增加客户端的案例。
一、授权服务器配置
我们需要将授权服务器改为使用“授权码”模式:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.authorizedGrantTypes("authorization_code","refresh_token")
}
除了模式的变更,我们还需要更改下客户端的回调地址,因为原来的回调地址没有路径,不方便我们的测试:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
// 重定向地址,这里是第三方客户端的地址,用来接收授权服务器返回的授权码
.redirectUris("http://localhost:8080/getAuthorizationCode");
}
然后就可以启动我们的授权服务器了。
二、资源服务器配置
在原先的基础上,我们新增一个Controller:
@GetMapping("info/getUserName")
@PreAuthorize("hasAuthority('user:query')")
public String getUserName(){
// 省略获取用户信息的逻辑,直接返回
return "zhangsansan123";
}
然后就可以启动我们的资源服务器了。
三、客户端配置
3.1 静态页面配置
客户端需要两个页面:
-
index.html是我们的主页,在主页上,用户点击按钮发起OAuth2.0的流程,表示允许客户端网站获取用户在资源服务器上的信息;
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>hello</title> </head> <body> <a href="http://localhost:8081/oauth/authorize?client_id=iSchool&response_type=code&scope=all&redirect_uri=http://localhost:8080/getAuthorizationCode">使用授权服务登录</a> </body> </html>
-
hello.html是具体的客户页面,客户端网站获取资源服务器上的用户信息后,将用户信息呈现在这个页面展示给用户;
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>hello</title> </head> <body> <h1 th:text="${userName}"></h1> </body> </html>
我们的静态页面使用了Thymeleaf,还需要一些配置信息:
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafConfig {
public static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
}
还有系统配置文件:
server:
port: 8080
springs:
application:
name: client
thymeleaf:
# 关闭thymeleaf的缓存
cache: false
3.2 HTTP请求配置
客户端需要多次请求授权服务器和资源服务器,因此我们使用Spring Boot自带的RestTemplate,需要先进行如下配置:
@Configuration
public class RestTemplateConfig {
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);
factory.setConnectTimeout(5000);
return factory;
}
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}
接受授权服务器返回访问令牌的时候,我们需要新建一个实体类:
@Data
public class AccessTokenBean {
private String access_token;
private String token_type;
private String refresh_token;
private Long expires_in;
private String scope;
}
3.3 Controller配置
主要包含两个路径,分别对应静态页面中的index和hello:
@Slf4j
@Controller
public class UserController {
private static final String GET_ACCESS_TOKEN_URL = "http://localhost:8081/oauth/token?client_id={clientId}&client_secret={client_secret}&grant_type={grant_type}&redirect_uri={redirect_uri}&code={code}";
private static final String GET_USER_NAME_URL = "http://localhost:8082/info/getUserName";
@Autowired
private RestTemplate restTemplate;
private Map<String, String> paramMap = new HashMap<String, String>();
/**
* 登录首页,供用户选择使用授权服务进行登录
*
* @return
*/
@GetMapping("/login")
public String getUserName() {
return "index";
}
/**
* 供授权服务器重定向传回授权码
* http://localhost:8080/getAuthorizationCode?code=UJfbrU
*
* @param authorizationCode
* @return
*/
@GetMapping("/getAuthorizationCode")
public String getAuthorizationCode(@RequestParam("code") String authorizationCode, Model model) {
log.info("授权码是{}", authorizationCode);
// 组装参数
paramMap.put("clientId", "iSchool");
paramMap.put("client_secret", "mysecret");
paramMap.put("grant_type", "authorization_code");
paramMap.put("redirect_uri", "http://localhost:8080/getAuthorizationCode");
paramMap.put("code", authorizationCode);
// 使用授权码再次访问授权服务器获取访问token
AccessTokenBean accessTokenBean = restTemplate.getForObject(GET_ACCESS_TOKEN_URL, AccessTokenBean.class, paramMap);
if (ObjectUtils.isEmpty(accessTokenBean) || ObjectUtils.isEmpty(accessTokenBean.getAccess_token())) {
log.error("未成功获取访问令牌!");
return null;
}
String accessToken = accessTokenBean.getAccess_token();
log.info("访问令牌是{}", accessToken);
// 使用访问令牌访问资源服务器获取用户信息
HttpHeaders header = new HttpHeaders();
// 指定JSON格式
header.setContentType(MediaType.APPLICATION_JSON);
header.add("Authorization", "Bearer " + accessToken);
HttpEntity<String> httpEntity = new HttpEntity<>(null, header);
ResponseEntity<String> responseEntity2 = restTemplate.exchange(GET_USER_NAME_URL, HttpMethod.GET, httpEntity, String.class);
String userName = responseEntity2.getBody();
// 设置用户信息并渲染页面
model.addAttribute("userName", userName);
return "hello";
}
}
到此,客户端的配置也都完成了,启动应用后,就可以进入到下面的测试阶段。
四、流程测试
作为用户,我肯定是先访问客户端的某个地址,比如网站的主页:
http://localhost:8080/login
主页的按钮就是供用户选择使用其它平台的账号信息作为本网站的账号信息的。

点击按钮,就会跳转到授权服务器的登录页面,用户需要使用他自己的账号密码登录:

登录后,我们在授权服务器代码里面配置了需要手动授权,所以需要用户手动确认授权:

授权完成后,授权服务器会重定向到配置好的客户端网站的地址,这个过程可以在浏览器的地址栏中发现,地址中还带上了授权服务器颁发的授权码。
客户端网站将授权码传递到后端,也就是我们的/getAuthorizationCode
服务,然后再次调用授权服务器换取访问令牌;
客户端网站拿到访问令牌后,再去调用资源服务器获取用户信息的接口,就是info/getUserName
服务;
由于资源服务器上的这个服务需要权限,因此资源服务器将令牌传递给授权服务器进行验证和授权,授权服务器验证访问令牌后告知资源服务器认证通过,并且这个用户拥有user:query
的权限;
资源服务器验证info/getUserName
服务的权限和授权服务器返回的权限是否一致,一致的话就返回用户的信息给客户端网站;
最后,客户端的网站得到了资源服务器返回的用户信息,将信息展示在hello页面上返回给用户:

到此,整个OAuth2.0的流程就完成了。
网友评论