OAuth2.0认证授权

本文最后更新于:2025年2月18日 下午

OAuth2.0认证授权

SSO 单点登录

模式介绍

四种认证模式:

授权码模式 - [authorization_code]

前后端项目的推荐模式,也是标准模式。

大致流程:首先通过app_idapp_secret等一系列参数获取授权码(这种可以通过二维码方式扫描打开授权登录页,然后用户登录授权后,授权服务器会将授权码和随机字符串返回给后端的接口,再由接口通过授权码请求授权服务器获取token和refresh_token) -> 再通过授权码获取tokenrefresh_token,再用token从资源服务器获取资源。token过期后,可以用refresh_token获取新token,就不用再次获取授权码了,除非刷新的refresh_token也到期了(通常,refresh_token的有效期远大于token。例如:token 3天有效,而refresh_token 15天有效)

注意:前端只需要返回token即可,且token推荐保存到cookie中,并设置token只读,从而禁止前端js读取操作。

简单模式 - [implicit]

这个主要是给无后台的纯前端项目用的,认证授权服务器的回调地址的参数也是只有前端能处理

密码模式 - [password]

直接拿用户的账号密码授权 【不安全】

客户端模式 - [client_credentials]

主要用于服务器与服务器的通信,不涉及前端

认证/授权服务

认证服务器:1、需允许所有人访问

Maven依赖

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
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>

<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

配置类

AuthorizationServerConfig.java

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
@Configuration
@EnableAuthorizationServer // 开启oauth2,auth server模式
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

// 密码加密
@Autowired
private PasswordEncoder passwordEncoder;

// 配置客户端 http://localhost:3001/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://127.0.0.1:8080/callback&scope=scope1&state=admin
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// client的id和密码
.withClient("client1")
.secret(passwordEncoder.encode("123123"))

// 给client一个id,这个在client的配置里要用的
.resourceIds("resource1")

// 允许的申请token的方式,测试用例在test项目里都有.
// authorization_code授权码模式,这个是标准模式
// implicit简单模式,这个主要是给无后台的纯前端项目用的
// password密码模式,直接拿用户的账号密码授权,不安全
// client_credentials客户端模式,用clientid和密码授权,和用户无关的授权方式
// refresh_token使用有效的refresh_token去重新生成一个token,之前的会失效
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")

// 授权的范围,每个resource会设置自己的范围.
.scopes("scope1", "scope2")

// 自动授权:若为true则登录成功后,用户无需再手动选择是否授权,false则弹出确认授权页面
.autoApprove(true)
//.autoApprove("scope1") // 或者指定自动批准的scope
// 这个相当于是client的域名,重定向给code的时候会跳转这个域名
.redirectUris("http://127.0.0.1:8080/callback","http://127.0.0.1:8080/index2");

/*.and()

.withClient("client2")
.secret(passwordEncoder.encode("123123"))
.resourceIds("resource1")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.scopes("all")
.autoApprove(false)
.redirectUris("http://www.qq.com");*/
}

// 客户端详细业务
@Autowired
private ClientDetailsService clientDetailsService;

// token存储
@Autowired
private TokenStore tokenStore;

// jwt的token转换器
@Autowired
private JwtAccessTokenConverter tokenConverter;

// 配置token管理服务
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setClientDetailsService(clientDetailsService);
// 设置以支持 刷新token
defaultTokenServices.setSupportRefreshToken(true);

// 配置token的存储方法
defaultTokenServices.setTokenStore(tokenStore);
// token有效期300秒 -> 5分钟
defaultTokenServices.setAccessTokenValiditySeconds(300);
// 刷新token有效期1500秒 -> 25分钟
defaultTokenServices.setRefreshTokenValiditySeconds(1500);

// 配置token增加,把一般token转换为jwt token
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenConverter));
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
return defaultTokenServices;
}

// 密码模式才需要配置,认证管理器
@Autowired
private AuthenticationManager authenticationManager;

// 把上面的各个组件组合在一起
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)// 认证管理器
.authorizationCodeServices(new InMemoryAuthorizationCodeServices())// 授权码管理
.tokenServices(tokenServices())// token管理
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}

// 配置哪些接口可以被访问
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")// oauth/token_key公开
.checkTokenAccess("permitAll()")// oauth/check_token公开
.allowFormAuthenticationForClients();// 允许表单认证
}
}

TokenConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class TokenConfig {

//配置如何把普通token转换成jwt token
@Bean
public JwtAccessTokenConverter tokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//使用对称秘钥加密token,resource那边会用这个秘钥校验token
converter.setSigningKey("uaa123");
return converter;
}

//配置token的存储方法
@Bean
public TokenStore tokenStore() {
//把用户信息都存储在token当中,相当于存储在客户端,性能好很多
return new JwtTokenStore(tokenConverter());
}
}

