├── demo-frontend
├── src
│ ├── assets
│ │ └── .gitkeep
│ ├── app
│ │ ├── pages
│ │ │ ├── index
│ │ │ │ ├── index.component.scss
│ │ │ │ ├── index.component.html
│ │ │ │ ├── index.component.ts
│ │ │ │ └── index.component.spec.ts
│ │ │ ├── show-user-list
│ │ │ │ ├── show-user-list.component.scss
│ │ │ │ ├── show-user-list.component.html
│ │ │ │ ├── show-user-list.component.spec.ts
│ │ │ │ └── show-user-list.component.ts
│ │ │ ├── show-user-detail
│ │ │ │ ├── show-user-detail.component.scss
│ │ │ │ ├── show-user-detail.component.spec.ts
│ │ │ │ ├── show-user-detail.component.ts
│ │ │ │ └── show-user-detail.component.html
│ │ │ ├── show-personal-role
│ │ │ │ ├── show-personal-role.component.scss
│ │ │ │ ├── show-personal-role.component.html
│ │ │ │ ├── show-personal-role.component.ts
│ │ │ │ └── show-personal-role.component.spec.ts
│ │ │ ├── show-personal-detail
│ │ │ │ ├── show-personal-detail.component.scss
│ │ │ │ ├── show-personal-detail.component.ts
│ │ │ │ ├── show-personal-detail.component.spec.ts
│ │ │ │ └── show-personal-detail.component.html
│ │ │ ├── update-personal-detail
│ │ │ │ ├── update-personal-detail.component.scss
│ │ │ │ ├── update-personal-detail.component.spec.ts
│ │ │ │ ├── update-personal-detail.component.ts
│ │ │ │ └── update-personal-detail.component.html
│ │ │ ├── register
│ │ │ │ ├── register.component.scss
│ │ │ │ ├── register.component.spec.ts
│ │ │ │ ├── register.component.html
│ │ │ │ └── register.component.ts
│ │ │ ├── login
│ │ │ │ ├── login.component.scss
│ │ │ │ ├── login.component.spec.ts
│ │ │ │ ├── login.component.html
│ │ │ │ └── login.component.ts
│ │ │ └── reset-user-password
│ │ │ │ ├── reset-user-password.component.scss
│ │ │ │ ├── reset-user-password.component.spec.ts
│ │ │ │ ├── reset-user-password.component.html
│ │ │ │ └── reset-user-password.component.ts
│ │ ├── shared
│ │ │ ├── pipe
│ │ │ │ ├── gender.pipe.spec.ts
│ │ │ │ └── gender.pipe.ts
│ │ │ ├── guard
│ │ │ │ ├── auth.guard.spec.ts
│ │ │ │ ├── login.guard.spec.ts
│ │ │ │ ├── auth.guard.ts
│ │ │ │ └── login.guard.ts
│ │ │ ├── common
│ │ │ │ ├── common.service.spec.ts
│ │ │ │ ├── auth.interceptor.spec.ts
│ │ │ │ ├── auth.interceptor.ts
│ │ │ │ └── common.service.ts
│ │ │ ├── icons-provider.module.ts
│ │ │ └── shared.module.ts
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.component.scss
│ │ ├── app.component.html
│ │ └── app.module.ts
│ ├── favicon.ico
│ ├── styles.scss
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── theme.less
│ ├── index.html
│ ├── main.ts
│ ├── test.ts
│ └── polyfills.ts
├── proxy.json
├── .editorconfig
├── tsconfig.app.json
├── tsconfig.spec.json
├── .browserslistrc
├── .gitignore
├── tsconfig.json
├── README.md
├── package.json
├── karma.conf.js
└── angular.json
├── docs
└── img
│ ├── register.png
│ ├── userlist.png
│ ├── 文件目录结构0.png
│ ├── 文件目录结构1.png
│ ├── 文件目录结构2.png
│ ├── 文件目录结构3.png
│ ├── userdetail.png
│ └── changepassword.png
├── authorization-server
├── src
│ ├── main
│ │ ├── resources
│ │ │ ├── application.yml
│ │ │ ├── application-default.yml
│ │ │ ├── mapper
│ │ │ │ ├── RoleMapper.xml
│ │ │ │ ├── ResourceMapper.xml
│ │ │ │ ├── ClientMapper.xml
│ │ │ │ └── UserMapper.xml
│ │ │ └── generator
│ │ │ │ └── generatorConfig.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ ├── generator
│ │ │ ├── mapper
│ │ │ │ ├── RoleMapper.java
│ │ │ │ ├── ClientMapper.java
│ │ │ │ ├── ResourceMapper.java
│ │ │ │ └── UserMapper.java
│ │ │ └── model
│ │ │ │ ├── Role.java
│ │ │ │ ├── Resource.java
│ │ │ │ ├── User.java
│ │ │ │ └── Client.java
│ │ │ ├── config
│ │ │ ├── MyBatisConfig.java
│ │ │ ├── ValidatorConfig.java
│ │ │ └── security
│ │ │ │ ├── CustomLogoutHandler.java
│ │ │ │ ├── WebSecurityConfig.java
│ │ │ │ └── AuthConfig.java
│ │ │ ├── user
│ │ │ ├── bean
│ │ │ │ ├── RoleVO.java
│ │ │ │ ├── UserChangePasswordVO.java
│ │ │ │ ├── UserRegisterVO.java
│ │ │ │ └── UserVO.java
│ │ │ ├── UserController.java
│ │ │ ├── valid
│ │ │ │ ├── ValidUsername.java
│ │ │ │ └── ValidToken.java
│ │ │ └── UserService.java
│ │ │ ├── AuthorizationServiceApplication.java
│ │ │ └── client
│ │ │ ├── ClientService.java
│ │ │ └── ClientVO.java
│ └── test
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ ├── AuthorizationServiceApplicationTests.java
│ │ │ ├── user
│ │ │ ├── UserControllerTest.java
│ │ │ ├── UserServiceTest.java
│ │ │ └── UserMapperTest.java
│ │ │ ├── utils
│ │ │ └── TestRequestUtils.java
│ │ │ └── auth
│ │ │ └── AuthTest.java
│ │ └── resources
│ │ ├── application.yaml
│ │ └── sql
│ │ ├── unit-test-data.sql
│ │ └── unit-test-schema.sql
└── pom.xml
├── resource-server
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ ├── generator
│ │ │ │ ├── mapper
│ │ │ │ │ ├── UserDetailMapper.java
│ │ │ │ │ └── UserMapper.java
│ │ │ │ └── model
│ │ │ │ │ ├── User.java
│ │ │ │ │ └── UserDetail.java
│ │ │ │ ├── user
│ │ │ │ ├── vo
│ │ │ │ │ ├── UserVO.java
│ │ │ │ │ ├── ResetUserPasswordVO.java
│ │ │ │ │ └── UserDetailVO.java
│ │ │ │ ├── UserService.java
│ │ │ │ └── UserController.java
│ │ │ │ ├── config
│ │ │ │ ├── MyBatisConfig.java
│ │ │ │ ├── ResourceConfig.java
│ │ │ │ ├── WebSecurityConfig.java
│ │ │ │ └── ValidatorConfig.java
│ │ │ │ └── ResourceServiceApplication.java
│ │ └── resources
│ │ │ ├── application-default.yml
│ │ │ ├── application.yml
│ │ │ ├── mapper
│ │ │ ├── UserDetailMapper.xml
│ │ │ └── UserMapper.xml
│ │ │ └── generator
│ │ │ └── generatorConfig.xml
│ └── test
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ ├── ResourceServiceApplicationTests.java
│ │ │ ├── config
│ │ │ └── TestResourceConfig.java
│ │ │ └── user
│ │ │ ├── UserMapperTest.java
│ │ │ ├── UserServiceTest.java
│ │ │ ├── UserProcessByMySQLModeTest.java
│ │ │ ├── UserControllerTest.java
│ │ │ └── UserProcessTest.java
│ │ └── resources
│ │ ├── sql
│ │ ├── unit-test-data.sql
│ │ └── unit-test-schema.sql
│ │ └── application.yaml
└── pom.xml
├── .gitignore
├── pom.xml
├── LICENSE
├── README.md
└── sql
├── initial-data.sql
└── schema.sql
/demo-frontend/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/index/index.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-list/show-user-list.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-detail/show-user-detail.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-role/show-personal-role.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-detail/show-personal-detail.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/update-personal-detail/update-personal-detail.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/img/register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/register.png
--------------------------------------------------------------------------------
/docs/img/userlist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/userlist.png
--------------------------------------------------------------------------------
/docs/img/文件目录结构0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/文件目录结构0.png
--------------------------------------------------------------------------------
/docs/img/文件目录结构1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/文件目录结构1.png
--------------------------------------------------------------------------------
/docs/img/文件目录结构2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/文件目录结构2.png
--------------------------------------------------------------------------------
/docs/img/文件目录结构3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/文件目录结构3.png
--------------------------------------------------------------------------------
/docs/img/userdetail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/userdetail.png
--------------------------------------------------------------------------------
/docs/img/changepassword.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/docs/img/changepassword.png
--------------------------------------------------------------------------------
/demo-frontend/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq253498229/spring-boot-oauth2-example/HEAD/demo-frontend/src/favicon.ico
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-role/show-personal-role.component.html:
--------------------------------------------------------------------------------
1 |
您的角色:
2 | - {{item}}
3 |
--------------------------------------------------------------------------------
/demo-frontend/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | .right {
4 | float: right;
5 | }
6 |
--------------------------------------------------------------------------------
/demo-frontend/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | oauthUrl: 'http://localhost:8080',
4 | api: 'http://localhost:8081'
5 | };
6 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/index/index.component.html:
--------------------------------------------------------------------------------
1 | 这个是主页
2 | 这个页面的内容是公开的,谁都能看到
3 | 要想看到私有页面请先登录
4 | 登录账号:
5 | 普通用户-用户名密码都是: user
6 | 管理员用户-用户名密码都是: admin
7 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/register/register.component.scss:
--------------------------------------------------------------------------------
1 | [nz-form] {
2 | max-width: 600px;
3 | }
4 |
5 | .phone-select {
6 | width: 70px;
7 | }
8 |
9 | .register-are {
10 | margin-bottom: 8px;
11 | }
12 |
--------------------------------------------------------------------------------
/authorization-server/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | mybatis:
2 | mapper-locations: classpath*:mapper/*.xml
3 | spring:
4 | main:
5 | allow-circular-references: true
6 | server:
7 | error:
8 | include-binding-errors: always
9 | include-message: always
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/pipe/gender.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import {GenderPipe} from './gender.pipe';
2 |
3 | describe('GenderPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new GenderPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/mapper/RoleMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.mapper;
2 |
3 | import com.example.generator.model.Role;
4 | import tk.mybatis.mapper.common.Mapper;
5 |
6 | public interface RoleMapper extends Mapper {
7 | }
--------------------------------------------------------------------------------
/demo-frontend/proxy.json:
--------------------------------------------------------------------------------
1 | {
2 | "/user": {
3 | "target": "http://localhost:8081",
4 | "secure": false,
5 | "ws": true
6 | },
7 | "/oauth": {
8 | "target": "http://localhost:8080",
9 | "secure": false,
10 | "ws": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/mapper/ClientMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.mapper;
2 |
3 | import com.example.generator.model.Client;
4 | import tk.mybatis.mapper.common.Mapper;
5 |
6 | public interface ClientMapper extends Mapper {
7 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/mapper/ResourceMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.mapper;
2 |
3 | import com.example.generator.model.Resource;
4 | import tk.mybatis.mapper.common.Mapper;
5 |
6 | public interface ResourceMapper extends Mapper {
7 | }
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/login/login.component.scss:
--------------------------------------------------------------------------------
1 | .login-form {
2 | max-width: 300px;
3 | }
4 |
5 | .login-form-margin {
6 | margin-bottom: 16px;
7 | }
8 |
9 | .login-form-forgot {
10 | float: right;
11 | }
12 |
13 | .login-form-button {
14 | width: 100%;
15 | }
16 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/generator/mapper/UserDetailMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.mapper;
2 |
3 | import com.example.generator.model.UserDetail;
4 | import tk.mybatis.mapper.common.Mapper;
5 |
6 | public interface UserDetailMapper extends Mapper {
7 | }
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/reset-user-password/reset-user-password.component.scss:
--------------------------------------------------------------------------------
1 | .login-form {
2 | max-width: 300px;
3 | }
4 |
5 | .login-form-margin {
6 | margin-bottom: 16px;
7 | }
8 |
9 | .login-form-forgot {
10 | float: right;
11 | }
12 |
13 | .login-form-button {
14 | width: 100%;
15 | }
16 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/user/vo/UserVO.java:
--------------------------------------------------------------------------------
1 | package com.example.user.vo;
2 |
3 | import com.example.generator.model.UserDetail;
4 | import lombok.Data;
5 | import lombok.EqualsAndHashCode;
6 |
7 | @EqualsAndHashCode(callSuper = true)
8 | @Data
9 | public class UserVO extends UserDetail {
10 | private String username;
11 | }
12 |
--------------------------------------------------------------------------------
/resource-server/src/test/java/com/example/ResourceServiceApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class ResourceServiceApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/authorization-server/src/test/java/com/example/AuthorizationServiceApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class AuthorizationServiceApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/demo-frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/demo-frontend/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/config/MyBatisConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import tk.mybatis.spring.annotation.MapperScan;
5 |
6 | /**
7 | * MyBatis相关配置
8 | */
9 | @Configuration
10 | @MapperScan(basePackages = "com.example.generator.mapper")
11 | public class MyBatisConfig {
12 | }
13 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/config/MyBatisConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import tk.mybatis.spring.annotation.MapperScan;
5 |
6 | /**
7 | * MyBatis相关配置
8 | */
9 | @Configuration
10 | @MapperScan(basePackages = "com.example.generator.mapper")
11 | public class MyBatisConfig {
12 | }
13 |
--------------------------------------------------------------------------------
/demo-frontend/src/theme.less:
--------------------------------------------------------------------------------
1 | // Custom Theming for NG-ZORRO
2 | // For more information: https://ng.ant.design/docs/customize-theme/en
3 | @import "../node_modules/ng-zorro-antd/ng-zorro-antd.less";
4 |
5 | // Override less variables to here
6 | // View all variables: https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/components/style/themes/default.less
7 |
8 | // @primary-color: #1890ff;
9 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/index/index.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-index',
5 | templateUrl: './index.component.html',
6 | styleUrls: ['./index.component.scss']
7 | })
8 | export class IndexComponent implements OnInit {
9 |
10 | constructor() {
11 | }
12 |
13 | ngOnInit(): void {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/user/vo/ResetUserPasswordVO.java:
--------------------------------------------------------------------------------
1 | package com.example.user.vo;
2 |
3 | import lombok.Data;
4 |
5 | import javax.validation.constraints.NotBlank;
6 |
7 | @Data
8 | public class ResetUserPasswordVO {
9 | @NotBlank(message = "用户名不能为空")
10 | private String username;
11 | @NotBlank(message = "密码不能为空")
12 | private String password;
13 | }
14 |
--------------------------------------------------------------------------------
/demo-frontend/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SpringBoot Angular OAuth2 Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resource-server/src/main/resources/application-default.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: root
4 | password: root
5 | url: jdbc:mysql://localhost:3306/spring-boot-oauth2-example
6 | driver-class-name: com.mysql.cj.jdbc.Driver
7 | logging:
8 | pattern:
9 | console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
10 |
--------------------------------------------------------------------------------
/authorization-server/src/main/resources/application-default.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: root
4 | password: root
5 | url: jdbc:mysql://localhost:3306/spring-boot-oauth2-example
6 | driver-class-name: com.mysql.cj.jdbc.Driver
7 | logging:
8 | pattern:
9 | console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
10 |
--------------------------------------------------------------------------------
/authorization-server/src/test/java/com/example/user/UserControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
4 | import org.springframework.test.web.servlet.MockMvc;
5 |
6 | import javax.annotation.Resource;
7 |
8 | /**
9 | * 用户接口测试
10 | */
11 | @WebMvcTest(UserController.class)
12 | class UserControllerTest {
13 | @Resource
14 | MockMvc mockMvc;
15 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/bean/RoleVO.java:
--------------------------------------------------------------------------------
1 | package com.example.user.bean;
2 |
3 | import com.example.generator.model.Resource;
4 | import com.example.generator.model.Role;
5 | import lombok.Data;
6 | import lombok.EqualsAndHashCode;
7 |
8 | import java.util.List;
9 |
10 | @EqualsAndHashCode(callSuper = true)
11 | @Data
12 | public class RoleVO extends Role {
13 | private List resourceList;
14 | }
15 |
--------------------------------------------------------------------------------
/demo-frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/demo-frontend/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/guard/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthGuard } from './auth.guard';
4 |
5 | describe('AuthGuard', () => {
6 | let guard: AuthGuard;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | guard = TestBed.inject(AuthGuard);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(guard).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/guard/login.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 |
3 | import {LoginGuard} from './login.guard';
4 |
5 | describe('LoginGuard', () => {
6 | let guard: LoginGuard;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | guard = TestBed.inject(LoginGuard);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(guard).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/ResourceServiceApplication.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class ResourceServiceApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(ResourceServiceApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/bean/UserChangePasswordVO.java:
--------------------------------------------------------------------------------
1 | package com.example.user.bean;
2 |
3 | import com.example.user.valid.ValidToken;
4 | import lombok.Data;
5 |
6 | import javax.validation.constraints.NotBlank;
7 |
8 | @Data
9 | public class UserChangePasswordVO {
10 | @NotBlank(message = "token不能为空")
11 | @ValidToken
12 | private String token;
13 | @NotBlank(message = "密码不能为空")
14 | private String password;
15 | }
16 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/AuthorizationServiceApplication.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class AuthorizationServiceApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(AuthorizationServiceApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/common/common.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 |
3 | import {CommonService} from './common.service';
4 |
5 | describe('CommonService', () => {
6 | let service: CommonService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(CommonService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/resource-server/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | mybatis:
2 | mapper-locations: classpath*:mapper/*.xml
3 | security:
4 | oauth2:
5 | client:
6 | client-id: client
7 | client-secret: secret
8 | resource:
9 | token-info-uri: http://localhost:8080/oauth/check_token
10 | spring:
11 | jackson:
12 | serialization:
13 | FAIL_ON_EMPTY_BEANS: false
14 | server:
15 | port: 8081
16 | error:
17 | include-binding-errors: always
18 | include-message: always
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/bean/UserRegisterVO.java:
--------------------------------------------------------------------------------
1 | package com.example.user.bean;
2 |
3 | import com.example.user.valid.ValidUsername;
4 | import lombok.Data;
5 |
6 | import javax.validation.constraints.NotBlank;
7 |
8 | /**
9 | * 注册用户时使用的vo
10 | */
11 | @Data
12 | public class UserRegisterVO {
13 | @NotBlank(message = "用户名不能为空")
14 | @ValidUsername
15 | private String username;
16 | @NotBlank(message = "密码不能为空")
17 | private String password;
18 | }
19 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/pipe/gender.pipe.ts:
--------------------------------------------------------------------------------
1 | import {Pipe, PipeTransform} from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'gender'
5 | })
6 | export class GenderPipe implements PipeTransform {
7 |
8 | transform(value: unknown, ...args: unknown[]): unknown {
9 | if (value === 1) {
10 | return '男'
11 | } else if (value === 2) {
12 | return '女'
13 | } else if (value === 3) {
14 | return '其它'
15 | } else {
16 | return ''
17 | }
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/common/auth.interceptor.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 |
3 | import {AuthInterceptor} from './auth.interceptor';
4 |
5 | describe('AuthInterceptor', () => {
6 | beforeEach(() => TestBed.configureTestingModule({
7 | providers: [
8 | AuthInterceptor
9 | ]
10 | }));
11 |
12 | it('should be created', () => {
13 | const interceptor: AuthInterceptor = TestBed.inject(AuthInterceptor);
14 | expect(interceptor).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/mapper/UserMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.mapper;
2 |
3 | import com.example.generator.model.User;
4 | import com.example.user.bean.UserVO;
5 | import tk.mybatis.mapper.common.Mapper;
6 |
7 | import java.util.List;
8 |
9 | public interface UserMapper extends Mapper {
10 | List findAllFetchRoleAndResource(String username);
11 |
12 | void initRoleByUserId(Integer id);
13 |
14 | void changePassword(String username, String password);
15 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/icons-provider.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {NZ_ICONS, NzIconModule} from 'ng-zorro-antd/icon';
3 |
4 | import {DashboardOutline, FormOutline, MenuFoldOutline, MenuUnfoldOutline} from '@ant-design/icons-angular/icons';
5 |
6 | const icons = [MenuFoldOutline, MenuUnfoldOutline, DashboardOutline, FormOutline];
7 |
8 | @NgModule({
9 | imports: [NzIconModule],
10 | exports: [NzIconModule],
11 | providers: [
12 | {provide: NZ_ICONS, useValue: icons}
13 | ]
14 | })
15 | export class IconsProviderModule {
16 | }
17 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/user/vo/UserDetailVO.java:
--------------------------------------------------------------------------------
1 | package com.example.user.vo;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class UserDetailVO {
7 | private Integer id;
8 | /**
9 | * 关联t_user表id
10 | */
11 | private Integer userId;
12 |
13 | /**
14 | * 用户姓名
15 | */
16 | private String name;
17 |
18 | /**
19 | * 年龄
20 | */
21 | private Integer age;
22 |
23 | /**
24 | * 电子邮箱
25 | */
26 | private String email;
27 |
28 | /**
29 | * 性别,1为男性,2为女性,3为其它
30 | */
31 | private Integer gender;
32 | }
33 |
--------------------------------------------------------------------------------
/authorization-server/src/main/resources/mapper/RoleMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/config/ResourceConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
5 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
6 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
7 |
8 | @Configuration
9 | @EnableResourceServer
10 | @EnableGlobalMethodSecurity(prePostEnabled = true)
11 | public class ResourceConfig extends ResourceServerConfigurerAdapter {
12 | }
13 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-role/show-personal-role.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {HttpClient} from "@angular/common/http";
3 |
4 | @Component({
5 | selector: 'app-show-personal-role',
6 | templateUrl: './show-personal-role.component.html',
7 | styleUrls: ['./show-personal-role.component.scss']
8 | })
9 | export class ShowPersonalRoleComponent implements OnInit {
10 | list: any = []
11 |
12 | constructor(
13 | private http: HttpClient,
14 | ) {
15 | }
16 |
17 | ngOnInit(): void {
18 | this.http.get(`/user/showPersonalRole`).subscribe(r => {
19 | this.list = r
20 | })
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/authorization-server/src/main/resources/mapper/ResourceMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/demo-frontend/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/generator/mapper/UserMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.mapper;
2 |
3 | import com.example.generator.model.User;
4 | import com.example.user.vo.UserDetailVO;
5 | import com.example.user.vo.UserVO;
6 | import org.apache.ibatis.annotations.Param;
7 | import tk.mybatis.mapper.common.Mapper;
8 |
9 | import java.util.List;
10 |
11 | public interface UserMapper extends Mapper {
12 | List selectAllFetchDetail(Integer id);
13 |
14 | List showPersonalRole(String username);
15 |
16 | UserDetailVO showPersonalDetail(String username);
17 |
18 | void updatePersonalDetail(@Param("item") UserDetailVO userDetailVO);
19 | }
--------------------------------------------------------------------------------
/demo-frontend/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {RouterTestingModule} from '@angular/router/testing';
3 | import {AppComponent} from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async () => {
7 | await TestBed.configureTestingModule({
8 | imports: [
9 | RouterTestingModule
10 | ],
11 | declarations: [
12 | AppComponent
13 | ],
14 | }).compileComponents();
15 | });
16 |
17 | it('should create the app', () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.componentInstance;
20 | expect(app).toBeTruthy();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/authorization-server/src/test/resources/application.yaml:
--------------------------------------------------------------------------------
1 | logging:
2 | level:
3 | com.example: info
4 | # org.apache.http: debug
5 | # org.springframework.security: debug
6 | pattern:
7 | # console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
8 | console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
9 |
10 | ---
11 | spring:
12 | profiles:
13 | active: unit-test
14 | sql:
15 | init:
16 | schema-locations: classpath*:sql/unit-test-schema.sql
17 | data-locations: classpath*:sql/unit-test-data.sql
--------------------------------------------------------------------------------
/resource-server/src/test/resources/sql/unit-test-data.sql:
--------------------------------------------------------------------------------
1 | insert into t_user (id, username, password)
2 | values (1, 'user', '$2a$10$UYfUeE54zgz3eb5sMDt2B.I7DZhXQxlboFHAkQjMGe3grcAmWXQCa'),
3 | (2, 'admin', '$2a$10$P05jOa7SIFEPf58XrPk1euoFcAgzLavabdLUXd8uFIrHZ/pflZr02');
4 | insert into t_user_detail (id, user_id, name, age, email, gender)
5 | values (1, 1, '测试用户', 12, 'test@test.com', 1);
6 | insert into t_user_detail (id, user_id, name, age, email, gender)
7 | values (2, 2, '管理员用户', 66, 'admin@test.com', 2);
8 | insert into t_role (id, name, description)
9 | values (1, 'user', '普通用户'),
10 | (2, 'admin', '系统管理员')
11 | ;
12 | insert into r_user_role (user_id, role_id)
13 | values (1, 1),
14 | (2, 1),
15 | (2, 2)
16 | ;
--------------------------------------------------------------------------------
/demo-frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/index/index.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {ComponentFixture, TestBed} from '@angular/core/testing';
2 |
3 | import {IndexComponent} from './index.component';
4 |
5 | describe('IndexComponent', () => {
6 | let component: IndexComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [IndexComponent]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(IndexComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-list/show-user-list.component.html:
--------------------------------------------------------------------------------
1 | 用户列表
2 |
3 |
4 |
5 | | 用户名 |
6 | 姓名 |
7 | 年龄 |
8 | 性别 |
9 | 电子邮箱 |
10 | 操作 |
11 |
12 |
13 |
14 |
15 | | {{data.username}} |
16 | {{data.name}} |
17 | {{data.age}} |
18 | {{data.gender | gender}} |
19 | {{data.email}} |
20 |
21 | 查看详情
22 |
23 | 重置密码
24 | |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/login/login.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { LoginComponent } from './login.component';
4 |
5 | describe('LoginComponent', () => {
6 | let component: LoginComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ LoginComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(LoginComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/model/Role.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.Id;
9 | import javax.persistence.Table;
10 |
11 | /**
12 | * 表名:t_role
13 | * 表注释:角色表
14 | */
15 | @Getter
16 | @Setter
17 | @Table(name = "t_role")
18 | public class Role {
19 | @Id
20 | @Column(name = "id")
21 | @GeneratedValue(generator = "JDBC")
22 | private Integer id;
23 |
24 | /**
25 | * 角色名
26 | */
27 | @Column(name = "name")
28 | private String name;
29 |
30 | /**
31 | * 角色描述
32 | */
33 | @Column(name = "description")
34 | private String description;
35 | }
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/register/register.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {ComponentFixture, TestBed} from '@angular/core/testing';
2 |
3 | import {RegisterComponent} from './register.component';
4 |
5 | describe('RegisterComponent', () => {
6 | let component: RegisterComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [RegisterComponent]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(RegisterComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-detail/show-personal-detail.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {HttpClient} from "@angular/common/http";
3 |
4 | @Component({
5 | selector: 'app-show-personal-detail',
6 | templateUrl: './show-personal-detail.component.html',
7 | styleUrls: ['./show-personal-detail.component.scss']
8 | })
9 | export class ShowPersonalDetailComponent implements OnInit {
10 | details: any = {
11 | name: undefined,
12 | age: undefined,
13 | email: undefined,
14 | gender: undefined,
15 | }
16 |
17 | constructor(
18 | private http: HttpClient,
19 | ) {
20 | }
21 |
22 | ngOnInit(): void {
23 | this.http.get(`/user/showPersonalDetail`).subscribe(r => {
24 | this.details = r
25 | })
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/model/Resource.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.Id;
9 | import javax.persistence.Table;
10 |
11 | /**
12 | * 表名:t_resource
13 | * 表注释:资源表
14 | */
15 | @Getter
16 | @Setter
17 | @Table(name = "t_resource")
18 | public class Resource {
19 | @Id
20 | @Column(name = "id")
21 | @GeneratedValue(generator = "JDBC")
22 | private Integer id;
23 |
24 | /**
25 | * 资源名,唯一,英文,尽量短
26 | */
27 | @Column(name = "name")
28 | private String name;
29 |
30 | /**
31 | * 资源描述
32 | */
33 | @Column(name = "description")
34 | private String description;
35 | }
--------------------------------------------------------------------------------
/resource-server/src/test/java/com/example/config/TestResourceConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config;
2 |
3 | import org.springframework.boot.test.context.TestConfiguration;
4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
6 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
7 |
8 | @TestConfiguration
9 | public class TestResourceConfig extends ResourceServerConfigurerAdapter {
10 | @Override
11 | public void configure(HttpSecurity http) {
12 | }
13 |
14 | @Override
15 | public void configure(ResourceServerSecurityConfigurer resources) {
16 | resources.stateless(false);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/demo-frontend/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | oauthUrl: 'http://localhost:8080',
8 | api: 'http://localhost:8081'
9 | };
10 |
11 | /*
12 | * For easier debugging in development mode, you can import the following file
13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
14 | *
15 | * This import should be commented out in production mode because it will have a negative impact
16 | * on performance if an error is thrown.
17 | */
18 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
19 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-list/show-user-list.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ShowUserListComponent } from './show-user-list.component';
4 |
5 | describe('ShowUserListComponent', () => {
6 | let component: ShowUserListComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ShowUserListComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ShowUserListComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/resource-server/src/test/resources/application.yaml:
--------------------------------------------------------------------------------
1 | logging:
2 | level:
3 | com.example: debug
4 | # org.apache.http: debug
5 | # org.springframework.security: debug
6 | pattern:
7 | # console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
8 | console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
9 |
10 | ---
11 | spring:
12 | profiles: unit-test
13 | sql:
14 | init:
15 | schema-locations: classpath*:sql/unit-test-schema.sql
16 | data-locations: classpath*:sql/unit-test-data.sql
17 |
18 | ---
19 | spring:
20 | profiles: unit-test-mysql-mode
21 | datasource:
22 | url: jdbc:h2:mem:testdb;MODE=MySQL
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-detail/show-user-detail.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ShowUserDetailComponent } from './show-user-detail.component';
4 |
5 | describe('ShowUserDetailComponent', () => {
6 | let component: ShowUserDetailComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ShowUserDetailComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ShowUserDetailComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/demo-frontend/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: {
11 | context(path: string, deep?: boolean, filter?: RegExp): {
12 | (id: string): T;
13 | keys(): string[];
14 | };
15 | };
16 |
17 | // First, initialize the Angular testing environment.
18 | getTestBed().initTestEnvironment(
19 | BrowserDynamicTestingModule,
20 | platformBrowserDynamicTesting(),
21 | );
22 |
23 | // Then we find all the tests.
24 | const context = require.context('./', true, /\.spec\.ts$/);
25 | // And load the modules.
26 | context.keys().map(context);
27 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-role/show-personal-role.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ShowPersonalRoleComponent } from './show-personal-role.component';
4 |
5 | describe('ShowPersonalRoleComponent', () => {
6 | let component: ShowPersonalRoleComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ShowPersonalRoleComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ShowPersonalRoleComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/reset-user-password/reset-user-password.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ResetUserPasswordComponent } from './reset-user-password.component';
4 |
5 | describe('ResetUserPasswordComponent', () => {
6 | let component: ResetUserPasswordComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ResetUserPasswordComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ResetUserPasswordComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/resource-server/src/main/resources/mapper/UserDetailMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-detail/show-personal-detail.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ShowPersonalDetailComponent } from './show-personal-detail.component';
4 |
5 | describe('ShowPersonalDetailComponent', () => {
6 | let component: ShowPersonalDetailComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ShowPersonalDetailComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ShowPersonalDetailComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/update-personal-detail/update-personal-detail.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { UpdatePersonalDetailComponent } from './update-personal-detail.component';
4 |
5 | describe('UpdatePersonalDetailComponent', () => {
6 | let component: UpdatePersonalDetailComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ UpdatePersonalDetailComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(UpdatePersonalDetailComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/login/login.component.html:
--------------------------------------------------------------------------------
1 | 登录
2 |
23 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/generator/model/User.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.Id;
9 | import javax.persistence.Table;
10 |
11 | /**
12 | * 表名:t_user
13 | * 表注释:用户表
14 | */
15 | @Getter
16 | @Setter
17 | @Table(name = "t_user")
18 | public class User {
19 | @Id
20 | @Column(name = "id")
21 | @GeneratedValue(generator = "JDBC")
22 | private Integer id;
23 |
24 | /**
25 | * 用户名
26 | */
27 | @Column(name = "username")
28 | private String username;
29 |
30 | /**
31 | * 密码,根据需求加密后存储
32 | */
33 | @Column(name = "password")
34 | private String password;
35 |
36 | /**
37 | * 是否可用,1为可用,0为不可用,默认1可用
38 | */
39 | @Column(name = "enable")
40 | private Boolean enable;
41 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/model/User.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.Id;
9 | import javax.persistence.Table;
10 |
11 | /**
12 | * 表名:t_user
13 | * 表注释:用户表
14 | */
15 | @Getter
16 | @Setter
17 | @Table(name = "t_user")
18 | public class User {
19 | @Id
20 | @Column(name = "id")
21 | @GeneratedValue(generator = "JDBC")
22 | private Integer id;
23 |
24 | /**
25 | * 用户名
26 | */
27 | @Column(name = "username")
28 | private String username;
29 |
30 | /**
31 | * 密码,根据需求加密后存储
32 | */
33 | @Column(name = "password")
34 | private String password;
35 |
36 | /**
37 | * 是否可用,1为可用,0为不可用,默认1可用
38 | */
39 | @Column(name = "enable")
40 | private Boolean enable;
41 | }
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-list/show-user-list.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {HttpClient} from "@angular/common/http";
3 | import {Router} from "@angular/router";
4 |
5 | @Component({
6 | selector: 'app-show-user-list',
7 | templateUrl: './show-user-list.component.html',
8 | styleUrls: ['./show-user-list.component.scss']
9 | })
10 | export class ShowUserListComponent implements OnInit {
11 | dataSet: any = []
12 |
13 | constructor(
14 | private http: HttpClient,
15 | private router: Router) {
16 | }
17 |
18 | ngOnInit(): void {
19 | this.load()
20 | }
21 |
22 | load() {
23 | this.http.get(`/user`).subscribe(r => {
24 | this.dataSet = r
25 | })
26 | }
27 |
28 | details(data: any) {
29 | this.router.navigate(['/showUserDetail'], {queryParams: {id: data.id}})
30 | }
31 |
32 | changePassword(data: any) {
33 | this.router.navigate(['/resetUserPassword'], {queryParams: {id: data.id}})
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/demo-frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "es2017",
20 | "module": "es2020",
21 | "lib": [
22 | "es2020",
23 | "dom"
24 | ]
25 | },
26 | "angularCompilerOptions": {
27 | "enableI18nLegacyMessageIdFormat": false,
28 | "strictInjectionParameters": true,
29 | "strictInputAccessModifiers": true,
30 | "strictTemplates": true
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/UserController.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.user.bean.UserChangePasswordVO;
4 | import com.example.user.bean.UserRegisterVO;
5 | import org.springframework.validation.annotation.Validated;
6 | import org.springframework.web.bind.annotation.PostMapping;
7 | import org.springframework.web.bind.annotation.RequestBody;
8 | import org.springframework.web.bind.annotation.RestController;
9 |
10 | import javax.annotation.Resource;
11 |
12 | @RestController
13 | public class UserController {
14 | @Resource
15 | UserService userService;
16 |
17 | /**
18 | * 用户注册
19 | */
20 | @PostMapping("/oauth/register")
21 | public void register(@RequestBody @Validated UserRegisterVO userRegisterVO) {
22 | userService.register(userRegisterVO);
23 | }
24 |
25 | @PostMapping("/oauth/changePassword")
26 | public void changePassword(@RequestBody @Validated UserChangePasswordVO userChangePasswordVO) {
27 | userService.changePassword(userChangePasswordVO);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/config/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
7 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
8 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
9 | import org.springframework.security.crypto.password.PasswordEncoder;
10 |
11 | @Configuration
12 | @EnableWebSecurity
13 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
14 | @Bean
15 | public PasswordEncoder passwordEncoder() {
16 | return new BCryptPasswordEncoder();
17 | }
18 |
19 | @Override
20 | protected void configure(HttpSecurity http) throws Exception {
21 | super.configure(http);
22 | http.csrf().disable();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/client/ClientService.java:
--------------------------------------------------------------------------------
1 | package com.example.client;
2 |
3 | import com.example.generator.mapper.ClientMapper;
4 | import com.example.generator.model.Client;
5 | import org.springframework.security.oauth2.provider.ClientDetails;
6 | import org.springframework.security.oauth2.provider.ClientDetailsService;
7 | import org.springframework.security.oauth2.provider.ClientRegistrationException;
8 | import org.springframework.stereotype.Service;
9 | import tk.mybatis.mapper.entity.Example;
10 |
11 | import javax.annotation.Resource;
12 |
13 | @Service
14 | public class ClientService implements ClientDetailsService {
15 | @Resource
16 | ClientMapper clientMapper;
17 |
18 | @Override
19 | public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
20 | Example example = new Example(Client.class);
21 | example.createCriteria().andEqualTo("clientId", clientId);
22 | Client client = clientMapper.selectOneByExample(example);
23 | return ClientVO.from(client);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.example
6 | spring-boot-oauth2-example
7 | 0.0.1-SNAPSHOT
8 | pom
9 | PARENT::ROOT
10 |
11 |
12 | authorization-server
13 | resource-server
14 |
15 |
16 |
17 |
18 | aliyun
19 | https://maven.aliyun.com/repository/central
20 |
21 |
22 |
23 |
24 | aliyun
25 | https://maven.aliyun.com/repository/central
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/authorization-server/src/test/java/com/example/user/UserServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.generator.mapper.UserMapper;
4 | import com.example.user.bean.UserVO;
5 | import org.junit.jupiter.api.Assertions;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockito.InjectMocks;
9 | import org.mockito.Mock;
10 | import org.mockito.Mockito;
11 | import org.mockito.junit.jupiter.MockitoExtension;
12 |
13 | import java.util.List;
14 |
15 | @ExtendWith(MockitoExtension.class)
16 | class UserServiceTest {
17 |
18 | @InjectMocks
19 | UserService userService;
20 | @Mock
21 | UserMapper userMapper;
22 |
23 | @Test
24 | void findAllFetchRoleAndResource() {
25 | UserVO userVO = new UserVO();
26 | Mockito.when(userMapper.findAllFetchRoleAndResource(null)).thenReturn(List.of(userVO));
27 |
28 | List users = userService.findAllFetchRoleAndResource();
29 | Assertions.assertNotNull(users);
30 | Assertions.assertFalse(users.isEmpty());
31 | Assertions.assertEquals(1, users.size());
32 | }
33 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 大胃王
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/login/login.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {FormBuilder, FormGroup, Validators} from "@angular/forms";
3 | import {CommonService} from "../../shared/common/common.service";
4 | import {Router} from "@angular/router";
5 |
6 | @Component({
7 | selector: 'app-login',
8 | templateUrl: './login.component.html',
9 | styleUrls: ['./login.component.scss']
10 | })
11 | export class LoginComponent implements OnInit {
12 | validateForm: FormGroup;
13 |
14 | constructor(
15 | private fb: FormBuilder,
16 | private service: CommonService,
17 | private router: Router,
18 | ) {
19 | this.validateForm = this.fb.group({
20 | username: [null, Validators.required],
21 | password: [null, Validators.required],
22 | })
23 | }
24 |
25 | ngOnInit(): void {
26 | }
27 |
28 | submitForm() {
29 | let rawValue = this.validateForm.getRawValue();
30 | this.service.login(rawValue.username, rawValue.password).subscribe(r => {
31 | this.service.token = r
32 | this.service.success(`登录成功`)
33 | this.router.navigate(['/index'])
34 | })
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/config/ValidatorConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config;
2 |
3 | import org.hibernate.validator.HibernateValidator;
4 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
8 |
9 | import javax.validation.Validation;
10 | import javax.validation.Validator;
11 |
12 | /**
13 | * 校验相关配置
14 | */
15 | @Configuration
16 | public class ValidatorConfig {
17 | @Bean
18 | public Validator validator(AutowireCapableBeanFactory autowireCapableBeanFactory) {
19 | return Validation.byProvider(HibernateValidator.class)
20 | .configure()
21 | // 在ConstraintValidator实现类中允许注入spring bean
22 | .constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
23 | // 在校验到错误后立刻返回错误信息,而不是全部都进行校验
24 | .failFast(true)
25 | .buildValidatorFactory().getValidator();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/generator/model/UserDetail.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.Id;
9 | import javax.persistence.Table;
10 |
11 | /**
12 | * 表名:t_user_detail
13 | * 表注释:用户详情表
14 | */
15 | @Getter
16 | @Setter
17 | @Table(name = "t_user_detail")
18 | public class UserDetail {
19 | @Id
20 | @Column(name = "id")
21 | @GeneratedValue(generator = "JDBC")
22 | private Integer id;
23 |
24 | /**
25 | * 关联t_user表id
26 | */
27 | @Column(name = "user_id")
28 | private Integer userId;
29 |
30 | /**
31 | * 用户姓名
32 | */
33 | @Column(name = "name")
34 | private String name;
35 |
36 | /**
37 | * 年龄
38 | */
39 | @Column(name = "age")
40 | private Integer age;
41 |
42 | /**
43 | * 电子邮箱
44 | */
45 | @Column(name = "email")
46 | private String email;
47 |
48 | /**
49 | * 性别,1为男性,2为女性,3为其它
50 | */
51 | @Column(name = "gender")
52 | private Integer gender;
53 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/config/ValidatorConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config;
2 |
3 | import org.hibernate.validator.HibernateValidator;
4 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
8 |
9 | import javax.validation.Validation;
10 | import javax.validation.Validator;
11 |
12 | /**
13 | * 校验相关配置
14 | */
15 | @Configuration
16 | public class ValidatorConfig {
17 | @Bean
18 | public Validator validator(AutowireCapableBeanFactory autowireCapableBeanFactory) {
19 | return Validation.byProvider(HibernateValidator.class)
20 | .configure()
21 | // 在ConstraintValidator实现类中允许注入spring bean
22 | .constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
23 | // 在校验到错误后立刻返回错误信息,而不是全部都进行校验
24 | .failFast(true)
25 | .buildValidatorFactory().getValidator();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demo-frontend/README.md:
--------------------------------------------------------------------------------
1 | # DemoFrontend
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.2.5.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/authorization-server/src/test/resources/sql/unit-test-data.sql:
--------------------------------------------------------------------------------
1 | insert into t_client (id, client_id, client_secret, registered_redirect_uri_str)
2 | values (1, 'client', '$2a$10$4exGEs2hdi0C5C8wiDzirOft5WLC7/WJFXVqz7cXhVw6/tnFOstZi', 'http://localhost:4200/login');
3 | insert into t_user (id, username, password)
4 | values (1, 'user', '$2a$10$UYfUeE54zgz3eb5sMDt2B.I7DZhXQxlboFHAkQjMGe3grcAmWXQCa'),
5 | (2, 'admin', '$2a$10$P05jOa7SIFEPf58XrPk1euoFcAgzLavabdLUXd8uFIrHZ/pflZr02');
6 | insert into t_role (id, name, description)
7 | values (1, 'user', '普通用户'),
8 | (2, 'admin', '系统管理员')
9 | ;
10 | insert into r_user_role (user_id, role_id)
11 | values (1, 1),
12 | (2, 1),
13 | (2, 2)
14 | ;
15 | insert into t_resource (id, name, description)
16 | values (1, 'showPersonalRole', '查看个人角色'),
17 | (2, 'updatePersonalDetail', '编辑个人信息'),
18 | (3, 'showUserList', '查看用户列表'),
19 | (4, 'showUserDetail', '查看用户详情'),
20 | (5, 'resetUserPassword', '重置用户密码'),
21 | (6, 'showPersonalDetail', '查看个人信息')
22 | ;
23 | insert into r_role_resource (role_id, resource_id)
24 | values (1, 1),
25 | (1, 2),
26 | (2, 3),
27 | (2, 4),
28 | (2, 5),
29 | (1, 6)
30 | ;
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-detail/show-user-detail.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {ActivatedRoute, Router} from "@angular/router";
3 | import {CommonService} from "../../shared/common/common.service";
4 | import {HttpClient} from "@angular/common/http";
5 |
6 | @Component({
7 | selector: 'app-show-user-detail',
8 | templateUrl: './show-user-detail.component.html',
9 | styleUrls: ['./show-user-detail.component.scss']
10 | })
11 | export class ShowUserDetailComponent implements OnInit {
12 | details: any = {
13 | name: undefined,
14 | age: undefined,
15 | email: undefined,
16 | gender: undefined,
17 | }
18 |
19 | constructor(
20 | private route: ActivatedRoute,
21 | private router: Router,
22 | private service: CommonService,
23 | private http: HttpClient,
24 | ) {
25 | }
26 |
27 | ngOnInit(): void {
28 | this.route.queryParams.subscribe(r => {
29 | if (!r['id']) {
30 | this.service.info(`请先选择用户`)
31 | this.router.navigate(['/showUserList'])
32 | } else {
33 | this.http.get(`/user/details/${r['id']}`).subscribe(r1 => {
34 | this.details = r1
35 | })
36 | }
37 | })
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/resource-server/src/test/resources/sql/unit-test-schema.sql:
--------------------------------------------------------------------------------
1 | create schema `spring-boot-oauth2-example`;
2 |
3 | create table t_user
4 | (
5 | id int auto_increment primary key,
6 | username varchar(100) not null comment '用户名',
7 | password varchar(100) not null comment '密码,根据需求加密后存储',
8 | enable bit default 1 not null comment '是否可用,1为可用,0为不可用,默认1可用',
9 | constraint t_user_username_uindex unique (username)
10 | );
11 |
12 | create table t_user_detail
13 | (
14 | id int auto_increment primary key,
15 | user_id int not null comment '关联t_user表id',
16 | name varchar(20) null comment '用户姓名',
17 | age int null comment '年龄',
18 | email varchar(100) null comment '电子邮箱',
19 | gender int null comment '性别,1为男性,2为女性,3为其它',
20 | constraint t_user_detail_user_id_uindex unique (user_id)
21 | );
22 |
23 | create table t_role
24 | (
25 | id int auto_increment primary key,
26 | name varchar(200) not null comment '角色名',
27 | description varchar(200) not null comment '角色描述',
28 | constraint t_role_name_uindex unique (name)
29 | );
30 |
31 | create table r_user_role
32 | (
33 | user_id int not null,
34 | role_id int not null,
35 | primary key (user_id, role_id)
36 | );
37 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/update-personal-detail/update-personal-detail.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {HttpClient} from "@angular/common/http";
3 | import {FormBuilder, FormGroup} from "@angular/forms";
4 | import {CommonService} from "../../shared/common/common.service";
5 |
6 | @Component({
7 | selector: 'app-update-personal-detail',
8 | templateUrl: './update-personal-detail.component.html',
9 | styleUrls: ['./update-personal-detail.component.scss']
10 | })
11 | export class UpdatePersonalDetailComponent implements OnInit {
12 | validateForm: FormGroup
13 |
14 | constructor(
15 | private http: HttpClient,
16 | private fb: FormBuilder,
17 | private service: CommonService,
18 | ) {
19 | this.validateForm = this.fb.group({
20 | id: [],
21 | userId: [],
22 | name: [],
23 | age: [],
24 | email: [],
25 | gender: [],
26 | })
27 | }
28 |
29 | ngOnInit(): void {
30 | this.http.get(`/user/showPersonalDetail`).subscribe(r => {
31 | this.validateForm.patchValue(r)
32 | })
33 | }
34 |
35 | submitForm() {
36 | this.http.post(`/user/updatePersonalDetail`, this.validateForm.getRawValue()).subscribe(() => {
37 | this.service.success(`保存成功`)
38 | })
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/demo-frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo-frontend",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve --proxy-config proxy.json",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "~13.2.0",
14 | "@angular/common": "~13.2.0",
15 | "@angular/compiler": "~13.2.0",
16 | "@angular/core": "~13.2.0",
17 | "@angular/forms": "~13.2.0",
18 | "@angular/platform-browser": "~13.2.0",
19 | "@angular/platform-browser-dynamic": "~13.2.0",
20 | "@angular/router": "~13.2.0",
21 | "ng-zorro-antd": "^13.1.1",
22 | "rxjs": "~7.5.0",
23 | "tslib": "^2.3.0",
24 | "zone.js": "~0.11.4"
25 | },
26 | "devDependencies": {
27 | "@angular-devkit/build-angular": "~13.2.5",
28 | "@angular/cli": "~13.2.5",
29 | "@angular/compiler-cli": "~13.2.0",
30 | "@types/jasmine": "~3.10.0",
31 | "@types/node": "^12.11.1",
32 | "jasmine-core": "~4.0.0",
33 | "karma": "~6.3.0",
34 | "karma-chrome-launcher": "~3.1.0",
35 | "karma-coverage": "~2.1.0",
36 | "karma-jasmine": "~4.0.0",
37 | "karma-jasmine-html-reporter": "~1.7.0",
38 | "typescript": "~4.5.2"
39 | }
40 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/valid/ValidUsername.java:
--------------------------------------------------------------------------------
1 | package com.example.user.valid;
2 |
3 | import com.example.user.UserService;
4 |
5 | import javax.annotation.Resource;
6 | import javax.validation.Constraint;
7 | import javax.validation.ConstraintValidator;
8 | import javax.validation.ConstraintValidatorContext;
9 | import javax.validation.Payload;
10 | import java.lang.annotation.Retention;
11 | import java.lang.annotation.RetentionPolicy;
12 | import java.lang.annotation.Target;
13 |
14 | import static java.lang.annotation.ElementType.*;
15 |
16 | /**
17 | * 校验用户名是否存在,在注册用户之前使用
18 | */
19 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
20 | @Retention(RetentionPolicy.RUNTIME)
21 | @Constraint(validatedBy = ValidUsername.UsernameValidator.class)
22 | public @interface ValidUsername {
23 | String message() default "用户名已存在";
24 |
25 | Class>[] groups() default {};
26 |
27 | Class extends Payload>[] payload() default {};
28 |
29 | class UsernameValidator implements ConstraintValidator {
30 | @Resource
31 | UserService userService;
32 |
33 | @Override
34 | public boolean isValid(String value, ConstraintValidatorContext context) {
35 | return userService.selectUserByUsername(value) == null;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular+SpringBoot+OAuth2授权集成的示例项目
2 |
3 | ## 主要功能
4 |
5 | ### 授权服务器(权限中心)
6 |
7 | - [x] 注册账号
8 | - [x] 通过授权码的方式登录(单点登录),并获取授权码
9 | - [x] 通过授权码获取token
10 | - [x] 通过密码方式登录,并获取token
11 | - [x] 修改密码
12 | - [x] 注销账号
13 | - [x] 通过check_token接口获取用户权限(JWT模式用的比较少)
14 |
15 | ### 资源服务器(客户端,负责具体业务)
16 |
17 | 普通用户
18 |
19 | - [x] 查看个人角色
20 | - [x] 查看个人信息
21 | - [x] 编辑个人信息
22 |
23 | 管理员用户
24 |
25 | - [x] 查看用户列表
26 | - [x] 查看用户详情
27 | - [x] 重置用户密码
28 |
29 | ## 截图展示
30 |
31 | 
32 |
33 | 
34 |
35 | 
36 |
37 | 
38 |
39 | ## 文件结构
40 |
41 | 
42 |
43 | 
44 |
45 | 
46 |
47 | 
48 |
49 | ## 授权和资源服务器的集成以及分离
50 |
51 | 本项目是授权和资源服务器的分离示例。
52 |
53 | 集成示例可以参考: https://gitee.com/consolelog/spring-boot-oauth2-single-service-example
54 |
55 | git地址: https://gitee.com/consolelog/spring-boot-oauth2-single-service-example.git
56 |
57 | ## 参考文档
58 |
59 | https://projects.spring.io/spring-security-oauth/docs/oauth2.html
60 |
61 | https://docs.spring.io/spring-security-oauth2-boot/docs/2.6.x/reference/html5/
62 |
63 | https://consolelog.gitee.io/docs-oauth2/
64 |
65 | https://docs.spring.io/spring-security/reference/index.html
66 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/valid/ValidToken.java:
--------------------------------------------------------------------------------
1 | package com.example.user.valid;
2 |
3 | import org.springframework.security.oauth2.provider.token.TokenStore;
4 |
5 | import javax.annotation.Resource;
6 | import javax.validation.Constraint;
7 | import javax.validation.ConstraintValidator;
8 | import javax.validation.ConstraintValidatorContext;
9 | import javax.validation.Payload;
10 | import java.lang.annotation.Retention;
11 | import java.lang.annotation.RetentionPolicy;
12 | import java.lang.annotation.Target;
13 |
14 | import static java.lang.annotation.ElementType.*;
15 |
16 | /**
17 | * 校验token是否存在,在修改密码之前使用
18 | */
19 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
20 | @Retention(RetentionPolicy.RUNTIME)
21 | @Constraint(validatedBy = ValidToken.TokenValidator.class)
22 | public @interface ValidToken {
23 | String message() default "token无法识别";
24 |
25 | Class>[] groups() default {};
26 |
27 | Class extends Payload>[] payload() default {};
28 |
29 | class TokenValidator implements ConstraintValidator {
30 | @Resource
31 | TokenStore tokenStore;
32 |
33 | @Override
34 | public boolean isValid(String value, ConstraintValidatorContext context) {
35 | return tokenStore.readAuthentication(value) != null;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/guard/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {ActivatedRouteSnapshot, CanActivateChild, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
3 | import {catchError, map, Observable, throwError} from 'rxjs';
4 | import {CommonService} from "../common/common.service";
5 |
6 | @Injectable({
7 | providedIn: 'root'
8 | })
9 | export class AuthGuard implements CanActivateChild {
10 |
11 | constructor(
12 | private service: CommonService,
13 | private router: Router,
14 | ) {
15 | }
16 |
17 | canActivateChild(
18 | route: ActivatedRouteSnapshot,
19 | state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree {
20 | let user = this.service.user;
21 | if (user == null || user == {}) {
22 | this.router.navigate(['/index'])
23 | return false
24 | }
25 | if (this.service.expired) {
26 | return this.service.refreshToken().pipe(map(r => {
27 | if (r) {
28 | return true
29 | } else {
30 | this.service.info(`登录已过期,请重新登录`)
31 | this.router.navigate(['/index'])
32 | return false
33 | }
34 | }), catchError(e => {
35 | this.service.error(`登录状态刷新失败,请重新登录`)
36 | this.service.noAuth()
37 | return throwError(`error: ${e.error}`)
38 | }))
39 | }
40 | return true;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/reset-user-password/reset-user-password.component.html:
--------------------------------------------------------------------------------
1 | 重置用户密码
2 |
35 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-user-detail/show-user-detail.component.html:
--------------------------------------------------------------------------------
1 | 用户个人信息
2 |
35 |
36 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/show-personal-detail/show-personal-detail.component.html:
--------------------------------------------------------------------------------
1 | 您的个人信息
2 |
35 |
36 |
--------------------------------------------------------------------------------
/sql/initial-data.sql:
--------------------------------------------------------------------------------
1 | # client表
2 | # client:secret
3 | insert ignore into t_client (id, client_id, client_secret, registered_redirect_uri_str)
4 | values (1, 'client', '$2a$10$4exGEs2hdi0C5C8wiDzirOft5WLC7/WJFXVqz7cXhVw6/tnFOstZi', 'http://localhost:4200/login');
5 | #用户表
6 | # user:user
7 | # admin:admin
8 | insert ignore into t_user (id, username, password)
9 | values (1, 'user', '$2a$10$UYfUeE54zgz3eb5sMDt2B.I7DZhXQxlboFHAkQjMGe3grcAmWXQCa'),
10 | (2, 'admin', '$2a$10$P05jOa7SIFEPf58XrPk1euoFcAgzLavabdLUXd8uFIrHZ/pflZr02');
11 | # 角色表
12 | insert ignore into t_role (id, name, description)
13 | values (1, 'user', '普通用户'),
14 | (2, 'admin', '系统管理员')
15 | ;
16 | # 用户角色关系表
17 | insert ignore into r_user_role (user_id, role_id)
18 | values (1, 1),
19 | (2, 1),
20 | (2, 2)
21 | ;
22 | # 资源表
23 | insert ignore into t_resource (id, name, description)
24 | values (1, 'showPersonalRole', '查看个人角色'),
25 | (2, 'updatePersonalDetail', '编辑个人信息'),
26 | (3, 'showUserList', '查看用户列表'),
27 | (4, 'showUserDetail', '查看用户详情'),
28 | (5, 'resetUserPassword', '重置用户密码'),
29 | (6, 'showPersonalDetail', '查看个人信息')
30 | ;
31 | # 角色资源关系表
32 | insert ignore into r_role_resource (role_id, resource_id)
33 | values (1, 1),
34 | (1, 2),
35 | (2, 3),
36 | (2, 4),
37 | (2, 5),
38 | (1, 6)
39 | ;
40 | insert into t_user_detail (id, user_id, name, age, email, gender)
41 | values (1, 1, '测试用户', 12, 'test@test.com', 1),
42 | (2, 2, '管理员用户', 66, 'admin@test.com', 2);
--------------------------------------------------------------------------------
/authorization-server/src/test/java/com/example/user/UserMapperTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.config.MyBatisConfig;
4 | import com.example.generator.mapper.UserMapper;
5 | import com.example.user.bean.UserVO;
6 | import org.junit.jupiter.api.Test;
7 | import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
8 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
9 | import org.springframework.context.annotation.Import;
10 | import org.springframework.test.context.ActiveProfiles;
11 | import tk.mybatis.mapper.autoconfigure.MapperAutoConfiguration;
12 |
13 | import javax.annotation.Resource;
14 | import java.util.List;
15 |
16 | import static org.junit.jupiter.api.Assertions.assertEquals;
17 |
18 | @MybatisTest
19 | @Import(MyBatisConfig.class)
20 | @ImportAutoConfiguration(MapperAutoConfiguration.class)
21 | @ActiveProfiles("unit-test")
22 | public class UserMapperTest {
23 | @Resource
24 | UserMapper userMapper;
25 |
26 | @Test
27 | void findAllFetchRoleAndResource() {
28 | List allFetchRoleAndResource = userMapper.findAllFetchRoleAndResource(null);
29 | assertEquals(allFetchRoleAndResource.size(), 2);
30 | assertEquals(allFetchRoleAndResource.get(0).getRoleVOList().size(), 1);
31 | assertEquals(allFetchRoleAndResource.get(1).getRoleVOList().size(), 2);
32 | assertEquals(allFetchRoleAndResource.get(0).getAuthorities().size(), 3);
33 | assertEquals(allFetchRoleAndResource.get(1).getAuthorities().size(), 6);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/common/auth.interceptor.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
3 | import {catchError, mergeMap, Observable, throwError} from 'rxjs';
4 | import {CommonService} from "./common.service";
5 |
6 | @Injectable()
7 | export class AuthInterceptor implements HttpInterceptor {
8 |
9 | constructor(
10 | private service: CommonService,
11 | ) {
12 | }
13 |
14 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
15 | if (request.url.startsWith('/oauth/')) {
16 | return next.handle(request);
17 | }
18 | let token = this.service.token
19 | if (token != null && token != {}) {
20 | if (this.service.expired) {
21 | return this.service.refreshToken().pipe(mergeMap(() => {
22 | token = this.service.token
23 | return this.getAuthReq(request, token, next);
24 | }), catchError(e => {
25 | this.service.error(`登录状态刷新失败,请重新登录`)
26 | this.service.noAuth()
27 | return throwError(`error: ${e.error}`)
28 | }))
29 | } else {
30 | return this.getAuthReq(request, token, next);
31 | }
32 | } else {
33 | return next.handle(request);
34 | }
35 | }
36 |
37 | getAuthReq(request: HttpRequest, token: any, next: HttpHandler) {
38 | const authReq = request.clone({
39 | headers: request.headers.set('Authorization', `${token.token_type} ${token.access_token}`)
40 | });
41 | return next.handle(authReq);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/authorization-server/src/main/resources/mapper/ClientMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo-frontend/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, './coverage/demo-frontend'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/resource-server/src/test/java/com/example/user/UserMapperTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.config.MyBatisConfig;
4 | import com.example.generator.mapper.UserMapper;
5 | import com.example.user.vo.UserVO;
6 | import org.junit.jupiter.api.Test;
7 | import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
8 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
9 | import org.springframework.context.annotation.Import;
10 | import org.springframework.test.context.ActiveProfiles;
11 | import tk.mybatis.mapper.autoconfigure.MapperAutoConfiguration;
12 |
13 | import javax.annotation.Resource;
14 | import java.util.List;
15 |
16 | import static org.junit.jupiter.api.Assertions.assertEquals;
17 |
18 | @MybatisTest
19 | @Import(MyBatisConfig.class)
20 | @ImportAutoConfiguration(MapperAutoConfiguration.class)
21 | @ActiveProfiles("unit-test")
22 | class UserMapperTest {
23 | @Resource
24 | UserMapper userMapper;
25 |
26 | @Test
27 | void selectAllFetchDetail() {
28 | List userVOS = userMapper.selectAllFetchDetail(null);
29 | assertEquals(2, userVOS.size());
30 |
31 | List userVOS1 = userMapper.selectAllFetchDetail(1);
32 | assertEquals(1, userVOS1.size());
33 | UserVO userVO = userVOS1.get(0);
34 | assertEquals(1, userVO.getId());
35 | assertEquals("user", userVO.getUsername());
36 | assertEquals(1, userVO.getUserId());
37 | assertEquals("测试用户", userVO.getName());
38 | assertEquals(12, userVO.getAge());
39 | assertEquals("test@test.com", userVO.getEmail());
40 | assertEquals(1, userVO.getGender());
41 | }
42 | }
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/update-personal-detail/update-personal-detail.component.html:
--------------------------------------------------------------------------------
1 | 修改个人信息
2 |
41 |
42 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {CommonService} from "./shared/common/common.service";
3 | import {environment} from "../environments/environment";
4 | import {Router} from "@angular/router";
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | styleUrls: ['./app.component.scss']
10 | })
11 | export class AppComponent implements OnInit {
12 | isCollapsed = false;
13 |
14 | userInfo = {
15 | user_name: '',
16 | authorities: [],
17 | }
18 |
19 | constructor(
20 | private service: CommonService,
21 | private router: Router,
22 | ) {
23 | }
24 |
25 | ngOnInit(): void {
26 | this.userInfo = this.service.user
27 | this.service.user$.subscribe(r => {
28 | this.userInfo = r
29 | })
30 | }
31 |
32 | sso() {
33 | const redirectUri = "http://localhost:4200/login"
34 | const clientId = 'client'
35 | const state = this.router.url
36 | const parameter = `response_type=code&redirect_uri=${redirectUri}&client_id=${clientId}&state=${state}`;
37 | window.location.href = `${environment.oauthUrl}/oauth/authorize?${parameter}`
38 | }
39 |
40 | logout() {
41 | const redirectUri = "http://localhost:4200"
42 | const token = this.service.token['access_token']
43 | this.service.clearAuth()
44 | window.location.href = `${environment.oauthUrl}/oauth/logout?token=${token}&redirect=${redirectUri}`
45 | }
46 |
47 | matchAuthority(resource: string) {
48 | if (!this.userInfo || !this.userInfo.authorities) {
49 | return false
50 | }
51 | const authorities = this.userInfo.authorities as string[]
52 | return authorities.indexOf(resource) > -1
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | text-rendering: optimizeLegibility;
4 | -webkit-font-smoothing: antialiased;
5 | -moz-osx-font-smoothing: grayscale;
6 | }
7 |
8 | .app-layout {
9 | height: 100%;
10 | }
11 |
12 | .menu-sidebar {
13 | position: relative;
14 | z-index: 10;
15 | min-height: 100vh;
16 | box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
17 | }
18 |
19 | .header-trigger {
20 | height: 64px;
21 | padding: 20px 24px;
22 | font-size: 20px;
23 | cursor: pointer;
24 | transition: all .3s, padding 0s;
25 | }
26 |
27 | .trigger:hover {
28 | color: #1890ff;
29 | }
30 |
31 | .sidebar-logo {
32 | position: relative;
33 | height: 64px;
34 | padding-left: 24px;
35 | overflow: hidden;
36 | line-height: 64px;
37 | background: #001529;
38 | transition: all .3s;
39 | }
40 |
41 | .sidebar-logo img {
42 | display: inline-block;
43 | height: 32px;
44 | width: 32px;
45 | vertical-align: middle;
46 | }
47 |
48 | .sidebar-logo h1 {
49 | display: inline-block;
50 | margin: 0 0 0 20px;
51 | color: #fff;
52 | font-weight: 600;
53 | font-size: 14px;
54 | font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
55 | vertical-align: middle;
56 | }
57 |
58 | nz-header {
59 | padding: 0;
60 | width: 100%;
61 | z-index: 2;
62 | }
63 |
64 | .app-header {
65 | position: relative;
66 | height: 64px;
67 | padding: 0;
68 | background: #fff;
69 | box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
70 | }
71 |
72 | nz-content {
73 | margin: 24px;
74 | }
75 |
76 | .inner-content {
77 | padding: 24px;
78 | background: #fff;
79 | height: 100%;
80 | }
81 |
82 | .login-div {
83 | display: inline-block;
84 | float: right;
85 | margin-right: 24px;
86 | }
87 |
--------------------------------------------------------------------------------
/authorization-server/src/main/resources/generator/generatorConfig.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
33 |
34 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/bean/UserVO.java:
--------------------------------------------------------------------------------
1 | package com.example.user.bean;
2 |
3 | import cn.hutool.core.bean.BeanUtil;
4 | import com.example.generator.model.User;
5 | import lombok.Data;
6 | import lombok.EqualsAndHashCode;
7 | import org.springframework.security.core.GrantedAuthority;
8 | import org.springframework.security.core.userdetails.UserDetails;
9 | import org.springframework.util.ObjectUtils;
10 |
11 | import java.util.ArrayList;
12 | import java.util.Collection;
13 | import java.util.List;
14 | import java.util.stream.Collectors;
15 |
16 | @EqualsAndHashCode(callSuper = true)
17 | @Data
18 | public class UserVO extends User implements UserDetails {
19 | private List roleVOList;
20 |
21 | public static UserVO from(User user) {
22 | UserVO userVO = new UserVO();
23 | BeanUtil.copyProperties(user, userVO);
24 | return userVO;
25 | }
26 |
27 | @Override
28 | public Collection extends GrantedAuthority> getAuthorities() {
29 | if (ObjectUtils.isEmpty(this.roleVOList)) {
30 | return new ArrayList<>();
31 | }
32 | return this.roleVOList.stream()
33 | .flatMap(s -> s.getResourceList().stream())
34 | .map(s -> (GrantedAuthority) s::getName)
35 | .collect(Collectors.toList());
36 | }
37 |
38 | @Override
39 | public boolean isAccountNonExpired() {
40 | return true;
41 | }
42 |
43 | @Override
44 | public boolean isAccountNonLocked() {
45 | return true;
46 | }
47 |
48 | @Override
49 | public boolean isCredentialsNonExpired() {
50 | return true;
51 | }
52 |
53 | @Override
54 | public boolean isEnabled() {
55 | return this.getEnable() != null && this.getEnable();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/resource-server/src/main/resources/generator/generatorConfig.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 |
30 |
33 |
34 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/generator/model/Client.java:
--------------------------------------------------------------------------------
1 | package com.example.generator.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.Id;
9 | import javax.persistence.Table;
10 |
11 | /**
12 | * 表名:t_client
13 | * 表注释:client表
14 | */
15 | @Getter
16 | @Setter
17 | @Table(name = "t_client")
18 | public class Client {
19 | @Id
20 | @Column(name = "id")
21 | @GeneratedValue(generator = "JDBC")
22 | private Integer id;
23 |
24 | @Column(name = "client_id")
25 | private String clientId;
26 |
27 | @Column(name = "client_secret")
28 | private String clientSecret;
29 |
30 | /**
31 | * access_token 有效期,单位:秒,默认7200秒,即2小时。
32 | */
33 | @Column(name = "access_token_validity_seconds")
34 | private Integer accessTokenValiditySeconds;
35 |
36 | /**
37 | * refresh_token 有效期,单位:秒。默认172800秒,即2天。
38 | */
39 | @Column(name = "refresh_token_validity_seconds")
40 | private Integer refreshTokenValiditySeconds;
41 |
42 | /**
43 | * 授权类型
44 | */
45 | @Column(name = "authorized_grant_types_str")
46 | private String authorizedGrantTypesStr;
47 |
48 | /**
49 | * 跳转 uri
50 | */
51 | @Column(name = "registered_redirect_uri_str")
52 | private String registeredRedirectUriStr;
53 |
54 | @Column(name = "scope_str")
55 | private String scopeStr;
56 |
57 | @Column(name = "auto_approve_scope")
58 | private String autoApproveScope;
59 |
60 | @Column(name = "authorities_str")
61 | private String authoritiesStr;
62 |
63 | @Column(name = "additional_information_str")
64 | private String additionalInformationStr;
65 |
66 | @Column(name = "resource_ids_str")
67 | private String resourceIdsStr;
68 | }
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {CommonModule} from '@angular/common';
3 | import {HttpClientModule} from "@angular/common/http";
4 | import {FormsModule, ReactiveFormsModule} from "@angular/forms";
5 | import {NzMenuModule} from "ng-zorro-antd/menu";
6 | import {NzFormModule} from "ng-zorro-antd/form";
7 | import {NzLayoutModule} from "ng-zorro-antd/layout";
8 | import {IconsProviderModule} from "./icons-provider.module";
9 | import {NzDividerModule} from "ng-zorro-antd/divider";
10 | import {NzButtonModule} from "ng-zorro-antd/button";
11 | import {NzTableModule} from "ng-zorro-antd/table";
12 | import {NzInputModule} from "ng-zorro-antd/input";
13 | import {NzSelectModule} from "ng-zorro-antd/select";
14 | import {NzMessageModule} from "ng-zorro-antd/message";
15 | import {GenderPipe} from "./pipe/gender.pipe";
16 | import {NzCheckboxModule} from "ng-zorro-antd/checkbox";
17 |
18 | const THIRD_MODULES: any[] = [
19 | FormsModule,
20 | ReactiveFormsModule,
21 | HttpClientModule,
22 | ];
23 | const NG_ZORRO_MODULES: any[] = [
24 | IconsProviderModule,
25 | NzLayoutModule,
26 | NzFormModule,
27 | NzMenuModule,
28 | NzButtonModule,
29 | NzTableModule,
30 | NzDividerModule,
31 | NzInputModule,
32 | NzSelectModule,
33 | NzMessageModule,
34 | NzCheckboxModule,
35 | ];
36 | const COMPONENTS: any[] = [];
37 | const DIRECTIVES: any[] = [];
38 | const PIPES: any[] = [
39 | GenderPipe,
40 | ];
41 |
42 | @NgModule({
43 | declarations: [
44 | ...PIPES,
45 | ...COMPONENTS,
46 | ...DIRECTIVES,
47 | ],
48 | imports: [
49 | CommonModule,
50 | ...THIRD_MODULES,
51 | ...NG_ZORRO_MODULES,
52 | ],
53 | exports: [
54 | ...THIRD_MODULES,
55 | ...NG_ZORRO_MODULES,
56 | ...PIPES,
57 | ...COMPONENTS,
58 | ...DIRECTIVES,
59 | ],
60 | })
61 | export class SharedModule {
62 | }
63 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/register/register.component.html:
--------------------------------------------------------------------------------
1 | 注册用户
2 |
3 |
40 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/guard/login.guard.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {
3 | ActivatedRouteSnapshot,
4 | CanActivate,
5 | CanActivateChild,
6 | Router,
7 | RouterStateSnapshot,
8 | UrlTree
9 | } from '@angular/router';
10 | import {map, Observable} from 'rxjs';
11 | import {HttpClient} from "@angular/common/http";
12 | import {CommonService} from "../common/common.service";
13 |
14 | @Injectable({
15 | providedIn: 'root'
16 | })
17 | export class LoginGuard implements CanActivate, CanActivateChild {
18 | constructor(
19 | private http: HttpClient,
20 | private router: Router,
21 | private service: CommonService,
22 | ) {
23 | }
24 |
25 | getTokenByCode(code: string, state: string | null): Observable {
26 | const grantType = 'authorization_code'
27 | const param = `grant_type=${grantType}&code=${code}&redirect_uri=http://localhost:4200/login`
28 | const url = `/oauth/token?${param}`;
29 | const headers = {
30 | 'Authorization': `Basic ${btoa('client:secret')}`
31 | }
32 | return this.http.post(url, null, {headers}).pipe(map(r => {
33 | this.service.token = r
34 | this.service.success(`登录成功`)
35 | if (!!state) {
36 | this.router.navigate([state])
37 | } else {
38 | this.router.navigate(['/index'])
39 | }
40 | return false
41 | }))
42 | }
43 |
44 | canActivate(
45 | route: ActivatedRouteSnapshot,
46 | state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree {
47 | const code = route.queryParamMap.get('code')
48 | if (!!code) {
49 | const state = route.queryParamMap.get('state')
50 | return this.getTokenByCode(code, state)
51 | }
52 | return true;
53 | }
54 |
55 | canActivateChild(
56 | childRoute: ActivatedRouteSnapshot,
57 | state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree {
58 | console.log('canActivateChild')
59 | return true;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/user/UserService.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.generator.mapper.UserMapper;
4 | import com.example.generator.model.User;
5 | import com.example.user.vo.ResetUserPasswordVO;
6 | import com.example.user.vo.UserDetailVO;
7 | import com.example.user.vo.UserVO;
8 | import org.springframework.security.crypto.password.PasswordEncoder;
9 | import org.springframework.stereotype.Service;
10 | import tk.mybatis.mapper.entity.Example;
11 |
12 | import javax.annotation.Resource;
13 | import java.util.List;
14 |
15 | @Service
16 | public class UserService {
17 | @Resource
18 | UserMapper userMapper;
19 | @Resource
20 | PasswordEncoder passwordEncoder;
21 |
22 | public List userList() {
23 | return userMapper.selectAllFetchDetail(null);
24 | }
25 |
26 | public UserVO showUserDetail(Integer id) {
27 | return userMapper.selectAllFetchDetail(id).get(0);
28 | }
29 |
30 | public void resetUserPassword(ResetUserPasswordVO resetUserPasswordVO) {
31 | User user = getUserByUsername(resetUserPasswordVO.getUsername());
32 | user.setPassword(passwordEncoder.encode(resetUserPasswordVO.getPassword()));
33 | userMapper.updateByPrimaryKeySelective(user);
34 | }
35 |
36 | public User getUserByUsername(String username) {
37 | Example example = new Example(User.class);
38 | example.createCriteria().andEqualTo("username", username);
39 | return userMapper.selectOneByExample(example);
40 | }
41 |
42 | public List showPersonalRole(String username) {
43 | return userMapper.showPersonalRole(username);
44 | }
45 |
46 | public UserDetailVO showPersonalDetail(String username) {
47 | return userMapper.showPersonalDetail(username);
48 | }
49 |
50 | public void updatePersonalDetail(UserDetailVO userDetailVO, String username) {
51 | User user = getUserByUsername(username);
52 | userDetailVO.setUserId(user.getId());
53 | userMapper.updatePersonalDetail(userDetailVO);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/reset-user-password/reset-user-password.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {ActivatedRoute, Router} from "@angular/router";
3 | import {CommonService} from "../../shared/common/common.service";
4 | import {HttpClient} from "@angular/common/http";
5 | import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
6 |
7 | @Component({
8 | selector: 'app-reset-user-password',
9 | templateUrl: './reset-user-password.component.html',
10 | styleUrls: ['./reset-user-password.component.scss']
11 | })
12 | export class ResetUserPasswordComponent implements OnInit {
13 | validateForm: FormGroup;
14 |
15 | constructor(
16 | private route: ActivatedRoute,
17 | private router: Router,
18 | private service: CommonService,
19 | private http: HttpClient,
20 | private fb: FormBuilder,
21 | ) {
22 | this.validateForm = this.fb.group({
23 | username: [{value: null, disabled: true}],
24 | password: [null, [Validators.required]],
25 | passwordAgain: [null, [Validators.required, this.confirmationValidator]],
26 | })
27 | }
28 |
29 | ngOnInit(): void {
30 | this.route.queryParams.subscribe(r => {
31 | if (!r['id']) {
32 | this.service.info(`请先选择用户`)
33 | this.router.navigate(['/showUserList'])
34 | } else {
35 | this.http.get(`/user/details/${r['id']}`).subscribe(r1 => {
36 | this.validateForm.patchValue({username: r1['username']})
37 | })
38 | }
39 | })
40 | }
41 |
42 | get password() {
43 | return this.validateForm.get('password') as FormControl
44 | }
45 |
46 | confirmationValidator = (control: FormControl): { [s: string]: boolean } => {
47 | if (!control.value) {
48 | return {required: true};
49 | } else if (control.value !== this.password.value) {
50 | return {confirm: true, error: true};
51 | }
52 | return {};
53 | };
54 |
55 | submitForm() {
56 | this.http.post(`/user/resetUserPassword`, this.validateForm.getRawValue()).subscribe(() => {
57 | this.service.success(`密码重置成功,即将跳转到用户列表`)
58 | this.router.navigate(['/showUserList'])
59 | })
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/pages/register/register.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
3 | import {HttpClient} from "@angular/common/http";
4 | import {Router} from "@angular/router";
5 | import {CommonService} from "../../shared/common/common.service";
6 |
7 | @Component({
8 | selector: 'app-register',
9 | templateUrl: './register.component.html',
10 | styleUrls: ['./register.component.scss']
11 | })
12 | export class RegisterComponent implements OnInit {
13 | validateForm: FormGroup;
14 |
15 | constructor(
16 | private fb: FormBuilder,
17 | private http: HttpClient,
18 | private router: Router,
19 | private service: CommonService,
20 | ) {
21 | this.validateForm = this.fb.group({
22 | username: [null, [Validators.required]],
23 | password: [null, [Validators.required]],
24 | passwordAgain: [null, [Validators.required, this.confirmationValidator]],
25 | agree: [false],
26 | })
27 | }
28 |
29 | ngOnInit(): void {
30 | }
31 |
32 | get password() {
33 | return this.validateForm.get('password') as FormControl
34 | }
35 |
36 | confirmationValidator = (control: FormControl): { [s: string]: boolean } => {
37 | if (!control.value) {
38 | return {required: true};
39 | } else if (control.value !== this.password.value) {
40 | return {confirm: true, error: true};
41 | }
42 | return {};
43 | };
44 |
45 | submitForm() {
46 | if (this.validateForm.get('agree')?.value === false) {
47 | this.service.info(`请阅读并同意协议`)
48 | return
49 | }
50 | this.http.post(`/oauth/register`, this.validateForm.getRawValue()).subscribe({
51 | next: () => {
52 | this.service.success(`注册成功,即将跳转到登录页面`)
53 | this.router.navigate(['/login'])
54 | },
55 | error: (e) => {
56 | if (e?.status === 400 && e?.error?.errors[0]?.defaultMessage) {
57 | this.service.error(`注册失败:${e.error.errors[0].defaultMessage}`)
58 | } else {
59 | this.service.error(`注册失败`)
60 | }
61 | }
62 | })
63 | }
64 |
65 | agreement() {
66 | this.service.info(`哎,啥都没有`)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/authorization-server/src/test/java/com/example/utils/TestRequestUtils.java:
--------------------------------------------------------------------------------
1 | package com.example.utils;
2 |
3 | import cn.hutool.json.JSONObject;
4 | import cn.hutool.json.JSONUtil;
5 | import lombok.Data;
6 | import org.springframework.http.HttpEntity;
7 | import org.springframework.http.HttpHeaders;
8 | import org.springframework.http.MediaType;
9 | import org.springframework.util.LinkedMultiValueMap;
10 | import org.springframework.util.MultiValueMap;
11 |
12 | /**
13 | * 测试时构建请求参数
14 | */
15 | public class TestRequestUtils {
16 | public static JsonRequestBuilder jsonRequest() {
17 | JsonRequestBuilder builder = new JsonRequestBuilder();
18 | builder.getHeaders().setContentType(MediaType.APPLICATION_JSON);
19 | return builder;
20 | }
21 |
22 | public static UrlEncodedRequestBuilder urlEncodedRequest() {
23 | UrlEncodedRequestBuilder builder = new UrlEncodedRequestBuilder();
24 | builder.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
25 | return builder;
26 | }
27 |
28 | @Data
29 | public static class UrlEncodedRequestBuilder {
30 | MultiValueMap body = new LinkedMultiValueMap<>();
31 | HttpHeaders headers = new HttpHeaders();
32 |
33 | public UrlEncodedRequestBuilder put(String key, String value) {
34 | this.getBody().add(key, value);
35 | return this;
36 | }
37 |
38 | public UrlEncodedRequestBuilder header(String name, String value) {
39 | this.getHeaders().set(name, value);
40 | return this;
41 | }
42 |
43 | public HttpEntity> build() {
44 | return new HttpEntity<>(body, headers);
45 | }
46 | }
47 |
48 | @Data
49 | public static class JsonRequestBuilder {
50 | HttpHeaders headers = new HttpHeaders();
51 | JSONObject body = JSONUtil.createObj();
52 |
53 | public JsonRequestBuilder set(String key, String value) {
54 | this.getBody().set(key, value);
55 | return this;
56 | }
57 |
58 | public JsonRequestBuilder header(String name, String value) {
59 | this.getHeaders().set(name, value);
60 | return this;
61 | }
62 |
63 | public HttpEntity build() {
64 | return new HttpEntity<>(body, headers);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/resource-server/src/main/java/com/example/user/UserController.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.user.vo.ResetUserPasswordVO;
4 | import com.example.user.vo.UserDetailVO;
5 | import com.example.user.vo.UserVO;
6 | import org.springframework.security.access.prepost.PreAuthorize;
7 | import org.springframework.validation.annotation.Validated;
8 | import org.springframework.web.bind.annotation.*;
9 |
10 | import javax.annotation.Resource;
11 | import java.security.Principal;
12 | import java.util.List;
13 |
14 | @RestController
15 | public class UserController {
16 | @Resource
17 | UserService userService;
18 |
19 | /**
20 | * 查看用户列表
21 | */
22 | @GetMapping("/user")
23 | @PreAuthorize("hasAuthority('showUserList')")
24 | public List userList() {
25 | return userService.userList();
26 | }
27 |
28 | /**
29 | * 查看用户详情
30 | */
31 | @GetMapping("/user/details/{id}")
32 | @PreAuthorize("hasAuthority('showUserDetail')")
33 | public UserVO showUserDetail(@PathVariable Integer id) {
34 | return userService.showUserDetail(id);
35 | }
36 |
37 | /**
38 | * 重置用户密码
39 | */
40 | @PostMapping("/user/resetUserPassword")
41 | @PreAuthorize("hasAuthority('resetUserPassword')")
42 | public void resetUserPassword(@RequestBody @Validated ResetUserPasswordVO resetUserPasswordVO) {
43 | userService.resetUserPassword(resetUserPasswordVO);
44 | }
45 |
46 | /**
47 | * 查看个人角色
48 | */
49 | @GetMapping("/user/showPersonalRole")
50 | @PreAuthorize("hasAuthority('showPersonalRole')")
51 | public List showPersonalRole(Principal principal) {
52 | String username = principal.getName();
53 | return userService.showPersonalRole(username);
54 | }
55 |
56 | /**
57 | * 查看个人信息
58 | */
59 | @GetMapping("/user/showPersonalDetail")
60 | @PreAuthorize("hasAuthority('showPersonalDetail')")
61 | public UserDetailVO showPersonalDetail(Principal principal) {
62 | String username = principal.getName();
63 | return userService.showPersonalDetail(username);
64 | }
65 |
66 | /**
67 | * 编辑个人信息
68 | */
69 | @PostMapping("/user/updatePersonalDetail")
70 | @PreAuthorize("hasAuthority('updatePersonalDetail')")
71 | public void updatePersonalDetail(@RequestBody UserDetailVO userDetailVO, Principal principal) {
72 | String username = principal.getName();
73 | userService.updatePersonalDetail(userDetailVO, username);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/demo-frontend/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including
12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js'; // Included with Angular CLI.
49 |
50 |
51 | /***************************************************************************************************
52 | * APPLICATION IMPORTS
53 | */
54 |
--------------------------------------------------------------------------------
/authorization-server/src/main/resources/mapper/UserMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
20 |
22 |
23 |
45 |
46 | insert into r_user_role (user_id, role_id)
47 | values (#{0}, 1)
48 |
49 |
50 | update t_user
51 | set password = #{param2}
52 | where username = #{param1}
53 |
54 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/shared/common/common.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {map, Observable, Subject} from "rxjs";
3 | import {NzMessageService} from "ng-zorro-antd/message";
4 | import {HttpClient} from "@angular/common/http";
5 | import {Router} from "@angular/router";
6 |
7 | @Injectable({
8 | providedIn: 'root'
9 | })
10 | export class CommonService {
11 | private tokenSource = new Subject()
12 | private userSource = new Subject()
13 | token$ = this.tokenSource.asObservable()
14 | user$ = this.userSource.asObservable()
15 |
16 | constructor(
17 | private message: NzMessageService,
18 | private http: HttpClient,
19 | private router: Router,
20 | ) {
21 | }
22 |
23 | get user() {
24 | return JSON.parse(localStorage.getItem('user') || '{}')
25 | }
26 |
27 | set user(user: any) {
28 | localStorage.setItem('user', JSON.stringify(user))
29 | this.userSource.next(user)
30 | }
31 |
32 | get expired() {
33 | // 提前10秒,避免踩点过期
34 | return this.user.exp * 1000 - 10000 < new Date().getTime()
35 | }
36 |
37 | clearAuth() {
38 | this.user = null
39 | this.token = null
40 | }
41 |
42 | noAuth() {
43 | this.clearAuth()
44 | this.router.navigate(['/index'])
45 | }
46 |
47 | get token() {
48 | return JSON.parse(localStorage.getItem('token') || '{}')
49 | }
50 |
51 | set token(token: any) {
52 | if (!!token) {
53 | const user = atob(token['access_token'].split('.')[1])
54 | this.user = JSON.parse(user)
55 | }
56 | localStorage.setItem('token', JSON.stringify(token))
57 | this.tokenSource.next(token)
58 | }
59 |
60 | success(message = `保存成功,正在返回列表`) {
61 | this.message.success(message)
62 | }
63 |
64 | error(message = `保存失败,请联系管理员`) {
65 | this.message.error(message)
66 | }
67 |
68 | info(message = `警告`) {
69 | this.message.info(message)
70 | }
71 |
72 | refreshToken(): Observable {
73 | let param = `grant_type=refresh_token&refresh_token=${this.token.refresh_token}`
74 | let url = `/oauth/token?${param}`
75 | let headers = {
76 | 'Authorization': `Basic ${btoa('client:secret')}`
77 | }
78 | return this.http.post(url, null, {headers}).pipe(map(r => {
79 | this.token = r
80 | return true
81 | }))
82 | }
83 |
84 | login(username: string, password: string) {
85 | let param = `grant_type=password&username=${username}&password=${password}`
86 | let url = `/oauth/token?${param}`
87 | let headers = {
88 | 'Authorization': `Basic ${btoa('client:secret')}`
89 | }
90 | return this.http.post(url, null, {headers})
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/resource-server/src/test/java/com/example/user/UserServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.generator.mapper.UserMapper;
4 | import com.example.generator.model.User;
5 | import com.example.user.vo.ResetUserPasswordVO;
6 | import com.example.user.vo.UserVO;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.Test;
9 | import org.junit.jupiter.api.extension.ExtendWith;
10 | import org.mockito.InjectMocks;
11 | import org.mockito.Mock;
12 | import org.mockito.Mockito;
13 | import org.mockito.junit.jupiter.MockitoExtension;
14 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
15 | import org.springframework.security.crypto.password.PasswordEncoder;
16 | import tk.mybatis.mapper.entity.Config;
17 | import tk.mybatis.mapper.mapperhelper.EntityHelper;
18 |
19 | import java.util.Arrays;
20 | import java.util.List;
21 |
22 | import static org.junit.jupiter.api.Assertions.assertEquals;
23 | import static org.mockito.ArgumentMatchers.any;
24 |
25 | @ExtendWith(MockitoExtension.class)
26 | class UserServiceTest {
27 | @InjectMocks
28 | UserService userService;
29 | @Mock
30 | UserMapper userMapper;
31 | @Mock
32 | PasswordEncoder passwordEncoder;
33 |
34 | @BeforeEach
35 | void setUp() {
36 | // mapper插件在测试之前要初始化table,否则构建example会失败
37 | EntityHelper.initEntityNameMap(User.class, new Config());
38 | }
39 |
40 | @Test
41 | void userList() {
42 | Mockito.when(userMapper.selectAllFetchDetail(null)).thenReturn(Arrays.asList(new UserVO(), new UserVO()));
43 | List userVOS = userService.userList();
44 | assertEquals(2, userVOS.size());
45 | }
46 |
47 | @Test
48 | void showUserDetail() {
49 | UserVO userVO = new UserVO();
50 | Mockito.when(userMapper.selectAllFetchDetail(1)).thenReturn(List.of(userVO));
51 | UserVO result = userService.showUserDetail(1);
52 | assertEquals(result, userVO);
53 | }
54 |
55 | @Test
56 | void resetUserPassword() {
57 | String username = "username";
58 | String password = "password";
59 |
60 | String encode = new BCryptPasswordEncoder().encode(password);
61 | Mockito.when(passwordEncoder.encode(password)).thenReturn(encode);
62 |
63 | User user = new User();
64 | user.setUsername(username);
65 | user.setPassword(encode);
66 |
67 | Mockito.when(userMapper.selectOneByExample(any())).thenReturn(user);
68 |
69 | ResetUserPasswordVO vo = new ResetUserPasswordVO();
70 | vo.setUsername(username);
71 | vo.setPassword(password);
72 | userService.resetUserPassword(vo);
73 | }
74 | }
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/config/security/CustomLogoutHandler.java:
--------------------------------------------------------------------------------
1 | package com.example.config.security;
2 |
3 | import org.springframework.security.core.Authentication;
4 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
5 | import org.springframework.security.oauth2.provider.token.TokenStore;
6 | import org.springframework.security.web.authentication.logout.LogoutHandler;
7 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
8 | import org.springframework.stereotype.Component;
9 | import org.springframework.util.ObjectUtils;
10 |
11 | import javax.annotation.Resource;
12 | import javax.servlet.http.HttpServletRequest;
13 | import javax.servlet.http.HttpServletResponse;
14 | import java.io.IOException;
15 |
16 | /**
17 | * 自定义注销相关处理逻辑
18 | */
19 | @Component
20 | public class CustomLogoutHandler implements LogoutHandler, LogoutSuccessHandler {
21 | @Resource
22 | TokenStore tokenStore;
23 |
24 | /**
25 | * 注销逻辑
26 | */
27 | @Override
28 | public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
29 | // 从请求中获取token,这一步也可以根据自己实际使用场景来获取,比如在header中取等等
30 | String token;
31 | if (!ObjectUtils.isEmpty(request.getHeader("Authorization")) && request.getHeader("Authorization").contains("bearer ")) {
32 | token = request.getHeader("Authorization").split("bearer ")[1];
33 | } else if (!ObjectUtils.isEmpty(request.getParameter("token"))) {
34 | token = request.getParameter("token");
35 | } else if (!ObjectUtils.isEmpty(request.getParameter("access_token"))) {
36 | token = request.getParameter("access_token");
37 | } else {
38 | return;
39 | }
40 | // 在token存储器中查询token
41 | OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
42 | if (oAuth2AccessToken == null) {
43 | return;
44 | }
45 | if (oAuth2AccessToken.getRefreshToken() != null) {
46 | // 在token存储中移除refresh_token
47 | tokenStore.removeRefreshToken(oAuth2AccessToken.getRefreshToken());
48 | }
49 | // 在token存储中移除access_token
50 | tokenStore.removeAccessToken(oAuth2AccessToken);
51 | }
52 |
53 | /**
54 | * 注销成功后逻辑
55 | */
56 | @Override
57 | public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
58 | // 在请求中获取重定向地址,这一步也可以根据自己实际使用场景来获取,比如在header中取等等
59 | String redirect = request.getParameter("redirect");
60 | // 重定向到新地址
61 | response.sendRedirect(redirect);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/client/ClientVO.java:
--------------------------------------------------------------------------------
1 | package com.example.client;
2 |
3 | import cn.hutool.core.bean.BeanUtil;
4 | import cn.hutool.json.JSONUtil;
5 | import com.example.generator.model.Client;
6 | import lombok.Data;
7 | import lombok.EqualsAndHashCode;
8 | import org.springframework.security.core.GrantedAuthority;
9 | import org.springframework.security.oauth2.provider.ClientDetails;
10 | import org.springframework.util.ObjectUtils;
11 | import org.springframework.util.StringUtils;
12 |
13 | import java.util.Collection;
14 | import java.util.Map;
15 | import java.util.Set;
16 | import java.util.stream.Collectors;
17 |
18 | @EqualsAndHashCode(callSuper = true)
19 | @Data
20 | public class ClientVO extends Client implements ClientDetails {
21 | public static ClientVO from(Client client) {
22 | ClientVO clientVO = new ClientVO();
23 | BeanUtil.copyProperties(client, clientVO);
24 | return clientVO;
25 | }
26 |
27 | @Override
28 | public Set getResourceIds() {
29 | return StringUtils.commaDelimitedListToSet(this.getResourceIdsStr());
30 | }
31 |
32 | @Override
33 | public boolean isSecretRequired() {
34 | return !ObjectUtils.isEmpty(this.getClientSecret());
35 | }
36 |
37 | @Override
38 | public boolean isScoped() {
39 | return !ObjectUtils.isEmpty(this.getScope());
40 | }
41 |
42 | @Override
43 | public Set getScope() {
44 | return StringUtils.commaDelimitedListToSet(this.getScopeStr());
45 | }
46 |
47 | @Override
48 | public Set getAuthorizedGrantTypes() {
49 | return StringUtils.commaDelimitedListToSet(this.getAuthorizedGrantTypesStr());
50 | }
51 |
52 | @Override
53 | public Set getRegisteredRedirectUri() {
54 | return StringUtils.commaDelimitedListToSet(this.getRegisteredRedirectUriStr());
55 | }
56 |
57 | @Override
58 | public Collection getAuthorities() {
59 | return StringUtils.commaDelimitedListToSet(this.getAuthoritiesStr())
60 | .stream()
61 | .map(s -> (GrantedAuthority) () -> s)
62 | .collect(Collectors.toList());
63 | }
64 |
65 | @Override
66 | public boolean isAutoApprove(String scope) {
67 | return !ObjectUtils.isEmpty(this.getAutoApproveScope())
68 | && StringUtils.commaDelimitedListToSet(this.getAutoApproveScope()).containsAll(this.getScope());
69 | }
70 |
71 | @Override
72 | public Map getAdditionalInformation() {
73 | if (ObjectUtils.isEmpty(this.getAdditionalInformationStr())) {
74 | return null;
75 | }
76 | return JSONUtil.parseObj(this.getAdditionalInformationStr());
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/authorization-server/src/test/resources/sql/unit-test-schema.sql:
--------------------------------------------------------------------------------
1 | create schema `spring-boot-oauth2-example`;
2 |
3 | create table `t_client`
4 | (
5 | id int auto_increment primary key,
6 | client_id varchar(100) not null,
7 | client_secret varchar(100) not null,
8 | access_token_validity_seconds int default 7200 not null comment 'access_token 有效期,单位:秒,默认7200秒,即2小时。',
9 | refresh_token_validity_seconds int default 172800 null comment 'refresh_token 有效期,单位:秒。默认172800秒,即2天。',
10 | authorized_grant_types_str varchar(200) default 'authorization_code,refresh_token,password' not null comment '授权类型',
11 | registered_redirect_uri_str varchar(500) null comment '跳转 uri',
12 | scope_str varchar(200) default 'app' not null,
13 | auto_approve_scope varchar(200) default 'app' null,
14 | authorities_str varchar(200) null,
15 | additional_information_str varchar(200) null,
16 | resource_ids_str varchar(200) null,
17 | constraint t_client_client_id_uindex unique (client_id)
18 | );
19 |
20 | create table t_user
21 | (
22 | id int auto_increment primary key,
23 | username varchar(100) not null comment '用户名',
24 | password varchar(100) not null comment '密码,根据需求加密后存储',
25 | enable bit default 1 not null comment '是否可用,1为可用,0为不可用,默认1可用',
26 | constraint t_user_username_uindex unique (username)
27 | );
28 |
29 | create table t_role
30 | (
31 | id int auto_increment primary key,
32 | name varchar(200) not null comment '角色名',
33 | description varchar(200) not null comment '角色描述',
34 | constraint t_role_name_uindex unique (name)
35 | );
36 |
37 | create table t_resource
38 | (
39 | id int auto_increment primary key,
40 | name varchar(40) not null comment '资源名,唯一,英文,尽量短',
41 | description varchar(200) not null comment '资源描述',
42 | constraint t_resource_name_uindex unique (name)
43 | );
44 |
45 | create table r_role_resource
46 | (
47 | role_id int not null,
48 | resource_id int not null,
49 | primary key (role_id, resource_id)
50 | );
51 |
52 | create table r_user_role
53 | (
54 | user_id int not null,
55 | role_id int not null,
56 | primary key (user_id, role_id)
57 | );
58 |
59 |
--------------------------------------------------------------------------------
/resource-server/src/test/java/com/example/user/UserProcessByMySQLModeTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import cn.hutool.json.JSONUtil;
4 | import com.example.config.TestResourceConfig;
5 | import com.example.user.vo.UserDetailVO;
6 | import org.junit.jupiter.api.Test;
7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 | import org.springframework.context.annotation.Import;
10 | import org.springframework.http.MediaType;
11 | import org.springframework.security.test.context.support.WithMockUser;
12 | import org.springframework.test.context.ActiveProfiles;
13 | import org.springframework.test.web.servlet.MockMvc;
14 |
15 | import javax.annotation.Resource;
16 |
17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21 |
22 | /**
23 | * 由于sql中使用了 on duplicate key update 语句,
24 | * h2数据库不支持,需要在配置中加上 MODE=MySQL;
25 | *
26 | * 而加上之后可能会影响其它测试用例,所以就提取到单独的一个测试类中
27 | */
28 | @SpringBootTest
29 | @AutoConfigureMockMvc
30 | @ActiveProfiles({"unit-test", "unit-test-mysql-mode"})
31 | @Import(TestResourceConfig.class)
32 | public class UserProcessByMySQLModeTest {
33 | @Resource
34 | MockMvc mockMvc;
35 |
36 | /**
37 | * {@link UserController#updatePersonalDetail(UserDetailVO)}
38 | */
39 | @Test
40 | @WithMockUser(authorities = {"updatePersonalDetail", "showPersonalDetail"})
41 | void updatePersonalDetail() throws Exception {
42 | String name = "测试用户111";
43 | Integer age = 22;
44 | String email = "test111@test.com";
45 | Integer gender = 2;
46 | UserDetailVO vo = new UserDetailVO();
47 | vo.setUserId(1);
48 | vo.setName(name);
49 | vo.setAge(age);
50 | vo.setEmail(email);
51 | vo.setGender(gender);
52 | mockMvc.perform(post("/user/updatePersonalDetail")
53 | .contentType(MediaType.APPLICATION_JSON)
54 | .content(JSONUtil.toJsonStr(vo))
55 | ).andExpect(status().isOk())
56 | ;
57 | mockMvc.perform(get("/user/showPersonalDetail"))
58 | .andExpect(status().isOk())
59 | .andExpect(jsonPath("$.id").value(1))
60 | .andExpect(jsonPath("$.userId").value(1))
61 | .andExpect(jsonPath("$.name").value(name))
62 | .andExpect(jsonPath("$.age").value(age))
63 | .andExpect(jsonPath("$.email").value(email))
64 | .andExpect(jsonPath("$.gender").value(gender))
65 | ;
66 |
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/config/security/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config.security;
2 |
3 | import com.example.user.UserService;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.security.authentication.AuthenticationManager;
7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
12 | import org.springframework.security.crypto.password.PasswordEncoder;
13 |
14 | import javax.annotation.Resource;
15 |
16 | /**
17 | * Web安全相关配置
18 | */
19 | @Configuration
20 | @EnableWebSecurity
21 | @EnableGlobalMethodSecurity(prePostEnabled = true)
22 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
23 | @Resource
24 | UserService userService;
25 | @Resource
26 | PasswordEncoder passwordEncoder;
27 | @Resource
28 | CustomLogoutHandler customLogoutHandler;
29 |
30 | /**
31 | * 为密码方式提供认证管理器
32 | */
33 | @Bean
34 | @Override
35 | public AuthenticationManager authenticationManagerBean() throws Exception {
36 | return super.authenticationManagerBean();
37 | }
38 |
39 | /**
40 | * 构建认证管理器
41 | */
42 | @Override
43 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
44 | auth
45 | // 提供用户详情实现
46 | .userDetailsService(userService)
47 | // 指定加密方式
48 | .passwordEncoder(passwordEncoder);
49 | }
50 |
51 | /**
52 | * http安全配置
53 | */
54 | @Override
55 | protected void configure(HttpSecurity http) throws Exception {
56 | http.authorizeRequests()
57 | // 注册接口允许未登录用户访问
58 | .antMatchers("/oauth/register").anonymous()
59 | // 修改密码接口允许所有人访问
60 | .antMatchers("/oauth/changePassword").permitAll()
61 | // 其余未指定的所有请求都需要授权
62 | .anyRequest().authenticated();
63 | http
64 | // 注销相关逻辑配置
65 | .logout()
66 | // 注销接口的url
67 | .logoutUrl("/oauth/logout")
68 | // 添加注销处理逻辑
69 | .addLogoutHandler(customLogoutHandler)
70 | // 注销之后的逻辑
71 | .logoutSuccessHandler(customLogoutHandler);
72 | // 关闭csrf校验
73 | http.csrf().disable();
74 | // 开启登录页面支持
75 | http.formLogin();
76 | // 开启http basic认证方式
77 | http.httpBasic();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
44 |
45 |
46 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/resource-server/src/main/resources/mapper/UserMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
34 |
46 |
48 |
49 |
50 |
62 |
71 |
--------------------------------------------------------------------------------
/demo-frontend/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {BrowserModule} from '@angular/platform-browser';
3 |
4 | import {AppComponent} from './app.component';
5 | import {RouterModule, Routes} from "@angular/router";
6 | import {NZ_I18N, zh_CN} from 'ng-zorro-antd/i18n';
7 | import {registerLocaleData} from '@angular/common';
8 | import zh from '@angular/common/locales/zh';
9 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
10 | import {SharedModule} from "./shared/shared.module";
11 | import {IndexComponent} from './pages/index/index.component';
12 | import {LoginGuard} from "./shared/guard/login.guard";
13 | import {ShowPersonalRoleComponent} from './pages/show-personal-role/show-personal-role.component';
14 | import {UpdatePersonalDetailComponent} from './pages/update-personal-detail/update-personal-detail.component';
15 | import {ShowUserListComponent} from './pages/show-user-list/show-user-list.component';
16 | import {ShowUserDetailComponent} from './pages/show-user-detail/show-user-detail.component';
17 | import {ResetUserPasswordComponent} from './pages/reset-user-password/reset-user-password.component';
18 | import {ShowPersonalDetailComponent} from './pages/show-personal-detail/show-personal-detail.component';
19 | import {AuthGuard} from "./shared/guard/auth.guard";
20 | import {LoginComponent} from './pages/login/login.component';
21 | import {HTTP_INTERCEPTORS} from "@angular/common/http";
22 | import {AuthInterceptor} from "./shared/common/auth.interceptor";
23 | import {RegisterComponent} from './pages/register/register.component';
24 |
25 | registerLocaleData(zh);
26 |
27 | const routes: Routes = [
28 | {path: '', redirectTo: 'index', pathMatch: 'full'},
29 | {path: 'index', component: IndexComponent},
30 | {path: 'login', component: LoginComponent, canActivate: [LoginGuard]},
31 | {
32 | path: '',
33 | canActivateChild: [AuthGuard], children: [
34 | {path: 'showPersonalRole', component: ShowPersonalRoleComponent},
35 | {path: 'updatePersonalDetail', component: UpdatePersonalDetailComponent},
36 | {path: 'showUserList', component: ShowUserListComponent},
37 | {path: 'showUserDetail', component: ShowUserDetailComponent},
38 | {path: 'resetUserPassword', component: ResetUserPasswordComponent},
39 | {path: 'showPersonalDetail', component: ShowPersonalDetailComponent},
40 | ]
41 | },
42 | {path: 'register', component: RegisterComponent},
43 | ]
44 |
45 | @NgModule({
46 | declarations: [
47 | AppComponent,
48 | IndexComponent,
49 | ShowPersonalRoleComponent,
50 | UpdatePersonalDetailComponent,
51 | ShowUserListComponent,
52 | ShowUserDetailComponent,
53 | ResetUserPasswordComponent,
54 | ShowPersonalDetailComponent,
55 | LoginComponent,
56 | RegisterComponent,
57 | ],
58 | imports: [
59 | BrowserModule,
60 | BrowserAnimationsModule,
61 | RouterModule.forRoot(routes),
62 | SharedModule,
63 | ],
64 | providers: [
65 | {provide: NZ_I18N, useValue: zh_CN},
66 | {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
67 | ],
68 | bootstrap: [AppComponent]
69 | })
70 | export class AppModule {
71 | }
72 |
--------------------------------------------------------------------------------
/sql/schema.sql:
--------------------------------------------------------------------------------
1 | # drop database `spring-boot-oauth2-example`;
2 | create database `spring-boot-oauth2-example`;
3 |
4 | use `spring-boot-oauth2-example`;
5 |
6 | create table `t_client`
7 | (
8 | id int auto_increment primary key,
9 | client_id varchar(100) not null,
10 | client_secret varchar(100) not null,
11 | access_token_validity_seconds int default 7200 not null comment 'access_token 有效期,单位:秒,默认7200秒,即2小时。',
12 | refresh_token_validity_seconds int default 172800 null comment 'refresh_token 有效期,单位:秒。默认172800秒,即2天。',
13 | authorized_grant_types_str varchar(200) default 'authorization_code,refresh_token,password' not null comment '授权类型',
14 | registered_redirect_uri_str varchar(500) null comment '跳转 uri',
15 | scope_str varchar(200) default 'app' not null,
16 | auto_approve_scope varchar(200) default 'app' null,
17 | authorities_str varchar(200) null,
18 | additional_information_str varchar(200) null,
19 | resource_ids_str varchar(200) null,
20 | constraint t_client_client_id_uindex unique (client_id)
21 | ) comment 'client表';
22 |
23 | create table t_role
24 | (
25 | id int auto_increment primary key,
26 | name varchar(200) not null comment '角色名',
27 | description varchar(200) not null comment '角色描述',
28 | constraint t_role_name_uindex unique (name)
29 | ) comment '角色表';
30 |
31 | create table t_resource
32 | (
33 | id int auto_increment primary key,
34 | name varchar(40) not null comment '资源名,唯一,英文,尽量短',
35 | description varchar(200) not null comment '资源描述',
36 | constraint t_resource_name_uindex unique (name)
37 | ) comment '资源表';
38 |
39 | create table r_role_resource
40 | (
41 | role_id int not null,
42 | resource_id int not null,
43 | primary key (role_id, resource_id)
44 | ) comment '角色资源中间表';
45 |
46 | create table t_user
47 | (
48 | id int auto_increment primary key,
49 | username varchar(100) not null comment '用户名',
50 | password varchar(100) not null comment '密码,根据需求加密后存储',
51 | enable bit default 1 not null comment '是否可用,1为可用,0为不可用,默认1可用',
52 | constraint t_user_username_uindex unique (username)
53 | ) comment '用户表';
54 |
55 | create table r_user_role
56 | (
57 | user_id int not null,
58 | role_id int not null,
59 | primary key (user_id, role_id)
60 | ) comment '用户角色中间表';
61 |
62 | create table t_user_detail
63 | (
64 | id int auto_increment primary key,
65 | user_id int not null comment '关联t_user表id',
66 | name varchar(20) null comment '用户姓名',
67 | age int null comment '年龄',
68 | email varchar(100) null comment '电子邮箱',
69 | gender int null comment '性别,1为男性,2为女性,3为其它',
70 | constraint t_user_detail_user_id_uindex unique (user_id)
71 | ) comment '用户详情表';
72 |
73 |
74 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/user/UserService.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import com.example.generator.mapper.UserMapper;
4 | import com.example.generator.model.User;
5 | import com.example.user.bean.UserChangePasswordVO;
6 | import com.example.user.bean.UserRegisterVO;
7 | import com.example.user.bean.UserVO;
8 | import org.springframework.security.core.userdetails.UserDetails;
9 | import org.springframework.security.core.userdetails.UserDetailsService;
10 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
11 | import org.springframework.security.crypto.password.PasswordEncoder;
12 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
13 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
14 | import org.springframework.security.oauth2.provider.token.TokenStore;
15 | import org.springframework.stereotype.Service;
16 | import org.springframework.util.ObjectUtils;
17 | import tk.mybatis.mapper.entity.Example;
18 |
19 | import javax.annotation.Resource;
20 | import java.util.List;
21 |
22 | import static org.hibernate.validator.internal.util.Contracts.assertNotNull;
23 |
24 | @Service
25 | public class UserService implements UserDetailsService {
26 | @Resource
27 | UserMapper userMapper;
28 | @Resource
29 | PasswordEncoder passwordEncoder;
30 | @Resource
31 | TokenStore tokenStore;
32 |
33 | public List findAllFetchRoleAndResource() {
34 | return userMapper.findAllFetchRoleAndResource(null);
35 | }
36 |
37 | @Override
38 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
39 | List allFetchRoleAndResource = userMapper.findAllFetchRoleAndResource(username);
40 | if (ObjectUtils.isEmpty(allFetchRoleAndResource)) {
41 | throw new UsernameNotFoundException("用户名不存在");
42 | }
43 | return allFetchRoleAndResource.get(0);
44 | }
45 |
46 | public void register(UserRegisterVO userRegisterVO) {
47 | User user = new User();
48 | user.setUsername(userRegisterVO.getUsername());
49 | user.setPassword(passwordEncoder.encode(userRegisterVO.getPassword()));
50 | userMapper.insertSelective(user);
51 | assertNotNull(user.getId());
52 | userMapper.initRoleByUserId(user.getId());
53 | }
54 |
55 | public User selectUserByUsername(String username) {
56 | Example example = new Example(User.class);
57 | example.createCriteria().andEqualTo("username", username);
58 | return userMapper.selectOneByExample(example);
59 | }
60 |
61 | public void changePassword(UserChangePasswordVO userChangePasswordVO) {
62 | String token = userChangePasswordVO.getToken();
63 | OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(token);
64 | String username = oAuth2Authentication.getName();
65 | userMapper.changePassword(username, passwordEncoder.encode(userChangePasswordVO.getPassword()));
66 | OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
67 | if (oAuth2AccessToken.getRefreshToken() != null) {
68 | // 在token存储中移除refresh_token
69 | tokenStore.removeRefreshToken(oAuth2AccessToken.getRefreshToken());
70 | }
71 | // 在token存储中移除access_token
72 | tokenStore.removeAccessToken(oAuth2AccessToken);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/authorization-server/src/main/java/com/example/config/security/AuthConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.config.security;
2 |
3 | import com.example.client.ClientService;
4 | import com.example.user.UserService;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.security.authentication.AuthenticationManager;
8 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
9 | import org.springframework.security.crypto.password.PasswordEncoder;
10 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
11 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
12 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
13 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
14 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
15 | import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
16 | import org.springframework.security.oauth2.provider.token.TokenStore;
17 | import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
18 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
19 |
20 | import javax.annotation.Resource;
21 |
22 | /**
23 | * 授权服务器相关配置
24 | */
25 | @Configuration
26 | @EnableAuthorizationServer
27 | public class AuthConfig extends AuthorizationServerConfigurerAdapter {
28 | @Resource
29 | ClientService clientService;
30 | @Resource
31 | UserService userService;
32 | @Resource
33 | AuthenticationManager authenticationManager;
34 |
35 | /**
36 | * 加密方式
37 | */
38 | @Bean
39 | public PasswordEncoder passwordEncoder() {
40 | return new BCryptPasswordEncoder();
41 | }
42 |
43 | /**
44 | * jwt类型token转换器
45 | */
46 | @Bean
47 | public AccessTokenConverter accessTokenConverter() {
48 | return new JwtAccessTokenConverter();
49 | }
50 |
51 | /**
52 | * token存储方式
53 | * fixme 测试时用内存级,生产以及多实例环境应该使用redis等方式
54 | */
55 | @Bean
56 | public TokenStore tokenStore() {
57 | return new InMemoryTokenStore();
58 | }
59 |
60 | /**
61 | * 授权服务器安全相关配置
62 | */
63 | @Override
64 | public void configure(AuthorizationServerSecurityConfigurer security) {
65 | security
66 | // 配置加密方式
67 | .passwordEncoder(passwordEncoder())
68 | // jwt单体服务模式一般不会用到,在资源服务器常用
69 | .checkTokenAccess("isAuthenticated()");
70 | }
71 |
72 | /**
73 | * 客户端相关配置
74 | */
75 | @Override
76 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
77 | // 配置客户端详情实现类
78 | clients.withClientDetails(clientService);
79 | }
80 |
81 | /**
82 | * 授权服务器默认接口配置
83 | */
84 | @Override
85 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
86 | endpoints
87 | // 配置使用jwt进行token转换
88 | .accessTokenConverter(accessTokenConverter())
89 | // 由于要使用password方式获取token,所以需要authenticationManager
90 | .authenticationManager(authenticationManager)
91 | // 配置用户详情实现类
92 | .userDetailsService(userService)
93 | // 配置token存储方式
94 | .tokenStore(tokenStore())
95 | ;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/demo-frontend/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "cli": {
4 | "analytics": false
5 | },
6 | "version": 1,
7 | "newProjectRoot": "projects",
8 | "projects": {
9 | "demo-frontend": {
10 | "projectType": "application",
11 | "schematics": {
12 | "@schematics/angular:component": {
13 | "style": "scss"
14 | },
15 | "@schematics/angular:application": {
16 | "strict": true
17 | }
18 | },
19 | "root": "",
20 | "sourceRoot": "src",
21 | "prefix": "app",
22 | "architect": {
23 | "build": {
24 | "builder": "@angular-devkit/build-angular:browser",
25 | "options": {
26 | "outputPath": "dist/demo-frontend",
27 | "index": "src/index.html",
28 | "main": "src/main.ts",
29 | "polyfills": "src/polyfills.ts",
30 | "tsConfig": "tsconfig.app.json",
31 | "inlineStyleLanguage": "scss",
32 | "assets": [
33 | "src/favicon.ico",
34 | "src/assets",
35 | {
36 | "glob": "**/*",
37 | "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
38 | "output": "/assets/"
39 | }
40 | ],
41 | "styles": [
42 | "src/theme.less",
43 | "src/styles.scss"
44 | ],
45 | "scripts": []
46 | },
47 | "configurations": {
48 | "production": {
49 | "budgets": [
50 | {
51 | "type": "initial",
52 | "maximumWarning": "500kb",
53 | "maximumError": "1mb"
54 | },
55 | {
56 | "type": "anyComponentStyle",
57 | "maximumWarning": "2kb",
58 | "maximumError": "4kb"
59 | }
60 | ],
61 | "fileReplacements": [
62 | {
63 | "replace": "src/environments/environment.ts",
64 | "with": "src/environments/environment.prod.ts"
65 | }
66 | ],
67 | "outputHashing": "all"
68 | },
69 | "development": {
70 | "buildOptimizer": false,
71 | "optimization": false,
72 | "vendorChunk": true,
73 | "extractLicenses": false,
74 | "sourceMap": true,
75 | "namedChunks": true
76 | }
77 | },
78 | "defaultConfiguration": "production"
79 | },
80 | "serve": {
81 | "builder": "@angular-devkit/build-angular:dev-server",
82 | "configurations": {
83 | "production": {
84 | "browserTarget": "demo-frontend:build:production"
85 | },
86 | "development": {
87 | "browserTarget": "demo-frontend:build:development"
88 | }
89 | },
90 | "defaultConfiguration": "development"
91 | },
92 | "extract-i18n": {
93 | "builder": "@angular-devkit/build-angular:extract-i18n",
94 | "options": {
95 | "browserTarget": "demo-frontend:build"
96 | }
97 | },
98 | "test": {
99 | "builder": "@angular-devkit/build-angular:karma",
100 | "options": {
101 | "main": "src/test.ts",
102 | "polyfills": "src/polyfills.ts",
103 | "tsConfig": "tsconfig.spec.json",
104 | "karmaConfig": "karma.conf.js",
105 | "inlineStyleLanguage": "scss",
106 | "assets": [
107 | "src/favicon.ico",
108 | "src/assets"
109 | ],
110 | "styles": [
111 | "src/styles.scss"
112 | ],
113 | "scripts": []
114 | }
115 | }
116 | }
117 | }
118 | },
119 | "defaultProject": "demo-frontend"
120 | }
121 |
--------------------------------------------------------------------------------
/resource-server/src/test/java/com/example/user/UserControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import cn.hutool.json.JSONUtil;
4 | import com.example.config.ResourceConfig;
5 | import com.example.config.TestResourceConfig;
6 | import com.example.user.vo.UserVO;
7 | import org.junit.jupiter.api.Test;
8 | import org.mockito.Mockito;
9 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
10 | import org.springframework.boot.test.mock.mockito.MockBean;
11 | import org.springframework.context.annotation.Import;
12 | import org.springframework.http.MediaType;
13 | import org.springframework.security.test.context.support.WithMockUser;
14 | import org.springframework.test.context.ActiveProfiles;
15 | import org.springframework.test.web.servlet.MockMvc;
16 |
17 | import javax.annotation.Resource;
18 | import java.util.Arrays;
19 |
20 | import static org.hamcrest.Matchers.hasSize;
21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
25 |
26 |
27 | @WebMvcTest
28 | @ActiveProfiles("unit-test")
29 | @Import({ResourceConfig.class, TestResourceConfig.class})
30 | class UserControllerTest {
31 | @Resource
32 | MockMvc mockMvc;
33 | @MockBean
34 | UserService userService;
35 |
36 | /**
37 | * 用户列表
38 | */
39 | @Test
40 | @WithMockUser(authorities = {"showUserList"})
41 | void userList() throws Exception {
42 | Mockito.when(userService.userList()).thenReturn(Arrays.asList(new UserVO(), new UserVO()));
43 | mockMvc.perform(get("/user"))
44 | .andExpect(status().isOk())
45 | .andExpect(jsonPath("$").isArray())
46 | .andExpect(jsonPath("$", hasSize(2)))
47 | ;
48 | }
49 |
50 | @Test
51 | void userList_401() throws Exception {
52 | mockMvc.perform(get("/user"))
53 | .andExpect(status().isUnauthorized())
54 | ;
55 | }
56 |
57 | @Test
58 | @WithMockUser
59 | void userList_403() throws Exception {
60 | mockMvc.perform(get("/user"))
61 | .andExpect(status().isForbidden())
62 | ;
63 | }
64 |
65 | @Test
66 | void showUserDetail_401() throws Exception {
67 | mockMvc.perform(get("/user/details/{id}", 1))
68 | .andExpect(status().isUnauthorized())
69 | ;
70 | }
71 |
72 | @Test
73 | @WithMockUser
74 | void showUserDetail_403() throws Exception {
75 | mockMvc.perform(get("/user/details/{id}", 1))
76 | .andExpect(status().isForbidden())
77 | ;
78 | }
79 |
80 | @Test
81 | @WithMockUser(authorities = {"showUserDetail"})
82 | void showUserDetail() throws Exception {
83 | UserVO userVO = new UserVO();
84 | userVO.setId(1);
85 | userVO.setUsername("test");
86 | userVO.setAge(12);
87 | userVO.setEmail("test@test.com");
88 | Mockito.when(userService.showUserDetail(1)).thenReturn(userVO);
89 | mockMvc.perform(get("/user/details/{id}", 1))
90 | .andExpect(status().isOk())
91 | .andExpect(jsonPath("$.id").value(1))
92 | .andExpect(jsonPath("$.username").value("test"))
93 | .andExpect(jsonPath("$.age").value(12))
94 | .andExpect(jsonPath("$.email").value("test@test.com"))
95 | ;
96 | }
97 |
98 | @Test
99 | @WithMockUser(authorities = {"resetUserPassword"})
100 | void resetUserPassword() throws Exception {
101 | String content = JSONUtil.createObj()
102 | .set("username", "username")
103 | .set("password", "password")
104 | .toString();
105 | mockMvc.perform(post("/user/resetUserPassword")
106 | .contentType(MediaType.APPLICATION_JSON)
107 | .content(content))
108 | .andExpect(status().isOk())
109 | ;
110 | }
111 |
112 | @Test
113 | void resetUserPassword_401() throws Exception {
114 | String content = JSONUtil.createObj()
115 | .set("username", "username")
116 | .set("password", "password")
117 | .toString();
118 | mockMvc.perform(post("/user/resetUserPassword")
119 | .contentType(MediaType.APPLICATION_JSON)
120 | .content(content))
121 | .andExpect(status().isUnauthorized())
122 | ;
123 | }
124 |
125 | @Test
126 | @WithMockUser
127 | void resetUserPassword_403() throws Exception {
128 | String content = JSONUtil.createObj()
129 | .set("username", "username")
130 | .set("password", "password")
131 | .toString();
132 | mockMvc.perform(post("/user/resetUserPassword")
133 | .contentType(MediaType.APPLICATION_JSON)
134 | .content(content))
135 | .andExpect(status().isForbidden())
136 | ;
137 | }
138 | }
--------------------------------------------------------------------------------
/resource-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.6.4
9 |
10 |
11 | com.example
12 | resource-server
13 | 0.0.1-SNAPSHOT
14 | MultipleService::ResourceServer
15 | 资源(客户端)服务器
16 |
17 |
18 | 11
19 |
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-security
26 |
27 |
28 | org.springframework.security
29 | spring-security-test
30 | test
31 |
32 |
33 | org.springframework.security.oauth
34 | spring-security-oauth2
35 | 2.5.1.RELEASE
36 |
37 |
38 | org.springframework.security.oauth.boot
39 | spring-security-oauth2-autoconfigure
40 | 2.6.3
41 |
42 |
43 |
44 |
45 | mysql
46 | mysql-connector-java
47 | runtime
48 |
49 |
50 | tk.mybatis
51 | mapper-spring-boot-starter
52 | 4.2.1
53 |
54 |
55 | org.mybatis.spring.boot
56 | mybatis-spring-boot-starter-test
57 | 2.2.2
58 | test
59 |
60 |
61 | org.mybatis.spring.boot
62 | mybatis-spring-boot-autoconfigure
63 | 2.2.2
64 | test
65 |
66 |
67 | com.h2database
68 | h2
69 | test
70 |
71 |
72 |
73 |
74 | cn.hutool
75 | hutool-all
76 | 5.7.22
77 |
78 |
79 | org.apache.httpcomponents
80 | httpclient
81 |
82 |
83 | org.springframework.boot
84 | spring-boot-starter-validation
85 |
86 |
87 | org.springframework.boot
88 | spring-boot-starter-web
89 |
90 |
91 | org.projectlombok
92 | lombok
93 | true
94 |
95 |
96 | org.springframework.boot
97 | spring-boot-starter-actuator
98 |
99 |
100 | org.springframework.boot
101 | spring-boot-starter-test
102 | test
103 |
104 |
105 |
106 |
107 |
108 |
109 | org.springframework.boot
110 | spring-boot-maven-plugin
111 |
112 |
113 |
114 | org.projectlombok
115 | lombok
116 |
117 |
118 |
119 |
120 |
121 | org.mybatis.generator
122 | mybatis-generator-maven-plugin
123 | 1.3.6
124 |
125 |
126 | ${basedir}/src/main/resources/generator/generatorConfig.xml
127 |
128 | true
129 |
130 |
131 |
132 | mysql
133 | mysql-connector-java
134 | 8.0.28
135 |
136 |
137 | tk.mybatis
138 | mapper
139 | 4.2.1
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | aliyun
149 | https://maven.aliyun.com/repository/central
150 |
151 |
152 |
153 |
154 | aliyun
155 | https://maven.aliyun.com/repository/central
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/authorization-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.6.4
9 |
10 |
11 | com.example
12 | authorization-server
13 | 0.0.1-SNAPSHOT
14 | MultipleService::AuthorizationServer
15 | 认证服务器
16 |
17 |
18 | 11
19 |
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-security
26 |
27 |
28 | org.springframework.security
29 | spring-security-test
30 | test
31 |
32 |
33 | org.springframework.security.oauth
34 | spring-security-oauth2
35 | 2.5.1.RELEASE
36 |
37 |
38 | org.springframework.security.oauth.boot
39 | spring-security-oauth2-autoconfigure
40 | 2.6.3
41 |
42 |
43 |
44 |
45 | mysql
46 | mysql-connector-java
47 | runtime
48 |
49 |
50 | tk.mybatis
51 | mapper-spring-boot-starter
52 | 4.2.1
53 |
54 |
55 | org.mybatis.spring.boot
56 | mybatis-spring-boot-starter-test
57 | 2.2.2
58 | test
59 |
60 |
61 | org.mybatis.spring.boot
62 | mybatis-spring-boot-autoconfigure
63 | 2.2.2
64 | test
65 |
66 |
67 | com.h2database
68 | h2
69 | test
70 |
71 |
72 |
73 |
74 | cn.hutool
75 | hutool-all
76 | 5.7.22
77 |
78 |
79 | org.apache.httpcomponents
80 | httpclient
81 |
82 |
83 | org.springframework.boot
84 | spring-boot-starter-validation
85 |
86 |
87 | org.springframework.boot
88 | spring-boot-starter-web
89 |
90 |
91 | org.projectlombok
92 | lombok
93 | true
94 |
95 |
96 | org.springframework.boot
97 | spring-boot-starter-actuator
98 |
99 |
100 | org.springframework.boot
101 | spring-boot-starter-test
102 | test
103 |
104 |
105 |
106 |
107 |
108 |
109 | org.springframework.boot
110 | spring-boot-maven-plugin
111 |
112 |
113 |
114 | org.projectlombok
115 | lombok
116 |
117 |
118 |
119 |
120 |
121 | org.mybatis.generator
122 | mybatis-generator-maven-plugin
123 | 1.3.6
124 |
125 |
126 | ${basedir}/src/main/resources/generator/generatorConfig.xml
127 |
128 | true
129 |
130 |
131 |
132 | mysql
133 | mysql-connector-java
134 | 8.0.28
135 |
136 |
137 | tk.mybatis
138 | mapper
139 | 4.2.1
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | aliyun
149 | https://maven.aliyun.com/repository/central
150 |
151 |
152 |
153 |
154 | aliyun
155 | https://maven.aliyun.com/repository/central
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/resource-server/src/test/java/com/example/user/UserProcessTest.java:
--------------------------------------------------------------------------------
1 | package com.example.user;
2 |
3 | import cn.hutool.json.JSONUtil;
4 | import com.example.config.TestResourceConfig;
5 | import com.example.generator.mapper.UserMapper;
6 | import com.example.generator.model.User;
7 | import com.example.user.vo.ResetUserPasswordVO;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.context.annotation.Import;
12 | import org.springframework.http.MediaType;
13 | import org.springframework.security.crypto.password.PasswordEncoder;
14 | import org.springframework.security.test.context.support.WithMockUser;
15 | import org.springframework.test.context.ActiveProfiles;
16 | import org.springframework.test.web.servlet.MockMvc;
17 |
18 | import javax.annotation.Resource;
19 | import java.security.Principal;
20 |
21 | import static org.hamcrest.Matchers.hasSize;
22 | import static org.junit.jupiter.api.Assertions.assertTrue;
23 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
27 |
28 | /**
29 | * 用户接口测试
30 | */
31 | @SpringBootTest
32 | @AutoConfigureMockMvc
33 | @ActiveProfiles("unit-test")
34 | @Import(TestResourceConfig.class)
35 | public class UserProcessTest {
36 | @Resource
37 | MockMvc mockMvc;
38 | @Resource
39 | UserMapper userMapper;
40 | @Resource
41 | PasswordEncoder passwordEncoder;
42 |
43 | /**
44 | * {@link UserController#userList()}
45 | */
46 | @Test
47 | @WithMockUser(authorities = {"showUserList"})
48 | void userList() throws Exception {
49 | mockMvc.perform(get("/user"))
50 | .andExpect(jsonPath("$").isArray())
51 | .andExpect(jsonPath("$", hasSize(2)))
52 |
53 | .andExpect(jsonPath("$[0].id").value(1))
54 | .andExpect(jsonPath("$[0].userId").value(1))
55 | .andExpect(jsonPath("$[0].name").value("测试用户"))
56 | .andExpect(jsonPath("$[0].age").value(12))
57 | .andExpect(jsonPath("$[0].email").value("test@test.com"))
58 | .andExpect(jsonPath("$[0].gender").value(1))
59 | .andExpect(jsonPath("$[0].username").value("user"))
60 |
61 | .andExpect(jsonPath("$[1].id").value(2))
62 | .andExpect(jsonPath("$[1].userId").value(2))
63 | .andExpect(jsonPath("$[1].name").value("管理员用户"))
64 | .andExpect(jsonPath("$[1].age").value(66))
65 | .andExpect(jsonPath("$[1].email").value("admin@test.com"))
66 | .andExpect(jsonPath("$[1].gender").value(2))
67 | .andExpect(jsonPath("$[1].username").value("admin"))
68 | ;
69 | }
70 |
71 | /**
72 | * {@link UserController#showUserDetail(Integer)}
73 | */
74 | @Test
75 | @WithMockUser(authorities = {"showUserDetail"})
76 | void showUserDetail() throws Exception {
77 | mockMvc.perform(get("/user/details/{id}", 1))
78 | .andExpect(jsonPath("$").isNotEmpty())
79 | .andExpect(jsonPath("$").isMap())
80 | .andExpect(jsonPath("$.id").value(1))
81 | .andExpect(jsonPath("$.userId").value(1))
82 | .andExpect(jsonPath("$.name").value("测试用户"))
83 | .andExpect(jsonPath("$.age").value(12))
84 | .andExpect(jsonPath("$.email").value("test@test.com"))
85 | .andExpect(jsonPath("$.gender").value(1))
86 | .andExpect(jsonPath("$.username").value("user"))
87 | ;
88 | mockMvc.perform(get("/user/details/{id}", 2))
89 | .andExpect(jsonPath("$").isNotEmpty())
90 | .andExpect(jsonPath("$").isMap())
91 | .andExpect(jsonPath("$.id").value(2))
92 | .andExpect(jsonPath("$.userId").value(2))
93 | .andExpect(jsonPath("$.name").value("管理员用户"))
94 | .andExpect(jsonPath("$.age").value(66))
95 | .andExpect(jsonPath("$.email").value("admin@test.com"))
96 | .andExpect(jsonPath("$.gender").value(2))
97 | .andExpect(jsonPath("$.username").value("admin"))
98 | ;
99 | }
100 |
101 | /**
102 | * {@link UserController#resetUserPassword(ResetUserPasswordVO)}
103 | */
104 | @Test
105 | @WithMockUser(authorities = {"resetUserPassword"})
106 | void resetUserPassword() throws Exception {
107 | User user = userMapper.selectByPrimaryKey(1);
108 | assertTrue(passwordEncoder.matches("user", user.getPassword()));
109 | String content = JSONUtil.createObj()
110 | .set("username", "user")
111 | .set("password", "user1")
112 | .toString();
113 | mockMvc.perform(post("/user/resetUserPassword")
114 | .contentType(MediaType.APPLICATION_JSON)
115 | .content(content)
116 | ).andExpect(status().isOk())
117 | ;
118 | User user1 = userMapper.selectByPrimaryKey(1);
119 | assertTrue(passwordEncoder.matches("user1", user1.getPassword()));
120 | }
121 |
122 | /**
123 | * {@link UserController#showPersonalRole(Principal)}
124 | */
125 | @Test
126 | @WithMockUser(username = "user", authorities = {"showPersonalRole"})
127 | void showPersonalRole() throws Exception {
128 | mockMvc.perform(get("/user/showPersonalRole"))
129 | .andExpect(status().isOk())
130 | .andExpect(jsonPath("$").isArray())
131 | .andExpect(jsonPath("$", hasSize(1)))
132 | .andExpect(jsonPath("$[0]").value("普通用户"))
133 | ;
134 | }
135 |
136 | /**
137 | * {@link UserController#showPersonalRole(Principal)}
138 | */
139 | @Test
140 | @WithMockUser(username = "admin", authorities = {"showPersonalRole"})
141 | void showPersonalRole_1() throws Exception {
142 | mockMvc.perform(get("/user/showPersonalRole"))
143 | .andExpect(status().isOk())
144 | .andExpect(jsonPath("$").isArray())
145 | .andExpect(jsonPath("$", hasSize(2)))
146 | .andExpect(jsonPath("$[0]").value("普通用户"))
147 | .andExpect(jsonPath("$[1]").value("系统管理员"))
148 | ;
149 | }
150 |
151 | /**
152 | * {@link UserController#showPersonalDetail(Principal)}
153 | */
154 | @Test
155 | @WithMockUser(username = "user", authorities = {"showPersonalDetail"})
156 | void showPersonalDetail() throws Exception {
157 | mockMvc.perform(get("/user/showPersonalDetail"))
158 | .andExpect(status().isOk())
159 | .andExpect(jsonPath("$.id").value(1))
160 | .andExpect(jsonPath("$.userId").value(1))
161 | .andExpect(jsonPath("$.name").value("测试用户"))
162 | .andExpect(jsonPath("$.age").value(12))
163 | .andExpect(jsonPath("$.email").value("test@test.com"))
164 | .andExpect(jsonPath("$.gender").value(1))
165 | ;
166 | }
167 |
168 | /**
169 | * {@link UserController#showPersonalDetail(Principal)}
170 | */
171 | @Test
172 | @WithMockUser(username = "admin", authorities = {"showPersonalDetail"})
173 | void showPersonalDetail_1() throws Exception {
174 | mockMvc.perform(get("/user/showPersonalDetail"))
175 | .andExpect(status().isOk())
176 | .andExpect(jsonPath("$.id").value(2))
177 | .andExpect(jsonPath("$.userId").value(2))
178 | .andExpect(jsonPath("$.name").value("管理员用户"))
179 | .andExpect(jsonPath("$.age").value(66))
180 | .andExpect(jsonPath("$.email").value("admin@test.com"))
181 | .andExpect(jsonPath("$.gender").value(2))
182 | ;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/authorization-server/src/test/java/com/example/auth/AuthTest.java:
--------------------------------------------------------------------------------
1 | package com.example.auth;
2 |
3 | import cn.hutool.core.map.MapUtil;
4 | import cn.hutool.core.util.ReUtil;
5 | import cn.hutool.json.JSONObject;
6 | import com.example.utils.TestRequestUtils;
7 | import org.junit.jupiter.api.Test;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 | import org.springframework.boot.test.web.client.TestRestTemplate;
10 | import org.springframework.http.HttpEntity;
11 | import org.springframework.http.HttpMethod;
12 | import org.springframework.http.ResponseEntity;
13 | import org.springframework.test.context.ActiveProfiles;
14 | import org.springframework.util.MultiValueMap;
15 |
16 | import javax.annotation.Resource;
17 | import java.util.HashMap;
18 | import java.util.Map;
19 |
20 | import static org.junit.jupiter.api.Assertions.*;
21 | import static org.springframework.security.oauth2.common.util.OAuth2Utils.*;
22 |
23 | /**
24 | * 权限测试
25 | */
26 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
27 | @ActiveProfiles("unit-test")
28 | public class AuthTest {
29 | @Resource
30 | TestRestTemplate testRestTemplate;
31 | public static final String TEST_CLIENT = "client";
32 | public static final String TEST_SECRET = "secret";
33 | public static final String TEST_USERNAME = "user";
34 | public static final String TEST_PASSWORD = "user";
35 |
36 | /**
37 | * password方式获取token
38 | */
39 | @Test
40 | void getToken_password() {
41 | // 1.测试获取token接口
42 | HttpEntity> entity = TestRequestUtils.urlEncodedRequest()
43 | .put(GRANT_TYPE, "password")
44 | .put("username", TEST_USERNAME)
45 | .put("password", TEST_PASSWORD)
46 | .build();
47 | ResponseEntity exchange = testRestTemplate.withBasicAuth(TEST_CLIENT, TEST_SECRET)
48 | .exchange("/oauth/token", HttpMethod.POST, entity, JSONObject.class);
49 |
50 | // 验证token
51 | assertTokenResult(exchange);
52 |
53 | assertNotNull(exchange.getBody());
54 | String token = exchange.getBody().getStr("access_token");
55 |
56 | // 2.测试check_token接口
57 | // jwt单体服务模式一般不会用到,在资源服务器常用
58 | HttpEntity> entity1 = TestRequestUtils.urlEncodedRequest()
59 | .put("token", token).build();
60 | ResponseEntity exchange1 = testRestTemplate
61 | .withBasicAuth(TEST_CLIENT, TEST_SECRET)
62 | .exchange("/oauth/check_token", HttpMethod.POST, entity1, JSONObject.class);
63 |
64 | // 验证结果
65 | JSONObject body1 = exchange1.getBody();
66 | assertEquals(200, exchange1.getStatusCodeValue());
67 | assertNotNull(body1);
68 | assertEquals(TEST_USERNAME, body1.getStr("user_name"));
69 | assertTrue(body1.getBool("active"));
70 | assertEquals(3, body1.getJSONArray("authorities").size());
71 | assertEquals(TEST_CLIENT, body1.getStr("client_id"));
72 | assertEquals(1, body1.getJSONArray("scope").size());
73 | assertNotNull(body1.getInt("exp"));
74 | assertNotNull(body1.getStr("jti"));
75 |
76 | String refreshToken = exchange.getBody().getStr("refresh_token");
77 |
78 | // 3.测试刷新token接口
79 | HttpEntity> entity2 = TestRequestUtils.urlEncodedRequest()
80 | .put(GRANT_TYPE, "refresh_token")
81 | .put("refresh_token", refreshToken)
82 | .build();
83 | ResponseEntity exchange2 = testRestTemplate.withBasicAuth(TEST_CLIENT, TEST_SECRET)
84 | .exchange("/oauth/token", HttpMethod.POST, entity2, JSONObject.class);
85 |
86 | // 验证返回token
87 | assertTokenResult(exchange2);
88 | }
89 |
90 | private void assertTokenResult(ResponseEntity exchange) {
91 | JSONObject body = exchange.getBody();
92 |
93 | assertEquals(200, exchange.getStatusCodeValue());
94 | assertNotNull(body);
95 | assertNotNull(body.get("token_type"));
96 | assertNotNull(body.get("access_token"));
97 | assertNotNull(body.get("refresh_token"));
98 | assertNotNull(body.get("scope"));
99 | assertNotNull(body.get("expires_in"));
100 | assertNotNull(body.get("jti"));
101 | }
102 |
103 | /**
104 | * code方式获取token
105 | */
106 | @Test
107 | void getToken_code() {
108 | // 1.访问权限url
109 | String redirectUri = "http://localhost:4200/login";
110 | HttpEntity> entity = TestRequestUtils.urlEncodedRequest()
111 | .put(RESPONSE_TYPE, "code")
112 | .put(REDIRECT_URI, redirectUri)
113 | .put(CLIENT_ID, TEST_CLIENT)
114 | .build();
115 |
116 | ResponseEntity exchange = testRestTemplate.withBasicAuth(TEST_USERNAME, TEST_PASSWORD)
117 | .exchange("/oauth/authorize", HttpMethod.POST, entity, String.class);
118 |
119 | assertTrue(exchange.getStatusCodeValue() == 303 || exchange.getStatusCodeValue() == 302);
120 | assertNotNull(exchange.getHeaders().getLocation());
121 | String query = exchange.getHeaders().getLocation().getQuery();
122 | assertTrue(ReUtil.contains("code=", query));
123 |
124 | // 在跳转页面url中拿到code
125 | String code = ReUtil.findAllGroup0("(?<=code=)\\S+", query).get(0);
126 |
127 | // 2.通过code获取token
128 | HttpEntity> entity1 = TestRequestUtils.urlEncodedRequest()
129 | .put(GRANT_TYPE, "authorization_code")
130 | .put(REDIRECT_URI, redirectUri)
131 | .put("code", code)
132 | .build();
133 | ResponseEntity exchange1 = testRestTemplate.withBasicAuth(TEST_CLIENT, TEST_SECRET)
134 | .exchange("/oauth/token", HttpMethod.POST, entity1, JSONObject.class);
135 |
136 | // 校验返回的token
137 | assertTokenResult(exchange1);
138 | }
139 |
140 | /**
141 | * 注册
142 | */
143 | @Test
144 | void register() {
145 | // 1.测试注册请求---只传用户名
146 | HttpEntity entity = TestRequestUtils.jsonRequest().set("username", "testUsername").build();
147 | ResponseEntity exchange = testRestTemplate.exchange("/oauth/register", HttpMethod.POST, entity, JSONObject.class);
148 |
149 | // 校验返回错误消息
150 | assertEquals(400, exchange.getStatusCodeValue());
151 | JSONObject body = exchange.getBody();
152 | assertNotNull(body);
153 | assertEquals("密码不能为空", body.getByPath("errors[0].defaultMessage"));
154 |
155 | // 2.测试注册请求---只传密码
156 | entity = TestRequestUtils.jsonRequest().set("password", "testPassword").build();
157 | exchange = testRestTemplate.exchange("/oauth/register", HttpMethod.POST, entity, JSONObject.class);
158 |
159 | // 校验返回错误消息
160 | assertEquals(400, exchange.getStatusCodeValue());
161 | body = exchange.getBody();
162 | assertNotNull(body);
163 | assertEquals("用户名不能为空", body.getByPath("errors[0].defaultMessage"));
164 |
165 | // 3.测试注册请求---正确的
166 | entity = TestRequestUtils.jsonRequest()
167 | .set("username", "testUsername")
168 | .set("password", "testPassword").build();
169 | exchange = testRestTemplate.exchange("/oauth/register", HttpMethod.POST, entity, JSONObject.class);
170 |
171 | // 校验注册成功
172 | assertEquals(200, exchange.getStatusCodeValue());
173 |
174 | // 4.使用新注册的用户获取token
175 | HttpEntity> entity1 = TestRequestUtils.urlEncodedRequest()
176 | .put(GRANT_TYPE, "password")
177 | .put("username", "testUsername")
178 | .put("password", "testPassword")
179 | .build();
180 | exchange = testRestTemplate.withBasicAuth(TEST_CLIENT, TEST_SECRET)
181 | .exchange("/oauth/token", HttpMethod.POST, entity1, JSONObject.class);
182 |
183 | // 校验返回的token
184 | assertTokenResult(exchange);
185 |
186 | // 5.使用重复的用户名注册
187 | exchange = testRestTemplate.exchange("/oauth/register", HttpMethod.POST, entity, JSONObject.class);
188 |
189 | // 校验返回的错误消息
190 | assertEquals(400, exchange.getStatusCodeValue());
191 | body = exchange.getBody();
192 | assertNotNull(body);
193 | assertEquals("用户名已存在", body.getByPath("errors[0].defaultMessage"));
194 | }
195 |
196 | /**
197 | * 注销
198 | */
199 | @Test
200 | void logout() {
201 | // 1.获取用户token
202 | HttpEntity> entity = TestRequestUtils.urlEncodedRequest()
203 | .put(GRANT_TYPE, "password")
204 | .put("username", TEST_USERNAME)
205 | .put("password", TEST_PASSWORD)
206 | .build();
207 | ResponseEntity exchange = testRestTemplate.withBasicAuth(TEST_CLIENT, TEST_SECRET)
208 | .exchange("/oauth/token", HttpMethod.POST, entity, JSONObject.class);
209 | // 校验返回结果
210 | assertTokenResult(exchange);
211 | // 暂存token
212 | assertNotNull(exchange.getBody());
213 | String token = exchange.getBody().getStr("access_token");
214 | // 2.通过用户身份调用注销接口
215 | String redirectUri = "http://localhost:4200/login";
216 | Map variables = MapUtil.builder(new HashMap())
217 | .put("token", token)
218 | .put("redirect", redirectUri)
219 | .build();
220 | // fixme 这里不知道为啥必须要加上httpclient依赖才能捕捉到30x的状态码,否则会报错
221 | ResponseEntity exchange1 = testRestTemplate.getForEntity("/oauth/logout?token={token}&redirect={redirect}",
222 | String.class, variables);
223 | // 校验返回结果
224 | assertTrue(exchange1.getStatusCodeValue() == 303 || exchange1.getStatusCodeValue() == 302);
225 | assertNotNull(exchange1.getHeaders().getLocation());
226 | assertEquals(redirectUri, exchange1.getHeaders().getLocation().toString());
227 | // 3.再次使用token调用check_token接口测试token是否失效
228 | HttpEntity> entity1 = TestRequestUtils.urlEncodedRequest()
229 | .put("token", token).build();
230 | ResponseEntity exchange2 = testRestTemplate
231 | .withBasicAuth(TEST_CLIENT, TEST_SECRET)
232 | .exchange("/oauth/check_token", HttpMethod.POST, entity1, JSONObject.class);
233 | // 校验返回的错误信息
234 | assertEquals(400, exchange2.getStatusCodeValue());
235 | assertNotNull(exchange2.getBody());
236 | assertEquals("invalid_token", exchange2.getBody().getStr("error"));
237 | }
238 |
239 | /**
240 | * 修改密码
241 | */
242 | @Test
243 | void changePassword() {
244 | // 1.获取用户token
245 | HttpEntity> entity = TestRequestUtils.urlEncodedRequest()
246 | .put(GRANT_TYPE, "password")
247 | .put("username", TEST_USERNAME)
248 | .put("password", TEST_PASSWORD)
249 | .build();
250 | ResponseEntity exchange = testRestTemplate.withBasicAuth(TEST_CLIENT, TEST_SECRET)
251 | .exchange("/oauth/token", HttpMethod.POST, entity, JSONObject.class);
252 | // 校验返回结果
253 | assertTokenResult(exchange);
254 | // 暂存token
255 | assertNotNull(exchange.getBody());
256 | String token = exchange.getBody().getStr("access_token");
257 | // 2.修改用户密码
258 | HttpEntity entity1 = TestRequestUtils.jsonRequest()
259 | .set("token", token)
260 | .set("password", "changePasswordNewPwd")
261 | .build();
262 | ResponseEntity exchange1 = testRestTemplate.exchange("/oauth/changePassword",
263 | HttpMethod.POST, entity1, JSONObject.class);
264 | // 校验返回结果
265 | assertEquals(200, exchange1.getStatusCodeValue());
266 | // 3.再次使用token调用check_token接口测试token是否失效
267 | HttpEntity> entity2 = TestRequestUtils.urlEncodedRequest()
268 | .put("token", token).build();
269 | ResponseEntity exchange2 = testRestTemplate
270 | .withBasicAuth(TEST_CLIENT, TEST_SECRET)
271 | .exchange("/oauth/check_token", HttpMethod.POST, entity2, JSONObject.class);
272 | // 校验返回的错误信息
273 | assertEquals(400, exchange2.getStatusCodeValue());
274 | assertNotNull(exchange2.getBody());
275 | assertEquals("invalid_token", exchange2.getBody().getStr("error"));
276 | // 4.使用新密码获取token
277 | HttpEntity> entity3 = TestRequestUtils.urlEncodedRequest()
278 | .put(GRANT_TYPE, "password")
279 | .put("username", TEST_USERNAME)
280 | .put("password", "changePasswordNewPwd")
281 | .build();
282 | ResponseEntity exchange3 = testRestTemplate.withBasicAuth(TEST_CLIENT, TEST_SECRET)
283 | .exchange("/oauth/token", HttpMethod.POST, entity3, JSONObject.class);
284 | // 校验返回结果
285 | assertTokenResult(exchange3);
286 | // 暂存token
287 | assertNotNull(exchange3.getBody());
288 | String token3 = exchange3.getBody().getStr("access_token");
289 | // 5.使用新的token调用check_token接口
290 | HttpEntity> entity4 = TestRequestUtils.urlEncodedRequest()
291 | .put("token", token3).build();
292 | ResponseEntity exchange4 = testRestTemplate
293 | .withBasicAuth(TEST_CLIENT, TEST_SECRET)
294 | .exchange("/oauth/check_token", HttpMethod.POST, entity4, JSONObject.class);
295 | // 校验返回结果
296 | JSONObject body1 = exchange4.getBody();
297 | assertEquals(200, exchange4.getStatusCodeValue());
298 | assertNotNull(body1);
299 | assertEquals(TEST_USERNAME, body1.getStr("user_name"));
300 | assertTrue(body1.getBool("active"));
301 | assertEquals(3, body1.getJSONArray("authorities").size());
302 | assertEquals(TEST_CLIENT, body1.getStr("client_id"));
303 | assertEquals(1, body1.getJSONArray("scope").size());
304 | assertNotNull(body1.getInt("exp"));
305 | assertNotNull(body1.getStr("jti"));
306 | }
307 | }
308 |
--------------------------------------------------------------------------------