Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
qq_41234584
spring-boot-demo
提交
dcfeb4f4
S
spring-boot-demo
项目概览
qq_41234584
/
spring-boot-demo
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
spring-boot-demo
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
dcfeb4f4
编写于
1月 07, 2020
作者:
E
EchoCow
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
✨
Add spring-boot-demo-oauth-authorization-server.
上级
0378ad01
变更
48
隐藏空白更改
内联
并排
Showing
48 changed file
with
2256 addition
and
23 deletion
+2256
-23
pom.xml
pom.xml
+14
-0
spring-boot-demo-oauth/pom.xml
spring-boot-demo-oauth/pom.xml
+51
-1
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/README.adoc
...h/spring-boot-demo-oauth-authorization-server/README.adoc
+273
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Code.png
...pring-boot-demo-oauth-authorization-server/image/Code.png
+0
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Confirm.png
...ng-boot-demo-oauth-authorization-server/image/Confirm.png
+0
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Login.png
...ring-boot-demo-oauth-authorization-server/image/Login.png
+0
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Logout.png
...ing-boot-demo-oauth-authorization-server/image/Logout.png
+0
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/pom.xml
...oauth/spring-boot-demo-oauth-authorization-server/pom.xml
+15
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java
...va/com/xkcoding/oauth/SpringBootDemoOauthApplication.java
+2
-1
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java
.../com/xkcoding/oauth/config/ClientLoginFailureHandler.java
+31
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java
...com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java
+30
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java
...kcoding/oauth/config/Oauth2AuthorizationServerConfig.java
+54
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java
...xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java
+74
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java
...ain/java/com/xkcoding/oauth/config/WebSecurityConfig.java
+54
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java
...src/main/java/com/xkcoding/oauth/config/package-info.java
+22
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java
...om/xkcoding/oauth/controller/AuthorizationController.java
+43
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java
.../java/com/xkcoding/oauth/controller/Oauth2Controller.java
+55
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java
...main/java/com/xkcoding/oauth/controller/package-info.java
+14
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java
...main/java/com/xkcoding/oauth/entity/SysClientDetails.java
+191
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java
...rver/src/main/java/com/xkcoding/oauth/entity/SysRole.java
+49
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java
...rver/src/main/java/com/xkcoding/oauth/entity/SysUser.java
+55
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java
...xkcoding/oauth/repostiory/SysClientDetailsRepository.java
+33
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java
...java/com/xkcoding/oauth/repostiory/SysUserRepository.java
+24
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java
...a/com/xkcoding/oauth/service/SysClientDetailsService.java
+67
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java
.../main/java/com/xkcoding/oauth/service/SysUserService.java
+59
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java
...oding/oauth/service/impl/SysClientDetailsServiceImpl.java
+73
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java
...a/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java
+76
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java
...rc/main/java/com/xkcoding/oauth/service/package-info.java
+7
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/application.yml
...h-authorization-server/src/main/resources/application.yml
+22
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/oauth2.jks
...-oauth-authorization-server/src/main/resources/oauth2.jks
+0
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/public.txt
...-oauth-authorization-server/src/main/resources/public.txt
+9
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/authorization.html
...on-server/src/main/resources/templates/authorization.html
+55
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/common/common.html
...on-server/src/main/resources/templates/common/common.html
+33
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/error.html
...horization-server/src/main/resources/templates/error.html
+45
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/login.html
...horization-server/src/main/resources/templates/login.html
+110
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/logout.html
...orization-server/src/main/resources/templates/logout.html
+44
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/registerTemplate.html
...server/src/main/resources/templates/registerTemplate.html
+155
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java
.../src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java
+22
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java
...com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java
+125
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java
...ava/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java
+94
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java
...xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java
+39
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java
...a/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java
+26
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java
.../com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java
+40
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/application.yml
...h-authorization-server/src/test/resources/application.yml
+21
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/import.sql
...-oauth-authorization-server/src/test/resources/import.sql
+10
-0
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/schema.sql
...-oauth-authorization-server/src/test/resources/schema.sql
+40
-0
spring-boot-demo-oauth/src/main/resources/application.yml
spring-boot-demo-oauth/src/main/resources/application.yml
+0
-4
spring-boot-demo-oauth/src/test/java/com/xkcoding/oauth/SpringBootDemoOauthApplicationTests.java
...m/xkcoding/oauth/SpringBootDemoOauthApplicationTests.java
+0
-17
未找到文件。
pom.xml
浏览文件 @
dcfeb4f4
...
...
@@ -86,6 +86,20 @@
<user.agent.version>
1.20
</user.agent.version>
</properties>
<repositories>
<repository>
<id>
aliyun
</id>
<name>
aliyun
</name>
<url>
https://maven.aliyun.com/repository/public
</url>
<releases>
<enabled>
true
</enabled>
</releases>
<snapshots>
<enabled>
false
</enabled>
</snapshots>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
...
...
spring-boot-demo-oauth/pom.xml
浏览文件 @
dcfeb4f4
...
...
@@ -5,7 +5,10 @@
<artifactId>
spring-boot-demo-oauth
</artifactId>
<version>
1.0.0-SNAPSHOT
</version>
<packaging>
jar
</packaging>
<modules>
<module>
spring-boot-demo-oauth-authorization-server
</module>
</modules>
<packaging>
pom
</packaging>
<name>
spring-boot-demo-oauth
</name>
<description>
Demo project for Spring Boot
</description>
...
...
@@ -28,10 +31,49 @@
<artifactId>
spring-boot-starter-web
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-security
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-thymeleaf
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-data-jpa
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.security.oauth.boot
</groupId>
<artifactId>
spring-security-oauth2-autoconfigure
</artifactId>
<version>
${spring.boot.version}
</version>
</dependency>
<dependency>
<groupId>
mysql
</groupId>
<artifactId>
mysql-connector-java
</artifactId>
<scope>
runtime
</scope>
</dependency>
<dependency>
<groupId>
com.h2database
</groupId>
<artifactId>
h2
</artifactId>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-test
</artifactId>
<scope>
test
</scope>
<exclusions>
<exclusion>
<groupId>
junit
</groupId>
<artifactId>
junit
</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
...
...
@@ -44,6 +86,14 @@
<artifactId>
lombok
</artifactId>
<optional>
true
</optional>
</dependency>
<dependency>
<groupId>
org.junit.jupiter
</groupId>
<artifactId>
junit-jupiter
</artifactId>
<version>
5.5.2
</version>
<scope>
test
</scope>
</dependency>
</dependencies>
<build>
...
...
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/README.adoc
0 → 100644
浏览文件 @
dcfeb4f4
= spring-boot-demo-oauth-authorization-server
Doc Writer <lzy@echocow.cn>
v1.0, 2019-01-07
:toc:
spring boot oauth2 授权服务器,
- 授权码模式、密码模式、刷新令牌
- 自定义 UserDetailService
- 自定义 ClientDetailService
- jwt 非对称加密
- 自定义登录授权页面
> SQL 语句
>
> - DDL: `src/test/resources/schema.sql`
> - DML: `src/test/resources/import.sql`
测试用例使用 h2 数据库,测试数据如下:
.测试客户端
|===
|客户端 id |客户端密钥 |资源服务器名称 |授权类型 | scopes| 回调地址
|oauth2
|oauth2
|oauth2
|authorization_code,password,refresh_token
|READ,WRITE
|http://example.com
|test
|oauth2
|oauth2
|authorization_code,password,refresh_token
|READ
|http://example.com
|error
|oauth2
|test
|authorization_code,password,refresh_token
|READ
|http://example.com
|===
.测试用户
|===
|用户名 |密码 |角色
|admin
|123456
|ROLE_ADMIN
|test
|123456
|ROLE_TEST
|===
== 授权码模式
> 测试用例:`com.xkcoding.oauth.oauth.AuthorizationCodeGrantTests`
=== 获取授权码
- 请求地址: http://localhost:8080/oauth/authorize?response_type=code&client_id=oauth2&redirect_uri=http://example.com&scope=READ
- 用户名:admin
- 密码:123456
image::image/Login.png[login]
=== 确认授权
登录成功以后,进入确认授权页面。已经确认过的用户,不会再次要求确认。
image::image/Confirm.png[confirm]
确认授权后,获取授权码
image::image/Code.png[code]
=== 请求 token
使用以下代码可以直接请求 token
[shell]
----
curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=GgX6QD' \
--data-urlencode 'redirect_uri=http://example.com' \
--data-urlencode 'client_id=oauth2' \
--data-urlencode 'scope=READ WRITE'
----
得到 token
[token]
----
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiUkVBRCJdLCJleHAiOjE1NzgzODY4MTYsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZjAyMDhiNTUtYTJjYS00NjI4LTg5YjEtNzI5MzY4MzAxOWNhIiwiY2xpZW50X2lkIjoib2F1dGgyIn0.RqJpsin6bMnwI57cGpODTplLeW_gtNWHo_l4SimyRLsnxpCWm5oY1EOb4qVHpXvCbhNsUj69D462P7le13OOmexysZIQhaoGZ_CbIlEp63XsCnr5nSKeX3dgQlyTUDjOUL0WUtY2lKqLCGMeX_rpVhfmSh3b7MC0Ntxq5ao-943QMXGRIeRvJgSkvfY2HBN6-zx1H6rE0wxnUfBC1M08kUkFYlSmsFchiz-E_oTzJvE2D8lA9g-eEFU6cZ_els4Q77Vvc_O6SXUZ7o65vFyLyUjLvh9QF1825SGIUUdXTUYSZjnSAXChhRIAT5pLRHK-gthIzpOaWrgj6ebUoG02Eg",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiUkVBRCJdLCJhdGkiOiJmMDIwOGI1NS1hMmNhLTQ2MjgtODliMS03MjkzNjgzMDE5Y2EiLCJleHAiOjE1NzgzODY4MTYsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiMGViNTU2MTQtYjgxYS00MTFmLTg1MTAtZThkMjZmODJmMjJhIiwiY2xpZW50X2lkIjoib2F1dGgyIn0.CBGcjirkf-3187SgbZr0ikauiCS8U9YLaoR4sNlRQjd-gaIeF5PChnIs_yAmG_VpqPFlPRdSl8DA05S2QnFpT3TkRjyP-LPDZgsVAPfczMAdVywU1zOKYZeq-gM6p9bmGEabbZoBlIxOImsjeyFSCui6UtRTZjNlj3AhGIzvs52T8bDqC796iHPDZvJ97MMgsEiRyu-mxDm1o1LMuBX9RHCx9rAkBVf52q36bqWMcYAlDOu1wYjpmhalSLZyWcmraQvClEitXGJI4eTFapTnuXQuWFIL-973V_5Shw98-bk65zZQOEheazHrUf-n4h-sYT4akehnYSVxX2UIg9XsCw",
"expires_in": 5999,
"scope": "READ",
"jti": "f0208b55-a2ca-4628-89b1-7293683019ca"
}
----
== 密码模式
> 测试用例:`com.xkcoding.oauth.oauth.ResourceOwnerPasswordGrantTests`
`test` 用户进行授权
[source]
----
curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \
--data-urlencode 'password=123456' \
--data-urlencode 'username=test' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=READ WRITE'
----
== 刷新令牌
携带 `refresh_token` 去请求
[source]
----
curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiUkVBRCJdLCJhdGkiOiJmMDIwOGI1NS1hMmNhLTQ2MjgtODliMS03MjkzNjgzMDE5Y2EiLCJleHAiOjE1NzgzODY4MTYsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiMGViNTU2MTQtYjgxYS00MTFmLTg1MTAtZThkMjZmODJmMjJhIiwiY2xpZW50X2lkIjoib2F1dGgyIn0.CBGcjirkf-3187SgbZr0ikauiCS8U9YLaoR4sNlRQjd-gaIeF5PChnIs_yAmG_VpqPFlPRdSl8DA05S2QnFpT3TkRjyP-LPDZgsVAPfczMAdVywU1zOKYZeq-gM6p9bmGEabbZoBlIxOImsjeyFSCui6UtRTZjNlj3AhGIzvs52T8bDqC796iHPDZvJ97MMgsEiRyu-mxDm1o1LMuBX9RHCx9rAkBVf52q36bqWMcYAlDOu1wYjpmhalSLZyWcmraQvClEitXGJI4eTFapTnuXQuWFIL-973V_5Shw98-bk65zZQOEheazHrUf-n4h-sYT4akehnYSVxX2UIg9XsCw'
----
== 解析令牌
携带令牌解析
[source]
----
curl --location --request POST 'http://127.0.0.1:8080/oauth/check_token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \
--data-urlencode 'token='
----
解析结果
[source]
----
{
"aud": [
"oauth2"
],
"user_name": "admin",
"scope": [
"READ",
"WRITE"
],
"active": true,
"exp": 1578389936,
"authorities": [
"ROLE_ADMIN"
],
"jti": "fe59fce9-6764-435e-8fa7-7320e11af811",
"client_id": "oauth2"
}
----
== 退出登录
授权码模式登陆是在授权服务器上登录的,所以退出也要在授权服务器上退出。
携带回调地址进行退出,退出完成后跳转到回调地址:
image::image/Logout.png[logout]
退出以后自动跳转到回调地址(要加 `http` 或 `https`)
== 获取公钥
通过访问 '/oauth/token_key' 获取 JWT 公钥
[source]
----
curl --location --request GET 'http://127.0.0.1:8080/oauth/token_key' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic b2F1dGgyOm9hdXRoMg=='
----
获取后
[source]
----
{
"alg": "SHA256withRSA",
"value": "-----BEGIN PUBLIC KEY-----\n......\n-----END PUBLIC KEY-----"
}
----
== 核心配置
=== 授权服务器配置
[Oauth2AuthorizationServerConfig]
----
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
// 自定义用户
.userDetailsService(sysUserService)
// 内存存储
.tokenStore(tokenStore)
// jwt 令牌转换
.accessTokenConverter(jwtAccessTokenConverter);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 从数据库读取我们自定义的客户端信息
clients.withClientDetails(sysClientDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
// 获取 token key 需要进行 basic 认证客户端信息
.tokenKeyAccess("isAuthenticated()")
// 获取 token 信息同样需要 basic 认证客户端信息
.checkTokenAccess("isAuthenticated()");
}
----
=== 安全配置
[WebSecurityConfig]
----
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 开启表单登录,授权码模式的时候进行登录
.formLogin()
// 路径等
.loginPage("/oauth/login")
.loginProcessingUrl("/authorization/form")
// 失败以后携带错误信息进行再次跳转登录页面
.failureHandler(clientLoginFailureHandler)
.and()
// 退出登录相关
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(clientLogoutSuccessHandler)
.and()
// 授权服务器安全配置
.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest()
.authenticated();
}
----
== 参考
- https://echocow.cn/articles/2019/07/14/1563096109754.html[Spring Security Oauth2 从零到一完整实践(三)授权服务器 ]
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Code.png
0 → 100644
浏览文件 @
dcfeb4f4
29.0 KB
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Confirm.png
0 → 100644
浏览文件 @
dcfeb4f4
22.1 KB
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Login.png
0 → 100644
浏览文件 @
dcfeb4f4
21.5 KB
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Logout.png
0 → 100644
浏览文件 @
dcfeb4f4
23.9 KB
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/pom.xml
0 → 100644
浏览文件 @
dcfeb4f4
<?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>
spring-boot-demo-oauth
</artifactId>
<groupId>
com.xkcoding
</groupId>
<version>
1.0.0-SNAPSHOT
</version>
</parent>
<modelVersion>
4.0.0
</modelVersion>
<artifactId>
spring-boot-demo-oauth-authorization-server
</artifactId>
</project>
spring-boot-demo-oauth/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java
→
spring-boot-demo-oauth/s
pring-boot-demo-oauth-authorization-server/s
rc/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java
浏览文件 @
dcfeb4f4
...
...
@@ -2,7 +2,6 @@ package com.xkcoding.oauth;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.web.bind.annotation.GetMapping
;
/**
* <p>
...
...
@@ -16,6 +15,8 @@ import org.springframework.web.bind.annotation.GetMapping;
* @copyright: Copyright (c) 2019
* @version: V1.0
* @modified: yangkai.shen
* @modified: EchoCow
* @date: Modified in 2020-01-6 21:12
*/
@SpringBootApplication
public
class
SpringBootDemoOauthApplication
{
...
...
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.config
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.security.core.AuthenticationException
;
import
org.springframework.security.web.authentication.AuthenticationFailureHandler
;
import
org.springframework.stereotype.Component
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.net.URLEncoder
;
/**
* 登录失败处理器,失败后携带失败信息重定向到登录地址重新登录.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/7 下午1:01
*/
@Slf4j
@Component
public
class
ClientLoginFailureHandler
implements
AuthenticationFailureHandler
{
@Override
public
void
onAuthenticationFailure
(
HttpServletRequest
request
,
HttpServletResponse
response
,
AuthenticationException
exception
)
throws
IOException
{
log
.
debug
(
"Login failed!"
);
response
.
setStatus
(
HttpStatus
.
UNAUTHORIZED
.
value
());
response
.
sendRedirect
(
"/oauth/login?error="
+
URLEncoder
.
encode
(
exception
.
getLocalizedMessage
(),
"UTF-8"
));
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.config
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.security.core.Authentication
;
import
org.springframework.security.web.authentication.logout.LogoutSuccessHandler
;
import
org.springframework.stereotype.Component
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
/**
* 客户团退出登录成功处理器.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午22:11
*/
@Slf4j
@Component
public
class
ClientLogoutSuccessHandler
implements
LogoutSuccessHandler
{
@Override
public
void
onLogoutSuccess
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Authentication
authentication
)
throws
IOException
{
response
.
setStatus
(
HttpStatus
.
FOUND
.
value
());
// 跳转到客户端的回调地址
response
.
sendRedirect
(
request
.
getParameter
(
"redirectUrl"
));
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.config
;
import
com.xkcoding.oauth.service.SysClientDetailsService
;
import
com.xkcoding.oauth.service.SysUserService
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.security.authentication.AuthenticationManager
;
import
org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer
;
import
org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter
;
import
org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer
;
import
org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer
;
import
org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer
;
import
org.springframework.security.oauth2.provider.token.TokenStore
;
import
org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
;
/**
* .
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:32
*/
@Configuration
@RequiredArgsConstructor
@EnableAuthorizationServer
public
class
Oauth2AuthorizationServerConfig
extends
AuthorizationServerConfigurerAdapter
{
private
final
SysClientDetailsService
sysClientDetailsService
;
private
final
SysUserService
sysUserService
;
private
final
TokenStore
tokenStore
;
private
final
AuthenticationManager
authenticationManager
;
private
final
JwtAccessTokenConverter
jwtAccessTokenConverter
;
@Override
public
void
configure
(
AuthorizationServerEndpointsConfigurer
endpoints
)
{
endpoints
.
authenticationManager
(
authenticationManager
)
.
userDetailsService
(
sysUserService
)
.
tokenStore
(
tokenStore
)
.
accessTokenConverter
(
jwtAccessTokenConverter
);
}
@Override
public
void
configure
(
ClientDetailsServiceConfigurer
clients
)
throws
Exception
{
// 从数据库读取我们自定义的客户端信息
clients
.
withClientDetails
(
sysClientDetailsService
);
}
@Override
public
void
configure
(
AuthorizationServerSecurityConfigurer
security
)
{
security
// 获取 token key 需要进行 basic 认证客户端信息
.
tokenKeyAccess
(
"isAuthenticated()"
)
// 获取 token 信息同样需要 basic 认证客户端信息
.
checkTokenAccess
(
"isAuthenticated()"
);
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.config
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Primary
;
import
org.springframework.core.io.ClassPathResource
;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.security.oauth2.provider.token.TokenStore
;
import
org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore
;
import
org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
;
import
org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory
;
import
java.security.KeyPair
;
/**
* token 相关配置.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:33
*/
@Configuration
@RequiredArgsConstructor
public
class
Oauth2AuthorizationTokenConfig
{
/**
* 声明 内存 TokenStore 实现,用来存储 token 相关.
* 默认实现有 mysql、redis
*
* @return InMemoryTokenStore
*/
@Bean
@Primary
public
TokenStore
tokenStore
()
{
return
new
InMemoryTokenStore
();
}
/**
* jwt 令牌 配置,非对称加密
*
* @return 转换器
*/
@Bean
public
JwtAccessTokenConverter
jwtAccessTokenConverter
()
{
final
JwtAccessTokenConverter
accessTokenConverter
=
new
JwtAccessTokenConverter
();
accessTokenConverter
.
setKeyPair
(
keyPair
());
return
accessTokenConverter
;
}
/**
* 密钥 keyPair.
* 可用于生成 jwt / jwk.
*
* @return keyPair
*/
@Bean
public
KeyPair
keyPair
()
{
KeyStoreKeyFactory
keyStoreKeyFactory
=
new
KeyStoreKeyFactory
(
new
ClassPathResource
(
"oauth2.jks"
),
"123456"
.
toCharArray
());
return
keyStoreKeyFactory
.
getKeyPair
(
"oauth2"
);
}
/**
* 加密方式,使用 BCrypt.
* 参数越大加密次数越多,时间越久.
* 默认为 10.
*
* @return PasswordEncoder
*/
@Bean
public
PasswordEncoder
passwordEncoder
()
{
return
new
BCryptPasswordEncoder
();
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.config
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.security.authentication.AuthenticationManager
;
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.EnableWebSecurity
;
import
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
;
/**
* 安全配置.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:27
*/
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity
(
prePostEnabled
=
true
,
securedEnabled
=
true
)
public
class
WebSecurityConfig
extends
WebSecurityConfigurerAdapter
{
private
final
ClientLogoutSuccessHandler
clientLogoutSuccessHandler
;
private
final
ClientLoginFailureHandler
clientLoginFailureHandler
;
@Override
protected
void
configure
(
HttpSecurity
http
)
throws
Exception
{
http
.
formLogin
()
.
loginPage
(
"/oauth/login"
)
.
failureHandler
(
clientLoginFailureHandler
)
.
loginProcessingUrl
(
"/authorization/form"
)
.
and
()
.
logout
()
.
logoutUrl
(
"/oauth/logout"
)
.
logoutSuccessHandler
(
clientLogoutSuccessHandler
)
.
and
()
.
authorizeRequests
()
.
antMatchers
(
"/oauth/**"
).
permitAll
()
.
anyRequest
()
.
authenticated
();
}
/**
* 授权管理.
*
* @return 认证管理对象
* @throws Exception 认证异常信息
*/
@Override
@Bean
public
AuthenticationManager
authenticationManagerBean
()
throws
Exception
{
return
super
.
authenticationManagerBean
();
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java
0 → 100644
浏览文件 @
dcfeb4f4
/**
* spring security oauth2 的相关配置。
* 使用 spring boot oauth2 自动配置。
* {@link com.xkcoding.oauth.config.Oauth2AuthorizationServerConfig}
* 授权服务器相关的配置,主要设置授权服务器如何读取客户端、用户信息和一些端点配置
* 可以在这里配置更多的东西,例如端点映射,token 增强等
*
* {@link com.xkcoding.oauth.config.Oauth2AuthorizationTokenConfig}
* 授权服务器 token 相关的配置,主要设置 jwt、加密方式等信息
*
* {@link com.xkcoding.oauth.config.ClientLogoutSuccessHandler}
* 资源服务器退出以后的处理。在授权码模式中,所有的客户端都需要跳转到授权服务器进行登录
* 当登录成功以后跳转到回调地址,如果用户需要登出,也要跳转到授权服务器这里进行登出
* 但是 spring security oauth2 似乎并没有这个逻辑。
* 所以自己给登出端点加了一个 redirect_url 参数,表示登出成功以后要跳转的地址
* 这个处理器就是来完成登出成功以后的跳转操作的。
*
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/7 上午9:16
*/
package
com.xkcoding.oauth.config
;
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.controller
;
import
org.springframework.security.oauth2.provider.AuthorizationRequest
;
import
org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
;
import
org.springframework.stereotype.Controller
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.SessionAttributes
;
import
org.springframework.web.servlet.ModelAndView
;
import
java.util.Map
;
/**
* 自定义确认授权页面.
* 需要注意的是: 不能在代码中 setComplete,因为整个授权流程并没有结束
* 我们只是在中途修改了它确认的一些信息而已。
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午4:42
*/
@Controller
@SessionAttributes
(
"authorizationRequest"
)
public
class
AuthorizationController
{
/**
* 自定义确认授权页面
* 当然你也可以使用 {@link AuthorizationEndpoint#setUserApprovalPage(String)} 方法
* 进行设置,但是 model 就没有那么灵活了
*
* @param model model
* @return ModelAndView
*/
@GetMapping
(
"/oauth/confirm_access"
)
public
ModelAndView
getAccessConfirmation
(
Map
<
String
,
Object
>
model
)
{
AuthorizationRequest
authorizationRequest
=
(
AuthorizationRequest
)
model
.
get
(
"authorizationRequest"
);
ModelAndView
view
=
new
ModelAndView
();
view
.
setViewName
(
"authorization"
);
view
.
addObject
(
"clientId"
,
authorizationRequest
.
getClientId
());
// 传递 scope 过去,Set 集合
view
.
addObject
(
"scopes"
,
authorizationRequest
.
getScope
());
return
view
;
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.controller
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.stereotype.Controller
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RequestParam
;
import
org.springframework.web.client.ResourceAccessException
;
import
org.springframework.web.servlet.ModelAndView
;
import
java.security.Principal
;
import
java.util.Objects
;
/**
* 页面控制器.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午4:30
*/
@Controller
@RequestMapping
(
"/oauth"
)
@RequiredArgsConstructor
public
class
Oauth2Controller
{
/**
* 授权码模式跳转到登录页面
*
* @return view
*/
@GetMapping
(
"/login"
)
public
String
loginView
()
{
return
"login"
;
}
/**
* 退出登录
*
* @param redirectUrl 退出完成后的回调地址
* @param principal 用户信息
* @return 结果
*/
@GetMapping
(
"/logout"
)
public
ModelAndView
logoutView
(
@RequestParam
(
"redirect_url"
)
String
redirectUrl
,
Principal
principal
)
{
if
(
Objects
.
isNull
(
principal
))
{
throw
new
ResourceAccessException
(
"请求错误,用户尚未登录"
);
}
ModelAndView
view
=
new
ModelAndView
();
view
.
setViewName
(
"logout"
);
view
.
addObject
(
"user"
,
principal
.
getName
());
view
.
addObject
(
"redirectUrl"
,
redirectUrl
);
return
view
;
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java
0 → 100644
浏览文件 @
dcfeb4f4
/**
* 控制器。除了业务逻辑的以外,提供两个控制器来帮助完成自定义:
* {@link com.xkcoding.oauth.controller.AuthorizationController}
* 自定义的授权控制器,重新设置到我们的界面中去,不使用他的默认实现
*
* {@link com.xkcoding.oauth.controller.Oauth2Controller}
* 页面跳转的控制器,这里拿出来是因为真的可以做很多事。比如登录的时候携带点什么
* 或者退出的时候携带什么标识,都可以。
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/7 上午11:25
* @see org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
*/
package
com.xkcoding.oauth.controller
;
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.entity
;
import
lombok.Data
;
import
org.springframework.security.core.GrantedAuthority
;
import
org.springframework.security.oauth2.provider.ClientDetails
;
import
org.springframework.security.oauth2.provider.client.BaseClientDetails
;
import
javax.persistence.*
;
import
java.util.*
;
import
java.util.stream.Collectors
;
/**
* 客户端信息.
* 这里实现了 ClientDetails 接口
* 个人建议不应该在实体类里面写任何逻辑代码
* 而为了避免实体类耦合严重不应该去实现这个接口的
* 但是这里为了演示和 {@link SysUser} 不同的方式,所以就选择实现这个接口了
* 另一种方式是写一个方法将它转化为默认实现 {@link BaseClientDetails} 比较好一点并且简单很多
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午12:54
*/
@Data
@Table
@Entity
public
class
SysClientDetails
implements
ClientDetails
{
/**
* 主键
*/
@Id
@GeneratedValue
(
strategy
=
GenerationType
.
IDENTITY
)
private
Long
id
;
/**
* client id
*/
private
String
clientId
;
/**
* client 密钥
*/
private
String
clientSecret
;
/**
* 资源服务器名称
*/
private
String
resourceIds
;
/**
* 授权域
*/
private
String
scopes
;
/**
* 授权类型
*/
private
String
grantTypes
;
/**
* 重定向地址,授权码时必填
*/
private
String
redirectUrl
;
/**
* 授权信息
*/
private
String
authorizations
;
/**
* 授权令牌有效时间
*/
private
Integer
accessTokenValiditySeconds
;
/**
* 刷新令牌有效时间
*/
private
Integer
refreshTokenValiditySeconds
;
/**
* 自动授权请求域
*/
private
String
autoApproveScopes
;
/**
* 是否安全
*
* @return 结果
*/
@Override
public
boolean
isSecretRequired
()
{
return
this
.
clientSecret
!=
null
;
}
/**
* 是否有 scopes
*
* @return 结果
*/
@Override
public
boolean
isScoped
()
{
return
this
.
scopes
!=
null
&&
!
this
.
scopes
.
isEmpty
();
}
/**
* scopes
*
* @return scopes
*/
@Override
public
Set
<
String
>
getScope
()
{
return
stringToSet
(
scopes
);
}
/**
* 授权类型
*
* @return 结果
*/
@Override
public
Set
<
String
>
getAuthorizedGrantTypes
()
{
return
stringToSet
(
grantTypes
);
}
@Override
public
Set
<
String
>
getResourceIds
()
{
return
stringToSet
(
resourceIds
);
}
/**
* 获取回调地址
*
* @return redirectUrl
*/
@Override
public
Set
<
String
>
getRegisteredRedirectUri
()
{
return
stringToSet
(
redirectUrl
);
}
/**
* 这里需要提一下
* 个人觉得这里应该是客户端所有的权限
* 但是已经有 scope 的存在可以很好的对客户端的权限进行认证了
* 那么在 oauth2 的四个角色中,这里就有可能是资源服务器的权限
* 但是一般资源服务器都有自己的权限管理机制,比如拿到用户信息后做 RBAC
* 所以在 spring security 的默认实现中直接给的是空的一个集合
* 这里我们也给他一个空的把
*
* @return GrantedAuthority
*/
@Override
public
Collection
<
GrantedAuthority
>
getAuthorities
()
{
return
Collections
.
emptyList
();
}
/**
* 判断是否自动授权
*
* @param scope scope
* @return 结果
*/
@Override
public
boolean
isAutoApprove
(
String
scope
)
{
if
(
autoApproveScopes
==
null
||
autoApproveScopes
.
isEmpty
())
{
return
false
;
}
Set
<
String
>
authorizationSet
=
stringToSet
(
authorizations
);
for
(
String
auto
:
authorizationSet
)
{
if
(
"true"
.
equalsIgnoreCase
(
auto
)
||
scope
.
matches
(
auto
))
{
return
true
;
}
}
return
false
;
}
/**
* additional information 是 spring security 的保留字段
* 暂时用不到,直接给个空的即可
*
* @return map
*/
@Override
public
Map
<
String
,
Object
>
getAdditionalInformation
()
{
return
Collections
.
emptyMap
();
}
private
Set
<
String
>
stringToSet
(
String
s
)
{
return
Arrays
.
stream
(
s
.
split
(
","
)).
collect
(
Collectors
.
toSet
());
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.entity
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
lombok.ToString
;
import
org.codehaus.jackson.annotate.JsonIgnore
;
import
javax.persistence.*
;
import
java.util.Set
;
/**
* 这里完全可以只用一个字段代替的
* 但是想了想还是模拟实际的情况来把
* 角色信息.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午12:44
*/
@Data
@Table
@Entity
@EqualsAndHashCode
(
exclude
=
{
"users"
})
@ToString
(
exclude
=
"users"
)
public
class
SysRole
{
/**
* 主键.
*/
@Id
@GeneratedValue
(
strategy
=
GenerationType
.
IDENTITY
)
private
Long
id
;
/**
* 角色名称,按照 spring security 规范
* 需要以 ROLE_ 开头.
*/
private
String
name
;
/**
* 角色描述.
*/
private
String
description
;
/**
* 当前角色所有用户.
*/
@ManyToMany
(
mappedBy
=
"roles"
,
fetch
=
FetchType
.
EAGER
)
private
Set
<
SysUser
>
users
;
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.entity
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
lombok.ToString
;
import
org.springframework.security.core.userdetails.User
;
import
org.springframework.security.core.userdetails.UserDetails
;
import
javax.persistence.*
;
import
java.util.Set
;
/**
* 用户实体.
* 避免实体类耦合,所以不去实现 {@link UserDetails} 接口
* 因为有且只有登录加载用户的时候才会需要这个接口
* 我们就手动构建一个 {@link User} 的默认实现就可以了
* 实现接口的方式可以参考 {@link SysClientDetails}
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午12:41
*/
@Data
@Table
@Entity
@EqualsAndHashCode
(
exclude
=
"roles"
)
@ToString
(
exclude
=
"roles"
)
public
class
SysUser
{
/**
* 主键.
*/
@Id
@GeneratedValue
(
strategy
=
GenerationType
.
IDENTITY
)
private
Long
id
;
/**
* 用户名.
*/
private
String
username
;
/**
* 密码.
*/
private
String
password
;
/**
* 当前用户所有角色.
*/
@ManyToMany
(
fetch
=
FetchType
.
EAGER
)
@JoinTable
(
name
=
"sys_user_role"
,
joinColumns
=
@JoinColumn
(
name
=
"user_id"
),
inverseJoinColumns
=
@JoinColumn
(
name
=
"role_id"
)
)
private
Set
<
SysRole
>
roles
;
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.repostiory
;
import
com.xkcoding.oauth.entity.SysClientDetails
;
import
org.springframework.data.jpa.repository.JpaRepository
;
import
org.springframework.data.jpa.repository.Modifying
;
import
java.util.Optional
;
/**
* 客户端信息.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:09
*/
public
interface
SysClientDetailsRepository
extends
JpaRepository
<
SysClientDetails
,
Long
>
{
/**
* 通过 clientId 查找客户端信息.
*
* @param clientId clientId
* @return 结果
*/
Optional
<
SysClientDetails
>
findFirstByClientId
(
String
clientId
);
/**
* 根据客户端 id 删除客户端
*
* @param clientId 客户端id
*/
@Modifying
void
deleteByClientId
(
String
clientId
);
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.repostiory
;
import
com.xkcoding.oauth.entity.SysUser
;
import
org.springframework.data.jpa.repository.JpaRepository
;
import
java.util.Optional
;
/**
* 用户信息仓库.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:08
*/
public
interface
SysUserRepository
extends
JpaRepository
<
SysUser
,
Long
>
{
/**
* 通过用户名查找用户.
*
* @param username 用户名
* @return 结果
*/
Optional
<
SysUser
>
findFirstByUsername
(
String
username
);
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.service
;
import
com.xkcoding.oauth.entity.SysClientDetails
;
import
org.springframework.security.oauth2.provider.ClientAlreadyExistsException
;
import
org.springframework.security.oauth2.provider.ClientDetailsService
;
import
org.springframework.security.oauth2.provider.ClientRegistrationService
;
import
org.springframework.security.oauth2.provider.NoSuchClientException
;
import
java.util.List
;
/**
* 声明自己的实现.
* 参见 {@link ClientRegistrationService}
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:39
*/
public
interface
SysClientDetailsService
extends
ClientDetailsService
{
/**
* 通过客户端 id 查询
*
* @param clientId 客户端 id
* @return 结果
*/
SysClientDetails
findByClientId
(
String
clientId
);
/**
* 添加客户端信息.
*
* @param clientDetails 客户端信息
* @throws ClientAlreadyExistsException 客户端已存在
*/
void
addClientDetails
(
SysClientDetails
clientDetails
)
throws
ClientAlreadyExistsException
;
/**
* 更新客户端信息,不包括 clientSecret.
*
* @param clientDetails 客户端信息
* @throws NoSuchClientException 找不到客户端异常
*/
void
updateClientDetails
(
SysClientDetails
clientDetails
)
throws
NoSuchClientException
;
/**
* 更新客户端密钥.
*
* @param clientId 客户端 id
* @param clientSecret 客户端密钥
* @throws NoSuchClientException 找不到客户端异常
*/
void
updateClientSecret
(
String
clientId
,
String
clientSecret
)
throws
NoSuchClientException
;
/**
* 删除客户端信息.
*
* @param clientId 客户端 id
* @throws NoSuchClientException 找不到客户端异常
*/
void
removeClientDetails
(
String
clientId
)
throws
NoSuchClientException
;
/**
* 查询所有
*
* @return 结果
*/
List
<
SysClientDetails
>
findAll
();
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.service
;
import
com.xkcoding.oauth.entity.SysUser
;
import
org.springframework.security.core.userdetails.UserDetailsService
;
import
java.util.List
;
/**
* .
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午3:44
*/
public
interface
SysUserService
extends
UserDetailsService
{
/**
* 查询所有用户
*
* @return 用户
*/
List
<
SysUser
>
findAll
();
/**
* 通过 id 查询用户
*
* @param id id
* @return 用户
*/
SysUser
findById
(
Long
id
);
/**
* 创建用户
*
* @param sysUser 用户
*/
void
createUser
(
SysUser
sysUser
);
/**
* 更新用户
*
* @param sysUser 用户
*/
void
updateUser
(
SysUser
sysUser
);
/**
* 更新用户 密码
*
* @param id 用户 id
* @param password 用户密码
*/
void
updatePassword
(
Long
id
,
String
password
);
/**
* 删除用户.
*
* @param id id
*/
void
deleteUser
(
Long
id
);
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.service.impl
;
import
com.xkcoding.oauth.entity.SysClientDetails
;
import
com.xkcoding.oauth.repostiory.SysClientDetailsRepository
;
import
com.xkcoding.oauth.service.SysClientDetailsService
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.security.oauth2.provider.*
;
import
org.springframework.stereotype.Service
;
import
java.util.List
;
/**
* 客户端 相关操作.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:37
*/
@Service
@RequiredArgsConstructor
public
class
SysClientDetailsServiceImpl
implements
SysClientDetailsService
{
private
final
SysClientDetailsRepository
sysClientDetailsRepository
;
private
final
PasswordEncoder
passwordEncoder
;
@Override
public
ClientDetails
loadClientByClientId
(
String
id
)
throws
ClientRegistrationException
{
return
sysClientDetailsRepository
.
findFirstByClientId
(
id
)
.
orElseThrow
(()
->
new
ClientRegistrationException
(
"Loading client exception."
));
}
@Override
public
SysClientDetails
findByClientId
(
String
clientId
)
{
return
sysClientDetailsRepository
.
findFirstByClientId
(
clientId
)
.
orElseThrow
(()
->
new
ClientRegistrationException
(
"Loading client exception."
));
}
@Override
public
void
addClientDetails
(
SysClientDetails
clientDetails
)
throws
ClientAlreadyExistsException
{
clientDetails
.
setId
(
null
);
if
(
sysClientDetailsRepository
.
findFirstByClientId
(
clientDetails
.
getClientId
()).
isPresent
())
{
throw
new
ClientAlreadyExistsException
(
String
.
format
(
"Client id %s already exist."
,
clientDetails
.
getClientId
()));
}
sysClientDetailsRepository
.
save
(
clientDetails
);
}
@Override
public
void
updateClientDetails
(
SysClientDetails
clientDetails
)
throws
NoSuchClientException
{
SysClientDetails
exist
=
sysClientDetailsRepository
.
findFirstByClientId
(
clientDetails
.
getClientId
())
.
orElseThrow
(()
->
new
NoSuchClientException
(
"No such client!"
));
clientDetails
.
setClientSecret
(
exist
.
getClientSecret
());
sysClientDetailsRepository
.
save
(
clientDetails
);
}
@Override
public
void
updateClientSecret
(
String
clientId
,
String
clientSecret
)
throws
NoSuchClientException
{
SysClientDetails
exist
=
sysClientDetailsRepository
.
findFirstByClientId
(
clientId
)
.
orElseThrow
(()
->
new
NoSuchClientException
(
"No such client!"
));
exist
.
setClientSecret
(
passwordEncoder
.
encode
(
clientSecret
));
sysClientDetailsRepository
.
save
(
exist
);
}
@Override
public
void
removeClientDetails
(
String
clientId
)
throws
NoSuchClientException
{
sysClientDetailsRepository
.
deleteByClientId
(
clientId
);
}
@Override
public
List
<
SysClientDetails
>
findAll
()
{
return
sysClientDetailsRepository
.
findAll
();
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.service.impl
;
import
com.xkcoding.oauth.entity.SysUser
;
import
com.xkcoding.oauth.repostiory.SysUserRepository
;
import
com.xkcoding.oauth.service.SysUserService
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.security.core.authority.SimpleGrantedAuthority
;
import
org.springframework.security.core.userdetails.User
;
import
org.springframework.security.core.userdetails.UserDetails
;
import
org.springframework.security.core.userdetails.UsernameNotFoundException
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.stereotype.Service
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* 用户相关操作.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午3:06
*/
@Service
@RequiredArgsConstructor
public
class
SysUserServiceImpl
implements
SysUserService
{
private
final
SysUserRepository
sysUserRepository
;
private
final
PasswordEncoder
passwordEncoder
;
@Override
public
UserDetails
loadUserByUsername
(
String
username
)
throws
UsernameNotFoundException
{
SysUser
sysUser
=
sysUserRepository
.
findFirstByUsername
(
username
)
.
orElseThrow
(()
->
new
UsernameNotFoundException
(
"User not found!"
));
List
<
SimpleGrantedAuthority
>
roles
=
sysUser
.
getRoles
().
stream
()
.
map
(
sysRole
->
new
SimpleGrantedAuthority
(
sysRole
.
getName
()))
.
collect
(
Collectors
.
toList
());
// 在这里手动构建 UserDetails 的默认实现
return
new
User
(
sysUser
.
getUsername
(),
sysUser
.
getPassword
(),
roles
);
}
@Override
public
List
<
SysUser
>
findAll
()
{
return
sysUserRepository
.
findAll
();
}
@Override
public
SysUser
findById
(
Long
id
)
{
return
sysUserRepository
.
findById
(
id
)
.
orElseThrow
(()
->
new
RuntimeException
(
"找不到用户"
));
}
@Override
public
void
createUser
(
SysUser
sysUser
)
{
sysUser
.
setId
(
null
);
sysUserRepository
.
save
(
sysUser
);
}
@Override
public
void
updateUser
(
SysUser
sysUser
)
{
sysUser
.
setPassword
(
null
);
sysUserRepository
.
save
(
sysUser
);
}
@Override
public
void
updatePassword
(
Long
id
,
String
password
)
{
SysUser
exist
=
findById
(
id
);
exist
.
setPassword
(
passwordEncoder
.
encode
(
password
));
sysUserRepository
.
save
(
exist
);
}
@Override
public
void
deleteUser
(
Long
id
)
{
sysUserRepository
.
deleteById
(
id
);
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java
0 → 100644
浏览文件 @
dcfeb4f4
/**
* service 层,继承并实现 spring 接口.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/7 上午9:16
*/
package
com.xkcoding.oauth.service
;
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/application.yml
0 → 100644
浏览文件 @
dcfeb4f4
server
:
port
:
8080
spring
:
datasource
:
url
:
jdbc:mysql://localhost:3306/oauth
username
:
root
password
:
123456
hikari
:
data-source-properties
:
useSSL
:
false
serverTimezone
:
GMT+8
useUnicode
:
true
characterEncoding
:
utf8
jpa
:
hibernate
:
ddl-auto
:
update
show-sql
:
true
logging
:
level
:
org.springframework.security
:
debug
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/oauth2.jks
0 → 100644
浏览文件 @
dcfeb4f4
文件已添加
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/public.txt
0 → 100644
浏览文件 @
dcfeb4f4
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkF9SyMHeGAsLMwbPsKj/
xpEtS0iCe8vTSBnIGBDZKmB3ma20Ry0Uzn3m+f40RwCXlxnUcvTw7ipoz0tMQERQ
b3X4DkYCJXPK6pAD+R9/J5odEwrO2eysByWfcbMjsZw2u5pH5hleMS0YqkrGQOxJ
pzlEcKxMePU5KYTbKUJkhOYPY+gQr61g6lF97WggSPtuQn1srT+Ptvfw6yRC4bdI
0zV5emfXjmoLUwaQTRoGYhOFrm97vpoKiltSNIDFW01J1Lr+l77ddDFC6cdiAC0H
5/eENWBBBTFWya8RlBTzHuikfFS1gP49PZ6MYJIVRs8p9YnnKTy7TVcGKY3XZMCA
mwIDAQAB
-----END PUBLIC KEY-----
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/authorization.html
0 → 100644
浏览文件 @
dcfeb4f4
<!DOCTYPE html>
<html
lang=
"zh"
xmlns:th=
"http://www.thymeleaf.org"
>
<head
th:replace=
"common/common::_header"
>
<title>
确认您的授权信息
</title>
</head>
<body>
<div
id=
"app"
>
<v-app>
<v-content>
<v-row
class=
"fill-height align-sm-center"
justify=
"center"
>
<v-col
class=
"pa-0"
>
<v-card
id=
"form-card"
class=
"px-6 pb-7 px-sm-10 pb-sm-9 mx-auto"
outlined
>
<v-form
ref=
"auth"
id=
"auth"
th:action=
"@{/oauth/authorize}"
method=
"post"
>
<v-spacer
class=
"pt-6 pt-sm-12"
></v-spacer>
<v-card-title
class=
"justify-center headline"
>
确认应用的授权信息
</v-card-title>
<div
class=
"text-center"
style=
"height:44px"
>
<v-btn
outlined
rounded
th:text=
"'客户端:' + ${#strings.toUpperCase(clientId)}"
></v-btn>
</div>
<v-spacer></v-spacer>
<v-list
shaped
>
<v-subheader>
当前应用将会获取您的以下权限:
</v-subheader>
<v-list-item-group
color=
"primary"
>
<v-list-item
th:each=
"scope : ${scopes}"
>
<v-list-item-content>
<input
type=
"hidden"
th:name=
"'scope.' + ${scope}"
value=
"true"
>
<v-list-item-title
th:text=
"${#strings.toUpperCase(scope)}"
></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
<input
type=
"hidden"
name=
"_csrf"
th:value=
"${_csrf.token}"
/>
<input
type=
"hidden"
name=
"user_oauth_approval"
value=
"true"
>
<v-card-actions
class=
"mt-6"
>
<v-spacer></v-spacer>
<v-btn
color=
"info"
type=
"submit"
>
确认授权
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-col>
</v-row>
</v-content>
</v-app>
</div>
<div
th:include=
"common/common::_footer"
></div>
<script>
new
Vue
({
el
:
'
#app
'
,
vuetify
:
new
Vuetify
(),
})
</script>
</body>
</html>
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/common/common.html
0 → 100644
浏览文件 @
dcfeb4f4
<!DOCTYPE html>
<html
lang=
"zh"
xmlns:th=
"http://www.thymeleaf.org"
>
<head
th:fragment=
"_header"
>
<!--/*@thymesVar id="title" type="java.lang.String"*/-->
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"
>
<link
href=
"https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
rel=
"stylesheet"
>
<link
href=
"https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css"
rel=
"stylesheet"
>
<link
href=
"https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css"
rel=
"stylesheet"
>
<style>
#form-card
{
width
:
30rem
;
}
@media
screen
and
(
max-width
:
450px
)
{
#form-card
{
width
:
100%
;
height
:
100%
;
padding
:
2rem
!important
;
}
}
.fill-height
{
height
:
100%
;
}
</style>
</head>
<body>
<footer
th:fragment=
"_footer"
>
<script
src=
"https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"
></script>
<script
src=
"https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"
></script>
</footer>
</body>
</html>
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/error.html
0 → 100644
浏览文件 @
dcfeb4f4
<!DOCTYPE html>
<html
lang=
"zh"
xmlns:th=
"http://www.thymeleaf.org"
>
<head
th:replace=
"common/common::_header"
>
<title>
发送了点小错误
</title>
</head>
<body>
<div
id=
"app"
>
<v-app>
<v-content>
<v-responsive
class=
"fill-height"
>
<v-container
class=
"fill-height"
>
<v-layout
align-center
justify-center
>
<v-flex
class=
"text-center"
>
<!--/*@thymesVar id="status" type="java.lang.String"*/-->
<!--/*@thymesVar id="message" type="java.lang.String"*/-->
<h1
class=
"display-1 blue--text"
th:text=
"${status} + ' ' + ${message}"
>
404 找不到页面
</h1>
<p
class=
"mt-2"
>
~~~
</p>
<v-btn
outlined
color=
"info"
@
click=
"handleBack"
>
点击返回
</v-btn>
</v-flex>
</v-layout>
</v-container>
</v-responsive>
</v-content>
</v-app>
</div>
<div
th:include=
"common/common::_footer"
></div>
<script>
new
Vue
({
el
:
'
#app
'
,
vuetify
:
new
Vuetify
(),
data
:
()
=>
({
text
:
'
未知错误
'
,
code
:
500
}),
methods
:
{
handleBack
()
{
window
.
history
.
go
(
-
1
)
}
}
})
</script>
</body>
</html>
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/login.html
0 → 100644
浏览文件 @
dcfeb4f4
<!DOCTYPE html>
<html
lang=
"zh"
xmlns:th=
"http://www.thymeleaf.org"
>
<head
th:replace=
"common/common::_header"
>
<title>
欢迎登录
</title>
</head>
<body>
<div
id=
"app"
>
<v-app>
<v-content>
<v-row
class=
"fill-height align-sm-center"
justify=
"center"
>
<v-col
class=
"pa-0"
>
<v-card
id=
"form-card"
class=
"px-6 pb-7 px-sm-10 pb-sm-9 mx-auto"
outlined
>
<v-form
ref=
"login"
id=
"login"
th:action=
"@{/authorization/form}"
method=
"post"
@
submit.native.prevent
>
<v-spacer
class=
"pt-6 pt-sm-12"
></v-spacer>
<v-card-title
class=
"justify-center headline"
>
欢迎登录
</v-card-title>
<div
class=
"login-user pb-2 text-center"
style=
"height:44px"
>
<v-btn
outlined
rounded
>
{{nameText}}
</v-btn>
</div>
<v-spacer></v-spacer>
<v-card-subtitle
class=
"text-center subtitle-1 pt-0"
>
<p
th:if=
"${param.error == null}"
>
{{infoText}}
</p>
<p
th:unless=
"${param.error == null}"
th:text=
"${param.error}"
style=
"color: red;"
></p>
</v-card-subtitle>
<v-spacer></v-spacer>
<v-card-text>
<v-window
v-model=
"window"
style=
"min-height:180px"
>
<v-window-item
:key=
"0"
>
<v-text-field
label=
"用户名/手机号/邮箱"
name=
"username"
type=
"text"
clearable
:rules=
"usernameRules"
outlined
v-model=
'user.username'
@
keyup.enter=
"next"
ref=
"username"
autofocus
:counter=
"55"
>
</v-text-field>
<input
type=
"hidden"
name=
"_csrf"
th:value=
"${_csrf.token}"
/>
</v-window-item>
<v-window-item
:key=
"1"
>
<v-text-field
label=
"账户密码"
name=
"password"
type=
"password"
required
clearable
outlined
v-model=
'user.password'
@
keyup.enter=
"next"
ref=
"password"
autofocus
>
</v-text-field>
</v-window-item>
</v-window>
</v-card-text>
<v-card-actions>
<v-btn
outlined
color=
"info"
@
click=
"previous"
>
{{previousText}}
</v-btn>
<v-spacer></v-spacer>
<v-btn
color=
"info"
type=
"button"
@
click=
"next"
v-show=
"window === 0"
>
下一步
</v-btn>
<v-btn
color=
"info"
type=
"submit"
v-show=
"window === 1"
>
登录
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-col>
</v-row>
</v-content>
</v-app>
</div>
<div
th:include=
"common/common::_footer"
></div>
<script >
new
Vue
({
el
:
'
#app
'
,
vuetify
:
new
Vuetify
(),
data
:
function
()
{
return
{
window
:
0
,
previousText
:
'
忘记密码
'
,
infoText
:
'
使用您的帐号进行登录
'
,
nameText
:
'
DEMO
'
,
user
:
{
username
:
null
,
password
:
null
},
usernameRules
:[
v
=>
!!
v
||
'
请输入用户名/手机号/邮箱
'
,
v
=>
!!
v
&&
v
.
length
<=
55
||
'
长度不合法
'
],
passwordRules
:[
v
=>
!!
v
||
'
请输入密码
'
]
}
},
watch
:
{
window
:
function
(
val
)
{
if
(
val
===
0
)
{
this
.
infoText
=
'
使用您的帐号进行登录
'
this
.
previousText
=
'
忘记密码
'
this
.
nameText
=
'
DEMO
'
}
else
if
(
val
===
1
)
{
this
.
infoText
=
'
要继续操作,请首先验证登录者是您本人
'
this
.
previousText
=
'
上一步
'
this
.
nameText
=
this
.
user
.
username
}
}
},
created
()
{
this
.
window
=
0
},
methods
:
{
previous
()
{
if
(
this
.
window
===
0
)
{
}
else
{
this
.
window
-=
1
}
},
next
()
{
if
(
this
.
window
===
0
)
this
.
$refs
.
username
.
validate
(
true
)
&&
(
this
.
window
+=
1
)
else
this
.
$refs
.
password
.
validate
(
true
)
&&
document
.
getElementById
(
"
login
"
).
submit
()
}
}
})
</script>
</body>
</html>
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/logout.html
0 → 100644
浏览文件 @
dcfeb4f4
<!DOCTYPE html>
<html
lang=
"zh"
xmlns:th=
"http://www.thymeleaf.org"
>
<head
th:replace=
"common/common::_header"
>
<title>
确认退出吗?
</title>
</head>
<body>
<div
id=
"app"
>
<v-app>
<v-content>
<v-row
class=
"fill-height align-sm-center"
justify=
"center"
>
<v-col
class=
"pa-0"
>
<v-card
id=
"form-card"
class=
"px-6 pb-7 px-sm-10 pb-sm-9 mx-auto"
outlined
>
<v-form
ref=
"auth"
id=
"auth"
th:action=
"@{/oauth/logout}"
method=
"post"
>
<v-spacer
class=
"pt-6 pt-sm-12"
></v-spacer>
<v-card-title
class=
"justify-center headline"
>
确认退出当前应用吗?
</v-card-title>
<div
class=
"text-center"
style=
"height:44px"
>
<v-btn
outlined
rounded
th:text=
"'用户:' + ${#strings.toUpperCase(user)}"
></v-btn>
</div>
<v-spacer></v-spacer>
<input
type=
"hidden"
name=
"_csrf"
th:value=
"${_csrf.token}"
/>
<input
type=
"hidden"
name=
"redirectUrl"
th:value=
"${redirectUrl}"
/>
<v-card-actions
class=
"mt-6"
>
<v-spacer></v-spacer>
<v-btn
color=
"info"
type=
"submit"
>
确认退出
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-col>
</v-row>
</v-content>
</v-app>
</div>
<div
th:include=
"common/common::_footer"
></div>
<script>
new
Vue
({
el
:
'
#app
'
,
vuetify
:
new
Vuetify
(),
})
</script>
</body>
</html>
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/registerTemplate.html
0 → 100644
浏览文件 @
dcfeb4f4
<!DOCTYPE html>
<html
lang=
"en"
xmlns:th=
"http://www.thymeleaf.org"
>
<head>
<meta
http-equiv=
"Content-Type"
content=
"text/html; charset=utf-8"
/>
<title></title>
<meta
charset=
"utf-8"
/>
<style
type=
"text/css"
>
.lesson
body
{
margin
:
0
;
padding
:
0
;
background
:
#fff
;
font-family
:
Verdana
,
Arial
,
Helvetica
,
sans-serif
,
serif
;
font-size
:
14px
;
line-height
:
24px
;
}
.lesson
div
,
.lesson
p
,
.lesson
span
,
.lesson
img
{
margin
:
0
;
padding
:
0
;
}
.lesson
img
{
border
:
none
;
}
.lesson
.container
{
margin
:
0
auto
;
}
.lesson
.title
{
margin
:
0
auto
;
background
:
#efefef
repeat-x
;
height
:
30px
;
text-align
:
center
;
font-weight
:
bold
;
padding-top
:
12px
;
font-size
:
16px
;
color
:
#2d2d2d
;
}
.lesson
.content
{
margin
:
4px
;
}
.lesson
.headline
{
padding
:
6px
;
color
:
#2d2d2d
;
}
.lesson
.top
,
.lesson
.bottom
{
display
:
block
;
font-size
:
1px
;
}
.lesson
.xb1
,
.lesson
.xb2
,
.lesson
.xb3
,
.lesson
.xb4
{
display
:
block
;
overflow
:
hidden
;
}
.lesson
.xb1
,
.lesson
.xb2
,
.lesson
.xb3
{
height
:
1px
;
}
.lesson
.xb2
,
.lesson
.xb3
,
.lesson
.xb4
{
border-left
:
1px
solid
#BCBCBC
;
border-right
:
1px
solid
#BCBCBC
;
}
.lesson
.xb1
{
margin
:
0
5px
;
background
:
#BCBCBC
;
}
.lesson
.xb2
{
margin
:
0
3px
;
border-width
:
0
2px
;
}
.lesson
.xb3
{
margin
:
0
2px
;
}
.lesson
.xb4
{
height
:
2px
;
margin
:
0
1px
;
}
.lesson
.lesson-content
{
display
:
block
;
}
.lesson
.line
{
margin-top
:
6px
;
border-top
:
1px
dashed
#B9B9B9
;
padding
:
4px
;
}
.lesson
.content
{
padding
:
6px
;
color
:
#666666
;
}
.lesson
.foot
{
padding
:
6px
;
color
:
#777
;
}
.lesson
.font-darkblue
{
color
:
#006699
;
font-weight
:
bold
;
}
.lesson
.font-lightblue
{
color
:
#008BD1
;
font-weight
:
bold
;
}
.lesson
.font-gray
{
color
:
#888
;
font-size
:
12px
;
}
</style>
</head>
<body>
<div
class=
"lesson"
>
<div
class=
"container"
>
<div
class=
"title"
>
云课程考试平台
</div>
<div
class=
"content"
>
<p
class=
"headline"
><b>
亲爱的用户,你好!
</b></p>
<b
class=
"top"
><b
class=
"xb1"
></b><b
class=
"xb2"
></b><b
class=
"xb3"
></b><b
class=
"xb4"
></b></b>
<div
class=
"lesson-content"
>
<div
class=
"content"
>
<p>
<!--/*@thymesVar id="type" type="java.lang.String"*/-->
<b
th:text=
"'欢迎您' + ${type}"
>
欢迎您注册
</b><span
id=
"userName"
class=
"font-darkblue"
>
云课程考试平台
</span>
</p>
<p><b>
<!--/*@thymesVar id="type" type="java.lang.String"*/-->
<b
th:text=
"${type}"
>
你的邮件
</b>
的验证码:
</b><span
class=
"font-lightblue"
><span
style=
"border-bottom: 1px dashed rgb(204, 204, 204); z-index: 1; position: static;"
>
<!--/*@thymesVar id="code" type="java.lang.String"*/-->
<b
th:text=
"${code}"
>
验证码
</b></span></span><br><span
class=
"font-gray"
>
(请输入该验证码完成
<span></span>
验证,验证码
<!--/*@thymesVar id="time" type="java.lang.Integer"*/-->
<b
th:text=
"${time}"
>
10
</b>
分钟内有效!)
</span></p>
<div
class=
"line"
>
如果您未申请云课程学习平台
<!--/*@thymesVar id="type" type="java.lang.String"*/-->
<span
th:text=
"${type}"
>
$(type)
</span>
服务,请忽略该邮件。
</div>
</div>
</div>
<b
class=
"bottom"
><b
class=
"xb4"
></b><b
class=
"xb3"
></b><b
class=
"xb2"
></b><b
class=
"xb1"
></b></b>
<p
class=
"foot"
>
如果仍有问题,请联系我们的管理员:
<span
style=
"border-bottom: 1px dashed rgb(204, 204, 204); z-index: 1; position: static;"
>
000-00000000
</span></p>
</div>
</div>
</div>
</body>
</html>
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
/**
* .
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午3:51
*/
public
class
PasswordEncodeTest
{
private
PasswordEncoder
passwordEncoder
=
new
BCryptPasswordEncoder
();
@Test
public
void
getPasswordWhenPassed
()
{
System
.
out
.
println
(
passwordEncoder
.
encode
(
"oauth2"
));
System
.
out
.
println
(
passwordEncoder
.
encode
(
"123456"
));
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.oauth
;
import
org.junit.jupiter.api.BeforeEach
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.MediaType
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.security.oauth2.client.OAuth2RestTemplate
;
import
org.springframework.security.oauth2.client.resource.UserRedirectRequiredException
;
import
org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest
;
import
org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider
;
import
org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails
;
import
org.springframework.util.LinkedMultiValueMap
;
import
org.springframework.util.MultiValueMap
;
import
java.net.URI
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
static
com
.
xkcoding
.
oauth
.
oauth
.
AuthorizationServerInfo
.
getUrl
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.*;
/**
* 授权码模式测试.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午8:43
*/
public
class
AuthorizationCodeGrantTests
{
private
AuthorizationCodeResourceDetails
resource
=
new
AuthorizationCodeResourceDetails
();
private
AuthorizationServerInfo
authorizationServerInfo
=
new
AuthorizationServerInfo
();
@BeforeEach
void
setUp
()
{
resource
.
setAccessTokenUri
(
getUrl
(
"/oauth/token"
));
resource
.
setClientId
(
"oauth2"
);
resource
.
setId
(
"oauth2"
);
resource
.
setScope
(
Arrays
.
asList
(
"READ"
,
"WRITE"
));
resource
.
setAccessTokenUri
(
getUrl
(
"/oauth/token"
));
resource
.
setUserAuthorizationUri
(
getUrl
(
"/oauth/authorize"
));
}
@Test
void
testCannotConnectWithoutToken
()
{
OAuth2RestTemplate
template
=
new
OAuth2RestTemplate
(
resource
);
assertThrows
(
UserRedirectRequiredException
.
class
,
()
->
template
.
getForObject
(
getUrl
(
"/oauth/me"
),
String
.
class
));
}
@Test
void
testAttemptedTokenAcquisitionWithNoRedirect
()
{
AuthorizationCodeAccessTokenProvider
provider
=
new
AuthorizationCodeAccessTokenProvider
();
assertThrows
(
UserRedirectRequiredException
.
class
,
()
->
provider
.
obtainAccessToken
(
resource
,
new
DefaultAccessTokenRequest
()));
}
/**
* 这里不使用他提供的是因为很多地方不符合我们的需要
* 比如 csrf,比如许多有些是自己自定义的端点这些
* 所以只有我们一步一步的来进行测试拿到授权码
*/
@Test
void
testCodeAcquisitionWithCorrectContext
()
{
// 1. 请求登录页面获取 _csrf 的 value 以及 cookie
ResponseEntity
<
String
>
page
=
authorizationServerInfo
.
getForString
(
"/oauth/login"
);
assertNotNull
(
page
.
getBody
());
String
cookie
=
page
.
getHeaders
().
getFirst
(
"Set-Cookie"
);
HttpHeaders
headers
=
new
HttpHeaders
();
headers
.
set
(
"Cookie"
,
cookie
);
Matcher
matcher
=
Pattern
.
compile
(
"(?s).*name=\"_csrf\".*?value=\"([^\"]+).*"
).
matcher
(
page
.
getBody
());
assertTrue
(
matcher
.
find
());
// 2. 添加表单数据
MultiValueMap
<
String
,
String
>
form
=
new
LinkedMultiValueMap
<>();
form
.
add
(
"username"
,
"admin"
);
form
.
add
(
"password"
,
"123456"
);
form
.
add
(
"_csrf"
,
matcher
.
group
(
1
));
// 3. 登录授权并获取登录成功的 cookie
ResponseEntity
<
Void
>
response
=
authorizationServerInfo
.
postForStatus
(
"/authorization/form"
,
headers
,
form
);
assertNotNull
(
response
);
cookie
=
response
.
getHeaders
().
getFirst
(
"Set-Cookie"
);
headers
=
new
HttpHeaders
();
headers
.
set
(
"Cookie"
,
cookie
);
headers
.
setAccept
(
Collections
.
singletonList
(
MediaType
.
ALL
));
// 4. 请求到 确认授权页面 ,获取确认授权页面的 _csrf 的 value
ResponseEntity
<
String
>
confirm
=
authorizationServerInfo
.
getForString
(
"/oauth/authorize?response_type=code&client_id=oauth2&redirect_uri=http://example.com&scope=READ"
,
headers
);
headers
=
confirm
.
getHeaders
();
// 确认过一次后,后面都会自动确认了,这里判断下是不是重定向请求
// 如果不是,就表示是第一次,需要确认授权
if
(!
confirm
.
getStatusCode
().
is3xxRedirection
())
{
assertNotNull
(
confirm
.
getBody
());
Matcher
matcherConfirm
=
Pattern
.
compile
(
"(?s).*name=\"_csrf\".*?value=\"([^\"]+).*"
).
matcher
(
confirm
.
getBody
());
assertTrue
(
matcherConfirm
.
find
());
headers
=
new
HttpHeaders
();
headers
.
set
(
"Cookie"
,
cookie
);
headers
.
setAccept
(
Collections
.
singletonList
(
MediaType
.
ALL
));
// 5. 构建 同意授权 的表单
form
=
new
LinkedMultiValueMap
<>();
form
.
add
(
"user_oauth_approval"
,
"true"
);
form
.
add
(
"scope.READ"
,
"true"
);
form
.
add
(
"_csrf"
,
matcherConfirm
.
group
(
1
));
// 6. 请求授权,获取 授权码
headers
=
authorizationServerInfo
.
postForHeaders
(
"/oauth/authorize"
,
form
,
headers
);
}
URI
location
=
headers
.
getLocation
();
assertNotNull
(
location
);
String
query
=
location
.
getQuery
();
assertNotNull
(
query
);
String
[]
result
=
query
.
split
(
"="
);
assertEquals
(
2
,
result
.
length
);
System
.
out
.
println
(
result
[
1
]);
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.oauth
;
import
org.springframework.http.*
;
import
org.springframework.http.client.ClientHttpRequest
;
import
org.springframework.http.client.ClientHttpResponse
;
import
org.springframework.http.client.SimpleClientHttpRequestFactory
;
import
org.springframework.util.MultiValueMap
;
import
org.springframework.web.client.RequestCallback
;
import
org.springframework.web.client.ResponseErrorHandler
;
import
org.springframework.web.client.RestTemplate
;
import
java.io.IOException
;
import
java.net.HttpURLConnection
;
/**
* 授权服务器工具类.
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午8:44
*/
@SuppressWarnings
(
"all"
)
public
class
AuthorizationServerInfo
{
public
static
final
String
HOST
=
"http://127.0.0.1:8080"
;
private
RestTemplate
client
;
public
AuthorizationServerInfo
()
{
client
=
new
RestTemplate
();
client
.
setRequestFactory
(
new
SimpleClientHttpRequestFactory
()
{
@Override
protected
void
prepareConnection
(
HttpURLConnection
connection
,
String
httpMethod
)
throws
IOException
{
super
.
prepareConnection
(
connection
,
httpMethod
);
connection
.
setInstanceFollowRedirects
(
false
);
}
});
client
.
setErrorHandler
(
new
ResponseErrorHandler
()
{
public
boolean
hasError
(
ClientHttpResponse
response
)
{
return
false
;
}
public
void
handleError
(
ClientHttpResponse
response
)
{
}
});
}
public
ResponseEntity
<
String
>
getForString
(
String
path
,
final
HttpHeaders
headers
)
{
return
client
.
exchange
(
getUrl
(
path
),
HttpMethod
.
GET
,
new
HttpEntity
<>(
null
,
headers
),
String
.
class
);
}
public
ResponseEntity
<
String
>
getForString
(
String
path
)
{
return
getForString
(
path
,
new
HttpHeaders
());
}
public
ResponseEntity
<
Void
>
postForStatus
(
String
path
,
HttpHeaders
headers
,
MultiValueMap
<
String
,
String
>
formData
)
{
HttpHeaders
actualHeaders
=
new
HttpHeaders
();
actualHeaders
.
putAll
(
headers
);
actualHeaders
.
setContentType
(
MediaType
.
APPLICATION_FORM_URLENCODED
);
return
client
.
exchange
(
getUrl
(
path
),
HttpMethod
.
POST
,
new
HttpEntity
<>(
formData
,
actualHeaders
),
(
Class
<
Void
>)
null
);
}
public
static
String
getUrl
(
String
path
)
{
return
HOST
+
path
;
}
public
HttpHeaders
postForHeaders
(
String
path
,
MultiValueMap
<
String
,
String
>
formData
,
final
HttpHeaders
headers
)
{
RequestCallback
requestCallback
=
new
NullRequestCallback
();
if
(
headers
!=
null
)
{
requestCallback
=
request
->
request
.
getHeaders
().
putAll
(
headers
);
}
StringBuilder
builder
=
new
StringBuilder
(
getUrl
(
path
));
if
(!
path
.
contains
(
"?"
))
{
builder
.
append
(
"?"
);
}
else
{
builder
.
append
(
"&"
);
}
for
(
String
key
:
formData
.
keySet
())
{
for
(
String
value
:
formData
.
get
(
key
))
{
builder
.
append
(
key
).
append
(
"="
).
append
(
value
);
builder
.
append
(
"&"
);
}
}
builder
.
deleteCharAt
(
builder
.
length
()
-
1
);
return
client
.
execute
(
builder
.
toString
(),
HttpMethod
.
POST
,
requestCallback
,
HttpMessage:
:
getHeaders
);
}
private
static
final
class
NullRequestCallback
implements
RequestCallback
{
public
void
doWithRequest
(
ClientHttpRequest
request
)
{
}
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.oauth
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.security.oauth2.client.OAuth2RestTemplate
;
import
org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails
;
import
org.springframework.security.oauth2.common.OAuth2AccessToken
;
import
java.util.Arrays
;
import
static
com
.
xkcoding
.
oauth
.
oauth
.
AuthorizationServerInfo
.
getUrl
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.*;
/**
* .
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午9:14
*/
public
class
ResourceOwnerPasswordGrantTests
{
@Test
void
testConnectDirectlyToResourceServer
()
{
assertNotNull
(
accessToken
());
}
public
static
String
accessToken
()
{
ResourceOwnerPasswordResourceDetails
resource
=
new
ResourceOwnerPasswordResourceDetails
();
resource
.
setAccessTokenUri
(
getUrl
(
"/oauth/token"
));
resource
.
setClientId
(
"oauth2"
);
resource
.
setClientSecret
(
"oauth2"
);
resource
.
setId
(
"oauth2"
);
resource
.
setScope
(
Arrays
.
asList
(
"READ"
,
"WRITE"
));
resource
.
setUsername
(
"admin"
);
resource
.
setPassword
(
"123456"
);
OAuth2RestTemplate
template
=
new
OAuth2RestTemplate
(
resource
);
OAuth2AccessToken
accessToken
=
template
.
getAccessToken
();
return
accessToken
.
getValue
();
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.repostiory
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.
assertNotNull
;
/**
* .
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:10
*/
@DataJpaTest
public
class
SysClientDetailsTest
{
@Autowired
private
SysClientDetailsRepository
sysClientDetailsRepository
;
@Test
public
void
autowiredSuccessWhenPassed
()
{
assertNotNull
(
sysClientDetailsRepository
);
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java
0 → 100644
浏览文件 @
dcfeb4f4
package
com.xkcoding.oauth.repostiory
;
import
com.xkcoding.oauth.entity.SysUser
;
import
org.junit.jupiter.api.DisplayName
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
;
import
java.util.Optional
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.*;
/**
* .
*
* @author <a href="https://echocow.cn">EchoCow</a>
* @date 2020/1/6 下午1:25
*/
@DataJpaTest
public
class
SysUserRepositoryTest
{
@Autowired
private
SysUserRepository
sysUserRepository
;
@Test
public
void
autowiredSuccessWhenPassed
()
{
assertNotNull
(
sysUserRepository
);
}
@Test
@DisplayName
(
"测试关联查询"
)
public
void
queryUserAndRoleWhenPassed
()
{
Optional
<
SysUser
>
admin
=
sysUserRepository
.
findFirstByUsername
(
"admin"
);
assertTrue
(
admin
.
isPresent
());
SysUser
sysUser
=
admin
.
orElseGet
(
SysUser:
:
new
);
assertNotNull
(
sysUser
.
getRoles
());
assertEquals
(
1
,
sysUser
.
getRoles
().
size
());
}
}
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/application.yml
0 → 100644
浏览文件 @
dcfeb4f4
server
:
port
:
8080
servlet
:
context-path
:
/demo
spring
:
datasource
:
url
:
jdbc:h2:mem:oauth2?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username
:
root
password
:
123456
jpa
:
hibernate
:
ddl-auto
:
create-drop
show-sql
:
true
properties
:
hibernate
:
format_sql
:
true
logging
:
level
:
org.springframework.security
:
debug
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/import.sql
0 → 100644
浏览文件 @
dcfeb4f4
-- 测试数据
INSERT
INTO
sys_client_details
(
id
,
access_token_validity_seconds
,
authorizations
,
auto_approve_scopes
,
client_id
,
client_secret
,
grant_types
,
redirect_url
,
refresh_token_validity_seconds
,
resource_ids
,
scopes
)
VALUES
(
1
,
6000
,
null
,
null
,
'oauth2'
,
'$2a$10$O8uM8kd5SbsuoITG3tBifOcarqqI8GP19vzbqDzVHP5ZV9yOfvpYS'
,
'authorization_code,password'
,
'http://example.com'
,
6000
,
'oauth2'
,
'READ,WRITE'
);
INSERT
INTO
sys_client_details
(
id
,
access_token_validity_seconds
,
authorizations
,
auto_approve_scopes
,
client_id
,
client_secret
,
grant_types
,
redirect_url
,
refresh_token_validity_seconds
,
resource_ids
,
scopes
)
VALUES
(
2
,
6000
,
null
,
null
,
'test'
,
'$2a$10$O8uM8kd5SbsuoITG3tBifOcarqqI8GP19vzbqDzVHP5ZV9yOfvpYS'
,
'authorization_code,password'
,
'http://example.com'
,
6000
,
'test'
,
'READ'
);
INSERT
INTO
sys_client_details
(
id
,
access_token_validity_seconds
,
authorizations
,
auto_approve_scopes
,
client_id
,
client_secret
,
grant_types
,
redirect_url
,
refresh_token_validity_seconds
,
resource_ids
,
scopes
)
VALUES
(
3
,
6000
,
null
,
null
,
'test'
,
'$2a$10$O8uM8kd5SbsuoITG3tBifOcarqqI8GP19vzbqDzVHP5ZV9yOfvpYS'
,
'authorization_code,password'
,
'http://example.com'
,
6000
,
'error'
,
'READ'
);
INSERT
INTO
sys_role
(
id
,
name
,
description
)
VALUES
(
1
,
'ROLE_ADMIN'
,
'管理员'
);
INSERT
INTO
sys_role
(
id
,
name
,
description
)
VALUES
(
2
,
'ROLE_TEST'
,
'测试'
);
INSERT
INTO
sys_user
(
id
,
username
,
password
)
VALUES
(
1
,
'admin'
,
'$2a$10$xLH.pDNz3d2frOBQ6Gc.wuHY4ghwlSyFDgy0Ta.psXmm1YJjNaV1G'
);
INSERT
INTO
sys_user
(
id
,
username
,
password
)
VALUES
(
2
,
'test'
,
'$2a$10$xLH.pDNz3d2frOBQ6Gc.wuHY4ghwlSyFDgy0Ta.psXmm1YJjNaV1G'
);
INSERT
INTO
sys_user_role
(
user_id
,
role_id
)
VALUES
(
1
,
1
);
INSERT
INTO
sys_user_role
(
user_id
,
role_id
)
VALUES
(
2
,
2
);
spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/schema.sql
0 → 100644
浏览文件 @
dcfeb4f4
create
table
sys_client_details
(
id
bigint
auto_increment
primary
key
,
access_token_validity_seconds
int
null
,
authorizations
varchar
(
255
)
null
,
auto_approve_scopes
varchar
(
255
)
null
,
client_id
varchar
(
255
)
null
,
client_secret
varchar
(
255
)
null
,
grant_types
varchar
(
255
)
null
,
redirect_url
varchar
(
255
)
null
,
refresh_token_validity_seconds
int
null
,
resource_ids
varchar
(
255
)
null
,
scopes
varchar
(
255
)
null
);
create
table
sys_role
(
id
bigint
auto_increment
primary
key
,
name
varchar
(
55
)
not
null
,
description
varchar
(
55
)
null
);
create
table
sys_user
(
id
bigint
auto_increment
primary
key
,
username
varchar
(
55
)
not
null
,
password
varchar
(
128
)
not
null
);
create
table
sys_user_role
(
id
bigint
auto_increment
primary
key
,
user_id
bigint
not
null
,
role_id
bigint
not
null
,
constraint
sys_user_role_sys_role_id_fk
foreign
key
(
role_id
)
references
sys_role
(
id
),
constraint
sys_user_role_sys_user_id_fk
foreign
key
(
user_id
)
references
sys_user
(
id
)
);
spring-boot-demo-oauth/src/main/resources/application.yml
已删除
100644 → 0
浏览文件 @
0378ad01
server
:
port
:
8080
servlet
:
context-path
:
/demo
\ No newline at end of file
spring-boot-demo-oauth/src/test/java/com/xkcoding/oauth/SpringBootDemoOauthApplicationTests.java
已删除
100644 → 0
浏览文件 @
0378ad01
package
com.xkcoding.oauth
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.springframework.boot.test.context.SpringBootTest
;
import
org.springframework.test.context.junit4.SpringRunner
;
@RunWith
(
SpringRunner
.
class
)
@SpringBootTest
public
class
SpringBootDemoOauthApplicationTests
{
@Test
public
void
contextLoads
()
{
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录