SecurityConfig.java

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
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// 密码模式才需要配置,认证管理器
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}

// 授权服务器需要开放所有限制,允许所有人访问
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll()

.and()
.formLogin()

.and()
.logout();
}


@Bean
public UserDetailsService userDetailsService() {
return s -> {
if ("admin".equals(s) || "user".equals(s)) {
return new MyUserDetails(s, passwordEncoder().encode(s), s);
}
return null;
};
}
}

MyUserDetails.java

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class MyUserDetails implements UserDetails {

private String username;
private String password;
private String perms;

public MyUserDetails() {
}

public MyUserDetails(String username, String password, String perms) {
this.username = username;
this.password = password;
this.perms = perms;
}

@Override
public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

@Override
public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getPerms() {
return perms;
}

public void setPerms(String perms) {
this.perms = perms;
}

////////////////////////////////////////////////

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Stream.of(perms.split(",")).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}

上面是模拟的用户数据是在内存中的,以下是在数据库中读取的:

MyUserDetails.java文件改成如下文件:

SecurityUser.java

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import lee.pojo.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* 查询数据库后返回的信息
* @author peter
* @date 2024/10/17
* @description TODO
*/
@Data
public class SecurityUser implements UserDetails {

// 当前登录用户
private transient User currentUserInfo;

// 当前权限
private List<String> permissionValueList;

public SecurityUser() {}

public SecurityUser(User currentUserInfo) {
if (currentUserInfo != null) {
this.currentUserInfo = currentUserInfo;
}
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (String permissionValue : permissionValueList) {
if (StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}

@Override
public String getPassword() {
return currentUserInfo.getPassword();
}

@Override
public String getUsername() {
return currentUserInfo.getUsername();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}

新增UserDetailsServiceImpl.java

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
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lee.mapper.UserMapper;
import lee.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

// 这个文件可以看成是BO业务验证类
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private UserMapper userMapper; // UserMapper和User文件就是熟悉的正常流程了

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("username", username);
User user = userMapper.selectOne(qw);

if (user == null) {
throw new UsernameNotFoundException("User not found");
}
System.out.println(user);
SecurityUser securityUser = new SecurityUser();
securityUser.setCurrentUserInfo(user);
List<String> list = new ArrayList<>();
list.add(user.getRole());
securityUser.setPermissionValueList(list);
System.out.println(securityUser);
return securityUser;
}
}

配置文件SecurityConfig.java变动:

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
52
53
54
55
56
57
58
59
60
61
import lee.model.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsServiceImpl userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// 密码模式才需要配置,认证管理器
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}

// 授权服务器需要开放所有限制,允许所有人访问
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll()

.and()
.formLogin()

.and()
.logout();
}


// @Bean
// public UserDetailsService userDetailsService() {
// return s -> {
// if ("admin".equals(s) || "user".equals(s)) {
// return new MyUserDetails(s, passwordEncoder().encode(s), s);
// }
// return null;
// };
// }
}

资源服务

资源服务器:只允许有特定访问资源权限的用户访问

资源服务器配置类

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
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.TokenStore;

@Configuration
//开启oauth2,reousrce server模式
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

@Autowired
private TokenStore tokenStore;

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
// 将资源服务器的 ID 设置为 "resource1",需与授权服务器中的资源 ID 配置一致,表示该资源需要授权访问。
.resourceId("resource1")
.tokenStore(tokenStore)

// 配置资源服务器为无状态,这意味着不会将认证信息存储在 HTTP session 中,而是依赖于每次请求携带的令牌(token)来验证身份
.stateless(true);
}

@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
//配置请求的访问控制。
.authorizeRequests()

// 表示要求请求具备 OAuth2 令牌中定义的 scope1 授权范围。如果令牌中没有 scope1 授权范围,则用户无权访问。
.antMatchers("/**").access("#oauth2.hasScope('scope1')")

.and()
// 配置会话管理策略。
// 将会话管理策略设置为 STATELESS,表示不会创建 HTTP 会话,服务不会在 session 中保存任何状态信息。
// 在无状态的 REST API 中,通常不使用会话,而是通过每次请求的 OAuth2 令牌来验证用户身份,达到无状态的效果
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

SecurityConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
* 资源服务器的配置
* @author peter
* @date 2024/10/31
* @className SecurityConfig
*/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 关闭了 CSRF(跨站请求伪造)保护功能,前后端分离一般不需要
.authorizeRequests() // 开始配置请求的访问控制。
.anyRequest().permitAll(); // 允许所有请求,不需要任何权限认证
}
}

TokenConfig.java

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
package lee.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class TokenConfig {

// 配置如何把普通token转换成jwt token
@Bean
public JwtAccessTokenConverter tokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

// 使用对称秘钥加密token,resource那边会用这个秘钥校验token
converter.setSigningKey("uaa123");
return converter;
}

