├── .gitignore ├── ChangeLog.txt ├── LICENSE ├── README.md ├── common ├── pom.xml └── src │ └── main │ └── java │ └── cn │ └── flizi │ └── core │ ├── annition │ └── Log.java │ └── util │ ├── R.java │ ├── SecurityUtils.java │ └── TreeUtils.java ├── doc ├── api.md ├── db │ ├── auth.sql │ ├── ext.sql │ └── scheme.sql ├── ext.md ├── filter.md ├── history.md ├── oauth.md └── pic │ ├── AbstractDaoAuthenticationConfigurer.png │ ├── AuthenticationManagerBuilder.png │ ├── HttpSecurity.png │ ├── WebSecurity.png │ ├── WebSecurityConfigurerAdapter.png │ ├── abstractauthenticationprocessingfilter.png │ ├── delegatingfilterproxy.png │ ├── demo-full-login.png │ ├── exceptiontranslationfilter.png │ ├── ext_1.png │ ├── ext_2.png │ ├── ext_3.png │ ├── filterchain.png │ ├── filterchainproxy.png │ ├── multi-securityfilterchain.png │ ├── oauth_basic.png │ ├── oauth_client_credentials.png │ ├── oauth_password.png │ ├── preview.png │ ├── providermanager-parent.png │ ├── providermanager.png │ ├── providermanagers-parent.png │ ├── rbac.png │ ├── securitycontextholder.png │ └── usernamepasswordauthenticationfilter.png ├── example ├── README.md ├── oauth2-client │ ├── README.md │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── cn │ │ │ └── flizi │ │ │ └── auth │ │ │ └── client │ │ │ ├── DemoApplication.java │ │ │ ├── properties │ │ │ └── ClientProperties.java │ │ │ └── web │ │ │ └── RestApiController.java │ │ └── resources │ │ └── application.yaml ├── oauth2-resource │ ├── README.md │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── cn │ │ │ │ └── flizi │ │ │ │ └── auth │ │ │ │ └── resource │ │ │ │ └── demo │ │ │ │ ├── DemoApplication.java │ │ │ │ ├── config │ │ │ │ └── ResourceConfig.java │ │ │ │ └── web │ │ │ │ └── RestApiController.java │ │ └── resources │ │ │ └── application.yaml │ │ └── test │ │ └── java │ │ └── cn │ │ └── flizi │ │ └── auth │ │ └── resource │ │ └── demo │ │ └── DemoApplicationTests.java └── pom.xml ├── extension ├── pom.xml ├── src │ └── main │ │ ├── java │ │ └── cn │ │ │ └── flizi │ │ │ └── ext │ │ │ └── rbac │ │ │ ├── ExtRestApi.java │ │ │ ├── RbacConfiguration.java │ │ │ ├── dto │ │ │ ├── SysMenuAdd.java │ │ │ ├── SysMenuUpdate.java │ │ │ ├── SysRoleAdd.java │ │ │ └── SysRoleUpdate.java │ │ │ ├── entity │ │ │ ├── SysAuthority.java │ │ │ ├── SysMenu.java │ │ │ ├── SysMenuAuthority.java │ │ │ ├── SysRole.java │ │ │ ├── SysRoleMenu.java │ │ │ └── SysRoleUser.java │ │ │ ├── mapper │ │ │ ├── SysAuthorityMapper.java │ │ │ ├── SysMenuAuthorityMapper.java │ │ │ ├── SysMenuMapper.java │ │ │ ├── SysRoleMapper.java │ │ │ ├── SysRoleMenuMapper.java │ │ │ └── SysRoleUserMapper.java │ │ │ └── service │ │ │ ├── SysAuthorityService.java │ │ │ ├── SysMenuService.java │ │ │ ├── SysRoleMenuService.java │ │ │ └── SysRoleService.java │ │ └── resources │ │ ├── META-INF │ │ └── spring.factories │ │ └── mapper │ │ ├── SysMenuMapper.xml │ │ └── SysRoleMapper.xml └── ui │ ├── .editorconfig │ ├── .env.development │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .travis.yml │ ├── babel.config.js │ ├── jest.config.js │ ├── jsconfig.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── index.html │ └── oauth2-callback.html │ ├── src │ ├── App.vue │ ├── api │ │ ├── login.js │ │ └── sys │ │ │ ├── authority.js │ │ │ ├── menu.js │ │ │ ├── role.js │ │ │ └── user.js │ ├── assets │ │ ├── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ └── pic │ │ │ ├── green_addr@2x.png │ │ │ ├── null_addr@2x.png │ │ │ ├── orange_addr@2x.png │ │ │ ├── red_addr@2x.png │ │ │ └── yellow_addr@2x.png │ ├── components │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ ├── Hamburger │ │ │ └── index.vue │ │ ├── IconSelect │ │ │ ├── index.vue │ │ │ └── requireIcons.js │ │ ├── RightPanel │ │ │ └── index.vue │ │ ├── SvgIcon │ │ │ └── index.vue │ │ └── ThemePicker │ │ │ └── index.vue │ ├── directive │ │ └── permission │ │ │ ├── index.js │ │ │ └── permission.js │ ├── icons │ │ ├── index.js │ │ ├── svg │ │ │ ├── light.svg │ │ │ ├── money.svg │ │ │ ├── password.svg │ │ │ ├── sun.svg │ │ │ └── user.svg │ │ └── svgo.yml │ ├── layout │ │ ├── components │ │ │ ├── AppMain.vue │ │ │ ├── Navbar.vue │ │ │ ├── Settings │ │ │ │ └── index.vue │ │ │ ├── Sidebar │ │ │ │ ├── FixiOSBug.js │ │ │ │ ├── Item.vue │ │ │ │ ├── Link.vue │ │ │ │ ├── Logo.vue │ │ │ │ ├── SidebarItem.vue │ │ │ │ └── index.vue │ │ │ ├── TagsView │ │ │ │ ├── ScrollPane.vue │ │ │ │ └── index.vue │ │ │ └── index.js │ │ ├── index.vue │ │ └── mixin │ │ │ └── ResizeHandler.js │ ├── main.js │ ├── permission.js │ ├── router │ │ └── index.js │ ├── settings.js │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ ├── app.js │ │ │ ├── permission.js │ │ │ ├── settings.js │ │ │ ├── tagsView.js │ │ │ └── user.js │ ├── styles │ │ ├── element-ui.scss │ │ ├── element-variables.scss │ │ ├── index.scss │ │ ├── mixin.scss │ │ ├── sidebar.scss │ │ ├── transition.scss │ │ └── variables.scss │ ├── utils │ │ ├── auth.js │ │ ├── get-page-title.js │ │ ├── index.js │ │ ├── open-window.js │ │ ├── request.js │ │ └── validate.js │ └── views │ │ ├── 404.vue │ │ ├── dashboard │ │ ├── components │ │ │ ├── MyCamera.vue │ │ │ ├── MyData.vue │ │ │ ├── MyEchart.vue │ │ │ ├── MyMap.vue │ │ │ ├── MyPanel.vue │ │ │ └── mixins │ │ │ │ └── resize.js │ │ └── index.vue │ │ ├── login │ │ ├── auth-redirect.vue │ │ └── index.vue │ │ └── menu │ │ ├── authority.vue │ │ ├── index.vue │ │ ├── menu.vue │ │ └── role.vue │ └── vue.config.js ├── oauth2 ├── pom.xml └── src │ ├── main │ ├── java │ │ └── cn │ │ │ └── flizi │ │ │ └── auth │ │ │ ├── App.java │ │ │ ├── config │ │ │ ├── AuthorizationConfig.java │ │ │ ├── CorsConfig.java │ │ │ ├── GlobalConfig.java │ │ │ ├── MessageLocalConfig.java │ │ │ ├── ResourceConfig.java │ │ │ ├── SwaggerConfig.java │ │ │ └── WebSecurityConfig.java │ │ │ ├── entity │ │ │ ├── Captcha.java │ │ │ ├── Sms.java │ │ │ └── User.java │ │ │ ├── mapper │ │ │ ├── CaptchaMapper.java │ │ │ ├── SmsMapper.java │ │ │ └── UserMapper.java │ │ │ ├── properties │ │ │ ├── CaptchaProperties.java │ │ │ ├── SmsProperties.java │ │ │ └── SocialProperties.java │ │ │ ├── security │ │ │ ├── AuthUser.java │ │ │ ├── CaptchaService.java │ │ │ ├── CustomWebResponseExceptionTranslator.java │ │ │ ├── ExceptionHandler.java │ │ │ ├── JwtUserAuthenticationConverter.java │ │ │ ├── RestAuthenticationEntryPoint.java │ │ │ ├── filter │ │ │ │ └── CaptchaValidationFilter.java │ │ │ └── social │ │ │ │ ├── SocialAuthenticationProvider.java │ │ │ │ ├── SocialCodeAuthenticationFilter.java │ │ │ │ ├── SocialCodeAuthenticationToken.java │ │ │ │ ├── SocialCodeTokenGranter.java │ │ │ │ ├── SocialDetailsService.java │ │ │ │ └── SocialUser.java │ │ │ ├── service │ │ │ ├── MysqlCaptchaService.java │ │ │ └── UserService.java │ │ │ └── web │ │ │ ├── MvcController.java │ │ │ └── RestApiController.java │ └── resources │ │ ├── application.yaml │ │ ├── messages_zh_CN.properties │ │ ├── public │ │ └── static │ │ │ └── base.css │ │ └── templates │ │ ├── auth-redirect.html │ │ ├── bind.html │ │ ├── confirm_access.html │ │ ├── index.html │ │ ├── login.html │ │ ├── reset.html │ │ ├── signup.html │ │ └── weixin-code.html │ └── test │ └── java │ └── cn │ └── flizi │ ├── auth │ └── AppTests.java │ └── ext │ └── rbac │ └── ExtRestApiTest.java └── pom.xml /.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 | application-pro.yaml 35 | .env.production 36 | node_modules 37 | vuejs -------------------------------------------------------------------------------- /ChangeLog.txt: -------------------------------------------------------------------------------- 1 | - 0.0.1 2 | 完成 oauth2-server 基本功能开发 3 | 4 | - 0.1.0 5 | 加入扩展模块,提供权限控制(开发中) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于 Spring Security OAuth 的统一账号管理平台 2 | 3 | ![preview](./doc/pic/preview.png) 4 | 5 | ~~前后端演示地址: https://tao.flizi.cn~~ 6 | 7 | 服务器没内存了, 所以把服务停了. 不提供演示了. 8 | 推荐个人另一个项目, 基于 OAuth2 的 SSO 登录和登出 https://gitee.com/taoroot/zhiyi 9 | 10 | - 支持密码模式演示 11 | - 输入账号 123456, 密码: 123456, 以及验证码后点击登录 12 | - 支持授权登录演示 13 | - 输入手机号 123456, 密码: 123456, 以及验证码后点击登录 14 | - 或使用微信登录(默认会自动创建账号) 15 | - 或先注册用户,再进行登录 16 | 17 | `注意!!: 这里将手机号作为用户账号使用` 18 | 19 | ![](./doc/pic/demo-full-login.png) 20 | ![](./doc/pic/rbac.png) 21 | 22 | ## 功能 23 | 24 | - OAuth2 密码登录(扩展支持图形验证码) (开发完成) 25 | - OAuth2 授权码登录 (开发完成) 26 | - OAuth2 刷新TOKEN (开发完成) 27 | - OAuth2 社区登录(自定义授权模式) (开发完成) 28 | - 微信公众平台(公众号)登录 (开发完成) 29 | - 微信开发平台登录 (开发完成) 30 | - 短信登录 (开发完成) 31 | - 权限管理(开发中) 32 | 33 | ## 目录结构 34 | 35 | - common 基础工具模块 36 | - example 示例模块 37 | - extension 扩展服务(提供权限控制, 可选模块) 38 | - oauth2 认证服务 39 | 40 | ## 文档 41 | 42 | - [项目地址](https://github.com/taoroot/oauth2-server) 43 | - [接口文档](./doc/api.md) 44 | - [Spring Security 过滤器总体架构分析](./doc/filter.md) 45 | - [Spring OAuth2 前世今生](./doc/history.md) 46 | 47 | ## 技术栈: 48 | 49 | - Spring Boot 50 | - Spring Security 51 | - Spring Security OAuth2 52 | - Mybatis 53 | 54 | ## 环境依赖: 55 | 56 | - JRE1.8 57 | - Mysql5.7 58 | 59 | ## 配置说明 60 | 61 | - 配置数据库 62 | 63 | ```yaml 64 | spring: 65 | datasource: 66 | driver-class-name: com.mysql.cj.jdbc.Driver 67 | username: root 68 | password: root 69 | url: jdbc:mysql://127.0.0.1:3306/auth?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 70 | ``` 71 | 72 | - 配置微信登录 73 | 74 | 公众平台用于在微信**内置浏览器**打开时登录 75 | 76 | 开放平台用于在非微信环境下**扫码登录** 77 | 78 | ```yaml 79 | social: 80 | wx-mp: # 微信公众平台 81 | key: xxxx 82 | secret: xxx 83 | wx-open: # 微信开放平台 84 | key: xxxx 85 | secret: xxx 86 | ``` 87 | 88 | - 配置图形验证码 89 | 90 | ```yaml 91 | captcha: 92 | enable: true # 启用 93 | base-str: '0123' # 随机字符 94 | length: 4 # 长度 95 | ``` 96 | 97 | - 配置手机验证码(腾讯云) 98 | 99 | ```yaml 100 | sms: 101 | enable: true # 启用 102 | secretId: 'xxxx' 103 | secretKey: 'xxxx' 104 | appId: 'xxx' 105 | sign: 'xxx' 106 | templateId: 'xxx' 107 | ``` 108 | 109 | - 页面配置 110 | 111 | ```yaml 112 | baseinfo: 113 | title: 浙江xxxx科技 # 标题 114 | beian: 浙ICP备xxxx号 # 备案号 115 | ``` 116 | 117 | # 编译 & 部署 118 | 119 | ```shell 120 | mvn package && java -jar auth-0.0.1.jar 121 | ``` 122 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | cn.flizi 8 | oauth2-parent 9 | 0.1.0 10 | 11 | jar 12 | 13 | common 14 | 15 | 16 | 17 | cn.hutool 18 | hutool-core 19 | ${hutool.version} 20 | 21 | 22 | org.springframework.security 23 | spring-security-core 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /common/src/main/java/cn/flizi/core/annition/Log.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.core.annition; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target({ElementType.PARAMETER, ElementType.METHOD}) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Documented 8 | public @interface Log { 9 | 10 | public String value() default ""; 11 | 12 | public int businessType() default 0; 13 | 14 | public int operatorType() default 0; 15 | 16 | public boolean saveParam() default true; 17 | public boolean saveResult() default false; 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/cn/flizi/core/util/R.java: -------------------------------------------------------------------------------- 1 | 2 | package cn.flizi.core.util; 3 | 4 | import lombok.*; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.io.Serializable; 8 | 9 | @ToString 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Accessors(chain = true) 13 | public class R implements Serializable { 14 | private static final long serialVersionUID = 1L; 15 | 16 | public static final String OK = "SUCCESS"; 17 | public static final String ERROR = "ERROR"; 18 | 19 | @Getter 20 | @Setter 21 | private String code; 22 | 23 | @Getter 24 | @Setter 25 | private String msg; 26 | 27 | @Getter 28 | @Setter 29 | private T data; 30 | 31 | public static R ok() { 32 | return instance(null, OK, null); 33 | } 34 | 35 | public static R ok(T data) { 36 | return instance(data, OK, null); 37 | } 38 | 39 | public static R ok(T data, String msg) { 40 | return instance(data, OK, msg); 41 | } 42 | 43 | public static R okMsg(String msg) { 44 | return instance(null, OK, msg); 45 | } 46 | 47 | public static R error() { 48 | return instance(null, ERROR, null); 49 | } 50 | 51 | public static R error(T data) { 52 | return instance(data, ERROR, null); 53 | } 54 | 55 | public static R error(T data, String msg) { 56 | return instance(data, ERROR, msg); 57 | } 58 | 59 | public static R errMsg(String msg) { 60 | return instance(null, ERROR, msg); 61 | } 62 | 63 | public static R instance(T data, String code, String msg) { 64 | R apiResult = new R<>(); 65 | apiResult.setCode(code); 66 | apiResult.setData(data); 67 | apiResult.setMsg(msg); 68 | return apiResult; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /common/src/main/java/cn/flizi/core/util/SecurityUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.core.util; 2 | 3 | import org.springframework.security.core.context.SecurityContextHolder; 4 | 5 | public class SecurityUtils { 6 | 7 | public static Integer userId() { 8 | String name = SecurityContextHolder.getContext().getAuthentication().getName(); 9 | if ("anonymousUser".equals(name)) { 10 | return -1; 11 | } 12 | return Integer.parseInt(name); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/cn/flizi/core/util/TreeUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.core.util; 2 | 3 | import cn.hutool.core.lang.tree.Tree; 4 | 5 | import java.util.List; 6 | 7 | public class TreeUtils { 8 | public static final int ROOT_PARENT_ID = -1; 9 | 10 | public static void computeSort(List> trees) { 11 | if (trees == null || trees.size() == 0) { 12 | return; 13 | } 14 | for (int i = 0; i < trees.size(); i++) { 15 | try { 16 | trees.get(i).putExtra("sortPrev", trees.get(i - 1).getId()); 17 | } catch (Exception e) { 18 | trees.get(i).putExtra("sortPrev", null); 19 | } 20 | try { 21 | trees.get(i).putExtra("sortNext", trees.get(i + 1).getId()); 22 | } catch (Exception e) { 23 | trees.get(i).putExtra("sortNext", null); 24 | } 25 | computeSort(trees.get(i).getChildren()); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /doc/api.md: -------------------------------------------------------------------------------- 1 | # 接口文档 2 | 3 | {xxx} 表示是一个参数变量 4 | 5 | ## 图形验证码 6 | 7 | ```http 8 | GET /captcha?key={timestamp} HTTP/1.1 9 | ``` 10 | ## 手机验证码 11 | 12 | ```http 13 | GET /sms?phone={phone} HTTP/1.1 14 | ``` 15 | 16 | ## OAuth2 授权码 17 | ```http 18 | GET /oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type={response_type}&scope={scope}&state={state} HTTP/1.1 19 | ``` 20 | 21 | ## OAuth2 密码登录 22 | 23 | 扩展支持图形验证码,提高接口安全性 24 | 25 | captchaKey: 图形验证码key, captchaCode: 图形验证码 26 | 27 | ```http 28 | POST /oauth/token HTTP/1.1 29 | Authorization: Basic {Base64({client_id};{secret})} 30 | Content-Type: application/x-www-form-urlencoded 31 | 32 | grant_type=password&username={username}&password={password}&scope={scope}&captchaKey={captchaKey}&captchaCode={captchaCode} 33 | ``` 34 | 35 | ## OAuth2 授权码登录 36 | 37 | ```http 38 | POST /oauth/token HTTP/1.1 39 | Authorization: Basic {Base64({client_id};{secret})} 40 | Content-Type: application/x-www-form-urlencoded 41 | 42 | grant_type=authorization_code&code={code}&scope={scope} 43 | ``` 44 | 45 | ## 手机号,微信登录 46 | 47 | ``` 48 | if (type == 'sms') 49 | code = '手机验证码' 50 | if (type == 'WX_MP') 51 | code = '微信公众平台授权码' 52 | if (type == 'WX_OPEN') 53 | code = '微信开放平台授权码' 54 | ``` 55 | 56 | ```http 57 | POST /oauth/token HTTP/1.1 58 | Authorization: Basic {Base64({client_id};{secret})} 59 | Content-Type: application/x-www-form-urlencoded 60 | 61 | grant_type=social&type={type}&code={code}&scope={scope} 62 | ``` 63 | 64 | 65 | ## 登录成功 66 | ```json 67 | { 68 | "access_token": "access_token", 69 | "token_type": "bearer", 70 | "expires_in": 43199, 71 | "scope": "read_user", 72 | "user_id": "11" 73 | } 74 | ``` 75 | 76 | 77 | ## 获取用户信息 78 | 79 | ```http 80 | GET /user_base HTTP/1.1 81 | Authorization: Bearer {access_token} 82 | ``` 83 | 84 | scope!=all 85 | ```json 86 | { 87 | "msg": "SUCCESS", 88 | "code": "SUCCESS", 89 | "name": "xxxxx" 90 | } 91 | ``` 92 | 93 | scope==all 94 | ```json 95 | { 96 | "msg": "SUCCESS", 97 | "code": "SUCCESS", 98 | "phone": "xxx", 99 | "name": "xxx", 100 | "email": "xxxx" 101 | } 102 | ``` 103 | 104 | # Postman 105 | 106 | ![](./pic/oauth_basic.png) 107 | ![](./pic/oauth_password.png) -------------------------------------------------------------------------------- /doc/db/scheme.sql: -------------------------------------------------------------------------------- 1 | create database `auth` default character set utf8mb4 collate utf8mb4_general_ci; 2 | -------------------------------------------------------------------------------- /doc/ext.md: -------------------------------------------------------------------------------- 1 | # 扩展 2 | 3 | ## 扩展token接口,增加 user_id 4 | 5 | ![img.png](pic/ext_1.png) 6 | 7 | ## 扩展token内容, 增加 tenant 字段 8 | 9 | 写入到token 10 | 11 | ![img_2.png](pic/ext_2.png) 12 | 13 | 从上下文获取 14 | 15 | ![img_3.png](pic/ext_3.png) 16 | -------------------------------------------------------------------------------- /doc/pic/AbstractDaoAuthenticationConfigurer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/AbstractDaoAuthenticationConfigurer.png -------------------------------------------------------------------------------- /doc/pic/AuthenticationManagerBuilder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/AuthenticationManagerBuilder.png -------------------------------------------------------------------------------- /doc/pic/HttpSecurity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/HttpSecurity.png -------------------------------------------------------------------------------- /doc/pic/WebSecurity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/WebSecurity.png -------------------------------------------------------------------------------- /doc/pic/WebSecurityConfigurerAdapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/WebSecurityConfigurerAdapter.png -------------------------------------------------------------------------------- /doc/pic/abstractauthenticationprocessingfilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/abstractauthenticationprocessingfilter.png -------------------------------------------------------------------------------- /doc/pic/delegatingfilterproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/delegatingfilterproxy.png -------------------------------------------------------------------------------- /doc/pic/demo-full-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/demo-full-login.png -------------------------------------------------------------------------------- /doc/pic/exceptiontranslationfilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/exceptiontranslationfilter.png -------------------------------------------------------------------------------- /doc/pic/ext_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/ext_1.png -------------------------------------------------------------------------------- /doc/pic/ext_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/ext_2.png -------------------------------------------------------------------------------- /doc/pic/ext_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/ext_3.png -------------------------------------------------------------------------------- /doc/pic/filterchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/filterchain.png -------------------------------------------------------------------------------- /doc/pic/filterchainproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/filterchainproxy.png -------------------------------------------------------------------------------- /doc/pic/multi-securityfilterchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/multi-securityfilterchain.png -------------------------------------------------------------------------------- /doc/pic/oauth_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/oauth_basic.png -------------------------------------------------------------------------------- /doc/pic/oauth_client_credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/oauth_client_credentials.png -------------------------------------------------------------------------------- /doc/pic/oauth_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/oauth_password.png -------------------------------------------------------------------------------- /doc/pic/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/preview.png -------------------------------------------------------------------------------- /doc/pic/providermanager-parent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/providermanager-parent.png -------------------------------------------------------------------------------- /doc/pic/providermanager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/providermanager.png -------------------------------------------------------------------------------- /doc/pic/providermanagers-parent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/providermanagers-parent.png -------------------------------------------------------------------------------- /doc/pic/rbac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/rbac.png -------------------------------------------------------------------------------- /doc/pic/securitycontextholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/securitycontextholder.png -------------------------------------------------------------------------------- /doc/pic/usernamepasswordauthenticationfilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/doc/pic/usernamepasswordauthenticationfilter.png -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | - oauth2-client 2 | - oauth2 客户端 3 | - oauth2-resource 4 | - oauth2 资源服务器 5 | -------------------------------------------------------------------------------- /example/oauth2-client/README.md: -------------------------------------------------------------------------------- 1 | # oauth2-client 2 | 3 | OAuth2 客户端 4 | 5 | 如何获取 client_id 和 client_secret 请看 6 | [oauth2-server](https://github.com/taoroot/oauth2-server) 7 | 8 | 这里没用 Security OAuth2 的 EnableOAuth2Client 9 | 因为他的实现都是基于session的,保持持久登录 10 | 因为我们的token是jwt,本身就是持久化功能, 所以实际上只是想获取的token返回给前端就好 11 | 12 | 13 | # 接口 14 | 15 | - 通过code换取 16 | 17 | ```http 18 | POST /token?code=qp4rOb HTTP/1.1 19 | ``` -------------------------------------------------------------------------------- /example/oauth2-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cn.flizi 7 | example 8 | 0.1.0 9 | 10 | example-oauth2-client 11 | jar 12 | 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-web 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/oauth2-client/src/main/java/cn/flizi/auth/client/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.client; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example/oauth2-client/src/main/java/cn/flizi/auth/client/properties/ClientProperties.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.client.properties; 2 | 3 | 4 | import lombok.Data; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Data 9 | @Configuration 10 | @ConfigurationProperties(prefix = "security.oauth2.client") 11 | public class ClientProperties { 12 | 13 | private String clientId; 14 | private String clientSecret; 15 | private String tokenInfoUri; 16 | } 17 | -------------------------------------------------------------------------------- /example/oauth2-client/src/main/java/cn/flizi/auth/client/web/RestApiController.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.client.web; 2 | 3 | import cn.flizi.auth.client.properties.ClientProperties; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.core.ParameterizedTypeReference; 6 | import org.springframework.http.HttpEntity; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.http.HttpMethod; 9 | import org.springframework.util.StringUtils; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | import org.springframework.web.client.RestTemplate; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | @RestController 19 | public class RestApiController { 20 | 21 | @Autowired 22 | private ClientProperties clientProperties; 23 | 24 | @PostMapping(value = "token") 25 | public Map userBase(@RequestParam Map params) { 26 | HashMap result = new HashMap<>(); 27 | String code = params.get("code"); 28 | if (!StringUtils.hasLength(code)) { 29 | result.put("code", "ERROR"); 30 | result.put("msg", "无效CODE"); 31 | return result; 32 | } 33 | 34 | String uri = String.format(clientProperties.getTokenInfoUri(), 35 | clientProperties.getClientId(), 36 | clientProperties.getClientSecret(), 37 | code); 38 | return getStringObjectMap(uri); 39 | } 40 | 41 | 42 | public Map getStringObjectMap(String url) { 43 | RestTemplate restTemplate = new RestTemplate(); 44 | return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(new HttpHeaders()), 45 | new ParameterizedTypeReference>() { 46 | }) 47 | .getBody(); 48 | } 49 | } -------------------------------------------------------------------------------- /example/oauth2-client/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | oauth2: 3 | client: 4 | clientId: git 5 | clientSecret: secret 6 | tokenInfoUri: 'http://localhost:8080/oauth/token?client_id=%s&client_secret=%s&code=%s&grant_type=authorization_code' 7 | 8 | spring: 9 | profiles: 10 | active: pro 11 | 12 | logging: 13 | level: 14 | root: INFO 15 | org.springframework.web: DEBUG 16 | org.springframework.security: DEBUG 17 | org.springframework.security.oauth2: DEBUG 18 | org.springframework.boot.autoconfigure: DEBUG 19 | cn.flizi: INFO 20 | -------------------------------------------------------------------------------- /example/oauth2-resource/README.md: -------------------------------------------------------------------------------- 1 | # OAuth Resource 演示 2 | 3 | 请先从 [oauth2-server](https://github.com/taoroot/oauth2-server) 4 | 获取 token 再来测试 5 | 6 | ## 测试接口 7 | 8 | ```http 9 | GET / HTTP/1.1 10 | Authorization: Bearer {access_token} 11 | ``` -------------------------------------------------------------------------------- /example/oauth2-resource/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cn.flizi 7 | example 8 | 0.1.0 9 | 10 | example-oauth2-resource 11 | jar 12 | 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-security 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-web 21 | 22 | 23 | 24 | org.springframework.security.oauth 25 | spring-security-oauth2 26 | 2.5.0.RELEASE 27 | 28 | 29 | 30 | org.springframework.security.oauth.boot 31 | spring-security-oauth2-autoconfigure 32 | 2.4.2 33 | 34 | 35 | 36 | org.springframework.security 37 | spring-security-test 38 | test 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /example/oauth2-resource/src/main/java/cn/flizi/auth/resource/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.resource.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 6 | 7 | @EnableWebSecurity 8 | @SpringBootApplication 9 | public class DemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /example/oauth2-resource/src/main/java/cn/flizi/auth/resource/demo/config/ResourceConfig.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.resource.demo.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 7 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 8 | 9 | @EnableResourceServer 10 | @Configuration 11 | public class ResourceConfig extends ResourceServerConfigurerAdapter { 12 | 13 | @Override 14 | public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 15 | resources.resourceId("test"); 16 | } 17 | 18 | @Override 19 | public void configure(HttpSecurity http) throws Exception { 20 | super.configure(http); 21 | } 22 | } -------------------------------------------------------------------------------- /example/oauth2-resource/src/main/java/cn/flizi/auth/resource/demo/web/RestApiController.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.resource.demo.web; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.core.context.SecurityContextHolder; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.HashMap; 9 | 10 | @RestController 11 | public class RestApiController { 12 | 13 | @GetMapping(value = "/") 14 | public HashMap userBase() { 15 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 16 | String userId = (String) authentication.getPrincipal(); 17 | HashMap result = new HashMap<>(); 18 | result.put("code", "SUCCESS"); 19 | result.put("msg", "资源服务器(test)"); 20 | HashMap data = new HashMap<>(); 21 | data.put("name", userId); 22 | result.put("data", data); 23 | return result; 24 | } 25 | } -------------------------------------------------------------------------------- /example/oauth2-resource/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | oauth2: 3 | resource: 4 | jwt: 5 | key-value: hello_world 6 | 7 | spring: 8 | profiles: 9 | active: pro 10 | 11 | logging: 12 | level: 13 | root: INFO 14 | org.springframework.web: DEBUG 15 | org.springframework.security: DEBUG 16 | org.springframework.security.oauth2: DEBUG 17 | org.springframework.boot.autoconfigure: DEBUG 18 | cn.flizi: INFO 19 | -------------------------------------------------------------------------------- /example/oauth2-resource/src/test/java/cn/flizi/auth/resource/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.resource.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | cn.flizi 8 | oauth2-parent 9 | 0.1.0 10 | 11 | pom 12 | 13 | example 14 | 15 | 16 | oauth2-client 17 | oauth2-resource 18 | 19 | 20 | -------------------------------------------------------------------------------- /extension/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | cn.flizi 8 | oauth2-parent 9 | 0.1.0 10 | 11 | jar 12 | 13 | extension 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-web 19 | 20 | 21 | com.baomidou 22 | mybatis-plus-extension 23 | 24 | 25 | io.swagger 26 | swagger-annotations 27 | 28 | 29 | cn.flizi 30 | common 31 | 32 | 33 | org.springframework.security 34 | spring-security-core 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-validation 39 | 40 | 41 | io.springfox 42 | springfox-core 43 | ${springfox-swagger} 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/RbacConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @MapperScan(basePackages = "cn.flizi.ext.rbac.mapper") 9 | @ComponentScan("cn.flizi.ext.rbac") 10 | public class RbacConfiguration { 11 | 12 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/dto/SysMenuAdd.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import lombok.Data; 5 | 6 | @Data 7 | @ApiModel 8 | public class SysMenuAdd { 9 | private String path; 10 | 11 | private String component; 12 | 13 | private Boolean hidden; 14 | 15 | private Boolean alwaysShow; 16 | 17 | private String redirect; 18 | 19 | private String title; 20 | 21 | private String authority; 22 | 23 | private String icon; 24 | 25 | private Boolean breadcrumb; 26 | 27 | private Boolean noCache; 28 | 29 | private Integer parentId; 30 | 31 | private Integer weight; 32 | 33 | private Integer type; 34 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/dto/SysMenuUpdate.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | @Data 10 | @ApiModel 11 | public class SysMenuUpdate { 12 | @ApiModelProperty(value = "ID", required = true, example = "1") 13 | @NotEmpty 14 | private Integer id; 15 | 16 | private String path; 17 | 18 | private String component; 19 | 20 | private Boolean hidden; 21 | 22 | private Boolean alwaysShow; 23 | 24 | private String redirect; 25 | 26 | private String title; 27 | 28 | private String authority; 29 | 30 | private String icon; 31 | 32 | private Boolean breadcrumb; 33 | 34 | private Boolean noCache; 35 | 36 | private Integer parentId; 37 | 38 | private Integer weight; 39 | 40 | private Integer type; 41 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/dto/SysRoleAdd.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | @Data 10 | @ApiModel 11 | public class SysRoleAdd { 12 | @ApiModelProperty(value = "角色标识", required = true, example = "USER_ADMIN") 13 | @NotEmpty 14 | private String name; 15 | @ApiModelProperty(value = "角色备注", required = true, example = "管理员") 16 | @NotEmpty 17 | private String remark; 18 | @ApiModelProperty(value = "菜单ID数组", required = true, example = "[1,2,3,4]") 19 | @NotEmpty 20 | private Integer[] menus; 21 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/dto/SysRoleUpdate.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | @Data 10 | @ApiModel 11 | public class SysRoleUpdate { 12 | @ApiModelProperty(value = "ID", required = true, example = "1") 13 | @NotEmpty 14 | private Integer id; 15 | @ApiModelProperty(value = "角色备注", required = true, example = "管理员") 16 | @NotEmpty 17 | private String remark; 18 | @ApiModelProperty(value = "菜单ID数组", required = true, example = "[1,2,3,4]") 19 | @NotEmpty 20 | private Integer[] menus; 21 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/entity/SysAuthority.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.baomidou.mybatisplus.extension.activerecord.Model; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | @TableName("sys_authority") 11 | @EqualsAndHashCode(callSuper = true) 12 | @Data 13 | public class SysAuthority extends Model { 14 | 15 | public static final int MENU = 0; 16 | public static final int FUNCTION = 1; 17 | 18 | @TableId(type = IdType.AUTO) 19 | private Integer id; 20 | 21 | private String name; 22 | 23 | private String method; 24 | 25 | private String path; 26 | 27 | private String authority; 28 | } 29 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/entity/SysMenu.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.baomidou.mybatisplus.extension.activerecord.Model; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | /** 13 | * Note: sub-menu only appear when route children.length >= 1 14 | * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html 15 | *

