版本:springboot2.2.6.RELEASE
参考文章:OAuth2 授权码模式
做如下修改:
1、auth-server(授权服务器)
修改configure(ClientDetailsServiceConfigurer) 方法部分:只需要在 authorizedGrantTypes 中增加 implicit 表示支持简化模式即可。
AuthorizationServer:
package cn.linst.authserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import java.lang.ref.SoftReference;
/**
* 简化模式。
*
* 在这个auth-server demo中,就是相当于第三方平台。
* 授权服务器。模拟第三方平台。授权服务器一方面是校验客户端,另一方面则是校验用户。
* 用户信息校验在SecurityConfig配置了。
*/
@EnableAuthorizationServer // 表示开启授权服务器的自动化配置
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
TokenStore tokenStore;
/**
* 保存的客户端信息
*/
@Autowired
ClientDetailsService clientDetailsService;
/**
* 令牌相关配置。用来配置 Token 的一些基本信息。
* 如 Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期等。
* 当 Token 快要过期的时候,需要获取一个新的token,获取新的 Token 时候,需要有一个凭证信息,refresh_token。这个 refresh_token 也是有有效期的
*/
@Bean
AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore);
// access_token 有效期
services.setAccessTokenValiditySeconds(60 * 60 * 2);
// refresh_token 有效期
services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
return services;
}
/**
* AuthorizationServerSecurityConfigurer用来配置令牌端点的安全约束,就是这个端点谁能访问,谁不能访问。
* checkTokenAccess是指一个 Token 校验的端点,这个端点我们设置为可以直接访问。在资源服务器收到 Token 之后,需要去校验 Token 的合法性,就会访问这个端点。
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 1. /oauth/check_token 资源服务器会自动校验这个接口,检查token对应的信息。
// 2. 这里配置为了让这个接口都能访问到。
// 3. 允许表单登录。
security.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
//
// /** 授权码模式
// * ClientDetailsServiceConfigurer 用来配置客户端的信息。
// * 校验客户端
// */
// @Override
// public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.inMemory()
// .withClient("clientIdForMyApplication") //应用在第三方平台注册,平台给应用生成appid。
// .secret(new BCryptPasswordEncoder().encode("123"))
// .resourceIds("res1") // 资源id
// .authorizedGrantTypes("authorization_code","refresh_token") // authorization_code:授权码模式 ;refresh_token:刷新token
// .scopes("all")
.autoApprove(true)
// .redirectUris("http://localhost:8082/index.html"); // 授权完成后,重定向的地址。
// }
/**
* 简化模式
* ClientDetailsServiceConfigurer 用来配置客户端的信息。
* 校验客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientIdForMyApplication") //应用在第三方平台注册,平台给应用生成appid。
.secret(new BCryptPasswordEncoder().encode("123"))
.resourceIds("res1") // 资源id
.authorizedGrantTypes("refresh_token","implicit") // authorization_code:授权码模式 ;implicit:支持简化模式
.scopes("all")
// .autoApprove(true)
.redirectUris("http://localhost:8082/01.html"); // 授权完成后,重定向的地址。
}
/**
* AuthorizationServerEndpointsConfigurer用来配置令牌的访问端点和令牌服务。
* authorizationCodeServices用来配置授权码的存储。
* tokenServices 这个 Bean 主要用来配置 Token 的一些基本信息。
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authorizationCodeServices(authorizationCodeServices())
.tokenServices(tokenServices());
}
/**
* 授权码保存的地方,存哪都行,一般存内存,授权码用一次就失效了没用了。
* 授权码与令牌区别:授权码是用来获取令牌的,使用一次就失效,令牌则是用来获取资源。
*
*/
@Bean
AuthorizationCodeServices authorizationCodeServices() {
return new InMemoryAuthorizationCodeServices();
}
}
2、因为简化模式没有服务端,我们只能通过 js 来请求资源服务器上的数据,所以资源服务器需要支持跨域,我们修改如下两个地方使之支持跨域。
user-server(资源服务器):
1)在 Controller 上添加 @CrossOrigin 注解使之支持跨域
package cn.linst.userserver;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
// 增加跨越支持
@CrossOrigin(value = "*")
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello。。。";
}
@GetMapping("/admin/hello")
public String admin() {
return "admin。。。";
}
}
2)配置 Spring Security 使之支持跨域,在configure(HttpSecurity http)方法增加cors
package cn.linst.userserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
/**
* 模拟第三方平台的资源服务器
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
* 这是因为资源服务器和授权服务器是分开的,资源服务器和授权服务器是放在一起的。
* 如果资源服务器和授权服务器是放在一起的,就不需要配置 RemoteTokenServices 了
*
*/
@Bean
RemoteTokenServices tokenServices() {
// 配置了一个 RemoteTokenServices 的实例。
// 配置了 access_token 的校验地址、client_id、client_secret 这三个信息。
// 当用户来资源服务器请求资源时,会携带上一个 access_token,通过这里的配置,就能够
// 校验出 token 是否正确等。
RemoteTokenServices services = new RemoteTokenServices();
services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
services.setClientId("clientIdForMyApplication");
services.setClientSecret("123");
return services;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("res1").tokenServices(tokenServices());
}
/**
* 配置一下资源的拦截规则,这就是 Spring Security 中的基本写法 。
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.anyRequest().authenticated().and().cors();
}
}
3、client-app(第三方应用)
在static目录下,新增01.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>hello world 简化模式title>
<script src="/jquery-3.5.0.min.js">script>
head>
<body>
你好,世界!
<a href="http://localhost:8080/oauth/authorize?client_id=clientIdForMyApplication&response_type=token&scope=all&redirect_uri=http://localhost:8082/01.html">第三方登录(简化模式)a>
<div id="div1">div>
<script>
var hash = window.location.hash;//提取出参数,类似这种格式#access_token=38653449-a3cd-414b-a313-7a1aa9bac0b1&token_type=bearer&expires_in=7069
if (hash && hash.length > 0) {
var params = hash.substring(1).split("&");
var token = params[0].split("=");//[access_token,38653449-a3cd-414b-a313-7a1aa9bac0b1]
$.ajax({
type: 'get',
headers: {
'Authorization': 'Bearer ' + token[1]
},
url: 'http://localhost:8081/hello',
success: function (data) {
$("#div1").html(data)
}
})
}
script>
body>
html>