// 配置token的存储方法
@Bean
public TokenStore tokenStore() {
//把用户信息都存储在token当中,相当于存储在客户端,性能好很多
return new JwtTokenStore(tokenConverter());
}
}

资源服务器供访问的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class IndexController {

@RequestMapping("user")
public String user() {
return "user";
}

//测试接口
@RequestMapping("admin")
@PreAuthorize("hasAnyAuthority('admin')")
public String admin() {
return "admin";
}

@RequestMapping("me")
public Principal me(Principal principal) {
return principal;
}

}

测试服务器

控制器
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
52
53
54
55
56
57
58
59
60
61
62
63
64
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* 类名称
*
* @author peter
* @date 2024/10/21
* @description TODO
*/
@Controller
public class indexController {

@RequestMapping("callback")
@ResponseBody
public String callback(
@RequestParam(value = "code")
String code, // 获取到的授权码
@RequestParam(value = "state")
String state) {
System.out.println("code: " + code + " state: " + state);
//https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ appId +"&secret="+appSecret+"&code="+code+"&grant_type=authorization_code";
return null;
}
@ResponseBody
@RequestMapping("index")
public String index(
@RequestParam(value = "access_token")
String access_token, // 获取到的授权码
@RequestParam(value = "token_type")
String token_type,
@RequestParam(value = "expires_in")
String expires_in, // 获取到的授权码
@RequestParam(value = "refresh_token")
String refresh_token) {
System.out.println("access_token: " + access_token + " token_type: " + token_type+"expires_in: " + expires_in + " refresh_token: " + refresh_token);
//https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ appId +"&secret="+appSecret+"&code="+code+"&grant_type=authorization_code";
return null;
}

@RequestMapping("index2")
public String index2(
//@RequestParam(value = "access_token")
String access_token, // 获取到的授权码
//@RequestParam(value = "token_type")
String token_type,
//@RequestParam(value = "expires_in")
String expires_in, // 获取到的授权码
//@RequestParam(value = "scope")
String scope,
//@RequestParam(value = "jti")
String jti, // 获取到的授权码
//@RequestParam(value = "state")
String state) {
System.out.println("access_token: " + access_token + " token_type: " + token_type+"expires_in: " + expires_in + " state: " + state+ " scope: " + scope+ " jti: " + jti);
//https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ appId +"&secret="+appSecret+"&code="+code+"&grant_type=authorization_code";

//http://127.0.0.1:8080/index2#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIl0sImV4cCI6MTcyOTY3MzE1NSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiNmE4ZTQxMjAtMmE1My00MTY3LTg4ZjItMzkzZmRiNWQyZTgzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.VnrB18dLY6bsKpJ_4E0PTvc3TGsek8je6iaL8PU6w_4&token_type=bearer&state=admin&expires_in=299&scope=scope1&jti=6a8e4120-2a53-4167-88f2-393fdb5d2e83
return "ces";
}
}

前端测试页
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
52
53
54
55
56
57
58
59
60
61
62
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>点击Test后,弹出表单登录,登录成功会返回到此页面,然后控制台会输出如下内容:</h2>
<p>
Access Token:<br>
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<br>
eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJf
bmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2Nv
cGUxIiwic2NvcGUyIl0sImV4cCI6MTcyOTgy
MzI4NCwiYXV0aG9yaXRpZXMiOlsiYWRtaW4i
XSwianRpIjoiNzJmOWYxNjItZjlhZS00Nzg5
LWI4ZmItYzA2Nzg2YzM5MTM1IiwiY2xpZW50
X2lkIjoiY2xpZW50MSJ9.<br>
fmNgfkfp9_7xuRLqbDx_Fr5FDKRQ90-YPduWUj7qzPM
</p>
<p>
Token Type: bearer
</p>
<p>
Expires In: 300
</p>
<p>
State: admin
</p>

<button id="login-btn" type="button">Test</button>
<script>
document.getElementById('login-btn').onclick = function() {
// 跳转到授权页面
window.location.href = `http://localhost:3001/oauth/authorize?client_id=client1&redirect_uri=http://127.0.0.1:8080/index2&grant_type=implicit&response_type=token&state=admin`;
};

// 检查 URL Fragment 中是否包含 access_token
if (window.location.hash) {
const hash = window.location.hash.substring(1); // 去掉开头的 #
const params = new URLSearchParams(hash); // 创建 URLSearchParams 对象

const accessToken = params.get('access_token'); // 获取 access_token
const tokenType = params.get('token_type'); // 获取 token_type
const expiresIn = params.get('expires_in'); // 获取 expires_in
const state = params.get('state'); // 获取 state

if (accessToken) {
console.log('Access Token:', accessToken);
console.log('Token Type:', tokenType);
console.log('Expires In:', expiresIn);
console.log('State:', state);

// 在这里你可以将 access_token 存储在本地存储或会话存储中
localStorage.setItem('access_token', accessToken);
}
}
</script>
</body>
</html>

