授权码模式
创建父工程
cloud-oauth2-parent
pom文件:
<?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>
<groupId>com.wj</groupId>
<artifactId>cloud-oauth2-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cloud-oauth2-base</module>
<module>cloud-oauth2-auth-server</module>
</modules>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<properties>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<mybatis-plus.version>3.2.0</mybatis-plus.version>
<druid.version>1.1.12</druid.version>
<kaptcha.version>2.3.2</kaptcha.version>
<fastjson.version>1.2.8</fastjson.version>
<commons-lang.version>2.6</commons-lang.version>
<commons-collections.version>3.2.2</commons-collections.version>
<commons-io.version>2.6</commons-io.version>
<!-- 定义版本号, 子模块直接引用-->
<oauth-security.version>1.0-SNAPSHOT</oauth-security.version>
</properties>
<!--依赖声明-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<!--maven不支持多继承,使用import来依赖管理配置-->
<scope>import</scope>
</dependency>
<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- kaptcha 用于图形验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
<!-- 工具类依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!--springboot 打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.4.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
创建子工程
cloud-oauth2-base
pom依赖
<?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">
<parent>
<artifactId>cloud-oauth2-parent</artifactId>
<groupId>com.wj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-oauth2-base</artifactId>
<dependencies>
<!--类中setter/getter,使用注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 工具类依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
</project>
创建通用返回类:com.wj.base.result.R
@Data
public class R implements Serializable {
// 响应业务状态
private Integer code;
// 响应消息
private String message;
// 响应中的数据
private Object data;
public R() {
}
public R(Object data) {
this.code = 200;
this.message = "OK";
this.data = data;
}
public R(String message, Object data) {
this.code = 200;
this.message = message;
this.data = data;
}
public R(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public static R ok() {
return new R(null);
}
public static R ok(String message) {
return new R(message, null);
}
public static R ok(Object data) {
return new R(data);
}
public static R ok(String message, Object data) {
return new R(message, data);
}
public static R build(Integer code, String message) {
return new R(code, message, null);
}
public static R build(Integer code, String message, Object data) {
return new R(code, message, data);
}
public String toJsonString() {
return JSON.toJSONString(this);
}
/**
* JSON字符串转成 R 对象
*/
public static R format(String json) {
try {
return JSON.parseObject(json, R.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
cloud-oauth2-auth-server
基本配置
pom依赖:
<?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">
<parent>
<artifactId>cloud-oauth2-parent</artifactId>
<groupId>com.wj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-oauth2-auth-server</artifactId>
<dependencies>
<dependency>
<artifactId>cloud-oauth2-base</artifactId>
<groupId>com.wj</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--spring mvc相关的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security、OAuth2 和JWT等 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- 注册到 Eureka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-->
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.4.RELEASE</version>
<configuration>
<mainClass>com.wj.oauth2.AuthServerApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8090
servlet:
context-path: /auth
spring:
datasource:
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/study-security?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
springboot主启动类com.wj.oauth2.AuthServerApplication:
@SpringBootApplication
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
}
配置类
统一管理Bean配置类: SpringSecurityBean
@Configuration
public class SpringSecurityBean {
@Bean //引入PasswordEncoder
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
SpringSecurity配置类:
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内存方式存储用户信息,这里为了方便就不从数据库中查询了
auth.inMemoryAuthentication().withUser("admin")
.password(passwordEncoder.encode("1234"))
.authorities("product");
}
}
认证服务器配置类:AuthorizationServerConfig
@Configuration
@EnableAuthorizationServer//开启认证服务器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
/** 配置被允许访问此认证服务器的客户端详情信息
* 方式1:内存方式管理
* 方式2:数据库管理
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用内存方式
clients.inMemory()
// 客户端id
.withClient("wj-pc")
// 客户端密码,要加密,不然一直要求登录
.secret(passwordEncoder.encode("wj-secret"))
// 资源id, 如商品资源
.resourceIds("product-server")
// 授权类型, 可同时支持多种授权类型
.authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
// 授权范围标识,哪部分资源可访问(all是标识,不是代表所有)
.scopes("all")
// false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码,
.autoApprove(false)
.redirectUris("http://www.baidu.com/");// 客户端回调地址
}
}
配置说明:
withClient:允许访问此认证服务器的客户端id , 如:PC、APP、小程序各不同的的客户端id。
secret:客户端密码,要加密存储,不然获取不到令牌一直要求登录,, 而且一定不能被泄露。
authorizedGrantTypes: 授权类型, 可同时支持多种授权类型:可配置:"authorization_code", "password", "implicit","client_credentials","refresh_token"
scopes:授权范围标识,如指定微服务名称,则只能访问指定的微服务。
autoApprove:false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码
redirectUris 当获取授权码后,认证服务器会重定向到这个URI,并且带着一个授权码code响应回来
令牌访问端点
Spring Security 对 OAuth2 默认提供了可直接访问端点,即URL:
/oauth/authorize:申请授权码 code, 涉及的类AuthorizationEndpoint
/oauth/token:获取令牌 token, 涉及的类TokenEndpoint/oauth/check_token:用于资源服务器请求端点来检查令牌是否有效, 涉及的类CheckTokenEndpoint
/oauth/confirm_access:用户确认授权提交, 涉及的类WhitelabelApprovalEndpoint
/oauth/error:授权服务错误信息, 涉及的类WhitelabelErrorEndpoint
/oauth/token_key:提供公有密匙的端点,使用 JWT 令牌时会使用 , 涉及的类TokenKeyEndpoint
测试
发送请求获取授权码code
访问:localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
这里的client_id是在AuthorizationServerConfig中配置的。
输入账号密码进行登陆,账号和密码:admin/1234 ,是在SpringSecurityConfig中配置的
登陆成功后,选择Approve,点击Authorize,这里跳转到www.baidu.com ,并且后面携带了code,这里的code就是授权码,后面我们就可以通过授权码来获取令牌(access_token)
通过授权码获取令牌
使用postman测试:http://localhost:8090/auth/oauth/token
这里的username和password是在AuthorizationServerConfig中配置的
设置为post请求,并设置请求体
这里的grant_type是authorization_code,code是上一步获取的code
发送请求后,获得了access_token
注意,code只能获取一次access_token,获取后就会失效,第二次获取就会失败
密码授权模式
密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己在服务提供商(认证服务器)上的用户名和密码,然后客户端通过用户提供的用户名和密码向服务提供商(认证服务器)获取令牌。
如果用户名和密码遗漏,服务提供商(认证服务器)无法判断客户端提交的用户和密码是否盗取来的,那意味着令牌就可随时获取,数据被丢失。
所以密码授权模式适用于产品都是企业内部的,用户名密码共享不要紧。如果是第三方这种不太适合。也适用手机APP提交用户名密码。
配置密码模式
修改SpringSecurityConfig配置类:
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内存方式存储用户信息
auth.inMemoryAuthentication().withUser("admin")
.password(passwordEncoder.encode("1234"))
.authorities("product");
}
/**
* password密码模式需要使用此认证管理器
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
修改AuthorizationServerConfig类:
@Configuration
@EnableAuthorizationServer//开启认证服务器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
/** 配置被允许访问此认证服务器的客户端详情信息
* 方式1:内存方式管理
* 方式2:数据库管理
* localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用内存方式
clients.inMemory()
// 客户端id
.withClient("wj-pc")
// 客户端密码,要加密,不然一直要求登录, 获取不到令牌, 而且一定不能被泄露
.secret(passwordEncoder.encode("wj-secret"))
// 资源id, 如商品资源
.resourceIds("product-server")
// 授权类型, 可同时支持多种授权类型
.authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
// 授权范围标识,哪部分资源可访问(all是标识,不是代表所有)
.scopes("all")
// false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码,
.autoApprove(false)
.redirectUris("http://www.baidu.com/");// 客户端回调地址
}
/**
* 重写父类的方法
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//密码模式需要设置此认证管理器
endpoints.authenticationManager(authenticationManager);
}
}
测试使用
使用postman测试:
访问:http://localhost:8090/auth/oauth/token
填上请求参数:
发送请求即可获取到access_token:
简化模式
不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,不需要先获取授权码。直接可以一次请求就可得到令牌,在redirect_uri指定的回调地址中传递令牌( access_token )。该模式适合直接运行在浏览器上的应用,不用后端支持(例如 Javascript 应用)
注意:只需要客户端id,客户端密码都不需要。
浏览器直接访问:localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=token
先登录:
登陆成功并授权后,直接跳转到指定页面,并在uri中返回了access_token
注意
-
简化模式不允许按照 OAuth2 规范发布刷新令牌(refresh token)。这种行为是有必要的,它要求在使用运行在浏览器中的程序时,用户必须在场,这样可以在任何需要的时候,给第三方应用授权。
-
当使用简化模式时,第三方应用始终需要通过重定向URI来注册,这样能确保不会将token传给不需要验证的客户端。如果不这样做,一些心怀不轨的用户可能先注册一个应用,然后试图让其他的应用来顶替,接收这个 token,这样可能导致灾难性的结果
客户端授权模式
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向服务提供商(认证服务器)进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求服务提供商(认证服务器)提供服务,其实不存在授权问题。
-
客户端向认证服务器进行身份认证,并要求一个访问令牌。
-
认证服务器确认无误后,向客户端提供访问令牌
测试
post测试:http://localhost:8090/auth/oauth/token
grant_type等于client_credentials
发送请求返回了access_token,注意响应结果没有刷新令牌的