这里写目录标题
- 一、Oauth2简介
- 1、简介
- 2、分析Oauth2认证的例子,网站使用微信认证的过程:
- 3、Oauth2.0认证流程如下:
- 1.角色:
- 2.常用术语:
- 3.令牌类型
- 4.特点
- 二、授权模式
- 1、授权码模式(Authorization Code)☆--用的最多、最复杂、最安全
- 2、简化授权模式(Implicit)
- 3、密码模式
- 4、客户端模式
- 5、刷新令牌
- 三、Spring Security Oauth2
- 1、授权服务器
- 2、Spring Security Oauth2架构
- 四、Spring Security Oauth2授权码模式
- 1、创建项目并添加依赖
- 2.自定义登录逻辑,修改用户和密码
- 1.service/UserService
- 2.pojo/User
- 3.config/SecurityConfig
- 3、Oauth2有两个服务器
- 1. 授权的配置服务器。
- 2.资源服务器
- 3.测试: 创建Usercontrolle
- 测试:
- 五、Spring Security Oauth2密码模式
- 修改aothoritiesServiceConfig
- 测试:
- 六、Redis存储token
- 1、加入Redis依赖:
- 2、配置redis在yaml中:
- 3、编写Redis配置类
- 4、在认证服务器配置中指定令牌的存储策略为Redis
一、Oauth2简介
1、简介
第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。
OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP、JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而OAUTH是简易的。互联网很多服务如Open API,很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。
Oauth协议目前发展到2.0版本,1.0版本过于复杂,2.0版本已得到广泛应用。
参考:https://baike.baidu.com/item/oAuth/7153134?fr=aladdin
Oauth 协议:https://tools.ietf.org/html/rfc6749
2、分析Oauth2认证的例子,网站使用微信认证的过程:
- 用户进入网站的登录页面,点击微信的图标以微信账号登录系统,用户是自己在微信里信息的资源拥有者。
点击“微信”出现一个二维码,此时用户扫描二维码,开始给网站授权。 - 资源拥有者同意给客户端授权
资源拥有者扫描二维码表示资源拥有者同意给客户端授权,微信会对资源拥有者的身份进行验证,验证通过后,微信会询问用户是否给授权网站访问自己的微信数据,用户点击“确认登录”表示同意授权,微信认证服务器会颁发一个授权码,并重定向到网站。 - 客户端获取到授权码,请求认证服务器申请令牌
此过程用户看不到,客户端应用程序请求认证服务器,请求携带授权码。 - 认证服务器向客户端响应令牌
认证服务器验证了客户端请求的授权码,如果合法则给客户端颁发令牌,令牌是客户端访问资源的通行证。此交互过程用户看不到,当客户端拿到令牌后,用户在网站看到已经登录成功。 - 客户端请求资源服务器的资源
客户端携带令牌访问资源服务器的资源。网站携带令牌请求访问微信服务器获取用户的基本信息。 - 资源服务器返回受保护资源
资源服务器校验令牌的合法性,如果合法则向用户响应资源信息内容。
注意:资源服务器和认证服务器可以是一个服务也可以分开的服务,如果是分开的服务资源服务器通常要请求认证服务器来校验令牌的合法性。
3、Oauth2.0认证流程如下:
1.角色:
- 客户端
本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:Android客户端、Web客户端(浏览
器端)、微信客户端等。 - 资源拥有者
通常为用户,也可以是应用程序,即该资源的拥有者。 - 授权服务器(也称认证服务器)
用来对资源拥有的身份进行认证、对访问资源进行授权。客户端要想访问资源需要通过认证服务器由资源拥有者授权后方可访问。 - 资源服务器
存储资源的服务器,比如,网站用户管理服务器存储了网站用户信息,网站相册服务器存储了用户的相册信息,微信的资源服务存储了微信的用户信息等。客户端最终访问资源服务器获取资源信息。
2.常用术语:
- 客户凭证(client Credentials) :客户端的clientId和密码用于认证客户
- 令牌(tokens) :授权服务器在接收到客户请求后,颁发的访问令牌
- 作用域(scopes) :客户请求访问令牌时,由资源拥有者额外指定的细分权限(permission)
3.令牌类型
- 授权码 :仅用于授权码授权类型,用于交换获取访问令牌和刷新令牌
- 访问令牌 :用于代表一个用户或服务直接去访问受保护的资源
- 刷新令牌 :用于去授权服务器获取一个刷新访问令牌
- BearerToken :不管谁拿到Token都可以访问资源,类似现金
- Proof of Possession(PoP) Token :可以校验client是否对Token有明确的拥有权
4.特点
- 优点:
更安全,客户端不接触用户密码,服务器端更易集中保护
广泛传播并被持续采用
短寿命和封装的token
资源服务器和授权服务器解耦
集中式授权,简化客户端
HTTP/JSON友好,易于请求和传递token
考虑多种客户端架构场景
客户可以具有不同的信任级别 - 缺点:
协议框架太宽泛,造成各种实现的兼容性和互操作性差
不是一个认证协议,本身并不能告诉你任何用户信息。
二、授权模式
1、授权码模式(Authorization Code)☆–用的最多、最复杂、最安全
资源用户
用户代理(浏览器简称为) 授权服务器
客户端
客户端先去授权服务中心获取授权码,再拿着授权码获取令牌
客户端通过用户代理携带标识和重定向的URI(Identifier/URI),去授权服务器,进行授权,需要用户进行授权(也就是资源用户进行授权)。
授权以后,授权服务器,会根据用书授权的结果返回给客户端授权码。
客户端拿到授权码以后,会把授权码以及重定向URI,再去授权服务中心申请令牌。会返回给客户端一个token以及刷新令牌
2、简化授权模式(Implicit)
资源拥有者(用户)
代理(浏览器)----------授权服务器
客户端----------------------后台客户端
3、密码模式
用户
客户端 -------- 授权服务器
用户直接把密码给客户端
客户端拿着密码去授权《-----》授权服务器根据用户的密码直接返回一个令牌
4、客户端模式
客户端进行相应的授权,授权服务器进行返回令牌。
例如dock的客户端,直接拉取镜像,先去申请镜像的令牌,根据令牌拉取镜像。
所以客户端模式:机器与机器的对接,不需要用户的参与。
5、刷新令牌
A:客户端去授权客户信息。
B:授权服务器会返回Token(刷新令牌)
C:客户端拿到令牌去资源服务器访问,获取受资源保护的资源
D:正常返回
E:重复C。客户端去范文资源服务器,结果过期了
F:资源服务器返回给客户端:令牌错误。
G:A操作。客户端去授权服务器,刷新令牌。
H:B操作。服务器返回客户端新令牌
三、Spring Security Oauth2
1、授权服务器
2、Spring Security Oauth2架构
流程:
- 用户访问,此时没有Token。Oauth2RestTemplate会报错,这个报错信息会被Oauth2ClientContextFilter捕获
并重定向到认证服务器 - 认证服务器通过Authorization Endpoint进行授权,并通过AuthorizationServerTokenServices生成授权码并返
回给客户端 - 客户端拿到授权码去认证服务器通过Token Endpoint调用AuthorizationServerTokenServices生成Token并返
回给客户端 - 客户端拿到Token去资源服务器访问资源,一般会通过Oauth2AuthenticationManager调用
ResourceServerTokenServices进行校验。校验通过可以获取资源。
四、Spring Security Oauth2授权码模式
1、创建项目并添加依赖
添加依赖:更改pox.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springsecurityoauth2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurityoauth2-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<!--引入springCloud的版本号-->
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<!--oauth2 的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--security的依赖
springClod 和 springboot都可以引入security的依赖
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<!--springCloud 的依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<!--只是在引入的时候生效-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.自定义登录逻辑,修改用户和密码
引入完了Security依赖后,会有默认的登录用户(账号是:user)和密码(密码是项目启动时控制台打印输出的。)。我们要自定义逻辑修改账号和密码
1.service/UserService
@Service
public class UserService implements UserDetailsService {
private PasswordEncoder passwordEncoder;
/***
* userDetailsService接口 返回的是UserDetails接口,被user实现
*
* 正常用户名是从前端传过来的,从而去数据库获得密码。在于前端的密码进行比较
* 但是现在:只是演示,密码就自己去写。
* 密码需要加密,需要用到容器、以及springSecurity配置类
*
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//模拟数据库中的密码。encoe是进行加密方法
String password = passwordEncoder.encode("123456");
//自己定义一个user类(从前端传来的name,数据库中的密码,账户的权限以逗号分隔)
return new User("admin",password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
2.pojo/User
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
public User(String username, String password, List<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
3.config/SecurityConfig
/**
* 这是配置类,容器
*
* PasswordEncoder:是一个接口,接口下有一个加密算法BCryptPasswordEncoder的类
*
*/
@Configuration
//启动 web安全
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
//进行放行的是:获取的令牌授权,登录,登出。 放行
.antMatchers("/oauth/**","/login/**","logout/**")
.permitAll()
.anyRequest()
.authenticated()
//可以通过and进行连接
.and()
//表单认证全部放心
.formLogin()
.permitAll();
}
}
3、Oauth2有两个服务器
1. 授权的配置服务器。
/**
* 授权服务器配置
*/
@Configuration
//启用授权服务器的配置
@EnableAuthorizationServer
public class AuthorizationServiceConfig extends AuthorizationServerConfigurerAdapter {
//进行密码加密:
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client-id 账户
//自定义为admin。正常是:在注册时,在授权服务器生成的
.withClient("admin")
//配置client-secret 密码加密
.secret(passwordEncoder.encode("112233"))
//访问令牌token的有效期。时间是秒
.accessTokenValiditySeconds(33600)
//配置client-uri .用于授权成功后,重定向跳转
.redirectUris("http://www.baidu.com")
//配置申请的授权范围
.scopes("all")
//配置grant_type, 表示授权码模式
.authorizedGrantTypes("authorization_code");
}
}
2.资源服务器
/**
* 资源配置服务器
*/
@Configuration
@EnableResourceServer
public class ResourceServiceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//所有的访问必须认证后才能被访问,
.anyRequest()
.authenticated()
.and()
.requestMatchers()
//获取服务器,获取一个资源。 放行下面资源
.antMatchers("/user/**");
}
}
3.测试: 创建Usercontrolle
@RestController
// 在Controller下的所有的方法,都可以放在资源服务器下面进行获取
@RequestMapping("/user")
public class UserController {
/**
* 获取当前的用户
* @param authentication
* @return
*/
@RequestMapping("/getCurrentUsre")
public Object getCurrentUser(Authentication authentication){
//对应的主体
return authentication.getPrincipal();
}
}
测试:
http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
启动页面: 输入账号密码:admin /123456
五、Spring Security Oauth2密码模式
修改aothoritiesServiceConfig
/**
* 使用密码配置所需配置:
* @param endpoints
* @throws Exception
*/
//重写方法:ctrl+0
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//因为密码模式实际上直接把密码,传给授权服务器
//通过authenticationManager,去通过密码授权操作。在注入authenticationManager
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client-id 账户
//自定义为admin。正常是:在注册时,在授权服务器生成的
.withClient("admin")
//配置client-secret 密码加密
.secret(passwordEncoder.encode("112233"))
//访问令牌token的有效期。时间是秒
//.accessTokenValiditySeconds(33600)
//配置client-uri .用于授权成功后,重定向跳转
.redirectUris("http://www.baidu.com")
//配置申请的授权范围
.scopes("all")
//配置grant_type, 表示授权码模式
//.authorizedGrantTypes("authorization_code");
//将授权码改为密码模式
// .authorizedGrantTypes("password");
.authorizedGrantTypes("authorization_code","password");
}
}
2.serviceConfig
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
测试:
通过密码模式实现:令牌。根据令牌去资源服务器获取相应的资源:
六、Redis存储token
代码中的token都是存储在内存当中的,在实际环境中是不合理的。
我们将其改为Redis当中。
1、加入Redis依赖:
<!--redis 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、配置redis在yaml中:
redis服务器远程地址:
#Redis配置
spring.redis.host=192.168.10.100
3、编写Redis配置类
@Configuration
public class RedisConfig {
@Autowired
public RedisConnectionFactory redisConnectionFactory;
/**
* token存储的位置
* 会自动把Token放到Redis当中去。
*
* 放完以后回到授权服务器,进行相应的配置
*/
@Bean
public TokenStore redisTokenStore(){
//里面存放redis连接工厂
return new RedisTokenStore(redisConnectionFactory);
}
}
4、在认证服务器配置中指定令牌的存储策略为Redis
//获取RedisConfig 的Bean
@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;
/**
* 使用密码配置所需配置:
* @param endpoints
* @throws Exception
*/
//重写方法:ctrl+0
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//因为密码模式实际上直接把密码,传给授权服务器
//通过authenticationManager,去通过密码授权操作。在注入authenticationManager
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore);
}