请求示例

授权码模式 - [authorization_code]
获取授权码

在OAuth2.0中,获取授权码的默认请求地址是/oauth/authorize

所需的参数:

  1. response_typecode。这是固定格式。
  2. grant_typeauthorization_code -> 认证类型:授权码模式。
  3. client_idclient1,客户端id,这是在认证服务器配置好的。只接受配置中存在的客户端。
  4. redirect_uri:回调地址,由后端接口接收回调返回的授权码等信息
  5. scope:【可选】作用域,资源服务器配置了用户可以访问的权限
  6. state:【可选】【推荐使用】随机字符串,用来验证信息,防止 CSRF 攻击。
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
// 测试需直接在浏览器访问url地址,然后会进行登录操作,登录成功后,若是自动授权,则会直接触发回调地址,否则需用户手动授权后再触发回调地址。可以在后端回调地址的接口中看到授权码和state

@Test
public void getCodeByAuthorization_code() throws IOException {

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 创建 GET 请求
String url = "http://localhost:3001/oauth/authorize" +
"?response_type=code" +
"&grant_type=authorization_code" +
"&client_id=client1" +
"&redirect_uri=http://127.0.0.1:8080/callback" +
"&scope=scope1" +
"&state=admin";
HttpGet httpGet = new HttpGet(url);

// 发送 GET 请求并检查重定向
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 302) {
String redirectUrl = response.getFirstHeader("Location").getValue();
System.out.println("Redirected to: " + redirectUrl);

// 手动提取授权码(示例)
URI uri = new URI(redirectUrl);
String query = uri.getQuery(); // 示例: code=abc123&state=admin
System.out.println("Query: " + query);
} else {
System.out.println("Failed with HTTP error code: " + statusCode);
}
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
获取tokenrefresh_token

在OAuth2.0中,用授权码获取token的默认请求地址是/oauth/token

必需的参数:

  1. grant_typeauthorization_code -> 认证类型:授权码模式。
  2. client_idclient1,客户端id,这是在认证服务器配置好的。只接受配置中存在的客户端。
  3. client_secret123123,客户端密码,这是也在认证服务器配置好的,而且密码是加密的。
  4. redirect_uri:回调地址,由后端接口接收回调返回的tokenrefresh_token等信息
  5. code:授权码,服务器回调地址中返回的参数。
  6. state:随机字符串,用来验证信息,防止 CSRF 攻击(可选但推荐)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// [授权码模式]  2、通过授权码获取token,有了授权码这里就可以运行后直接获取token了
@Test
public void getTokenByCode() throws IOException {
try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String url = "http://localhost:3001/oauth/token" +
"?grant_type=authorization_code" +
"&client_id=client1" +
"&client_secret=123123" +
"&redirect_uri=http://127.0.0.1:8080/callback" +
"&code=nTzOq3" +
"&state=admin";
HttpPost httpGet = new HttpPost(url);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
}else {
System.out.println("===========");
}
}catch (Exception e){
System.out.println();
}
}
使用refresh_token换取新token

在OAuth2.0中,用refresh_token获取token的默认请求地址是/oauth/token

所需的参数:

  1. grant_typerefresh_token -> 认证类型:刷新token
  2. refresh_token:通过时效长的refresh_token换取token
  3. scope:[可选项],默认会以上次token的作用域一致。认证服务器会设置资源服务器中的所有范围,每个资源服务器会设置自己的范围。例如:资源服务器的范围为scope1,若这里的参数是scope2则无法访问资源1中的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void getTokenByRefresh_token() throws IOException {

try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String url = "http://localhost:3001/oauth/token" +
"?grant_type=refresh_token" + "&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIl0sImF0aSI6Ijk2ZjgyYzFlLTczZWQtNDRkOC04M2RkLWQ0NmJhOTQ2OGU5NiIsImV4cCI6MTcyOTY3MTM4OSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiMzM1MjBhMGEtNTQzYy00MGI2LTgyMzItMTU0NDI1NGU5YWEzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.3wr1xfyKYpe9XN3z48Qz5Ya22Upn4SzW1VM9q5PYAac" +
"&scope=scope1";
HttpPost httpGet = new HttpPost(url);
String s = Base64.getEncoder().encodeToString("client1:123123".getBytes(StandardCharsets.UTF_8));
httpGet.setHeader("Authorization","Basic "+s);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
}else {
System.out.println("===========");
}
}catch (Exception e){
System.out.println();
}
}

授权的scope不对应资源服务器中的scope请求所返回的错误信息:

