授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。其具体的流程如下:
具体步骤:
在步骤A中客户端告知浏览器重定向到授权服务器的URI包含以下参数:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
在C步骤中授权服务器回应客户端的URI,包含以下参数:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
在D步骤中客户端向授权服务器申请令牌的HTTP请求,包含以下参数:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
在E步骤中授权服务器发送的HTTP回复,包含以下参数:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
更新令牌
如果用户访问的时候,客户端的访问令牌access_token已经过期,则需要使用更新令牌refresh_token申请一个新的访问令牌。
客户端发出更新令牌的HTTP请求,包含以下参数:
示例代码包含授权服务和资源服务
服务名 | 端口号 | 说明 |
---|---|---|
auth-server | 8080 | 授权服务器 |
resource-server | 8088 | 资源服务器 |
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-securityartifactId>
dependency>
/** * 授权服务器配置 * * @author simon * @create 2018-10-29 11:51 **/
@Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
UserDetailsService userDetailsService;
// 使用最基本的InMemoryTokenStore生成token
@Bean
public TokenStore memoryTokenStore() {
return new InMemoryTokenStore();
}
/** * 配置客户端详情服务 * 客户端详细信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息 * @param clients * @throws Exception */
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client1")//用于标识用户ID
.authorizedGrantTypes("authorization_code","refresh_token")//授权方式
.scopes("test")//授权范围
.secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456"));//客户端安全码,secret密码配置从 Spring Security 5.0开始必须以 {bcrypt}+加密后的密码 这种格式填写;
}
/** * 用来配置令牌端点(Token Endpoint)的安全约束. * @param security * @throws Exception */
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/* 配置token获取合验证时的策略 */
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
/** * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services) * @param endpoints * @throws Exception */
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 配置tokenStore,需要配置userDetailsService,否则refresh_token会报错
endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore()).userDetailsService(userDetailsService);
}
}
/** * 配置spring security * * @author simon * @create 2018-10-29 16:25 **/
@EnableWebSecurity//开启权限验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/** * 配置这个bean会在做AuthorizationServerConfigurer配置的时候使用 * @return * @throws Exception */
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/** * 配置用户 * 使用内存中的用户,实际项目中,一般使用的是数据库保存用户,具体的实现类可以使用JdbcDaoImpl或者JdbcUserDetailsManager * @return */
@Bean
@Override
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("admin")).authorities("USER").build());
return manager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
}
在启动类上添加注解@EnableAuthorizationServer
开启授权服务
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>priv.simon.resourcegroupId>
<artifactId>resource-serverartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>resource-servername>
<description>资源服务器description>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.6.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>Finchley.SR2spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
auth-server-url: http://localhost:8080 # 授权服务地址
server:
port: 8088
security:
oauth2:
client:
client-id: client1
client-secret: 123456
scope: test
access-token-uri: ${auth-server-url}/oauth/token
user-authorization-uri: ${auth-server-url}/oauth/authorize
resource:
token-info-uri: ${auth-server-url}/oauth/check_token #检查令牌
在启动类上新增注解@EnableResourceServer
开启资源服务,并提供资源获取接口
@EnableResourceServer
@RestController
@SpringBootApplication
public class ResourceServerApplication {
private static final Logger log = LoggerFactory.getLogger(ResourceServerApplication.class);
public static void main(String[] args) {
SpringApplication.run(ResourceServerApplication.class, args);
}
@GetMapping("/user")
public Authentication getUser(Authentication authentication) {
log.info("resource: user {}", authentication);
return authentication;
}
}
http://localhost:8080/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://baidu.com
如果没有登录,浏览器会重定向到登录界面
输入用户名和密码(admin/admin)点击登录,这时会进入授权页
点击授权,浏览器会从定向到回调地址上,携带code
参数
经过研究发现:
/oauth/token
端点:
那么授权服务器配置修改如下:
/** * 用来配置令牌端点(Token Endpoint)的安全约束. * @param security * @throws Exception */
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/* 配置token获取合验证时的策略 */
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
}
返回数据如下:
{
"access_token": "c0fa303f-77ad-427b-8f50-c5b3e031d109",
"token_type": "bearer",
"refresh_token": "a9b4a4c5-4e27-4328-ae1b-91cdd7c85f15",
"expires_in": 43199,
"scope": "test"
}
http://localhost:8088/user?access_token=c0fa303f-77ad-427b-8f50-c5b3e031d109
结果返回:
{
"authorities": [
{
"authority": "ROLE_test"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": null,
"tokenValue": "c0fa303f-77ad-427b-8f50-c5b3e031d109",
"tokenType": "Bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "ROLE_test"
}
],
"details": null,
"authenticated": true,
"principal": "admin",
"credentials": "N/A",
"name": "admin"
},
"principal": "admin",
"credentials": "",
"oauth2Request": {
"clientId": "client1",
"scope": [
"test"
],
"requestParameters": {
"client_id": "client1"
},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [],
"extensions": {},
"refreshTokenRequest": null,
"grantType": null
},
"clientOnly": false,
"name": "admin"
}
github下载源码