1.基本概念
简介,Oauth协议为用户资源的授权提供了一个安全的,开放而又简易的标准,同时,任何第三方都可以使用Oauth认证服务,目前Oauth是2.0版本使用最为广泛.
分析一下网站使用vx认证的过程:
1.首先用户想访问资源,需要认证,使用第三方认证比如(vx,qq,新浪等等)
2.用户确认使用第三方认证,那么需要向对应的第三方请求授权码,拿到授权码后,再用拿到的授权码去授权服务器请求令牌
3.服务端校验令牌,如果成功则授予相应的权限访问资源,获取对应的资源或者个人信息.
Oauth2这个是为了方便安全的用户(第三方)登录用的。
一开始听见oauth2这个词肯定是很懵的。这是啥,鉴权用的?认证用的?授权用的?跟shiro(java)是一个东西吗?
其实oauth就是一个流程。我们根据这个流程的要求写代码、
oauth有一个授权服务器。是作为用户的认证用的。对于服务端来说只需实现一个oauth的授权服务器。对于用户来说(调用授权认证的研发)只需根据流程发请求就可以了。
Oauth 有四种实体。 下面以用QQ登陆微博为例。
资源所有者(resource owner) 我们(普通用户)
资源服务器(resource server) QQ的后台服务器(获取账号,昵称,头像等)。
应用程序(client) 微博这个平台
授权服务器(Authorization Server) 认证QQ & 授权用的。
oauth有四种方式。 目的是不把密码暴露给第三方。(即不把QQ账号密码暴露给微博)
1.授权码 authorization code 也是安全等级最高的一版。 支持refresh token 一般都用这个,微博QQ登录就是这种方式。
2.密码 password credentials 支持refresh token 这种安全等级低。谁知道client会不会偷摸存你密码不。一般内部使用
3.简化模式 implicit 不支持refresh token 这种没有获取code的步骤。请求就给token、 没get到这个的优势
4.客户端模式 client credentials 不支持refresh token 这种是被信任的内部client使用。一般内部平台间使用
注: client_id client_secret 是用来oauth server鉴别client用的。
下面详细说明一下各个方式。简化模式就不说了。
1.授权码模式
授权码模式就是通过在授权服务器获得验证码和令牌双重校验,然后进行授权访问.
1)添加依赖:
<?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 http://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.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>spring-sercrity-student</groupId>
<artifactId>spring-sercrity-myself</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-cloud.version>2021.0.1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-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>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!--当oauth2项目启动出现无法读取的异常添加此依赖-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!--spring cloud整合security框架-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<!-- web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
加入SpringCloud依赖,而SpringCloud也整合了security和oauth2框架.
2)创建securityconfig:
/**
* 授权框架配置类
*/
@Configuration
//表示启动webSecurity
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
//放行oauth认证,登录认证,用户退出
.antMatchers("/oauth/**", "/login/**", "logout/**").permitAll()
//拦截未被放行的所有请求
.anyRequest().authenticated()
.and()
//表单认证全部放行
.formLogin().permitAll();
}
//返回一个加密器
@Bean
public PasswordEncoder getPw() {
return new BCryptPasswordEncoder();
}
}
3)自定义用户信息包装类
/**
* 用户信息包装类
*/
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 authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return userName;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
4)自定义认证类
/**
* 自定义认证
*/
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String encodePw = passwordEncoder.encode("123");
return new User(username,encodePw, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
这里只是演示,实际密码应该从数据库中查询,进行校验.
5)配置授权服务器
/**
* 授权服务器配置
*
* @author 秦杨
* @version 1.0
*/
@Configuration
//表示启用授权服务器配置
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//这里只是暂时写死,正常需要去授权服务器注册
//配置Client id
.withClient("admin")
//配置Client security(密钥)
.secret(passwordEncoder.encode("Vermouth2022"))
//设置Token失效时间
.accessTokenValiditySeconds(3600)
//重定向地址,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
//授权范围,申请读取哪部分内容
.scopes("all")
//授权类型
.authorizedGrantTypes("authorization_code");
}
}
这里拿到的客户端id和密钥将在第二次令牌验证的时候同时进行验证!!!
6)配置资源服务器
/**
* 资源服务配置
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestMatchers()
//放行需要获取的对应的资源
.antMatchers("/user/**");
}
}
当token令牌验证成功后就放行需要的功能.
具体操作流程:
这只是模拟授权码登录,实际上一般都是我们去vx这种第三方获取权限,所以也是访问他们的授权服务器和资源管理器,而这是为了了解流程.
当用户需要第三方授权时,我们会使用oauth协议,同时让用户扫描或者登录或者人脸识别等等来认证用户身份,同时将这些认证的信息发送到授权服务器.
授权请求:http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
这里url的信息必须跟授权服务器中定义的一致,否则会报错.
/oauth/是我们授权框架的接口
/authorize表示要进行授权,请求一个验证码,客户端id是admin,成功后跳转到百度,请求的权限范围是all.
当我们访问后,它会自动跳转到login界面,让我们进行认证
输入账号密码/扫码后,成功后就会成功进入到我们授权的页面里
Approve表示同意授权,Dent表示拒绝.
同意后我们就跳转到了指定的页面.
仔细看,页面的url中带了一个code,而其值就是我们授权服务器返回的一个验证码.
拿到验证码我们要再去授权服务器进行验证
要用post请求去发送信息,我们用postman去使用.
使用验证码认证,同时输入我们在授权服务器中写死的用户端id,这个正常情况应该要注册的,为了演示就写死了.
同时请求体中还要带上授权服务器需要的参数和返回的验证码.
点击发送后我们拿到一个授权服务器返回的token和一些信息.
{
"access_token": "5afbda45-62aa-4345-bd20-a4e647356cf2",
"token_type": "bearer",
"expires_in": 3599,
"scope": "all"
}
拿到了token之后,就可以去访问我们需要的资源了,带上这个token去进行请求
提交后就会访问我们需要的功能,然后拿到相应的资源,这就是授权码模式