以下是授权范围为scope1,却要访问scope2的情况

08:41:30.962 [main] INFO com.gargoylesoftware.htmlunit.WebClient - statusCode=[403] contentType=[application/json]
08:41:30.964 [main] INFO com.gargoylesoftware.htmlunit.WebClient - {“error”:“insufficient_scope”,“error_description”:“Insufficient scope for this resource”,“scope”:“scope1”}
08:41:30.964 [main] DEBUG com.gargoylesoftware.htmlunit.WebWindowImpl - setEnclosedPage: com.gargoylesoftware.htmlunit.UnexpectedPage@6326d182
08:41:30.964 [main] DEBUG com.gargoylesoftware.htmlunit.WebWindowImpl - destroyChildren

简单模式 - [implicit]

简单模式下,若接收请求的方法不包含GET请求,则会弹出登录框,成功登录后,若开启了自动授权,则可以直接获取到token,简单模式下没有refresh_token,到期后需重新请求。但是返回的回调地址只能由前端处理,只因地址参数是#开头的Fragment参数,token也在回调地址的参数中。

所需的参数:

  1. grant_typeimplicit -> 认证类型:简单模式【固定搭配】。
  2. client_idclient1,客户端id,这是在认证服务器配置好的。只接受配置中存在的客户端。
  3. response_typetoken固定搭配。
  4. state:随机字符串,用来验证信息,防止 CSRF 攻击(可选但推荐)。
  5. redirect_uri:这是返回给前端的回调地址。

前端页面示例:

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
52
53
54
55
56
57
58
59
60
61
62
63
<!-- 此页面的地址是http://127.0.0.1:8080/index2 -->
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>点击Test后,弹出表单登录,登录成功会返回到此页面,然后控制台会输出如下内容:</h2>
<p>
Access Token:<br>
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<br>
eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJf
bmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2Nv
cGUxIiwic2NvcGUyIl0sImV4cCI6MTcyOTgy
MzI4NCwiYXV0aG9yaXRpZXMiOlsiYWRtaW4i
XSwianRpIjoiNzJmOWYxNjItZjlhZS00Nzg5
LWI4ZmItYzA2Nzg2YzM5MTM1IiwiY2xpZW50
X2lkIjoiY2xpZW50MSJ9.<br>
fmNgfkfp9_7xuRLqbDx_Fr5FDKRQ90-YPduWUj7qzPM
</p>
<p>
Token Type: bearer
</p>
<p>
Expires In: 300
</p>
<p>
State: admin
</p>

<button id="login-btn" type="button">Test</button>
<script>
document.getElementById('login-btn').onclick = function() {
// 跳转到授权页面
window.location.href = `http://localhost:3001/oauth/authorize?client_id=client1&redirect_uri=http://127.0.0.1:8080/index2&grant_type=implicit&response_type=token&state=admin`;
};

// 检查 URL Fragment 中是否包含 access_token
if (window.location.hash) {
const hash = window.location.hash.substring(1); // 去掉开头的 #
const params = new URLSearchParams(hash); // 创建 URLSearchParams 对象

const accessToken = params.get('access_token'); // 获取 access_token
const tokenType = params.get('token_type'); // 获取 token_type
const expiresIn = params.get('expires_in'); // 获取 expires_in
const state = params.get('state'); // 获取 state

if (accessToken) {
console.log('Access Token:', accessToken);
console.log('Token Type:', tokenType);
console.log('Expires In:', expiresIn);
console.log('State:', state);

// 在这里你可以将 access_token 存储在本地存储或会话存储中
localStorage.setItem('access_token', accessToken);
}
}
</script>
</body>
</html>
密码模式 - [password]

密码模式下,不会弹出登录表单,若开启了自动授权,则可以直接获取到token,到期后需重新请求。

所需的参数:

  1. grant_typepassword -> 认证类型:密码模式【固定搭配】。
  2. client_idclient1,客户端id,这是在认证服务器配置好的。只接受配置中存在的客户端。
  3. client_secret123123固定搭配,与认证服务器中配置的客户端密码一致。
  4. username:用户名。
  5. password:用户密码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void getTokenByPassword() throws IOException {

try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String url = "http://localhost:3001/oauth/token" +
"?grant_type=password" +
"&client_id=client1" +
"&client_secret=123123" +
"&username=admin" +
"&password=admin";
HttpPost httpGet = new HttpPost(url);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
}else {
System.out.println("===========");
}
}catch (Exception e){
System.out.println();
}
}
客户端模式 - [client_credentials]

主要用于服务器与服务器的通信,不涉及前端。请求后可以直接获得token