16 | * hidden: true if set true, item will not show in the sidebar(default is false) 17 | * alwaysShow: true if set true, will always show the root menu 18 | * if not set alwaysShow, when item has more than one children route, 19 | * it will becomes nested mode, otherwise not show the root menu 20 | * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb 21 | * name:'router-name' the name is used by (must set!!!) 22 | * meta : { 23 | * roles: ['admin','editor'] control the page roles (you can set multiple roles) 24 | * title: 'title' the name show in sidebar and breadcrumb (recommend set) 25 | * icon: 'svg-name'/'el-icon-x' the icon show in the sidebar 26 | * breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) 27 | * activeMenu: '/example/list' if set path, the sidebar will highlight the path you set 28 | * } 29 | */ 30 | @TableName("sys_menu") 31 | @EqualsAndHashCode(callSuper = true) 32 | @Data 33 | public class SysMenu extends Model { 34 | 35 | public static final Integer MENU = 0; 36 | public static final Integer FUNCTION = 1; 37 | 38 | @TableId(type = IdType.AUTO) 39 | private Integer id; 40 | 41 | private String path; 42 | 43 | private String component; 44 | 45 | private Boolean hidden; 46 | 47 | private Boolean alwaysShow; 48 | 49 | private String redirect; 50 | 51 | private String name; 52 | 53 | private String title; 54 | 55 | private String authority; 56 | 57 | private String icon; 58 | 59 | private Boolean breadcrumb; 60 | 61 | private Boolean noCache; 62 | 63 | private Integer parentId; 64 | 65 | private Integer weight; 66 | 67 | private Integer type; 68 | 69 | private LocalDateTime createTime; 70 | 71 | private LocalDateTime updateTime; 72 | } 73 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/entity/SysMenuAuthority.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.baomidou.mybatisplus.extension.activerecord.Model; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NoArgsConstructor; 11 | 12 | import java.time.LocalDateTime; 13 | 14 | @Data 15 | @TableName("sys_menu_authority") 16 | @EqualsAndHashCode(callSuper = true) 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class SysMenuAuthority extends Model { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @TableId(type = IdType.AUTO) 24 | private Integer id; 25 | 26 | private Integer menuId; 27 | 28 | private Integer authorityId; 29 | 30 | private LocalDateTime createTime; 31 | 32 | private LocalDateTime updateTime; 33 | } 34 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/entity/SysRole.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.baomidou.mybatisplus.extension.activerecord.Model; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @TableName("sys_role") 14 | @EqualsAndHashCode(callSuper = true) 15 | public class SysRole extends Model { 16 | private static final long serialVersionUID = 1L; 17 | 18 | @TableId(type = IdType.AUTO) 19 | private Integer id; 20 | 21 | private String name; 22 | 23 | private String remark; 24 | 25 | private Boolean isLock = false; 26 | 27 | private LocalDateTime createTime; 28 | 29 | private LocalDateTime updateTime; 30 | } 31 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/entity/SysRoleMenu.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.baomidou.mybatisplus.extension.activerecord.Model; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @TableName("sys_role_menu") 14 | @EqualsAndHashCode(callSuper = true) 15 | public class SysRoleMenu extends Model { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | @TableId(type = IdType.AUTO) 20 | private Integer id; 21 | 22 | private Integer roleId; 23 | 24 | private Integer menuId; 25 | 26 | private LocalDateTime createTime; 27 | 28 | private LocalDateTime updateTime; 29 | } 30 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/entity/SysRoleUser.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.baomidou.mybatisplus.extension.activerecord.Model; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @TableName("sys_role_user") 14 | @EqualsAndHashCode(callSuper = true) 15 | public class SysRoleUser extends Model { 16 | private static final long serialVersionUID = 1L; 17 | 18 | @TableId(type = IdType.AUTO) 19 | private Integer id; 20 | 21 | private Integer roleId; 22 | 23 | private Integer userId; 24 | 25 | private LocalDateTime createTime; 26 | 27 | private LocalDateTime updateTime; 28 | } 29 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/mapper/SysAuthorityMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.mapper; 2 | 3 | import cn.flizi.ext.rbac.entity.SysAuthority; 4 | import cn.flizi.ext.rbac.entity.SysMenuAuthority; 5 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 6 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 7 | import com.baomidou.mybatisplus.core.metadata.IPage; 8 | import com.baomidou.mybatisplus.core.toolkit.Constants; 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 10 | import org.apache.ibatis.annotations.Mapper; 11 | import org.apache.ibatis.annotations.Param; 12 | import org.apache.ibatis.annotations.Select; 13 | 14 | @Mapper 15 | public interface SysAuthorityMapper extends BaseMapper { 16 | 17 | @Select("select a.authority, a.name, a.path, a.method, ma.id from sys_menu_authority ma " + 18 | "left join sys_authority a on a.id = ma.authority_id ${ew.customSqlSegment}") 19 | IPage selectByMenu(@Param("page") Page page, 20 | @Param(Constants.WRAPPER) LambdaQueryWrapper wrapper); 21 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/mapper/SysMenuAuthorityMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.mapper; 2 | 3 | import cn.flizi.ext.rbac.entity.SysMenuAuthority; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface SysMenuAuthorityMapper extends BaseMapper { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/mapper/SysMenuMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.mapper; 2 | 3 | import cn.flizi.ext.rbac.entity.SysMenu; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface SysMenuMapper extends BaseMapper { 9 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/mapper/SysRoleMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.mapper; 2 | 3 | import cn.flizi.ext.rbac.entity.SysRole; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.baomidou.mybatisplus.core.metadata.IPage; 6 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 7 | import org.apache.ibatis.annotations.Mapper; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author : zhiyi 13 | * Date: 2020/2/11 14 | */ 15 | @Mapper 16 | public interface SysRoleMapper extends BaseMapper { 17 | 18 | IPage getPage(Page page); 19 | 20 | List selectAuthoritiesByRole(Integer roleId); 21 | } 22 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/mapper/SysRoleMenuMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.mapper; 2 | 3 | import cn.flizi.ext.rbac.entity.SysRoleMenu; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface SysRoleMenuMapper extends BaseMapper { 9 | 10 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/mapper/SysRoleUserMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.mapper; 2 | 3 | import cn.flizi.ext.rbac.entity.SysRoleUser; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * @author : zhiyi 9 | * Date: 2020/2/11 10 | */ 11 | @Mapper 12 | public interface SysRoleUserMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/service/SysAuthorityService.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.service; 2 | 3 | import cn.flizi.ext.rbac.entity.SysAuthority; 4 | import cn.flizi.ext.rbac.entity.SysMenuAuthority; 5 | import cn.flizi.ext.rbac.mapper.SysAuthorityMapper; 6 | import com.baomidou.mybatisplus.core.metadata.IPage; 7 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 8 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 9 | import com.baomidou.mybatisplus.extension.service.IService; 10 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class SysAuthorityService extends ServiceImpl implements IService { 15 | 16 | public IPage pageByMenu(Integer menuId, Page page) { 17 | return baseMapper.selectByMenu(page, 18 | Wrappers.lambdaQuery() 19 | .eq(SysMenuAuthority::getMenuId, menuId) 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/service/SysMenuService.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.service; 2 | 3 | import cn.flizi.core.util.R; 4 | import cn.flizi.core.util.TreeUtils; 5 | import cn.flizi.ext.rbac.entity.SysMenu; 6 | import cn.flizi.ext.rbac.mapper.SysMenuMapper; 7 | import cn.hutool.core.lang.tree.Tree; 8 | import cn.hutool.core.lang.tree.TreeUtil; 9 | import cn.hutool.core.util.StrUtil; 10 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 11 | import com.baomidou.mybatisplus.extension.service.IService; 12 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 13 | import lombok.AllArgsConstructor; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.List; 17 | 18 | @Service 19 | @AllArgsConstructor 20 | public class SysMenuService extends ServiceImpl implements IService { 21 | 22 | public Object sort(Integer menuId, Integer index) { 23 | return null; 24 | } 25 | 26 | public Object removeForce(Integer id) { 27 | return null; 28 | } 29 | 30 | public Object addAuthorityByMenu(Integer menuId, List ids) { 31 | return null; 32 | } 33 | 34 | public Object removeAuthorityByMenu(Integer menuId, List ids) { 35 | return null; 36 | } 37 | 38 | public R> getTree() { 39 | List menuList = baseMapper.selectList(Wrappers.emptyWrapper()); 40 | List> authorityTree = TreeUtil.build(menuList, -1, (treeNode, tree) -> { 41 | tree.setId(treeNode.getId()); 42 | tree.setParentId(treeNode.getParentId()); 43 | tree.setWeight(treeNode.getWeight()); 44 | tree.setName(treeNode.getName()); 45 | tree.putExtra("path", treeNode.getPath()); 46 | tree.putExtra("type", treeNode.getType()); 47 | tree.putExtra("component", treeNode.getComponent()); 48 | tree.putExtra("hidden", treeNode.getHidden()); 49 | tree.putExtra("alwaysShow", treeNode.getAlwaysShow()); 50 | tree.putExtra("redirect", treeNode.getRedirect()); 51 | tree.putExtra("title", treeNode.getTitle()); 52 | tree.putExtra("icon", treeNode.getIcon()); 53 | tree.putExtra("authority", treeNode.getAuthority()); 54 | tree.putExtra("breadcrumb", treeNode.getBreadcrumb()); 55 | }); 56 | if (authorityTree.size() == 0) { 57 | return R.ok(menuList); 58 | } 59 | // 排序 60 | TreeUtils.computeSort(authorityTree); 61 | // 计算路径 62 | computePath(authorityTree); 63 | return R.ok(authorityTree); 64 | } 65 | 66 | public static void computePath(List> trees) { 67 | if (trees == null || trees.size() == 0) { 68 | return; 69 | } 70 | for (Tree parent : trees) { 71 | if (parent.getChildren() == null) { 72 | return; 73 | } 74 | parent.putExtra("absPath", parent.get("path")); 75 | for (Tree child : parent.getChildren()) { 76 | String path1 = (String) parent.get("path"); 77 | String path2 = (String) child.get("path"); 78 | if (!StrUtil.startWithAny(path2, "http", "https")) { 79 | child.putExtra("absPath", path1 + "/" + path2); 80 | } else { 81 | child.putExtra("absPath", path2); 82 | } 83 | } 84 | computePath(parent.getChildren()); 85 | } 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/service/SysRoleMenuService.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.service; 2 | 3 | import cn.flizi.ext.rbac.entity.SysRoleMenu; 4 | import cn.flizi.ext.rbac.mapper.SysRoleMenuMapper; 5 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 6 | import com.baomidou.mybatisplus.extension.service.IService; 7 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 8 | import lombok.AllArgsConstructor; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | @Service 16 | @AllArgsConstructor 17 | public class SysRoleMenuService extends ServiceImpl implements IService { 18 | 19 | /** 20 | * 全更新, 会先删除说有绑定关系,再重新添加 21 | * 22 | * @param roleId 角色id 23 | * @param menus 菜单ids 24 | */ 25 | public Boolean updateRoleMenu(Integer roleId, Integer[] menus) { 26 | List roleMenuList = Arrays.stream(menus).map(menuId -> { 27 | SysRoleMenu roleMenu = new SysRoleMenu(); 28 | roleMenu.setRoleId(roleId); 29 | roleMenu.setMenuId(menuId); 30 | return roleMenu; 31 | }).collect(Collectors.toList()); 32 | 33 | remove(Wrappers.lambdaQuery() 34 | .eq(SysRoleMenu::getRoleId, roleId)); 35 | saveBatch(roleMenuList); 36 | return true; 37 | } 38 | } -------------------------------------------------------------------------------- /extension/src/main/java/cn/flizi/ext/rbac/service/SysRoleService.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac.service; 2 | 3 | import cn.flizi.ext.rbac.dto.SysRoleAdd; 4 | import cn.flizi.ext.rbac.dto.SysRoleUpdate; 5 | import cn.flizi.ext.rbac.entity.SysRole; 6 | import cn.flizi.ext.rbac.mapper.SysRoleMapper; 7 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 8 | import com.baomidou.mybatisplus.extension.service.IService; 9 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 10 | import lombok.AllArgsConstructor; 11 | import org.springframework.beans.BeanUtils; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.util.Assert; 14 | 15 | @Service 16 | @AllArgsConstructor 17 | public class SysRoleService extends ServiceImpl implements IService { 18 | 19 | private final SysRoleMenuService sysRoleMenuService; 20 | 21 | /** 22 | * 创建角色 23 | * 24 | * @param body 25 | * @return 26 | */ 27 | public Boolean saveRole(SysRoleAdd body) { 28 | // 检查 29 | int count = count(Wrappers.lambdaQuery().eq(SysRole::getName, body.getName())); 30 | Assert.isTrue(count > 0, "角色标识重复"); 31 | 32 | // 创建 33 | SysRole sysRole = new SysRole(); 34 | BeanUtils.copyProperties(body, sysRole); 35 | sysRole.insert(); 36 | 37 | // 更新关联菜单 38 | return sysRoleMenuService.updateRoleMenu(sysRole.getId(), body.getMenus()); 39 | } 40 | 41 | 42 | /** 43 | * 更新角色 44 | * 45 | * @param body 46 | * @return 47 | */ 48 | public Boolean updateRole(SysRoleUpdate body) { 49 | // 检查 50 | Integer roleId = body.getId(); 51 | SysRole role = getById(roleId); 52 | Assert.notNull(role, "确实不存在"); 53 | Assert.isTrue(!role.getIsLock(), "当前角色状态被锁定,禁止修改"); 54 | 55 | // 更新关联菜单 56 | return sysRoleMenuService.updateRoleMenu(roleId, body.getMenus()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /extension/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | cn.flizi.ext.rbac.RbacConfiguration 3 | -------------------------------------------------------------------------------- /extension/src/main/resources/mapper/SysMenuMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /extension/src/main/resources/mapper/SysRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /extension/ui/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /extension/ui/.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://localhost:8080/' 6 | VUE_APP_OAUTH2_API = 'http://localhost:8080/' 7 | -------------------------------------------------------------------------------- /extension/ui/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | src/assets 3 | public 4 | dist 5 | -------------------------------------------------------------------------------- /extension/ui/.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 | logs/ 35 | 36 | 37 | *.java.hsp 38 | *.sonarj 39 | *.sw* 40 | .DS_Store 41 | bin 42 | build.sh 43 | integration-repo 44 | ivy-cache 45 | jxl.log 46 | jmx.log 47 | derby.log 48 | spring-test/test-output/ 49 | .gradle 50 | argfile* 51 | activemq-data/ 52 | 53 | classes/ 54 | /build 55 | buildSrc/build 56 | /spring-*/build 57 | /spring-core/kotlin-coroutines/build 58 | /framework-bom/build 59 | /integration-tests/build 60 | /src/asciidoc/build 61 | /build/ 62 | application-dev.yml 63 | 64 | # Eclipse artifacts, including WTP generated manifests 65 | spring-*/src/main/java/META-INF/MANIFEST.MF 66 | 67 | # IDEA artifacts and output dirs 68 | out 69 | test-output 70 | atlassian-ide-plugin.xml 71 | .gradletasknamecache 72 | 73 | 74 | node_modules/ 75 | dist/ 76 | npm-debug.log* 77 | yarn-debug.log* 78 | yarn-error.log* 79 | package-lock.json 80 | tests/**/coverage/ 81 | 82 | # Editor directories and files 83 | .vscode 84 | *.suo 85 | *.ntvs* 86 | *.njsproj 87 | *.sln 88 | 89 | node_modules 90 | *.log 91 | .temp 92 | 93 | http-client.** 94 | generate/ 95 | -------------------------------------------------------------------------------- /extension/ui/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /extension/ui/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app 4 | '@vue/cli-plugin-babel/preset' 5 | ], 6 | 'env': { 7 | 'development': { 8 | // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). 9 | // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. 10 | // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html 11 | 'plugins': ['dynamic-import-node'] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /extension/ui/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /extension/ui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /extension/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tao-ui", 3 | "version": "0.0.1", 4 | "description": "github.com/taoroot/oauth2-server", 5 | "author": "taoroot ", 6 | "scripts": { 7 | "dev": "vue-cli-service serve", 8 | "build:prod": "vue-cli-service build", 9 | "build:stage": "vue-cli-service build --mode staging", 10 | "preview": "node build/index.js --preview", 11 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml", 12 | "lint": "eslint --ext .js,.vue src", 13 | "test:unit": "jest --clearCache && vue-cli-service test:unit", 14 | "test:ci": "npm run lint && npm run test:unit" 15 | }, 16 | "dependencies": { 17 | "vue-count-to": "1.0.13", 18 | "echarts": "^4.8.0", 19 | "less": "^3.0.4", 20 | "less-loader": "^4.1.0", 21 | "@riophae/vue-treeselect": "0.4.0", 22 | "axios": "0.18.1", 23 | "core-js": "3.6.5", 24 | "element-ui": "2.13.2", 25 | "js-cookie": "2.2.0", 26 | "normalize.css": "7.0.0", 27 | "nprogress": "0.2.0", 28 | "path-to-regexp": "2.4.0", 29 | "vue": "2.6.10", 30 | "vue-cropper": "^0.5.5", 31 | "vue-router": "3.0.6", 32 | "vuex": "3.1.0" 33 | }, 34 | "devDependencies": { 35 | "@vue/cli-plugin-babel": "4.4.4", 36 | "@vue/cli-plugin-eslint": "4.4.4", 37 | "@vue/cli-plugin-unit-jest": "4.4.4", 38 | "@vue/cli-service": "4.4.4", 39 | "@vue/test-utils": "1.0.0-beta.29", 40 | "autoprefixer": "9.5.1", 41 | "babel-eslint": "10.1.0", 42 | "babel-jest": "23.6.0", 43 | "babel-plugin-dynamic-import-node": "2.3.3", 44 | "chalk": "2.4.2", 45 | "connect": "3.6.6", 46 | "eslint": "6.7.2", 47 | "eslint-plugin-vue": "6.2.2", 48 | "html-webpack-plugin": "3.2.0", 49 | "mockjs": "1.0.1-beta3", 50 | "runjs": "4.3.2", 51 | "sass": "1.26.8", 52 | "sass-loader": "8.0.2", 53 | "script-ext-html-webpack-plugin": "2.1.3", 54 | "serve-static": "1.13.2", 55 | "svg-sprite-loader": "4.1.3", 56 | "svgo": "1.2.2", 57 | "vue-template-compiler": "2.6.10" 58 | }, 59 | "browserslist": [ 60 | "> 1%", 61 | "last 2 versions" 62 | ], 63 | "engines": { 64 | "node": ">=8.9", 65 | "npm": ">= 3.0.0" 66 | }, 67 | "license": "MIT" 68 | } 69 | -------------------------------------------------------------------------------- /extension/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | 'plugins': { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | 'autoprefixer': {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /extension/ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 14 |

15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /extension/ui/public/oauth2-callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OAUTH2 回 调 8 | 9 | 10 |
11 |

OAUTH2 回 调

12 |
13 | 14 | 45 | -------------------------------------------------------------------------------- /extension/ui/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /extension/ui/src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | var Authorization = 'BASIC ' + btoa('test:secret') 4 | var VUE_APP_OAUTH2_API = process.env.VUE_APP_OAUTH2_API 5 | 6 | /** 7 | * OAuth2 密码登录 8 | * @param {*} data 9 | * @param {*} params 10 | * @returns 11 | */ 12 | export function oauth2Login(data, params) { 13 | return request({ 14 | url: VUE_APP_OAUTH2_API + 'oauth/token', 15 | method: 'post', 16 | headers: { 17 | Authorization: Authorization 18 | }, 19 | data: data, 20 | params: params 21 | }) 22 | } 23 | 24 | export function oauth2CodeLogin(params) { 25 | return request({ 26 | url: '/token', 27 | method: 'post', 28 | params: params 29 | }) 30 | } 31 | 32 | export function getInfo() { 33 | return request({ 34 | url: '/user_info', 35 | method: 'get' 36 | }) 37 | } 38 | 39 | export function loginPhone(params) { 40 | return request({ 41 | url: '/sms', 42 | method: 'post', 43 | params 44 | }) 45 | } 46 | 47 | export function getUserSocial() { 48 | return request({ 49 | url: '/upms/user_socials', 50 | method: 'get' 51 | }) 52 | } 53 | 54 | export function getUserProfile() { 55 | return request({ 56 | url: '/upms/user_info', 57 | method: 'get' 58 | }) 59 | } 60 | 61 | export function unbindUserSocial(id) { 62 | return request({ 63 | url: `/upms/user_social/${id}`, 64 | method: 'delete' 65 | }) 66 | } 67 | 68 | export function updateUser(data) { 69 | return request({ 70 | url: `/upms/user_info`, 71 | method: 'put', 72 | data 73 | }) 74 | } 75 | 76 | export function resetPassword(data) { 77 | return request({ 78 | url: `/upms/user_password`, 79 | method: 'put', 80 | data 81 | }) 82 | } 83 | 84 | export function getSms({ phone }) { 85 | return request({ 86 | url: `https://auth.flizi.cn/sms?phone=${phone}`, 87 | method: 'post' 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /extension/ui/src/api/sys/authority.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * 菜单具有的权限 5 | * @param {*} params 6 | */ 7 | export const getMenuAuthoritys = (params) => { 8 | return request({ 9 | url: '/api/sys/menu/authority/page', 10 | method: 'get', 11 | params 12 | }) 13 | } 14 | 15 | /** 16 | * 菜单具有的权限新增 17 | * @param {*} menuId 18 | * @param {*} data 19 | */ 20 | export const updateMenuAuthority = (menuId, data) => { 21 | return request({ 22 | url: `/sys/menu/authority?menuId=${menuId}`, 23 | method: 'post', 24 | data 25 | }) 26 | } 27 | 28 | /** 29 | * 菜单具有的权限移除 30 | * @param {*} menuId 31 | * @param {*} data 32 | */ 33 | export const deleteMenuAuthority = (menuId, data) => { 34 | return request({ 35 | url: `/sys/menu/authority?menuId=${menuId}`, 36 | method: 'delete', 37 | data 38 | }) 39 | } 40 | 41 | /** 42 | * 所有菜单 43 | * @param {*} params 44 | */ 45 | export const getAuthoritys = (params) => { 46 | return request({ 47 | url: '/sys/authority/page', 48 | method: 'get', 49 | params 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /extension/ui/src/api/sys/menu.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * 菜单树 5 | * @param {分页参数} params 6 | */ 7 | export const menuTree = (params) => { 8 | return request({ 9 | url: '/api/sys/menu/tree', 10 | method: 'get', 11 | params 12 | }) 13 | } 14 | 15 | /** 16 | * 新增 17 | * @param {数据体} data 18 | */ 19 | export const menuAdd = (data) => { 20 | return request({ 21 | url: '/api/sys/menu', 22 | method: 'post', 23 | data: data 24 | }) 25 | } 26 | 27 | /** 28 | * 批量删除 29 | * @param {id数组} ids 30 | */ 31 | export const menuDel = (ids) => { 32 | return request({ 33 | url: '/api/sys/menu', 34 | method: 'delete', 35 | params: { 36 | ids 37 | } 38 | }) 39 | } 40 | 41 | /** 42 | * 强制删除 43 | * @param {主键} id 44 | */ 45 | export const menuDelForce = (id) => { 46 | return request({ 47 | url: '/api/sys/menu/force', 48 | method: 'delete', 49 | params: { 50 | id 51 | } 52 | }) 53 | } 54 | 55 | /** 56 | * 更新 57 | * @param {数据体} data 58 | */ 59 | export function menuUpdate(data) { 60 | return request({ 61 | url: '/api/sys/menu', 62 | method: 'put', 63 | data: data 64 | }) 65 | } 66 | 67 | /** 68 | * 排序 69 | * @param {排序参数} params 70 | */ 71 | export function menuSort(params) { 72 | return request({ 73 | url: '/api/sys/menu/sort', 74 | method: 'put', 75 | params 76 | }) 77 | } 78 | 79 | -------------------------------------------------------------------------------- /extension/ui/src/api/sys/role.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * 分页 5 | * @param {分页参数} params 6 | */ 7 | export function rolePage(params) { 8 | return request({ 9 | url: '/api/sys/role/page', 10 | method: 'get', 11 | params 12 | }) 13 | } 14 | 15 | /** 16 | * 批量删除 17 | * @param {id数组} ids 18 | */ 19 | export function roleDel(ids) { 20 | return request({ 21 | url: `/api/sys/role`, 22 | method: 'delete', 23 | params: { 24 | ids: ids 25 | } 26 | }) 27 | } 28 | 29 | /** 30 | * 新增 31 | * @param {数据体} data 32 | */ 33 | export function roleAdd(data) { 34 | return request({ 35 | url: '/api/sys/role', 36 | method: 'post', 37 | data 38 | }) 39 | } 40 | 41 | /** 42 | * 更新 43 | * @param {数据体} data 44 | */ 45 | export function roleUpdate(data) { 46 | return request({ 47 | url: `/api/sys/role`, 48 | method: 'put', 49 | data 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /extension/ui/src/api/sys/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * 分页 5 | * @param {分页参数} params 6 | */ 7 | export function userPage(params) { 8 | return request({ 9 | url: '/sys/user/page', 10 | method: 'get', 11 | params 12 | }) 13 | } 14 | 15 | /** 16 | * 批量删除 17 | * @param {id数组} ids 18 | */ 19 | export function userDel(ids) { 20 | return request({ 21 | url: `/sys/user`, 22 | method: 'delete', 23 | params: { 24 | ids: ids 25 | } 26 | }) 27 | } 28 | 29 | /** 30 | * 新增 31 | * @param {数据体} data 32 | */ 33 | export function userAdd(data) { 34 | return request({ 35 | url: '/sys/user', 36 | method: 'post', 37 | data 38 | }) 39 | } 40 | 41 | /** 42 | * 更新 43 | * @param {数据体} data 44 | */ 45 | export function userUpdate(data) { 46 | return request({ 47 | url: `/sys/user`, 48 | method: 'put', 49 | data 50 | }) 51 | } 52 | 53 | /** 54 | * 更新用户状态 55 | * @param {用户ID} id 56 | * @param {状态} enabled 57 | */ 58 | export function userStatusChange(id, enabled) { 59 | const data = { 60 | id, 61 | enabled 62 | } 63 | return request({ 64 | url: `/sys/user`, 65 | method: 'put', 66 | data: data 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /extension/ui/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/404_images/404.png -------------------------------------------------------------------------------- /extension/ui/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /extension/ui/src/assets/pic/green_addr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/green_addr@2x.png -------------------------------------------------------------------------------- /extension/ui/src/assets/pic/null_addr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/null_addr@2x.png -------------------------------------------------------------------------------- /extension/ui/src/assets/pic/orange_addr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/orange_addr@2x.png -------------------------------------------------------------------------------- /extension/ui/src/assets/pic/red_addr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/red_addr@2x.png -------------------------------------------------------------------------------- /extension/ui/src/assets/pic/yellow_addr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/yellow_addr@2x.png -------------------------------------------------------------------------------- /extension/ui/src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 65 | 66 | 79 | -------------------------------------------------------------------------------- /extension/ui/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /extension/ui/src/components/IconSelect/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 45 | 46 | 70 | -------------------------------------------------------------------------------- /extension/ui/src/components/IconSelect/requireIcons.js: -------------------------------------------------------------------------------- 1 | 2 | const req = require.context('../../icons/svg', false, /\.svg$/) 3 | const requireAll = requireContext => requireContext.keys() 4 | 5 | const re = /\.\/(.*)\.svg/ 6 | 7 | const icons = requireAll(req).map(i => { 8 | return i.match(re)[1] 9 | }) 10 | 11 | export default icons 12 | -------------------------------------------------------------------------------- /extension/ui/src/components/RightPanel/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 77 | 78 | 85 | 86 | 140 | -------------------------------------------------------------------------------- /extension/ui/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /extension/ui/src/directive/permission/index.js: -------------------------------------------------------------------------------- 1 | import permission from './permission' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('permission', permission) 5 | } 6 | 7 | if (window.Vue) { 8 | window['permission'] = permission 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | permission.install = install 13 | export default permission 14 | -------------------------------------------------------------------------------- /extension/ui/src/directive/permission/permission.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | function checkPermission(el, binding) { 4 | const { value } = binding 5 | const roles = store.getters && store.getters.roles 6 | 7 | if (value && value instanceof Array) { 8 | if (value.length > 0) { 9 | const permissionRoles = value 10 | 11 | const hasPermission = roles.some(role => { 12 | return permissionRoles.includes(role) 13 | }) 14 | 15 | if (!hasPermission) { 16 | el.parentNode && el.parentNode.removeChild(el) 17 | } 18 | } 19 | } else { 20 | throw new Error(`need roles! Like v-permission="['admin','editor']"`) 21 | } 22 | } 23 | 24 | export default { 25 | inserted(el, binding) { 26 | checkPermission(el, binding) 27 | }, 28 | update(el, binding) { 29 | checkPermission(el, binding) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /extension/ui/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /extension/ui/src/icons/svg/light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/ui/src/icons/svg/money.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/ui/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/ui/src/icons/svg/sun.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/ui/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/ui/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/Settings/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 82 | 83 | 105 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/Sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 33 | 34 | 83 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 96 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 58 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/TagsView/ScrollPane.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 71 | 72 | 88 | -------------------------------------------------------------------------------- /extension/ui/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as Settings } from './Settings' 4 | export { default as AppMain } from './AppMain' 5 | export { default as TagsView } from './TagsView/index.vue' 6 | -------------------------------------------------------------------------------- /extension/ui/src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 58 | 59 | 96 | -------------------------------------------------------------------------------- /extension/ui/src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /extension/ui/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | 8 | import '@/styles/index.scss' // global css 9 | 10 | import App from './App' 11 | import store from './store' 12 | import router from './router' 13 | 14 | import '@/icons' // icon 15 | import '@/permission' // permission control 16 | 17 | // 权限判断指令 18 | import permission from './directive/permission/index.js' 19 | Vue.use(permission) 20 | 21 | // set ElementUI lang to EN 22 | // Vue.use(ElementUI, { locale }) 23 | // 如果想要中文版 element-ui,按如下方式声明 24 | Vue.use(ElementUI) 25 | 26 | Vue.config.productionTip = false 27 | 28 | new Vue({ 29 | el: '#app', 30 | router, 31 | store, 32 | render: h => h(App) 33 | }) 34 | -------------------------------------------------------------------------------- /extension/ui/src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import { Message } from 'element-ui' 4 | import NProgress from 'nprogress' // progress bar 5 | import 'nprogress/nprogress.css' // progress bar style 6 | import { getToken } from '@/utils/auth' // get token from cookie 7 | import getPageTitle from '@/utils/get-page-title' 8 | 9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration 10 | 11 | const whiteList = ['/login', '/login/index', '/auth-redirect'] // no redirect whitelist 12 | 13 | router.beforeEach(async(to, from, next) => { 14 | // start progress bar 15 | NProgress.start() 16 | 17 | // set page title 18 | document.title = getPageTitle(to.meta.title) 19 | 20 | // determine whether the user has logged in 21 | const hasToken = getToken() 22 | 23 | if (hasToken) { 24 | if (to.path === '/login') { 25 | // if is logged in, redirect to the home page 26 | next({ path: '/' }) 27 | NProgress.done() 28 | } else { 29 | const hasGetUserInfo = store.getters.name 30 | if (hasGetUserInfo) { 31 | next() 32 | } else { 33 | try { 34 | // get user info 35 | const { menus } = await store.dispatch('user/getInfo') 36 | 37 | const accessRoutes = await store.dispatch('permission/generateRoutes', menus) 38 | 39 | router.addRoutes(accessRoutes) 40 | 41 | next({ ...to, replace: true }) 42 | } catch (error) { 43 | // remove token and go to login page to re-login 44 | await store.dispatch('user/resetToken') 45 | Message.error(error || 'Has Error') 46 | next(`/login?redirect=${to.path}`) 47 | NProgress.done() 48 | } 49 | } 50 | } 51 | } else { 52 | /* has no token*/ 53 | 54 | if (whiteList.indexOf(to.path) !== -1) { 55 | // in the free login whitelist, go directly 56 | next() 57 | } else { 58 | // other pages that do not have permission to access are redirected to the login page. 59 | next(`/login?redirect=${to.path}`) 60 | NProgress.done() 61 | } 62 | } 63 | }) 64 | 65 | router.afterEach(() => { 66 | // finish progress bar 67 | NProgress.done() 68 | }) 69 | -------------------------------------------------------------------------------- /extension/ui/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | /* Layout */ 7 | import Layout from '@/layout' 8 | 9 | /** 10 | * Note: sub-menu only appear when route children.length >= 1 11 | * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html 12 | * 13 | * hidden: true if set true, item will not show in the sidebar(default is false) 14 | * alwaysShow: true if set true, will always show the root menu 15 | * if not set alwaysShow, when item has more than one children route, 16 | * it will becomes nested mode, otherwise not show the root menu 17 | * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb 18 | * name:'router-name' the name is used by (must set!!!) 19 | * meta : { 20 | roles: ['admin','editor'] control the page roles (you can set multiple roles) 21 | title: 'title' the name show in sidebar and breadcrumb (recommend set) 22 | icon: 'svg-name'/'el-icon-x' the icon show in the sidebar 23 | breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) 24 | activeMenu: '/example/list' if set path, the sidebar will highlight the path you set 25 | } 26 | */ 27 | 28 | /** 29 | * constantRoutes 30 | * a base page that does not have permission requirements 31 | * all roles can be accessed 32 | */ 33 | export const constantRoutes = [ 34 | { 35 | path: '/login', 36 | component: () => import('@/views/login/index'), 37 | hidden: true 38 | }, 39 | 40 | { 41 | path: '/auth-redirect', 42 | component: () => import('@/views/login/auth-redirect'), 43 | hidden: true 44 | }, 45 | 46 | { 47 | path: '/404', 48 | component: () => import('@/views/404'), 49 | hidden: true 50 | }, 51 | 52 | // { 53 | // path: '/login', 54 | // component: Layout, 55 | // redirect: '/login/index', 56 | // children: [{ 57 | // path: '/index', 58 | // component: () => import('@/views/login/index'), 59 | // hidden: true 60 | // }] 61 | // }, 62 | { 63 | path: '/', 64 | component: Layout, 65 | children: [{ 66 | path: 'Dashboard', 67 | name: 'Dashboard', 68 | component: () => import('@/views/dashboard/index'), 69 | meta: { title: 'Dashboard', icon: 'dashboard' } 70 | }] 71 | }, 72 | 73 | { 74 | path: '/menu', 75 | component: Layout, 76 | redirect: '/', 77 | children: [{ 78 | path: 'menu', 79 | name: 'menu', 80 | component: () => import('@/views/menu/index'), 81 | meta: { title: '权限管理', icon: 'menu' } 82 | }] 83 | } 84 | 85 | // 404 page must be placed at the end !!! 86 | // { path: '*', redirect: '/404', hidden: false } 87 | ] 88 | 89 | const createRouter = () => new Router({ 90 | // mode: 'history', // require service support 91 | scrollBehavior: () => ({ y: 0 }), 92 | routes: constantRoutes 93 | }) 94 | 95 | const router = createRouter() 96 | 97 | // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 98 | export function resetRouter() { 99 | const newRouter = createRouter() 100 | router.matcher = newRouter.matcher // reset router 101 | } 102 | 103 | export default router 104 | -------------------------------------------------------------------------------- /extension/ui/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: '前端分离完整演示', 3 | 4 | /** 5 | * @type {boolean} true | false 6 | * @description Whether show the settings right-panel 7 | */ 8 | showSettings: false, 9 | 10 | /** 11 | * @type {boolean} true | false 12 | * @description Whether need tagsView 13 | */ 14 | tagsView: true, 15 | 16 | /** 17 | * @type {boolean} true | false 18 | * @description Whether fix the header 19 | */ 20 | fixedHeader: true, 21 | 22 | /** 23 | * @type {boolean} true | false 24 | * @description Whether show the logo in sidebar 25 | */ 26 | sidebarLogo: true, 27 | 28 | /** 29 | * @type {string | array} 'production' | ['production', 'development'] 30 | * @description Need show err logs component. 31 | * The default is only used in the production env 32 | * If you want to also use it in dev, you can pass ['production', 'development'] 33 | */ 34 | errorLog: 'production' 35 | } 36 | -------------------------------------------------------------------------------- /extension/ui/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | visitedViews: state => state.tagsView.visitedViews, 7 | cachedViews: state => state.tagsView.cachedViews, 8 | permission_routes: state => state.permission.routes, 9 | roles: state => state.user.roles, 10 | name: state => state.user.name 11 | } 12 | export default getters 13 | -------------------------------------------------------------------------------- /extension/ui/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | 5 | Vue.use(Vuex) 6 | 7 | // https://webpack.js.org/guides/dependency-management/#requirecontext 8 | const modulesFiles = require.context('./modules', true, /\.js$/) 9 | 10 | // you do not need `import app from './modules/app'` 11 | // it will auto require all vuex module from modules file 12 | const modules = modulesFiles.keys().reduce((modules, modulePath) => { 13 | // set './app.js' => 'app' 14 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') 15 | const value = modulesFiles(modulePath) 16 | modules[moduleName] = value.default 17 | return modules 18 | }, {}) 19 | 20 | const store = new Vuex.Store({ 21 | modules, 22 | getters 23 | }) 24 | 25 | export default store 26 | -------------------------------------------------------------------------------- /extension/ui/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const state = { 4 | sidebar: { 5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 6 | withoutAnimation: false 7 | }, 8 | device: 'desktop' 9 | } 10 | 11 | const mutations = { 12 | TOGGLE_SIDEBAR: state => { 13 | state.sidebar.opened = !state.sidebar.opened 14 | state.sidebar.withoutAnimation = false 15 | if (state.sidebar.opened) { 16 | Cookies.set('sidebarStatus', 1) 17 | } else { 18 | Cookies.set('sidebarStatus', 0) 19 | } 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 0) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | } 30 | 31 | const actions = { 32 | toggleSideBar({ commit }) { 33 | commit('TOGGLE_SIDEBAR') 34 | }, 35 | closeSideBar({ commit }, { withoutAnimation }) { 36 | commit('CLOSE_SIDEBAR', withoutAnimation) 37 | }, 38 | toggleDevice({ commit }, device) { 39 | commit('TOGGLE_DEVICE', device) 40 | } 41 | } 42 | 43 | export default { 44 | namespaced: true, 45 | state, 46 | mutations, 47 | actions 48 | } 49 | -------------------------------------------------------------------------------- /extension/ui/src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { constantRoutes } from '@/router' 2 | import Layout from '@/layout/index' 3 | 4 | const state = { 5 | routes: [], 6 | addRoutes: [] 7 | } 8 | 9 | const mutations = { 10 | SET_ROUTES: (state, routes) => { 11 | state.addRoutes = routes 12 | state.routes = constantRoutes.concat(routes) 13 | } 14 | } 15 | 16 | const actions = { 17 | generateRoutes({ commit }, menus) { 18 | return new Promise(resolve => { 19 | var accessedRoutes = filterAsyncRouter(menus || []) 20 | accessedRoutes.push({ path: '*', redirect: '/404', hidden: false }) 21 | commit('SET_ROUTES', accessedRoutes) 22 | resolve(accessedRoutes) 23 | }) 24 | } 25 | } 26 | 27 | // 遍历后台传来的路由字符串,转换为组件对象 28 | function filterAsyncRouter(asyncRouterMap) { 29 | return asyncRouterMap.filter(route => { 30 | if (route.component) { 31 | // Layout组件特殊处理 32 | if (route.component === 'Layout') { 33 | route.component = Layout 34 | } else { 35 | route.component = loadView(route.component) 36 | } 37 | } 38 | if (route.children != null && route.children && route.children.length) { 39 | route.children = filterAsyncRouter(route.children) 40 | } 41 | return true 42 | }) 43 | } 44 | 45 | export const loadView = (view) => { // 路由懒加载 46 | return (resolve) => require([`@/views/${view}`], resolve) 47 | } 48 | 49 | export default { 50 | namespaced: true, 51 | state, 52 | mutations, 53 | actions 54 | } 55 | -------------------------------------------------------------------------------- /extension/ui/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import variables from '@/styles/element-variables.scss' 2 | import defaultSettings from '@/settings' 3 | 4 | const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings 5 | 6 | const state = { 7 | theme: variables.theme, 8 | showSettings: showSettings, 9 | tagsView: tagsView, 10 | fixedHeader: fixedHeader, 11 | sidebarLogo: sidebarLogo 12 | } 13 | 14 | const mutations = { 15 | CHANGE_SETTING: (state, { key, value }) => { 16 | // eslint-disable-next-line no-prototype-builtins 17 | if (state.hasOwnProperty(key)) { 18 | state[key] = value 19 | } 20 | } 21 | } 22 | 23 | const actions = { 24 | changeSetting({ commit }, data) { 25 | commit('CHANGE_SETTING', data) 26 | } 27 | } 28 | 29 | export default { 30 | namespaced: true, 31 | state, 32 | mutations, 33 | actions 34 | } 35 | -------------------------------------------------------------------------------- /extension/ui/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { oauth2Login, getInfo, oauth2CodeLogin } from '@/api/login' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | import { resetRouter } from '@/router' 4 | 5 | const getDefaultState = () => { 6 | return { 7 | token: getToken(), 8 | name: '', 9 | avatar: '', 10 | roles: [] 11 | } 12 | } 13 | 14 | const state = getDefaultState() 15 | 16 | const mutations = { 17 | RESET_STATE: (state) => { 18 | Object.assign(state, getDefaultState()) 19 | }, 20 | SET_TOKEN: (state, token) => { 21 | state.token = token 22 | }, 23 | SET_NAME: (state, name) => { 24 | state.name = name 25 | }, 26 | SET_AVATAR: (state, avatar) => { 27 | state.avatar = avatar 28 | } 29 | } 30 | 31 | const actions = { 32 | // user login 33 | oauth2Login({ commit }, userInfo) { 34 | const { username, password, captchaKey, captchaCode, loginType, type, code } = userInfo 35 | console.log(userInfo) 36 | return new Promise((resolve, reject) => { 37 | var data = new FormData() 38 | if (loginType) data.append('grant_type', loginType) 39 | // grant_type = password 40 | if (username) data.append('username', username.trim()) 41 | if (password) data.append('password', password.trim()) 42 | // grant_type = social 43 | if (type) data.append('type', type.trim()) 44 | if (code) data.append('code', code.trim()) 45 | 46 | // captcha params 47 | var params = {} 48 | if (captchaKey) params.captchaKey = captchaKey 49 | if (captchaCode) params.captchaCode = captchaCode 50 | 51 | oauth2Login(data, params).then(response => { 52 | const { access_token } = response 53 | commit('SET_TOKEN', access_token) 54 | setToken(access_token) 55 | resolve() 56 | }).catch(error => { 57 | reject(error) 58 | }) 59 | }) 60 | }, 61 | 62 | oauth2CodeLogin({ commit }, formData) { 63 | return new Promise((resolve, reject) => { 64 | oauth2CodeLogin(formData).then(response => { 65 | const { access_token } = response 66 | commit('SET_TOKEN', access_token) 67 | setToken(access_token) 68 | resolve() 69 | }).catch(error => { 70 | console.log(error) 71 | reject(error) 72 | }) 73 | }) 74 | }, 75 | 76 | // get user info 77 | getInfo({ commit, state }) { 78 | return new Promise((resolve, reject) => { 79 | getInfo().then(response => { 80 | const { nickname, avatar } = response 81 | commit('SET_NAME', nickname || '匿名用户') 82 | commit('SET_AVATAR', avatar) 83 | resolve(response) 84 | }).catch(error => { 85 | reject(error) 86 | }) 87 | }) 88 | }, 89 | 90 | // user logout 91 | logout({ commit }) { 92 | return new Promise((resolve, reject) => { 93 | removeToken() 94 | resetRouter() 95 | commit('RESET_STATE') 96 | resolve() 97 | }) 98 | }, 99 | 100 | // remove token 101 | resetToken({ commit }) { 102 | return new Promise(resolve => { 103 | removeToken() // must remove token first 104 | commit('RESET_STATE') 105 | resolve() 106 | }) 107 | }, 108 | 109 | // save token 110 | saveToken({ commit }, token) { 111 | return new Promise(resolve => { 112 | commit('SET_TOKEN', token) 113 | setToken(token) 114 | resolve() 115 | }) 116 | } 117 | } 118 | 119 | export default { 120 | namespaced: true, 121 | state, 122 | mutations, 123 | actions 124 | } 125 | 126 | -------------------------------------------------------------------------------- /extension/ui/src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | body .el-table th.gutter{ display: table-cell!important; } 19 | 20 | // to fixed https://github.com/ElemeFE/element/issues/2461 21 | .el-dialog { 22 | transform: none; 23 | left: 0; 24 | position: relative; 25 | margin: 0 auto; 26 | } 27 | 28 | // refine element ui upload 29 | .upload-container { 30 | .el-upload { 31 | width: 100%; 32 | 33 | .el-upload-dragger { 34 | width: 100%; 35 | height: 200px; 36 | } 37 | } 38 | } 39 | 40 | // dropdown 41 | .el-dropdown-menu { 42 | a { 43 | display: block 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /extension/ui/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * I think element-ui's default theme color is too light for long-term use. 3 | * So I modified the default color and you can modify it to your liking. 4 | **/ 5 | 6 | /* theme color */ 7 | $--color-primary: #1890ff; 8 | $--color-success: #13ce66; 9 | $--color-warning: #ffba00; 10 | $--color-danger: #ff4949; 11 | // $--color-info: #1E1E1E; 12 | 13 | $--button-font-weight: 400; 14 | 15 | // $--color-text-regular: #1f2d3d; 16 | 17 | $--border-color-light: #dfe4ed; 18 | $--border-color-lighter: #e6ebf5; 19 | 20 | $--table-border: 1px solid #dfe6ec; 21 | 22 | /* icon font path, required */ 23 | $--font-path: "~element-ui/lib/theme-chalk/fonts"; 24 | 25 | @import "~element-ui/packages/theme-chalk/src/index"; 26 | 27 | // the :export directive is the magic sauce for webpack 28 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 29 | :export { 30 | theme: $--color-primary; 31 | } -------------------------------------------------------------------------------- /extension/ui/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | 7 | body { 8 | height: 100%; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | background-color: rgb(240,242,245) 14 | } 15 | 16 | label { 17 | font-weight: 700; 18 | } 19 | 20 | 21 | html { 22 | height: 100%; 23 | box-sizing: border-box; 24 | } 25 | 26 | #app { 27 | height: 100%; 28 | } 29 | 30 | *, 31 | *:before, 32 | *:after { 33 | box-sizing: inherit; 34 | } 35 | 36 | a:focus, 37 | a:active { 38 | outline: none; 39 | } 40 | 41 | a, 42 | a:focus, 43 | a:hover { 44 | cursor: pointer; 45 | color: inherit; 46 | text-decoration: none; 47 | } 48 | 49 | div:focus { 50 | outline: none; 51 | } 52 | 53 | .clearfix { 54 | &:after { 55 | visibility: hidden; 56 | display: block; 57 | font-size: 0; 58 | content: " "; 59 | clear: both; 60 | height: 0; 61 | } 62 | } 63 | 64 | //main-container全局样式 65 | .app-container { 66 | padding: 50px; 67 | } 68 | 69 | .components-container { 70 | margin: 30px 50px; 71 | position: relative; 72 | } 73 | 74 | .pagination-container { 75 | margin-top: 30px; 76 | } 77 | 78 | .text-center { 79 | text-align: center 80 | } 81 | 82 | .sub-navbar { 83 | height: 50px; 84 | line-height: 50px; 85 | position: relative; 86 | width: 100%; 87 | text-align: right; 88 | padding-right: 20px; 89 | transition: 600ms ease position; 90 | background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); 91 | 92 | .subtitle { 93 | font-size: 20px; 94 | color: #fff; 95 | } 96 | 97 | &.draft { 98 | background: #d0d0d0; 99 | } 100 | 101 | &.deleted { 102 | background: #d0d0d0; 103 | } 104 | } 105 | 106 | .link-type, 107 | .link-type:focus { 108 | color: #337ab7; 109 | cursor: pointer; 110 | 111 | &:hover { 112 | color: rgb(32, 160, 255); 113 | } 114 | } 115 | 116 | .filter-container { 117 | padding-bottom: 10px; 118 | 119 | .filter-item { 120 | display: inline-block; 121 | vertical-align: middle; 122 | margin-bottom: 10px; 123 | } 124 | } 125 | 126 | //refine vue-multiselect plugin 127 | .multiselect { 128 | line-height: 16px; 129 | } 130 | 131 | .multiselect--active { 132 | z-index: 1000 !important; 133 | } 134 | 135 | 136 | .el-card__header{ 137 | padding: 15px!important; 138 | box-sizing: border-box; 139 | } 140 | .el-card__body{ 141 | padding: 20px!important; 142 | } 143 | 144 | .el-card + .el-card{ 145 | margin-top: 10px; 146 | } 147 | -------------------------------------------------------------------------------- /extension/ui/src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /extension/ui/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /extension/ui/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // sidebar 2 | $menuText:#bfcbd9; 3 | $menuActiveText:rgb(0, 123, 247); 4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 5 | 6 | $menuBg:#2b2f3a; 7 | $menuHover:#263445; 8 | 9 | $subMenuBg:#1f2d3d; 10 | $subMenuHover:#001528; 11 | 12 | $sideBarWidth: 210px; 13 | 14 | // the :export directive is the magic sauce for webpack 15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 16 | :export { 17 | menuText: $menuText; 18 | menuActiveText: $menuActiveText; 19 | subMenuActiveText: $subMenuActiveText; 20 | menuBg: $menuBg; 21 | menuHover: $menuHover; 22 | subMenuBg: $subMenuBg; 23 | subMenuHover: $subMenuHover; 24 | sideBarWidth: $sideBarWidth; 25 | } 26 | -------------------------------------------------------------------------------- /extension/ui/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'vue_admin_template_token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /extension/ui/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const title = defaultSettings.title || 'Vue Admin Template' 4 | 5 | export default function getPageTitle(pageTitle) { 6 | if (pageTitle) { 7 | return `${pageTitle} - ${title}` 8 | } 9 | return `${title}` 10 | } 11 | -------------------------------------------------------------------------------- /extension/ui/src/utils/open-window.js: -------------------------------------------------------------------------------- 1 | /** 2 | *Created by PanJiaChen on 16/11/29. 3 | * @param {string} url 4 | * @param {Sting} title 5 | * @param {Number} w 6 | * @param {Number} h 7 | */ 8 | export default function openWindow(url, title, w, h) { 9 | // Fixes dual-screen position Most browsers Firefox 10 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 11 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 12 | 13 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width 14 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height 15 | 16 | const left = ((width / 2) - (w / 2)) + dualScreenLeft 17 | const top = ((height / 2) - (h / 2)) + dualScreenTop 18 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left) 19 | 20 | // Puts focus on the newWindow 21 | if (window.focus) { 22 | newWindow.focus() 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /extension/ui/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { MessageBox, Message } from 'element-ui' 3 | import store from '@/store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // create an axios instance 7 | const service = axios.create({ 8 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 9 | // withCredentials: true, // send cookies when cross-domain requests 10 | timeout: 5000, // request timeout, 11 | paramsSerializer: function(params) { 12 | return require('qs').stringify(params, { arrayFormat: 'repeat' }) 13 | }, 14 | validateStatus: function(status) { 15 | return status >= 200 && status < 600 // default 16 | } 17 | }) 18 | 19 | // request interceptor 20 | service.interceptors.request.use( 21 | config => { 22 | // do something before request is sent 23 | if (store.getters.token && !config.headers['Authorization']) { 24 | // let each request carry token 25 | // ['X-Token'] is a custom headers key 26 | // please modify it according to the actual situation 27 | config.headers['Authorization'] = 'Bearer ' + getToken() 28 | } 29 | return config 30 | }, 31 | error => { 32 | // do something with request error 33 | console.log(error) // for debug 34 | return Promise.reject(error) 35 | } 36 | ) 37 | 38 | // response interceptor 39 | service.interceptors.response.use( 40 | /** 41 | * If you want to get http information such as headers or status 42 | * Please return response => response 43 | */ 44 | 45 | /** 46 | * Determine the request status by custom code 47 | * Here is just an example 48 | * You can also judge the status by HTTP Status Code 49 | */ 50 | response => { 51 | if (response.status !== 200) { 52 | // Bad Request 53 | var errmsg = response.data.error_description || '未知错误, 请联系管理员' 54 | if (response.status === 400) { 55 | Message({ 56 | message: errmsg, 57 | type: 'error', 58 | duration: 2 * 1000 59 | }) 60 | } 61 | return Promise.reject(new Error(errmsg)) 62 | } 63 | // OAuth2.0 不是通用数据格式 64 | if (response.data.code === undefined) { 65 | return response.data 66 | } 67 | 68 | // 通用数据 69 | const res = response.data 70 | 71 | // if the custom code is not 20000, it is judged as an error. 72 | if (res.code !== 'SUCCESS') { 73 | Message({ 74 | message: res.msg || '未知错误', 75 | type: 'error', 76 | duration: 2 * 1000 77 | }) 78 | 79 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; 80 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 81 | // to re-login 82 | MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { 83 | confirmButtonText: 'Re-Login', 84 | cancelButtonText: 'Cancel', 85 | type: 'warning' 86 | }).then(() => { 87 | store.dispatch('user/resetToken').then(() => { 88 | location.reload() 89 | }) 90 | }) 91 | } 92 | return Promise.reject(new Error(res.msg || 'Error')) 93 | } else { 94 | return res 95 | } 96 | }, 97 | error => { 98 | console.log('err ', error) // for debug 99 | Message({ 100 | message: error.message, 101 | type: 'error', 102 | duration: 5 * 1000 103 | }) 104 | return Promise.reject(error) 105 | } 106 | ) 107 | 108 | export default service 109 | -------------------------------------------------------------------------------- /extension/ui/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * @param {string} path 7 | * @returns {Boolean} 8 | */ 9 | export function isExternal(path) { 10 | return /^(https?:|mailto:|tel:)/.test(path) 11 | } 12 | 13 | /** 14 | * @param {string} str 15 | * @returns {Boolean} 16 | */ 17 | export function validUsername(str) { 18 | const valid_map = ['user', 'editor'] 19 | return valid_map.indexOf(str.trim()) >= 0 20 | } 21 | -------------------------------------------------------------------------------- /extension/ui/src/views/dashboard/components/MyCamera.vue: -------------------------------------------------------------------------------- 1 | 12 | 17 | 56 | -------------------------------------------------------------------------------- /extension/ui/src/views/dashboard/components/MyData.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 84 | -------------------------------------------------------------------------------- /extension/ui/src/views/dashboard/components/MyEchart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 135 | -------------------------------------------------------------------------------- /extension/ui/src/views/dashboard/components/mixins/resize.js: -------------------------------------------------------------------------------- 1 | import { debounce } from '@/utils' 2 | 3 | export default { 4 | data() { 5 | return { 6 | $_sidebarElm: null, 7 | $_resizeHandler: null 8 | } 9 | }, 10 | mounted() { 11 | this.$_resizeHandler = debounce(() => { 12 | if (this.chart) { 13 | this.chart.resize() 14 | } 15 | }, 100) 16 | this.$_initResizeEvent() 17 | this.$_initSidebarResizeEvent() 18 | }, 19 | beforeDestroy() { 20 | this.$_destroyResizeEvent() 21 | this.$_destroySidebarResizeEvent() 22 | }, 23 | // to fixed bug when cached by keep-alive 24 | // https://github.com/PanJiaChen/vue-element-admin/issues/2116 25 | activated() { 26 | this.$_initResizeEvent() 27 | this.$_initSidebarResizeEvent() 28 | }, 29 | deactivated() { 30 | this.$_destroyResizeEvent() 31 | this.$_destroySidebarResizeEvent() 32 | }, 33 | methods: { 34 | // use $_ for mixins properties 35 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 36 | $_initResizeEvent() { 37 | window.addEventListener('resize', this.$_resizeHandler) 38 | }, 39 | $_destroyResizeEvent() { 40 | window.removeEventListener('resize', this.$_resizeHandler) 41 | }, 42 | $_sidebarResizeHandler(e) { 43 | if (e.propertyName === 'width') { 44 | this.$_resizeHandler() 45 | } 46 | }, 47 | $_initSidebarResizeEvent() { 48 | this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0] 49 | this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler) 50 | }, 51 | $_destroySidebarResizeEvent() { 52 | this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /extension/ui/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 75 | 81 | 82 | -------------------------------------------------------------------------------- /extension/ui/src/views/login/auth-redirect.vue: -------------------------------------------------------------------------------- 1 | 6 | 41 | -------------------------------------------------------------------------------- /extension/ui/src/views/menu/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 43 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/App.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @MapperScan(basePackages = "cn.flizi.auth.mapper") 8 | @SpringBootApplication 9 | public class App { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(App.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.core.annotation.Order; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.web.cors.CorsConfiguration; 10 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 11 | import org.springframework.web.filter.CorsFilter; 12 | 13 | @Configuration 14 | @Order(Integer.MIN_VALUE) 15 | public class CorsConfig extends WebSecurityConfigurerAdapter { 16 | 17 | @Bean 18 | public CorsFilter corsFilter() { 19 | CorsConfiguration config = new CorsConfiguration(); 20 | config.addAllowedMethod("*"); 21 | config.addAllowedHeader("*"); 22 | config.addAllowedOrigin("*"); 23 | UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); 24 | configSource.registerCorsConfiguration("/**", config); 25 | return new CorsFilter(configSource); 26 | } 27 | 28 | @Override 29 | protected void configure(HttpSecurity http) throws Exception { 30 | http.requestMatchers().antMatchers(HttpMethod.OPTIONS, "/**"); 31 | http.cors(); 32 | } 33 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/config/GlobalConfig.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.config; 2 | 3 | import cn.flizi.auth.properties.SocialProperties; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ModelAttribute; 9 | 10 | 11 | /** 12 | * 定义全局变量 13 | */ 14 | @ControllerAdvice 15 | public class GlobalConfig { 16 | 17 | @Value("${baseinfo.title}") 18 | private String appName; 19 | 20 | @Value("${baseinfo.beian}") 21 | private String beian; 22 | 23 | @Autowired 24 | private SocialProperties socialProperties; 25 | 26 | @ModelAttribute 27 | public void addAttributes(Model model) { 28 | model.addAttribute("app_name", appName); 29 | model.addAttribute("beian", beian); 30 | model.addAttribute("wx_mp", socialProperties.getWxMp().getKey()); 31 | model.addAttribute("wx_open", socialProperties.getWxOpen().getKey()); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/config/MessageLocalConfig.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.config; 2 | 3 | import org.springframework.context.MessageSource; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.support.ReloadableResourceBundleMessageSource; 7 | 8 | import java.util.Locale; 9 | 10 | /** 11 | * 中文 配置 12 | */ 13 | @Configuration 14 | public class MessageLocalConfig { 15 | 16 | @Bean 17 | public MessageSource messageSource() { 18 | Locale.setDefault(Locale.CHINA); 19 | ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); 20 | messageSource.addBasenames("classpath:messages_zh_CN"); 21 | return messageSource; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/config/ResourceConfig.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.config; 2 | 3 | import cn.flizi.auth.security.RestAuthenticationEntryPoint; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.autoconfigure.security.oauth2.authserver.AuthorizationServerProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 9 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 10 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 11 | 12 | @EnableResourceServer 13 | @Configuration 14 | public class ResourceConfig extends ResourceServerConfigurerAdapter { 15 | 16 | @Autowired 17 | private RestAuthenticationEntryPoint restAuthenticationEntryPoint; 18 | 19 | @Override 20 | public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 21 | resources.authenticationEntryPoint(restAuthenticationEntryPoint); 22 | } 23 | 24 | @Override 25 | public void configure(HttpSecurity http) throws Exception { 26 | http.requestMatchers().antMatchers("/user_base", "/user_info", "/api/**"); 27 | http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint); 28 | super.configure(http); 29 | } 30 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.ApiInfoBuilder; 6 | import springfox.documentation.builders.PathSelectors; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.service.*; 9 | import springfox.documentation.spi.DocumentationType; 10 | import springfox.documentation.spi.service.contexts.SecurityContext; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 13 | 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | @Configuration 18 | @EnableSwagger2 19 | public class SwaggerConfig { 20 | 21 | /** 22 | * 名称 23 | */ 24 | private String name = "swagger"; 25 | 26 | /** 27 | * 版本号 28 | */ 29 | private String version = "0.1"; 30 | 31 | /** 32 | * 前缀 33 | */ 34 | private String prefix = "/"; 35 | 36 | /** 37 | * 扫描包的基本前缀 38 | */ 39 | private String basePackage = "cn.flizi"; 40 | 41 | 42 | @Bean 43 | public Docket api() { 44 | return new Docket(DocumentationType.SWAGGER_2) 45 | .apiInfo(apiInfo()) 46 | .select() 47 | .apis(RequestHandlerSelectors.basePackage(basePackage)) 48 | .build() 49 | .securitySchemes(securitySchemes()) 50 | .securityContexts(securityContexts()); 51 | } 52 | 53 | private List securitySchemes() { 54 | return Collections.singletonList(new ApiKey("Authorization", "Authorization", "header")); 55 | } 56 | 57 | 58 | private List securityContexts() { 59 | return Collections.singletonList( 60 | SecurityContext.builder() 61 | .securityReferences(defaultAuth()) 62 | .forPaths(PathSelectors.regex("/.*")) 63 | .build() 64 | ); 65 | } 66 | 67 | private List defaultAuth() { 68 | AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); 69 | AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; 70 | authorizationScopes[0] = authorizationScope; 71 | return Collections.singletonList( 72 | new SecurityReference("Authorization", authorizationScopes)); 73 | } 74 | 75 | private ApiInfo apiInfo() { 76 | return new ApiInfoBuilder() 77 | .title(name) 78 | .version(version) 79 | .build(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/config/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.config; 2 | 3 | import cn.flizi.auth.security.filter.CaptchaValidationFilter; 4 | import cn.flizi.auth.security.social.SocialAuthenticationProvider; 5 | import cn.flizi.auth.security.social.SocialCodeAuthenticationFilter; 6 | import cn.flizi.auth.security.social.SocialDetailsService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.security.authentication.AuthenticationManager; 11 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 12 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 15 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 16 | import org.springframework.security.core.userdetails.UserDetailsService; 17 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 18 | 19 | /** 20 | * Spring Security 配置 21 | */ 22 | @Configuration 23 | @EnableWebSecurity 24 | @EnableGlobalMethodSecurity(prePostEnabled = true) 25 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 26 | 27 | @Autowired 28 | private UserDetailsService userDetailsService; 29 | @Autowired 30 | private SocialDetailsService socialDetailsService; 31 | @Autowired 32 | private CaptchaValidationFilter captchaValidationFilter; 33 | 34 | /** 35 | * 注入 Spring 容器中, 在授权服务器中密码模式下,验证用户密码正确性, 以及第三方授权码验证 36 | */ 37 | @Bean 38 | @Override 39 | public AuthenticationManager authenticationManagerBean() throws Exception { 40 | return super.authenticationManagerBean(); 41 | } 42 | 43 | @Override 44 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 45 | auth.authenticationProvider(new SocialAuthenticationProvider(socialDetailsService)); 46 | auth.userDetailsService(userDetailsService); 47 | } 48 | 49 | /** 50 | * 配置 HttpSecurity 51 | */ 52 | @Override 53 | protected void configure(HttpSecurity http) throws Exception { 54 | SocialCodeAuthenticationFilter socialCodeAuthenticationFilter = new SocialCodeAuthenticationFilter(); 55 | socialCodeAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); 56 | 57 | http 58 | .csrf().disable() 59 | .formLogin() 60 | .loginPage("/login").and() 61 | .authorizeRequests() 62 | // 对外开放接口 63 | .antMatchers("/login", "/social", "/reset", "/captcha", "/signup", 64 | "/sms", "/auth-redirect", "/weixin-code").permitAll() 65 | // 静态资源 66 | .antMatchers("/static/**").permitAll() 67 | // swagger api 68 | .antMatchers("/v2/api-docs", "/swagger-ui/**", "/swagger-resources/**").permitAll() 69 | .anyRequest().authenticated() 70 | .and() 71 | .addFilterBefore(captchaValidationFilter, UsernamePasswordAuthenticationFilter.class) 72 | .addFilterBefore(socialCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) 73 | .httpBasic(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/entity/Captcha.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class Captcha { 6 | private String captchaId; 7 | private String key; 8 | private String code; 9 | private Date createTime; 10 | 11 | public String getCaptchaId() { 12 | return captchaId; 13 | } 14 | 15 | public void setCaptchaId(String captchaId) { 16 | this.captchaId = captchaId; 17 | } 18 | 19 | public String getKey() { 20 | return key; 21 | } 22 | 23 | public void setKey(String key) { 24 | this.key = key; 25 | } 26 | 27 | public String getCode() { 28 | return code; 29 | } 30 | 31 | public void setCode(String code) { 32 | this.code = code; 33 | } 34 | 35 | public Date getCreateTime() { 36 | return createTime; 37 | } 38 | 39 | public void setCreateTime(Date createTime) { 40 | this.createTime = createTime; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/entity/Sms.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class Sms { 6 | private String smsId; 7 | private String phone; 8 | private String code; 9 | private Date createTime; 10 | 11 | public String getSmsId() { 12 | return smsId; 13 | } 14 | 15 | public void setSmsId(String smsId) { 16 | this.smsId = smsId; 17 | } 18 | 19 | public String getPhone() { 20 | return phone; 21 | } 22 | 23 | public void setPhone(String phone) { 24 | this.phone = phone; 25 | } 26 | 27 | public String getCode() { 28 | return code; 29 | } 30 | 31 | public void setCode(String code) { 32 | this.code = code; 33 | } 34 | 35 | public Date getCreateTime() { 36 | return createTime; 37 | } 38 | 39 | public void setCreateTime(Date createTime) { 40 | this.createTime = createTime; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/entity/User.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.entity; 2 | 3 | public class User { 4 | private String userId; 5 | private String password; 6 | private String phone; 7 | private String email; 8 | private String wxOpenid; 9 | private String wxUnionid; 10 | 11 | public String getUserId() { 12 | return userId; 13 | } 14 | 15 | public void setUserId(String userId) { 16 | this.userId = userId; 17 | } 18 | 19 | public String getPassword() { 20 | return password; 21 | } 22 | 23 | public void setPassword(String password) { 24 | this.password = password; 25 | } 26 | 27 | public String getPhone() { 28 | return phone; 29 | } 30 | 31 | public void setPhone(String phone) { 32 | this.phone = phone; 33 | } 34 | 35 | public String getEmail() { 36 | return email; 37 | } 38 | 39 | public void setEmail(String email) { 40 | this.email = email; 41 | } 42 | 43 | public String getWxOpenid() { 44 | return wxOpenid; 45 | } 46 | 47 | public void setWxOpenid(String wxOpenid) { 48 | this.wxOpenid = wxOpenid; 49 | } 50 | 51 | public String getWxUnionid() { 52 | return wxUnionid; 53 | } 54 | 55 | public void setWxUnionid(String wxUnionid) { 56 | this.wxUnionid = wxUnionid; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/mapper/CaptchaMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.mapper; 2 | 3 | import cn.flizi.auth.entity.Captcha; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | @Mapper 7 | public interface CaptchaMapper { 8 | 9 | @Options(useGeneratedKeys = true, keyProperty = "captchaId", keyColumn = "captcha_id") 10 | @Insert("INSERT INTO captcha (`key`, code) VALUES (#{key}, #{code})") 11 | void insert(Captcha captcha); 12 | 13 | @Select("SELECT * FROM captcha WHERE `key`=#{key} ORDER BY create_time DESC LIMIT 1") 14 | Captcha getCaptchaByKey(String key); 15 | 16 | @Delete("DELETE FROM captcha WHERE `key`=#{key}") 17 | void delete(String key); 18 | } 19 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/mapper/SmsMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.mapper; 2 | 3 | import cn.flizi.auth.entity.Sms; 4 | import org.apache.ibatis.annotations.Insert; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Options; 7 | import org.apache.ibatis.annotations.Select; 8 | 9 | @Mapper 10 | public interface SmsMapper { 11 | 12 | @Options(useGeneratedKeys = true, keyProperty = "smsId", keyColumn = "sms_id") 13 | @Insert("INSERT INTO sms (phone, code)" + 14 | "VALUES (#{phone},#{code})") 15 | void insert(Sms sms); 16 | 17 | @Select("select * from sms where phone=#{phone} order by create_time desc limit 1") 18 | Sms getCodeByPhone(String phone); 19 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.mapper; 2 | 3 | import cn.flizi.auth.entity.User; 4 | import org.apache.ibatis.annotations.*; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | @Mapper 8 | public interface UserMapper { 9 | 10 | @Select("SELECT * FROM user WHERE phone = #{name} or email = #{name}") 11 | User loadUserByUsername(String name); 12 | 13 | @Select("SELECT * FROM user WHERE `user_id` = #{userId}") 14 | User loadUserByUserId(String userId); 15 | 16 | @Select("SELECT * FROM user WHERE ${column} = #{value}") 17 | User loadUserByColumn(@Param("column") String column, @Param("value") String value); 18 | 19 | @Options(useGeneratedKeys = true, keyProperty = "userId", keyColumn = "user_id") 20 | @Insert("INSERT INTO user (password, phone, email, wx_openid, wx_unionid)" + 21 | "VALUES (#{password},#{phone},#{email}, #{wxOpenid}, #{wxUnionid})") 22 | void insert(User user); 23 | 24 | @Update("update user set password=#{password} where phone=#{phone}") 25 | void updatePassword(@Param("phone") String phone, @Param("password") String password); 26 | 27 | @Update("update user set phone=#{phone} where `user_id`=#{userId}") 28 | void updatePhone(@Param("userId") String userId, @Param("phone") String phone); 29 | 30 | @Update("update user set wx_openid=#{openid} where `user_id`=#{userId}") 31 | void updateWxOpenId(@Param("userId") String userId, @Param("openid") String openid); 32 | 33 | @Update("update user set wx_unionid=#{unionid} where `user_id`=#{userId}") 34 | void updateWxUnionId(@Param("userId") String userId, @Param("unionid") String unionid); 35 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/properties/CaptchaProperties.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.properties; 2 | 3 | 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @ConfigurationProperties(prefix = "captcha") 9 | public class CaptchaProperties { 10 | private Boolean enable = true; 11 | private Integer length = 4; 12 | private String baseStr = "023456789"; 13 | 14 | public Boolean getEnable() { 15 | return enable; 16 | } 17 | 18 | public void setEnable(Boolean enable) { 19 | this.enable = enable; 20 | } 21 | 22 | public Integer getLength() { 23 | return length; 24 | } 25 | 26 | public void setLength(Integer length) { 27 | this.length = length; 28 | } 29 | 30 | public String getBaseStr() { 31 | return baseStr; 32 | } 33 | 34 | public void setBaseStr(String baseStr) { 35 | this.baseStr = baseStr; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "CaptchaProperties{" + 41 | "enable=" + enable + 42 | ", length=" + length + 43 | ", baseStr='" + baseStr + '\'' + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/properties/SmsProperties.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.properties; 2 | 3 | 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @ConfigurationProperties(prefix = "sms") 9 | public class SmsProperties { 10 | private Boolean enable = true; 11 | private String secretId; 12 | private String secretKey; 13 | private String appId; 14 | private String sign; 15 | private String templateId; 16 | 17 | public Boolean getEnable() { 18 | return enable; 19 | } 20 | 21 | public void setEnable(Boolean enable) { 22 | this.enable = enable; 23 | } 24 | 25 | public String getSecretId() { 26 | return secretId; 27 | } 28 | 29 | public void setSecretId(String secretId) { 30 | this.secretId = secretId; 31 | } 32 | 33 | public String getSecretKey() { 34 | return secretKey; 35 | } 36 | 37 | public void setSecretKey(String secretKey) { 38 | this.secretKey = secretKey; 39 | } 40 | 41 | public String getAppId() { 42 | return appId; 43 | } 44 | 45 | public void setAppId(String appId) { 46 | this.appId = appId; 47 | } 48 | 49 | public String getSign() { 50 | return sign; 51 | } 52 | 53 | public void setSign(String sign) { 54 | this.sign = sign; 55 | } 56 | 57 | public String getTemplateId() { 58 | return templateId; 59 | } 60 | 61 | public void setTemplateId(String templateId) { 62 | this.templateId = templateId; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "SmsProperties{" + 68 | "enable=" + enable + 69 | ", secretId='" + secretId + '\'' + 70 | ", secretKey='" + secretKey + '\'' + 71 | ", appId='" + appId + '\'' + 72 | ", sign='" + sign + '\'' + 73 | ", templateId='" + templateId + '\'' + 74 | '}'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/properties/SocialProperties.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.properties; 2 | 3 | 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @ConfigurationProperties(prefix = "social") 9 | public class SocialProperties { 10 | 11 | private WxMp wxMp = new WxMp(); 12 | private WxOpen wxOpen = new WxOpen(); 13 | 14 | public WxMp getWxMp() { 15 | return wxMp; 16 | } 17 | 18 | public void setWxMp(WxMp wxMp) { 19 | this.wxMp = wxMp; 20 | } 21 | 22 | public WxOpen getWxOpen() { 23 | return wxOpen; 24 | } 25 | 26 | public void setWxOpen(WxOpen wxOpen) { 27 | this.wxOpen = wxOpen; 28 | } 29 | 30 | public static class WxMp { 31 | private String key; 32 | private String secret; 33 | 34 | public String getKey() { 35 | return key; 36 | } 37 | 38 | public void setKey(String key) { 39 | this.key = key; 40 | } 41 | 42 | public String getSecret() { 43 | return secret; 44 | } 45 | 46 | public void setSecret(String secret) { 47 | this.secret = secret; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "WxOpen{" + 53 | "key='" + key + '\'' + 54 | ", secret='" + secret + '\'' + 55 | '}'; 56 | } 57 | } 58 | 59 | public static class WxOpen { 60 | private String key; 61 | private String secret; 62 | 63 | public String getKey() { 64 | return key; 65 | } 66 | 67 | public void setKey(String key) { 68 | this.key = key; 69 | } 70 | 71 | public String getSecret() { 72 | return secret; 73 | } 74 | 75 | public void setSecret(String secret) { 76 | this.secret = secret; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return "WxOpen{" + 82 | "key='" + key + '\'' + 83 | ", secret='" + secret + '\'' + 84 | '}'; 85 | } 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return "SocialProperties{" + 91 | "wxMp=" + wxMp + 92 | ", wxOpen=" + wxOpen + 93 | '}'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/AuthUser.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.core.userdetails.User; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * Security 的 User 类 和 用户表 User 类重名, 这里集成一次以作区分 10 | * Security 的 User 类 username 对应 用户表 user_id 字段 11 | * 扩展 字段 phone 12 | */ 13 | public class AuthUser extends User { 14 | 15 | private String phone; 16 | 17 | private Integer tenant; 18 | 19 | public void setPhone(String phone) { 20 | this.phone = phone; 21 | } 22 | 23 | public String getPhone() { 24 | return phone; 25 | } 26 | 27 | public Integer getTenant() { 28 | return tenant; 29 | } 30 | 31 | 32 | 33 | public AuthUser(String username, String password, boolean enabled, Collection authorities) { 34 | super(username, password, enabled, true, true, true, authorities); 35 | } 36 | 37 | public AuthUser(String username, Collection authorities) { 38 | super(username, "N/A", true, true, true, true, authorities); 39 | } 40 | 41 | public AuthUser(String username, Integer tenant, Collection authorities) { 42 | super(username, "N/A", true, true, true, true, authorities); 43 | this.tenant = tenant; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/CaptchaService.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security; 2 | 3 | public interface CaptchaService { 4 | 5 | void set(String key, String value, long expiresInSeconds); 6 | 7 | boolean exists(String key); 8 | 9 | void delete(String key); 10 | 11 | String get(String key); 12 | 13 | default boolean check(String key, String value) { 14 | if (!exists(key)) { 15 | return false; 16 | } 17 | String s = get(key); 18 | boolean b = s != null && s.equalsIgnoreCase(value); 19 | if (b) { 20 | delete(key); 21 | } 22 | return b; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/CustomWebResponseExceptionTranslator.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; 6 | import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; 7 | 8 | import java.util.HashMap; 9 | 10 | public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator { 11 | 12 | @Override 13 | public ResponseEntity translate(Exception e) throws Exception { 14 | HashMap result = new HashMap<>(); 15 | result.put("code", "ERROR"); 16 | result.put("msg", e.getMessage()); 17 | return new ResponseEntity<>(result, HttpStatus.OK); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security; 2 | 3 | import cn.flizi.core.util.R; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.validation.BindException; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.HttpRequestMethodNotSupportedException; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import org.springframework.web.servlet.NoHandlerFoundException; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.util.List; 13 | 14 | @RestControllerAdvice 15 | public class ExceptionHandler { 16 | @Autowired 17 | private HttpServletRequest request; 18 | 19 | @org.springframework.web.bind.annotation.ExceptionHandler(HttpRequestMethodNotSupportedException.class) 20 | public R handler(HttpRequestMethodNotSupportedException e) { 21 | return R.errMsg("不支持 " + e.getMethod() + " 请求方式"); 22 | } 23 | 24 | @org.springframework.web.bind.annotation.ExceptionHandler(NoHandlerFoundException.class) 25 | public R handler(NoHandlerFoundException e) { 26 | return R.errMsg("不存在 " + e.getRequestURL() + " 路径"); 27 | } 28 | 29 | @org.springframework.web.bind.annotation.ExceptionHandler(BindException.class) 30 | public R validExceptionHandler(BindException e) { 31 | StringBuilder message = new StringBuilder(); 32 | List fieldErrors = e.getBindingResult().getFieldErrors(); 33 | for (FieldError error : fieldErrors) { 34 | message.append(error.getField()).append(error.getDefaultMessage()).append(","); 35 | } 36 | message = new StringBuilder(message.substring(0, message.length() - 1)); 37 | String basePath = request.getScheme() 38 | + "://" + request.getServerName() + ":" + request.getServerPort() + "/swagger-ui/index.html"; 39 | return R.error(basePath, message.toString()); 40 | } 41 | 42 | @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class) 43 | public R handler(Exception e) { 44 | return R.errMsg(e.getMessage()); 45 | } 46 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/JwtUserAuthenticationConverter.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security; 2 | 3 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * 生成 JWT 的时候加入 tenant 字段, 解析jwt的时候, 将 tenant放入 Authentication中 11 | */ 12 | public class JwtUserAuthenticationConverter extends DefaultUserAuthenticationConverter { 13 | 14 | public static final String TENANT_ID = "tenant"; 15 | 16 | 17 | public Map convertUserAuthentication(Authentication authentication) { 18 | Map response = (Map) super.convertUserAuthentication(authentication); 19 | response.put(TENANT_ID, 1); 20 | return response; 21 | } 22 | 23 | @Override 24 | public Authentication extractAuthentication(Map map) { 25 | Authentication authentication = super.extractAuthentication(map); 26 | if (authentication != null) { 27 | String userId = authentication.getName(); 28 | Integer tenantId = (Integer) map.get(TENANT_ID); 29 | AuthUser principal = new AuthUser(userId, tenantId, authentication.getAuthorities()); 30 | return new UsernamePasswordAuthenticationToken(principal, "N/A", authentication.getAuthorities()); 31 | } 32 | return null; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/RestAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.AuthenticationException; 5 | import org.springframework.security.web.AuthenticationEntryPoint; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.servlet.HandlerExceptionResolver; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | 14 | @Component("restAuthenticationEntryPoint") 15 | public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { 16 | 17 | @Autowired 18 | private HandlerExceptionResolver handlerExceptionResolver; 19 | 20 | @Override 21 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { 22 | handlerExceptionResolver.resolveException(request, response, null, exception); 23 | } 24 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/social/SocialCodeAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security.social; 2 | 3 | import cn.flizi.auth.security.social.SocialCodeAuthenticationToken; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.AuthenticationException; 6 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 7 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | public class SocialCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { 13 | 14 | private String typeParameter = "type"; 15 | private String codeParameter = "code"; 16 | private String redirectParameter = "redirect_uri"; 17 | 18 | public void setTypeParameter(String typeParameter) { 19 | this.typeParameter = typeParameter; 20 | } 21 | 22 | public void setCodeParameter(String codeParameter) { 23 | this.codeParameter = codeParameter; 24 | } 25 | 26 | public void setRedirectParameter(String redirectParameter) { 27 | this.redirectParameter = redirectParameter; 28 | } 29 | 30 | public SocialCodeAuthenticationFilter() { 31 | super(new AntPathRequestMatcher("/social", "POST")); 32 | } 33 | 34 | public Authentication attemptAuthentication(HttpServletRequest request, 35 | HttpServletResponse response) throws AuthenticationException { 36 | 37 | String type = obtainUsername(request); 38 | String code = obtainPassword(request); 39 | String redirect = obtainRedirect(request); 40 | 41 | if (type == null) { 42 | type = ""; 43 | } 44 | 45 | if (code == null) { 46 | code = ""; 47 | } 48 | 49 | type = type.trim(); 50 | code = code.trim(); 51 | 52 | SocialCodeAuthenticationToken authRequest = new SocialCodeAuthenticationToken(type, code, redirect); 53 | 54 | setDetails(request, authRequest); 55 | 56 | return this.getAuthenticationManager().authenticate(authRequest); 57 | } 58 | 59 | protected String obtainPassword(HttpServletRequest request) { 60 | return request.getParameter(codeParameter); 61 | } 62 | 63 | protected String obtainUsername(HttpServletRequest request) { 64 | return request.getParameter(typeParameter); 65 | } 66 | 67 | protected String obtainRedirect(HttpServletRequest request) { 68 | return request.getParameter(redirectParameter); 69 | } 70 | 71 | 72 | protected void setDetails(HttpServletRequest request, 73 | SocialCodeAuthenticationToken authRequest) { 74 | authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/social/SocialCodeAuthenticationToken.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security.social; 2 | 3 | import org.springframework.security.authentication.AbstractAuthenticationToken; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.SpringSecurityCoreVersion; 6 | 7 | import java.util.Collection; 8 | 9 | public class SocialCodeAuthenticationToken extends AbstractAuthenticationToken { 10 | 11 | private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; 12 | 13 | // ~ Instance fields 14 | // ================================================================================================ 15 | 16 | private final Object type; 17 | private Object code; 18 | 19 | public String getRedirectUri() { 20 | return redirectUri; 21 | } 22 | 23 | public void setRedirectUri(String redirectUri) { 24 | this.redirectUri = redirectUri; 25 | } 26 | 27 | private String redirectUri; 28 | 29 | // ~ Constructors 30 | // =================================================================================================== 31 | public SocialCodeAuthenticationToken(String type, String code, String redirectUri) { 32 | super(null); 33 | this.type = type; 34 | this.code = code; 35 | this.redirectUri = redirectUri; 36 | setAuthenticated(false); 37 | } 38 | /** 39 | * This constructor can be safely used by any code that wishes to create a 40 | * UsernamePasswordAuthenticationToken, as the {@link #isAuthenticated()} 41 | * will return false. 42 | */ 43 | public SocialCodeAuthenticationToken(Object type, Object code) { 44 | super(null); 45 | this.type = type; 46 | this.code = code; 47 | setAuthenticated(false); 48 | } 49 | 50 | /** 51 | * This constructor should only be used by AuthenticationManager or 52 | * AuthenticationProvider implementations that are satisfied with 53 | * producing a trusted (i.e. {@link #isAuthenticated()} = true) 54 | * authentication token. 55 | * 56 | * @param type 57 | * @param code 58 | * @param authorities 59 | */ 60 | public SocialCodeAuthenticationToken(Object type, Object code, Collection authorities) { 61 | super(authorities); 62 | this.type = type; 63 | this.code = code; 64 | super.setAuthenticated(true); // must use super, as we override 65 | } 66 | 67 | // ~ Methods 68 | // ======================================================================================================== 69 | 70 | public Object getCredentials() { 71 | return this.code; 72 | } 73 | 74 | public Object getPrincipal() { 75 | return this.type; 76 | } 77 | 78 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { 79 | if (isAuthenticated) { 80 | throw new IllegalArgumentException( 81 | "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); 82 | } 83 | 84 | super.setAuthenticated(false); 85 | } 86 | 87 | @Override 88 | public void eraseCredentials() { 89 | super.eraseCredentials(); 90 | code = null; 91 | } 92 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/social/SocialCodeTokenGranter.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security.social; 2 | 3 | import org.springframework.security.authentication.AbstractAuthenticationToken; 4 | import org.springframework.security.authentication.AccountStatusException; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.authentication.BadCredentialsException; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; 9 | import org.springframework.security.oauth2.provider.*; 10 | import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; 11 | import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; 12 | 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * 该方法是扩展了Security OAuth来实现的 Rest API 请求 18 | */ 19 | public class SocialCodeTokenGranter extends AbstractTokenGranter { 20 | 21 | private static final String GRANT_TYPE = "social"; 22 | 23 | private final AuthenticationManager authenticationManager; 24 | 25 | public SocialCodeTokenGranter(AuthenticationManager authenticationManager, 26 | AuthorizationServerTokenServices tokenServices, 27 | ClientDetailsService clientDetailsService, 28 | OAuth2RequestFactory requestFactory) { 29 | this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); 30 | } 31 | 32 | protected SocialCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, 33 | ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { 34 | super(tokenServices, clientDetailsService, requestFactory, grantType); 35 | this.authenticationManager = authenticationManager; 36 | } 37 | 38 | @Override 39 | protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { 40 | 41 | Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters()); 42 | String type = parameters.get("type"); 43 | String code = parameters.get("code"); 44 | String redirectUri = parameters.get("redirect_uri"); 45 | // Protect from downstream leaks of code 46 | parameters.remove("code"); 47 | 48 | Authentication userAuth = new SocialCodeAuthenticationToken(type, code, redirectUri); 49 | ((AbstractAuthenticationToken) userAuth).setDetails(parameters); 50 | try { 51 | userAuth = authenticationManager.authenticate(userAuth); 52 | } catch (AccountStatusException | BadCredentialsException ase) { 53 | // If the type/code are wrong the spec says we should send 400/invalid grant 54 | //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) 55 | throw new InvalidGrantException(ase.getMessage()); 56 | } 57 | 58 | if (userAuth == null || !userAuth.isAuthenticated()) { 59 | throw new InvalidGrantException("Could not authenticate user: " + type); 60 | } 61 | 62 | OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); 63 | return new OAuth2Authentication(storedOAuth2Request, userAuth); 64 | } 65 | } -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/social/SocialDetailsService.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security.social; 2 | 3 | import org.springframework.security.core.userdetails.UserDetails; 4 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 5 | 6 | public interface SocialDetailsService { 7 | 8 | UserDetails loadUserBySocial(String type, String code, String redirectUri) throws UsernameNotFoundException; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/security/social/SocialUser.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.security.social; 2 | 3 | import org.springframework.security.core.AuthenticatedPrincipal; 4 | 5 | public class SocialUser implements AuthenticatedPrincipal { 6 | 7 | private String accessToken; 8 | 9 | private String username; 10 | 11 | private String nickname; 12 | 13 | private String avatar; 14 | 15 | @Override 16 | public String getName() { 17 | return username; 18 | } 19 | 20 | public String getAccessToken() { 21 | return accessToken; 22 | } 23 | 24 | public void setAccessToken(String accessToken) { 25 | this.accessToken = accessToken; 26 | } 27 | 28 | public String getUsername() { 29 | return username; 30 | } 31 | 32 | public void setUsername(String username) { 33 | this.username = username; 34 | } 35 | 36 | public String getNickname() { 37 | return nickname; 38 | } 39 | 40 | public void setNickname(String nickname) { 41 | this.nickname = nickname; 42 | } 43 | 44 | public String getAvatar() { 45 | return avatar; 46 | } 47 | 48 | public void setAvatar(String avatar) { 49 | this.avatar = avatar; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /oauth2/src/main/java/cn/flizi/auth/service/MysqlCaptchaService.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth.service; 2 | 3 | import cn.flizi.auth.entity.Captcha; 4 | import cn.flizi.auth.mapper.CaptchaMapper; 5 | import cn.flizi.auth.security.CaptchaService; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.util.StringUtils; 8 | 9 | import java.util.Date; 10 | 11 | @Service 12 | public class MysqlCaptchaService implements CaptchaService { 13 | private final CaptchaMapper captchaMapper; 14 | 15 | public MysqlCaptchaService(CaptchaMapper captchaMapper) { 16 | this.captchaMapper = captchaMapper; 17 | } 18 | 19 | @Override 20 | public void set(String key, String value, long expiresInSeconds) { 21 | Captcha captcha = new Captcha(); 22 | captcha.setKey(key); 23 | captcha.setCode(value); 24 | captchaMapper.insert(captcha); 25 | } 26 | 27 | @Override 28 | public boolean exists(String key) { 29 | if (!StringUtils.hasLength(key)) { 30 | return false; 31 | } 32 | Captcha captcha = captchaMapper.getCaptchaByKey(key); 33 | if (captcha == null) { 34 | return false; 35 | } 36 | long now = new Date().getTime(); 37 | if ((now - captcha.getCreateTime().getTime()) > 60 * 1000) { 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | @Override 44 | public void delete(String key) { 45 | captchaMapper.delete(key); 46 | } 47 | 48 | @Override 49 | public String get(String key) { 50 | Captcha captchaByKey = captchaMapper.getCaptchaByKey(key); 51 | return captchaByKey.getCode(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /oauth2/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | # https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ 2 | mybatis: 3 | configuration: 4 | map-underscore-to-camel-case: true 5 | 6 | spring: 7 | profiles: 8 | active: pro 9 | datasource: 10 | driver-class-name: com.mysql.cj.jdbc.Driver 11 | username: root 12 | password: root 13 | url: jdbc:mysql://127.0.0.1:3306/auth?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 14 | 15 | security: 16 | oauth2: 17 | authorization: 18 | jwt: 19 | key-value: hello_world 20 | 21 | logging: 22 | level: 23 | root: INFO 24 | org.springframework.web: DEBUG 25 | org.springframework.security: DEBUG 26 | org.springframework.security.oauth2: DEBUG 27 | org.springframework.boot.autoconfigure: DEBUG 28 | 29 | baseinfo: 30 | title: 浙江xxxx科技 31 | beian: 浙ICP备xxxx号 32 | 33 | 34 | # 图形验证码配置 35 | captcha: 36 | enable: true # 启用 37 | base-str: '0123' # 随机字符 38 | length: 4 # 长度 39 | 40 | # 手机登录配置(腾讯云) 41 | sms: 42 | enable: true # 启用 43 | secretId: 'xxxx' 44 | secretKey: 'xxxx' 45 | appId: 'xxx' 46 | sign: 'xxx' 47 | templateId: 'xxx' 48 | 49 | dingtalk: 50 | access_token: xxx 51 | 52 | # 社会账号登录 53 | social: 54 | wx-mp: # 微信公众平台 55 | key: 'xxx' 56 | secret: 'xxx' 57 | wx-open: # 微信开放平台 58 | key: 'xxx' 59 | secret: 'xxxx' -------------------------------------------------------------------------------- /oauth2/src/main/resources/public/static/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | a { 6 | text-decoration: none; 7 | color: #4D4D4D; 8 | } 9 | 10 | 11 | input:-webkit-autofill { 12 | -webkit-box-shadow: 0 0 0px 32px #fff inset; 13 | /**通过边框阴影遮挡住背景*/ 14 | -webkit-text-fill-color: #333; 15 | /*自动填充内容的文本颜色*/ 16 | } 17 | 18 | #Wrapper { 19 | border-radius: 3px; 20 | } 21 | 22 | .Label { 23 | display: flex; 24 | border-bottom: 1px solid #ebebeb; 25 | } 26 | 27 | .Input { 28 | -webkit-box-flex: 1; 29 | -ms-flex: 1 1; 30 | flex: 1 1; 31 | padding: 0; 32 | overflow: hidden; 33 | font-family: inherit; 34 | font-size: inherit; 35 | font-weight: inherit; 36 | background: transparent; 37 | resize: none; 38 | border: none; 39 | 40 | outline: none; 41 | 42 | width: 100%; 43 | height: 48px; 44 | padding: 0; 45 | color: #8590a6; 46 | } 47 | 48 | .Button { 49 | display: inline-block; 50 | text-align: center; 51 | cursor: pointer; 52 | background: none; 53 | border-radius: 3px; 54 | margin: 0; 55 | padding: 0; 56 | border: none; 57 | font-size: 16px; 58 | line-height: 16px; 59 | color: #409eff; 60 | } 61 | 62 | .Button2 { 63 | color:#333; 64 | outline: none; 65 | } 66 | 67 | .Header { 68 | position: relative; 69 | display: flex; 70 | justify-content: center; 71 | align-items: center; 72 | flex-direction: column; 73 | color: #409eff; 74 | font-weight: 700; 75 | height: 80px; 76 | padding: 0; 77 | border-radius: 5px 5px 0 0; 78 | } 79 | 80 | 81 | .Header-title { 82 | font-size: 25px; 83 | } 84 | 85 | .Header-subTitle { 86 | font-size: 16px; 87 | } 88 | 89 | .Form { 90 | margin-top: 20px; 91 | padding: 20px 20px; 92 | } 93 | 94 | .Form-submitButton { 95 | width: 100%; 96 | height: 40px; 97 | margin-top: 32px; 98 | 99 | display: inline-block; 100 | padding: 0 16px; 101 | font-size: 14px; 102 | line-height: 32px; 103 | text-align: center; 104 | cursor: pointer; 105 | background: none; 106 | border: 1px solid; 107 | border-radius: 3px; 108 | color: #fff; 109 | font-size: 16px; 110 | background-color: #06f; 111 | } 112 | 113 | 114 | @media screen and (min-width:500px) { 115 | #Wrapper { 116 | margin: 70px auto; 117 | max-width: 350px; 118 | padding-bottom: 20px; 119 | } 120 | .Form { 121 | box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); 122 | } 123 | } 124 | 125 | .Tools { 126 | display: flex; 127 | justify-content: space-between; 128 | padding: 20px 16px; 129 | font-weight: 700; 130 | color: #409eff 131 | } 132 | 133 | 134 | .Footer { 135 | text-align: center; 136 | margin-top: 60px; 137 | color: #333; 138 | } -------------------------------------------------------------------------------- /oauth2/src/main/resources/templates/auth-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 15 |
16 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /oauth2/src/main/resources/templates/confirm_access.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 授权 - XXX 13 | 14 | 15 | 16 |
17 |
18 |
授权
19 |
XXX
20 |
21 |
22 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /oauth2/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 首页 - XXX 13 | 19 | 20 | 21 | 22 |
23 |
24 |
首页
25 |
XXX
26 |
27 |
28 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 69 | 70 | -------------------------------------------------------------------------------- /oauth2/src/test/java/cn/flizi/auth/AppTests.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.auth; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class AppTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /oauth2/src/test/java/cn/flizi/ext/rbac/ExtRestApiTest.java: -------------------------------------------------------------------------------- 1 | package cn.flizi.ext.rbac; 2 | 3 | 4 | import cn.flizi.auth.App; 5 | import cn.flizi.core.util.R; 6 | import lombok.extern.log4j.Log4j2; 7 | import org.assertj.core.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.security.test.context.support.WithMockUser; 12 | 13 | import java.util.List; 14 | 15 | @Log4j2 16 | @SpringBootTest(classes = App.class) 17 | class ExtRestApiTest { 18 | 19 | @Autowired 20 | private ExtRestApi extRestApi; 21 | 22 | @Test 23 | public void contextLoads() throws Exception { 24 | Assertions.assertThat(extRestApi).isNotNull(); 25 | } 26 | 27 | @Test 28 | @WithMockUser(username = "1", authorities = {"sys:menu:tree"}) 29 | public void sysMenuTree() throws Exception { 30 | R> listR = extRestApi.sysMenuTree(); 31 | log.info(listR); 32 | Assertions.assertThat(listR).isNotNull(); 33 | } 34 | } --------------------------------------------------------------------------------