OAuth2-授权码模式 登录流程
一.案例架构
主要包括如下服务:
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的基本配置
在这里配置的,就是用户的用户名/密码/角色信息。
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("test1") .password(new BCryptPasswordEncoder().encode("123456")) .roles("admin") .and() .withUser("test2") .password(new BCryptPasswordEncoder().encode("123456")) .roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().formLogin(); } }
配置授权服务器
@Configuration public class AccessTokenConfig {
//生成的 Token 要往哪里存储 可以存在 Redis 中,也可以存在内存中,也可以结合 JWT 等等 @Bean TokenStore tokenStore() { return new InMemoryTokenStore(); //它存在内存中 } }
// 这个类继承AuthorizationServerConfigureAdapter 对授权服务器的详细配置 @EnableAuthorizationServer //表示开启授权服务器的自动化配置。 @Configuration public class AuthorizationServer extends AuthorizationServerConfigurerAdapter { @Autowired TokenStore tokenStore; @Autowired ClientDetailsService clientDetailsService;
//主要用来配置 Token 的一些基本信息 @Bean AuthorizationServerTokenServices tokenServices() { DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); services.setSupportRefreshToken(true); //是否支持刷新 services.setTokenStore(tokenStore); //token 存储的位置 services.setAccessTokenValiditySeconds(60 * 60 * 2); //token的有效期 services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); // 刷新token的有效期 return services; }
//用来配置令牌端点的安全约束,也就是这个端点谁能访问,谁不能访问 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.checkTokenAccess("permitAll()") //checkTokenAccess 是指一个 Token 校验的端点 .allowFormAuthenticationForClients(); //设置为可以直接访问 当资源服务器收到 Token 之后,需要去校验Token 的合法性,就会访问这个端点 }
//配置客户端的详细信息 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 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 }
//配置令牌的访问端点和令牌服务 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authorizationCodeServices(authorizationCodeServices()) //authorizationCodeService用来配置授权码的存储 这里我们是存在在内存中 .tokenServices(tokenServices()); //配置令牌的存储 即 access_token 的存储位置 } @Bean AuthorizationCodeServices authorizationCodeServices() { return new InMemoryAuthorizationCodeServices(); } }
授权码和令牌有什么区别?授权码是用来获取令牌的,使用一次就失效,令牌则是用来获取资源的
三.资源服务器搭建
资源服务器就是用来存放用户的资源,例如你在微信上的图像、openid 等信息,用户从授权服务器上拿到 access_token 之后,接下来就可以通过 access_token 来资源服务器请求数据。
我们创建一个新的 Spring Boot 项目,叫做 user-server ,作为我们的资源服务器,创建时,添加如下依赖:
1.web
2.spring cloud security
3.spirng cloud OAuth2
配置资源服务器 (如果是小项目,资源和授权服务器可以放一起,大项目就要分开)
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//配置了一个 RemoteTokenServices 的实例 这是因为资源服务器和授权服务器是分开的,如果放一起就不需要配置 RemoteTokeService @Bean RemoteTokenServices tokenServices() { RemoteTokenServices services = new RemoteTokenServices(); services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); //access_token 的校验地址 services.setClientId("test"); //client_id services.setClientSecret("123"); //client_secret return services; }
//资源拦截规则 @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("res1").tokenServices(tokenServices()); }
//请求规则 @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated(); } }
测试接口
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } @GetMapping("/admin/hello") public String admin() { return "admin"; } }
四.第三方应用搭建
一个普通的 Spring Boot 工程,创建时加入 Thymeleaf 依赖和 Web 依赖:
在 resources/templates 目录下,创建 index.html ,内容如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Oauth2</title>
</head>
<body>
你好,test!
<a href="http://localhost:8080/oauth/authorize?client_id=test&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html">第三方登录</a>
<h1 th:text="${msg}"></h1>
</body>
</html>
@Controller public class HelloController { @Autowired RestTemplate restTemplate; @GetMapping("/index.html") public String hello(String code, Model model) { if (code != null) { MultiValueMap<String, String> 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<String,String> 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 = new HttpHeaders(); headers.add("Authorization", "Bearer " + access_token); HttpEntity<Object> httpEntity = new HttpEntity<>(headers); ResponseEntity<String> 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" }
原文地址:https://www.cnblogs.com/xp0813/p/12781646.html
- C++ 隐式类型转换
- IE漏洞调试之CVE-2013-3893
- C++ STL之迭代器注意事项
- 设计3D标签为什么要有一个字符间隙tracking?为什么要重写getPrefferedSize()?画三遍的顺序有讲究
- C++STL之整理算法
- Offset2lib攻击测试:看我如何全面绕过64位Linux的内核防护
- C++ STL之查找算法
- 教你一招 | Python3新特性(一) :字符串
- C++ STL之set的基本操作
- Android ClassLoader详解
- 揭秘银行木马Chthonic:网银大盗ZeuS的最新变种
- C++STL之map的基本操作
- android EventBus 3.0使用指南
- C++ STL之deque的基本操作
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Spark 3.0.1 Structured Streaming 提交程序异常解决
- 一起来探索下小程序包的魔数
- 新浪微博IPAD客户端XSS(file域) + 构造Worm
- Firefox 31~34远程命令执行漏洞的分析
- emlog绕过验证码刷评论
- cmseasy最新注入+360webscan的绕过分析
- 新型任意文件读取漏洞的研究
- Chrome XSS Auditor Bypass Using SVG
- ngx_lua_waf针对性改写
- Wordpress < 4.1.2 存储型XSS分析与稳定POC
- 重构Sec-News之路
- ThinkPHP留后门技巧
- 『三个白帽』某题的writeup
- 创造tips的秘籍——PHP回调后门
- Joomla远程代码执行漏洞分析(总结)