所需的参数:

  1. grant_typeclient_credentials -> 认证类型:客户端模式【固定搭配】。
  2. client_idclient1,客户端id,这是在认证服务器配置好的。只接受配置中存在的客户端。
  3. client_secret123123固定搭配,与认证服务器中配置的客户端密码一致。
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
@Test
public void getTokenByClient() throws IOException {

try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String url = "http://localhost:3001/oauth/token" +
"?grant_type=client_credentials" +
"&client_id=client1" +
"&client_secret=123123";
HttpPost httpGet = new HttpPost(url);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
}else {
System.out.println("===========");
}
}catch (Exception e){
System.out.println();
}
}

/*
返回数据示例:
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJleHAiOjE3Mjk4Mjc2NTgsImp0aSI6IjRjOGUwODdiLTZiOWQtNGY4Mi1iNTdiLWQxMDQ3Njk4MDQ5MiIsImNsaWVudF9pZCI6ImNsaWVudDEifQ.LS1zjDPuygQBu59y1SRaYvhYL-kNV5-TTrI0_x16QdY","token_type":"bearer","expires_in":299,"scope":"scope1 scope2","jti":"4c8e087b-6b9d-4f82-b57b-d10476980492"}

数据属性解释:
access_token:即所需的token令牌
token_type: bearer >> token类型,即token的存储格式为:("Authorization","bearer access_token的值");
expires_in:有效时间 300秒,这时间可以在认证服务器配置
scope:授权范围
jti: jwt的唯一id值
*/

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package lee;

import com.gargoylesoftware.htmlunit.HttpMethod;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;

public class TT {

// 认证服务器地址
public static String authorization_server = "http://127.0.0.1:3001/";
// 资源服务器地址
public static String resource_server = "http://127.0.0.1:4001/";


// [授权码模式] 1、获取授权码 http://localhost:3001/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://127.0.0.1:8080/callback&scope=scope1&state=admin
@Test
public void getCodeByAuthorization_code() throws IOException {

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 创建 GET 请求
String url = "http://localhost:3001/oauth/authorize" +
"?response_type=code" +
"&grant_type=authorization_code" +
"&client_id=client1" +
"&redirect_uri=http://127.0.0.1:8080/callback" +
"&scope=scope1" +
"&state=admin";
HttpGet httpGet = new HttpGet(url);

// 发送 GET 请求并检查重定向
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 302) {
String redirectUrl = response.getFirstHeader("Location").getValue();
System.out.println("Redirected to: " + redirectUrl);

// 手动提取授权码(示例)
URI uri = new URI(redirectUrl);
String query = uri.getQuery(); // 示例: code=abc123&state=admin
System.out.println("Query: " + query);
} else {
System.out.println("Failed with HTTP error code: " + statusCode);
}
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

// [授权码模式] 2、通过授权码获取token
@Test
public void getTokenByCode() throws IOException {
String[] params = new String[]{
"client_id", "client1",
"client_secret", "123123",
"&redirect_uri=http://127.0.0.1:8080/index" +
"code", "LSji7Z",
"state", "admin",
"grant_type", "authorization_code"
};
//HttpUtil.send(HttpMethod.POST, authorization_server + "oauth/authorize", null, null, params);
try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String url = "http://localhost:3001/oauth/token" +
"?grant_type=authorization_code" +
"&client_id=client1" +
"&client_secret=123123" +
"&redirect_uri=http://127.0.0.1:8080/callback" +
"&code=nTzOq3" +
"&state=admin";
HttpPost httpGet = new HttpPost(url);
// httpGet.setURI(new URI(authorization_server + "oauth/token"));
// HttpParams httpParams = httpClient.getParams();
// httpParams.setParameter("client_id", "client1");
// httpParams.setParameter("client_secret", "123123");
// httpParams.setParameter("code", "Lcb0mj");
// httpParams.setParameter("grant_type", "authorization_code");
// httpGet.setParams(httpParams);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
}else {
System.out.println("===========");
}
}catch (Exception e){
System.out.println();
}
}

// [更新token]
@Test
public void getTokenByRefresh_token() throws IOException {
String[] params = new String[]{
"grant_type", "refresh_token",
"refresh_token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIl0sImF0aSI6Ijk2ZjgyYzFlLTczZWQtNDRkOC04M2RkLWQ0NmJhOTQ2OGU5NiIsImV4cCI6MTcyOTY3MTM4OSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiMzM1MjBhMGEtNTQzYy00MGI2LTgyMzItMTU0NDI1NGU5YWEzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.3wr1xfyKYpe9XN3z48Qz5Ya22Upn4SzW1VM9q5PYAac",
"scope", "scope1"
};
//HttpUtil.send(HttpMethod.POST, authorization_server + "oauth/token", null, null, params);

try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String url = "http://localhost:3001/oauth/token" +
"?grant_type=refresh_token" +
// "&client_id=client1" +
// "&client_secret=123123" +
"&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIl0sImF0aSI6Ijk2ZjgyYzFlLTczZWQtNDRkOC04M2RkLWQ0NmJhOTQ2OGU5NiIsImV4cCI6MTcyOTY3MTM4OSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiMzM1MjBhMGEtNTQzYy00MGI2LTgyMzItMTU0NDI1NGU5YWEzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.3wr1xfyKYpe9XN3z48Qz5Ya22Upn4SzW1VM9q5PYAac" +
"&scope=scope1";
HttpPost httpGet = new HttpPost(url);
String s = Base64.getEncoder().encodeToString("client1:123123".getBytes(StandardCharsets.UTF_8));
httpGet.setHeader("Authorization","Basic "+s);
// httpGet.setURI(new URI(authorization_server + "oauth/token"));
// HttpParams httpParams = httpClient.getParams();
// httpParams.setParameter("client_id", "client1");
// httpParams.setParameter("client_secret", "123123");
// httpParams.setParameter("code", "Lcb0mj");
// httpParams.setParameter("grant_type", "authorization_code");
// httpGet.setParams(httpParams);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
}else {
System.out.println("===========");
}
}catch (Exception e){
System.out.println();
}

/*
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTU4MjcwMzYxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYjA5MDFlZDYtOTNjMC00MjhjLTg2MzEtMTBiZWY4ZmJkZTYzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.kogkaxd1x-XkfqIR8avqSL5y6caa0Kk_DFWjepjVO70",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImF0aSI6ImIwOTAxZWQ2LTkzYzAtNDI4Yy04NjMxLTEwYmVmOGZiZGU2MyIsImV4cCI6MTU4MjcwNDgxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiOThmYTc3NDQtNTU4ZS00MjI0LThmYjEtZThiNGY3ZjNlNGE5IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.Ex8rX6eMfXr7_pmC6sftIfIvFExyjx4_lzRYZqWHeII",
"expires_in":299,
"scope":"scope1 scope2",
"jti":"b0901ed6-93c0-428c-8631-10bef8fbde63"
}*/
}

// [密码模式] 获取jwt token,把用户信息加密成token,服务端不存储token、 通过密码方式获取令牌 code: Lcb0mj state: admin
@Test
public void getTokenByPassword() throws IOException {
String[] params = new String[]{
"client_id", "client1",
"client_secret", "123123",
"grant_type", "password",
"username", "admin",
"password", "admin"
};
HttpUtil.send(HttpMethod.POST, authorization_server + "oauth/token", null, null, params);
/*
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTU4MjcwMzYxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYjA5MDFlZDYtOTNjMC00MjhjLTg2MzEtMTBiZWY4ZmJkZTYzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.kogkaxd1x-XkfqIR8avqSL5y6caa0Kk_DFWjepjVO70",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImF0aSI6ImIwOTAxZWQ2LTkzYzAtNDI4Yy04NjMxLTEwYmVmOGZiZGU2MyIsImV4cCI6MTU4MjcwNDgxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiOThmYTc3NDQtNTU4ZS00MjI0LThmYjEtZThiNGY3ZjNlNGE5IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.Ex8rX6eMfXr7_pmC6sftIfIvFExyjx4_lzRYZqWHeII",
"expires_in":299,
"scope":"scope1 scope2",
"jti":"b0901ed6-93c0-428c-8631-10bef8fbde63"
}*/
}

// [简单模式]
@Test
public void getTokenByImplicit() throws IOException {
String[] params = new String[]{
"client_id", "client1",
"client_secret", "123123",
"redirect_uri", "http://127.0.0.1:8080/index2",
"grant_type", "implicit",
"response_type", "token",
"state", "admin"
};
HttpUtil.send(HttpMethod.POST, authorization_server + "oauth/authorize", null, null, params);
// http://localhost:3001/oauth/authorize?client_id=client1&redirect_uri=http://127.0.0.1:8080/index2&grant_type=implicit&response_type=token&state=admin


try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String url = "http://localhost:3001/oauth/token" +
"?grant_type=implicit" +
"&client_id=client1" +
"&client_secret=123123" +
"&response_type=token" +
"&state=admin" +
"&redirect_uri=http://127.0.0.1:8080/index2";
HttpPost httpGet = new HttpPost(url);
// httpGet.setURI(new URI(authorization_server + "oauth/token"));
// HttpParams httpParams = httpClient.getParams();
// httpParams.setParameter("client_id", "client1");
// httpParams.setParameter("client_secret", "123123");
// httpParams.setParameter("code", "Lcb0mj");
// httpParams.setParameter("grant_type", "authorization_code");
// httpGet.setParams(httpParams);
HttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
}else {
System.out.println("===========");
}
}catch (Exception e){
System.out.println();
}
// http://127.0.0.1:8080/index2#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIl0sImV4cCI6MTcyOTY3MzU1MiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiNmRkNmFlM2QtMmNlMi00YzJhLTliNTMtMzI5NzhiOGQxNTQ0IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.YIn13yRxIrWMFiPuiO6lBAUDsG0BSAeAvqjSn_b6pj4&token_type=bearer&state=admin&expires_in=299&scope=scope1&jti=6dd6ae3d-2ce2-4c2a-9b53-32978b8d1544
/*
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTU4MjcwMzYxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYjA5MDFlZDYtOTNjMC00MjhjLTg2MzEtMTBiZWY4ZmJkZTYzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.kogkaxd1x-XkfqIR8avqSL5y6caa0Kk_DFWjepjVO70",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImF0aSI6ImIwOTAxZWQ2LTkzYzAtNDI4Yy04NjMxLTEwYmVmOGZiZGU2MyIsImV4cCI6MTU4MjcwNDgxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiOThmYTc3NDQtNTU4ZS00MjI0LThmYjEtZThiNGY3ZjNlNGE5IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.Ex8rX6eMfXr7_pmC6sftIfIvFExyjx4_lzRYZqWHeII",
"expires_in":299,
"scope":"scope1 scope2",
"jti":"b0901ed6-93c0-428c-8631-10bef8fbde63"
}*/
}

// [客户端模式] 这个不属于OAuth2用户认证范围
@Test
public void getTokenByClient() throws IOException {
String[] params = new String[]{
"client_id", "client1",
"client_secret", "123123",
"grant_type", "client_credentials"
};
HttpUtil.send(HttpMethod.POST, authorization_server + "oauth/token", null, null, params);
/*
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTU4MjcwMzYxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYjA5MDFlZDYtOTNjMC00MjhjLTg2MzEtMTBiZWY4ZmJkZTYzIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.kogkaxd1x-XkfqIR8avqSL5y6caa0Kk_DFWjepjVO70",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImF0aSI6ImIwOTAxZWQ2LTkzYzAtNDI4Yy04NjMxLTEwYmVmOGZiZGU2MyIsImV4cCI6MTU4MjcwNDgxMywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiOThmYTc3NDQtNTU4ZS00MjI0LThmYjEtZThiNGY3ZjNlNGE5IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.Ex8rX6eMfXr7_pmC6sftIfIvFExyjx4_lzRYZqWHeII",
"expires_in":299,
"scope":"scope1 scope2",
"jti":"b0901ed6-93c0-428c-8631-10bef8fbde63"
}*/
}

// 验证jwt token
@Test
public void checkToken() throws IOException {
// String[] params = new String[]{
// "token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTU4MjcwODk0MCwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYjMyYjJjNmMtODE5NS00NTRkLTkwMDktOTljOGZlNDBjNDAxIiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.B4MJx52y1o2E9k5cG1MxlQjDRUvkEOBk0SvLg_hNg9M",
// };

String[] params = new String[]{
"token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTcyOTQ5MjczMSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiZThhNTU5YTAtZjYyNS00ZWVjLWJhZTUtOGQ5NWQ4MjAxY2M5IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.vyh3SnfnAiZQKO23BPej8lebkFw-CLELOL-SHPb-Qo0",
};
HttpUtil.send(HttpMethod.POST, authorization_server + "oauth/check_token", null, null, params);
//{"aud":["resource1"],"user_name":"admin","scope":["scope1","scope2"],"active":true,"exp":1582703613,"authorities":["admin"],"jti":"b0901ed6-93c0-428c-8631-10bef8fbde63","client_id":"client1"}
}

// 使用jwt token访问resource
@Test
public void getResourceByToken() throws IOException {
//Map<String, String> head = RootUtil.buildMap("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTU4MjcxMjk0MSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYzBiMTYzOGUtOThjNS00NTU2LTk0ZmMtYTgyNTE3NDhhYzM5IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.l_j9s5XYz3NfrvI-Hky19V-P9vRz9U4Q1Ltkep5Up9Q");

Map<String, String> head = RootUtil.buildMap("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2NvcGUxIiwic2NvcGUyIl0sImV4cCI6MTcyOTczODM3OCwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYTlkOTZkOTEtMjNiNC00Y2ZhLWE2YzctZGY5YWZiZTU0OTg0IiwiY2xpZW50X2lkIjoiY2xpZW50MSJ9.QZDoar_8oLe-F1eepbwTrjtemSPZdB89fSJxXysbiLQ");

HttpUtil.send(HttpMethod.POST,resource_server + "/me",head,null);
}
}

OAuth2.0认证授权
https://superlovelace.top/2024/10/18/OAuth2.0认证授权/
作者
棱境
发布于
2024年10月18日
更新于
2025年2月18日
许可协议