├── src ├── main │ ├── resources │ │ ├── bootstrap.yml │ │ ├── gzmu.jks │ │ ├── bootstrap-dev.yml │ │ ├── bootstrap-prod.yml │ │ ├── static │ │ │ └── css │ │ │ │ └── style.css │ │ ├── public.txt │ │ ├── application-prod.yml │ │ ├── banner.txt │ │ ├── application-dev.yml │ │ ├── templates │ │ │ ├── common │ │ │ │ └── common.html │ │ │ ├── error.html │ │ │ ├── logout.html │ │ │ ├── authorization.html │ │ │ └── login.html │ │ ├── application.yml │ │ └── logback-spring.xml │ └── java │ │ └── cn │ │ └── edu │ │ └── gzmu │ │ └── authserver │ │ ├── validate │ │ ├── ValidateCodeSender.java │ │ ├── ValidateCodeGenerator.java │ │ ├── ValidateCodeProcessor.java │ │ ├── ValidateCodeRepository.java │ │ ├── sms │ │ │ ├── SmsCodeProcessor.java │ │ │ ├── SmsCodeGenerator.java │ │ │ ├── SmsCodeRepository.java │ │ │ └── SmsCodeSender.java │ │ ├── email │ │ │ ├── EmailCodeProcessor.java │ │ │ ├── EmailCodeGenerator.java │ │ │ ├── EmailCodeRepository.java │ │ │ └── EmailCodeSender.java │ │ ├── ValidateCodeConfig.java │ │ ├── ValidateCodeSecurityConfig.java │ │ ├── ValidateCode.java │ │ ├── ValidateCodeController.java │ │ ├── ValidateCodeProcessorHolder.java │ │ ├── package-info.java │ │ ├── impl │ │ │ ├── AbstractValidateCodeRepository.java │ │ │ ├── ValidateCodeRepositoryImpl.java │ │ │ └── AbstractValidateCodeProcessor.java │ │ ├── ValidateCodeFilter.java │ │ └── ValidateCodeGrantTypeFilter.java │ │ ├── service │ │ ├── SysResService.java │ │ ├── SysUserService.java │ │ ├── AuthService.java │ │ ├── SysRoleService.java │ │ ├── impl │ │ │ ├── SysResServiceImpl.java │ │ │ ├── SysRoleServiceImpl.java │ │ │ └── ClientDetailsServiceImpl.java │ │ └── ClientRegistrationService.java │ │ ├── util │ │ ├── VerifyParameterPredicate.java │ │ ├── RandomCode.java │ │ ├── MapUtils.java │ │ ├── VerifyParameter.java │ │ ├── SubMailUtils.java │ │ └── EmailUtils.java │ │ ├── auth │ │ ├── package-info.java │ │ ├── sms │ │ │ ├── SmsUserDetailsService.java │ │ │ ├── package-info.java │ │ │ ├── SmsAuthenticationProvider.java │ │ │ ├── SmsAuthenticationToken.java │ │ │ ├── SmsAuthenticationSecurityConfig.java │ │ │ └── SmsAuthenticationFilter.java │ │ ├── email │ │ │ └── EmailUserDetailsService.java │ │ ├── handler │ │ │ ├── AuthLogoutSuccessHandler.java │ │ │ └── AuthFailureHandler.java │ │ ├── res │ │ │ └── AuthAccessDecisionManager.java │ │ ├── grant │ │ │ ├── EmailTokenGranter.java │ │ │ └── SmsTokenGranter.java │ │ ├── AuthTokenEnhancer.java │ │ └── Oauth2Helper.java │ │ ├── model │ │ ├── exception │ │ │ ├── ResourceException.java │ │ │ ├── ResourceExistException.java │ │ │ ├── ResourceNotFoundException.java │ │ │ └── ValidateCodeException.java │ │ ├── properties │ │ │ ├── Oauth2Properties.java │ │ │ ├── EmailConfig.java │ │ │ ├── EmailProperties.java │ │ │ └── SmsConfig.java │ │ ├── constant │ │ │ ├── ValidateCodeType.java │ │ │ ├── HttpMethod.java │ │ │ ├── AuthConstant.java │ │ │ ├── UserStatus.java │ │ │ ├── SecurityConstants.java │ │ │ ├── EntityType.java │ │ │ └── SysDataEnum.java │ │ └── entity │ │ │ ├── projection │ │ │ ├── TeacherOrdinary.java │ │ │ └── StudentOrdinary.java │ │ │ ├── Semester.java │ │ │ ├── SysData.java │ │ │ ├── SysRes.java │ │ │ ├── SysRole.java │ │ │ ├── SysUser.java │ │ │ ├── Teacher.java │ │ │ └── Student.java │ │ ├── controller │ │ ├── StudentController.java │ │ ├── SemesterController.java │ │ ├── SysDateController.java │ │ ├── JwkEndpoint.java │ │ ├── AuthorizationController.java │ │ ├── OauthController.java │ │ ├── HomeController.java │ │ ├── AuthController.java │ │ ├── TeacherController.java │ │ └── SysUserController.java │ │ ├── Application.java │ │ ├── repository │ │ ├── SysResRepository.java │ │ ├── SemesterRepository.java │ │ ├── ClientDetailsRepository.java │ │ ├── SysDataRepository.java │ │ ├── StudentRepository.java │ │ ├── TeacherRepository.java │ │ ├── SysRoleRepository.java │ │ └── SysUserRepository.java │ │ ├── config │ │ ├── package-info.java │ │ └── Oauth2ResourceServerConfig.java │ │ ├── handler │ │ └── AccessDeniedExceptionHandler.java │ │ ├── base │ │ ├── BaseEntity.java │ │ └── BaseRepository.java │ │ └── filter │ │ └── ApiNumberFilter.java └── test │ └── java │ └── cn │ └── edu │ └── gzmu │ └── authserver │ ├── PasswordTest.java │ ├── util │ ├── SubMailUtilsTest.java │ └── EmailUtilsTest.java │ └── ApplicationTests.java ├── docs ├── .vuepress │ ├── public │ │ ├── index.png │ │ ├── favicon-32x32.png │ │ ├── images │ │ │ ├── rbac.png │ │ │ └── icons │ │ │ │ ├── icon-72x72.png │ │ │ │ ├── icon-96x96.png │ │ │ │ ├── icon-128x128.png │ │ │ │ ├── icon-144x144.png │ │ │ │ ├── icon-152x152.png │ │ │ │ ├── icon-192x192.png │ │ │ │ ├── icon-384x384.png │ │ │ │ └── icon-512x512.png │ │ ├── apple-touch-icon.png │ │ └── manifest.json │ └── config.js ├── description │ ├── README.md │ └── OAuth2.md └── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── settings.gradle ├── simple.dockerfile ├── .github └── workflows │ ├── gradle-wrapper-validation.yml │ ├── docs-deploy.yml │ └── gradle-build.yml ├── Dockerfile ├── .gitignore ├── package.json ├── LICENSE └── gradlew.bat /src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | cache: 5 | type: caffeine -------------------------------------------------------------------------------- /src/main/resources/gzmu.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/src/main/resources/gzmu.jks -------------------------------------------------------------------------------- /docs/.vuepress/public/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/index.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/rbac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/rbac.png -------------------------------------------------------------------------------- /src/main/resources/bootstrap-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: dev 3 | cloud: 4 | consul: 5 | host: localhost 6 | port: 8500 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xms256m -Xmx512m 2 | org.gradle.parallel=true 3 | org.gradle.configureondemand=true 4 | org.gradle.daemon=false -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'auth-server' 7 | -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-72x72.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/main/resources/bootstrap-prod.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: prod 3 | cloud: 4 | consul: 5 | host: auth-consul-server-bootstrap 6 | port: 8500 -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzmuSoft/authorization-server/HEAD/docs/.vuepress/public/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 04 17:32:37 CST 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /simple.dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11-jre-slim 2 | LABEL version="1.0.0-SNAPSHOT" description="授权服务器" by="EchoCow" 3 | RUN mkdir /app 4 | COPY build/libs/authorization-server.jar /app/ 5 | EXPOSE 8888 6 | 7 | ENTRYPOINT ["java","-jar","/app/authorization-server.jar", "--spring.profiles.active=prod"] 8 | -------------------------------------------------------------------------------- /src/main/resources/static/css/style.css: -------------------------------------------------------------------------------- 1 | #form-card { 2 | width: 30rem; 3 | } 4 | @media screen and (max-width: 450px) { 5 | #form-card{ 6 | width: 100%; 7 | height: 100%; 8 | padding: 2rem !important; 9 | } 10 | } 11 | .fill-height { 12 | height: 100%; 13 | } -------------------------------------------------------------------------------- /docs/description/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 文档说明 3 | --- 4 | 5 | 本篇文档主要提供授权服务器的接口文档以及使用说明;叙述多个授权模式的使用流程等信息。 6 | 7 | ## 项目介绍 8 | 9 | 此项目为贵州民族大学的授权中心的授权服务器实现,基于标准的 [OAuth2](https://tools.ietf.org/html/rfc6749) 开放授权协议构建,作为授权服务器角色为资源服务器以及客户端提供功能实现。 10 | 11 | ## 授权模式 12 | 13 | 目前主要提供以下四种授权模式: 14 | 15 | 1. 授权码模式 —— 最为安全的授权模式 16 | 2. 密码模式 —— 最为简单的授权模式 17 | 3. 手机验证码模式 18 | 4. 邮箱验证码模式 19 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | name: Official Gradle Wrapper Validation Action 2 | 3 | on: [push] 4 | 5 | jobs: 6 | wrapper-validation: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Gradle Wrapper Validation 11 | uses: gradle/wrapper-validation-action@v1 12 | with: 13 | allow-snapshots: true 14 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeSender.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | /** 4 | * @author echo 5 | * @version 1.0 6 | * @date 19-4-14 14:14 7 | */ 8 | public interface ValidateCodeSender { 9 | 10 | /** 11 | * 发送验证码 12 | * 13 | * @param receive 接收方 14 | * @param code 验证码 15 | */ 16 | void send(String receive, ValidateCode code); 17 | } 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:5.4.1-jdk11 AS build 2 | LABEL version="1.0.0-SNAPSHOT" description="授权服务器" by="EchoCow" 3 | COPY --chown=gradle:gradle . /home/gradle/src 4 | WORKDIR /home/gradle/src 5 | RUN gradle bootjar 6 | 7 | FROM openjdk:11-jre-slim 8 | RUN mkdir /app 9 | COPY --from=build /home/gradle/src/build/libs/ /app/ 10 | EXPOSE 8888 11 | 12 | ENTRYPOINT ["java","-jar","/app/authorization-server.jar", "--spring.profiles.active=prod"] 13 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /index.png 4 | actionText: 快速阅览 → 5 | actionLink: /description/ 6 | features: 7 | - title: oauth 2 多种模式授权 8 | details: 更加安全完整的授权模式,具有高扩展性,第三方授权极其容易,提供多种授权模式支持。 9 | - title: RBAC 动态授权 10 | details: 所有资源通过角色进行灵活控制,自己管理应用权限,提供基于角色动态资源权限控制。 11 | - title: SSO、JWT 支持 12 | details: 提供 SSO 支持,多个应用一键登录,前后端分离,使用 JWT 令牌非对称密钥加密,具有高安全性。 13 | footer: MIT Licensed | Copyright © 2020-present GZMU Echo Cow 14 | --- 15 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/SysResService.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.SysRes; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * . 9 | * 10 | * @author EchoCow 11 | * @date 2020/1/1 下午9:22 12 | */ 13 | public interface SysResService { 14 | 15 | /** 16 | * 查所有 17 | * 18 | * @return 结果 19 | */ 20 | List findAll(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/public.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvy4bBhT5RpWt+jMOpqQA 3 | /Ln3ja2AhJbEn/DLBXyE6SsgmJoHJjqotIVTTGcg7cRbERuMbc7rzd6P00FgsOTq 4 | YnxZCOBRSugzZ7kdfD2vHtQOP1wVvm8Dew/zDdHS2NHeDGnOO0m/4DXuwlT4tFXe 5 | wQcYiY+g/n+px7EhJI8Lk9lTR2XT65AYF/NNtZIMbiICAduOjgfS2i3soOulPj4V 6 | xyyG55HKOkBsbK4NJFQce/j2qRV6pRM9ZptMbBurlMzmO0NHdHVCKs1jyKLsV8BD 7 | j6YHPihuc3fA96yri+c51PRjqPeS7SXCjyqjdbvsYKSCL4Z7XNJ7izsx9Prn6N4J 8 | awIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /.github/workflows/docs-deploy.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | jobs: 6 | docs: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | - name: docs-deploy 12 | uses: jenkey2011/vuepress-deploy@master 13 | env: 14 | ACCESS_TOKEN: ${{ secrets.DOCS_DEPLOY }} 15 | TARGET_BRANCH: gh-pages 16 | BUILD_SCRIPT: yarn && yarn docs:build 17 | BUILD_DIR: docs/.vuepress/dist/ 18 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/SysUserService.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.SysUser; 4 | 5 | /** 6 | * . 7 | * 8 | * @author EchoCow 9 | * @date 2020/3/23 下午1:17 10 | */ 11 | public interface SysUserService { 12 | 13 | /** 14 | * 通过用户名称查询 15 | * 16 | * @param username 用户名 17 | * @return 结果 18 | */ 19 | SysUser findByName(String username); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/util/VerifyParameterPredicate.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | /** 4 | * 函数式接口,用来自定义校验规则測試 5 | * 6 | * @param 第一个 7 | * @param 第二个 8 | * @author echo 9 | * @version 1.0 10 | * @date 19-5-22 14:54 11 | */ 12 | @FunctionalInterface 13 | public interface VerifyParameterPredicate { 14 | /** 15 | * 测试 16 | * 17 | * @param t 第一个 18 | * @param v 第二个 19 | * @return 结果 20 | */ 21 | boolean test(T t, V v); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import org.springframework.web.context.request.ServletWebRequest; 4 | 5 | 6 | /** 7 | * 创建验证码 8 | * 9 | * @author echo 10 | * @version 1.0 11 | * @date 19-4-14 11:27 12 | */ 13 | public interface ValidateCodeGenerator { 14 | /** 15 | * 生成验证码 16 | * 17 | * @param request 请求 18 | * @return 生成结果 19 | */ 20 | ValidateCode generate(ServletWebRequest request); 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | /build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | logs 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 | *.log 22 | /out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### node ### 35 | node_modules 36 | docs/.vuepress/dist -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 所有授权相关的类都放在这里. 3 | * {@link cn.edu.gzmu.authserver.auth.email} 邮箱登录相关 4 | * {@link cn.edu.gzmu.authserver.auth.grant} 对 spring security oauth2 做的扩展登录方式 5 | * {@link cn.edu.gzmu.authserver.auth.handler} 安全相关的处理器 6 | * {@link cn.edu.gzmu.authserver.auth.res} 作为资源服务器时,RBAC 模型的具体的权限控制操作 7 | * {@link cn.edu.gzmu.authserver.auth.sms} 手机登录相关 8 | * 9 | * @author EchoCow 10 | * @date 2020/1/3 上午11:04 11 | */ 12 | package cn.edu.gzmu.authserver.auth; -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/exception/ResourceException.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | * 资源异常 9 | * 10 | * @author echo 11 | * @date 19-6-18 下午10:18 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @EqualsAndHashCode(callSuper = true) 16 | public class ResourceException extends RuntimeException { 17 | private String message; 18 | 19 | public ResourceException() { 20 | this.message = "资源异常"; 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/StudentController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.rest.webmvc.RepositoryRestController; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | /** 9 | * @author EchoCow 10 | * @date 2019/8/4 下午8:49 11 | */ 12 | @RequiredArgsConstructor 13 | @RepositoryRestController 14 | @RequestMapping(AuthConstant.STUDENT) 15 | public class StudentController { 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/properties/Oauth2Properties.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.properties; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * Oauth2 配置 9 | * 10 | * @author echo 11 | * @date 19-6-18 下午5:32 12 | */ 13 | @Data 14 | @Component 15 | @ConfigurationProperties("application.security") 16 | public class Oauth2Properties { 17 | 18 | /** 19 | * token 有效期, 分钟为单位 20 | */ 21 | private Long accessTokenValiditySeconds = 30L; 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/Application.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | /** 8 | * @author EchoCow 9 | * @date 2019-6-11 10 | */ 11 | @SpringBootApplication 12 | @EnableDiscoveryClient 13 | public class Application { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(Application.class, args); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/SemesterController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.rest.webmvc.RepositoryRestController; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | /** 9 | * @author EchoCow 10 | * @date 2019/8/4 下午8:49 11 | */ 12 | @RepositoryRestController 13 | @RequiredArgsConstructor 14 | @RequestMapping(AuthConstant.SEMESTER) 15 | public class SemesterController { 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/SysDateController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.rest.webmvc.RepositoryRestController; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | 9 | /** 10 | * @author EchoCow 11 | * @date 2019/8/4 下午8:49 12 | */ 13 | @RequiredArgsConstructor 14 | @RepositoryRestController 15 | @RequestMapping(AuthConstant.SYS_DATA) 16 | public class SysDateController { 17 | 18 | } -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: prod 3 | datasource: 4 | type: com.alibaba.druid.pool.DruidDataSource 5 | driver-class-name: org.postgresql.Driver 6 | url: jdbc:postgresql://auth-db:5432/auth 7 | username: gzmu 8 | password: 147258369 9 | druid: 10 | filters: stat,wall,commonlogging 11 | web-stat-filter: 12 | enabled: true 13 | stat-view-servlet: 14 | enabled: true 15 | login-password: 123456 16 | login-username: admin 17 | url-pattern: /druid/* 18 | allow: 19 | redis: 20 | host: auth-redis 21 | password: -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/exception/ResourceExistException.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.exception; 2 | 3 | /** 4 | * 资源已存在异常 5 | * 6 | * @author echo 7 | * @version 1.0 8 | * @date 2019-5-21 10:43:57 9 | */ 10 | public class ResourceExistException extends RuntimeException { 11 | 12 | public ResourceExistException(){ 13 | super("资源已存在!"); 14 | } 15 | 16 | public ResourceExistException(String message) { 17 | super(message); 18 | } 19 | 20 | public ResourceExistException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/SysResRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | 4 | import cn.edu.gzmu.authserver.base.BaseRepository; 5 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 6 | import cn.edu.gzmu.authserver.model.entity.SysRes; 7 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 8 | 9 | /** 10 | * SysRole Repository 11 | * 12 | * @author echo 13 | * @version 1.0 14 | * @date 2019-5-7 11:05:31 15 | */ 16 | @RepositoryRestResource(path = AuthConstant.SYS_RES) 17 | public interface SysResRepository extends BaseRepository { 18 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/SemesterRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | 4 | import cn.edu.gzmu.authserver.base.BaseRepository; 5 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 6 | import cn.edu.gzmu.authserver.model.entity.Semester; 7 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 8 | 9 | /** 10 | * Semester Repository 11 | * 12 | * @author echo 13 | * @version 1.0 14 | * @date 2019-5-23 17:38:13 15 | */ 16 | @RepositoryRestResource(path = AuthConstant.SEMESTER) 17 | public interface SemesterRepository extends BaseRepository { 18 | 19 | } -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ____ _______ __ _ _ _ _ _ _____ _ _ 2 | / ___|__ / \/ | | | | / \ | | | |_ _| | | | 3 | | | _ / /| |\/| | | | |_____ / _ \| | | | | | | |_| | 4 | | |_| |/ /_| | | | |_| |_____/ ___ \ |_| | | | | _ | 5 | \____/____|_| |_|\___/ /_/ \_\___/ |_| |_| |_| 6 | ===================================================================== 7 | ${AnsiColor.BLUE} :: authorization-server :: 0.0.1-SNAPSHOT 8 | ${AnsiColor.BRIGHT_GREEN} :: Spring Boot :: ${spring-boot.formatted-version} 9 | ${AnsiColor.RED} :: School :: 贵州民族大学 10 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import org.springframework.web.context.request.ServletWebRequest; 4 | 5 | /** 6 | * 验证码处理器,封装不同验证码的处理逻辑 7 | * 8 | * @author echo 9 | * @version 1.0 10 | * @date 19-4-14 11:30 11 | */ 12 | public interface ValidateCodeProcessor { 13 | 14 | /** 15 | * 创建验证码 16 | * 17 | * @param request 请求 18 | * @throws Exception 异常 19 | */ 20 | void create(ServletWebRequest request) throws Exception; 21 | 22 | /** 23 | * 验证验证码 24 | * 25 | * @param request 请求 26 | */ 27 | void validate(ServletWebRequest request); 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authorization-server", 3 | "version": "1.0.0", 4 | "description": "贵州民族大学授权授权服务器", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/gzmuSoft/authorization-server" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/gzmuSoft/authorization-server/issues" 11 | }, 12 | "author": "EchoCow ", 13 | "license": "MIT", 14 | "scripts": { 15 | "docs:dev": "npx vuepress dev docs", 16 | "docs:build": "npx vuepress build docs" 17 | }, 18 | "devDependencies": { 19 | "@vuepress/plugin-back-to-top": "^1.4.0", 20 | "@vuepress/plugin-pwa": "^1.4.0", 21 | "vuepress": "^1.4.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/constant/ValidateCodeType.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.constant; 2 | 3 | /** 4 | * @author echo 5 | * @version 1.0 6 | * @date 19-4-14 16:23 7 | */ 8 | public enum ValidateCodeType { 9 | /** 10 | * 短信验证码 11 | */ 12 | SMS { 13 | @Override 14 | public String getParamNameOnValidate() { 15 | return SecurityConstants.GRANT_TYPE_SMS; 16 | } 17 | }, 18 | EMAIL { 19 | @Override 20 | public String getParamNameOnValidate() { 21 | return SecurityConstants.GRANT_TYPE_EMAIL; 22 | } 23 | }; 24 | public abstract String getParamNameOnValidate(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: dev 3 | datasource: 4 | type: com.alibaba.druid.pool.DruidDataSource 5 | driver-class-name: org.postgresql.Driver 6 | url: jdbc:postgresql://118.24.1.170:5432/gzmu 7 | username: postgres 8 | password: 147258369 9 | druid: 10 | filters: stat,wall,commonlogging 11 | web-stat-filter: 12 | enabled: true 13 | stat-view-servlet: 14 | enabled: true 15 | login-password: 123456 16 | login-username: admin 17 | url-pattern: /druid/* 18 | allow: 127.0.0.1 19 | redis: 20 | host: 127.0.0.1 21 | password: 22 | logging: 23 | level: 24 | cn.edu.gzmu.authserver: debug 25 | -------------------------------------------------------------------------------- /.github/workflows/gradle-build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.11 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build -x test 27 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/properties/EmailConfig.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.properties; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * @author Japoul 9 | * @version 1.0 10 | * @date 2019-05-21 15:46 11 | */ 12 | @Data 13 | @Component 14 | @ConfigurationProperties(prefix = "spring.mail") 15 | public class EmailConfig { 16 | /** 17 | * host 18 | */ 19 | private String host; 20 | /** 21 | * port 22 | */ 23 | private String port; 24 | /** 25 | * username 26 | */ 27 | private String username; 28 | /** 29 | * password 30 | */ 31 | private String password; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/constant/HttpMethod.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.constant; 2 | 3 | /** 4 | * @author echo 5 | * @version 1.0 6 | * @date 19-4-20 16:31 7 | */ 8 | public enum HttpMethod { 9 | /** 10 | * GET 方法 11 | */ 12 | GET, 13 | /** 14 | * POST 方法 15 | */ 16 | POST, 17 | /** 18 | * PUT 方法 19 | */ 20 | PUT, 21 | /** 22 | * PATCH 方法 23 | */ 24 | PATCH, 25 | /** 26 | * DELETE FANGFA 27 | */ 28 | DELETE, 29 | /** 30 | * 所有方法 31 | */ 32 | ALL; 33 | 34 | /** 35 | * 方法匹配 36 | * 37 | * @param name 匹配的方法 38 | * @return 结果 39 | */ 40 | public boolean match(String name) { 41 | return name().equalsIgnoreCase(name); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.Student; 4 | import cn.edu.gzmu.authserver.model.entity.SysData; 5 | import cn.edu.gzmu.authserver.model.entity.SysUser; 6 | 7 | /** 8 | * . 9 | * 10 | * @author EchoCow 11 | * @date 2020/1/1 下午9:26 12 | */ 13 | public interface AuthService { 14 | /** 15 | * 注册 16 | * 17 | * @param user 用户 18 | * @param student 学生 19 | * @param school 学校 20 | * @return 结果 21 | */ 22 | Object register(SysUser user, Student student, SysData school); 23 | 24 | /** 25 | * 用户信息 26 | * 27 | * @param userId 用户 id 28 | * @return 结果 29 | */ 30 | Object userDetails(Long userId); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/properties/EmailProperties.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.properties; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * @author echo 9 | * @version 1.0 10 | * @date 19-5-7 17:31 11 | */ 12 | @Data 13 | @Component 14 | @ConfigurationProperties(prefix = "application.email") 15 | public class EmailProperties { 16 | /** 17 | * 邮箱验证码长度 18 | */ 19 | private Integer codeLength = 4; 20 | /** 21 | * 是否只要数字 22 | */ 23 | private Boolean onlyNumber = false; 24 | /** 25 | * 验证码有效期 26 | */ 27 | private Integer codeExpireIn = 600; 28 | /** 29 | * 是否开发模式 30 | */ 31 | private Boolean dev = false; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/SysRoleService.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.SysRole; 4 | 5 | import java.util.Set; 6 | 7 | /** 8 | * . 9 | * 10 | * @author EchoCow 11 | * @date 2020/1/1 下午10:21 12 | */ 13 | public interface SysRoleService { 14 | 15 | /** 16 | * 查询所有的角色 17 | * 18 | * @param roles 角色 19 | * @return 结果 20 | */ 21 | Set findAllByRoles(Set roles); 22 | 23 | /** 24 | * 通过用户id查询所有 25 | * 26 | * @param userId 用户id 27 | * @return 结果 28 | */ 29 | Set findAllByUser(Long userId); 30 | 31 | /** 32 | * 通过资源id查询所有 33 | * 34 | * @param resId 资源id 35 | * @return 结果 36 | */ 37 | Set findAllByRes(Long resId); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/impl/SysResServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service.impl; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.SysRes; 4 | import cn.edu.gzmu.authserver.repository.SysResRepository; 5 | import cn.edu.gzmu.authserver.service.SysResService; 6 | import lombok.NonNull; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * . 14 | * 15 | * @author EchoCow 16 | * @date 2020/1/1 下午9:23 17 | */ 18 | @Service 19 | @RequiredArgsConstructor 20 | public class SysResServiceImpl implements SysResService { 21 | private final @NonNull SysResRepository sysResRepository; 22 | 23 | @Override 24 | public List findAll() { 25 | return sysResRepository.findAll(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目所有的配置信息都在这里. 3 | * 4 | * {@link cn.edu.gzmu.authserver.config.ApplicationConfig} 5 | * 应用的基本配置,包括 redis 工厂、rest 接口 6 | * {@link cn.edu.gzmu.authserver.config.AuthTokenStore} 7 | * 授权的 token 存储配置, 包括 redis 存储令牌,postgres 存储客户端信息 8 | * 以及 jwt 令牌和 jwk 的一些配置 9 | * {@link cn.edu.gzmu.authserver.config.Oauth2AuthorizationServerConfig} 10 | * 授权服务器的配置 11 | * {@link cn.edu.gzmu.authserver.config.Oauth2ResourceServerConfig} 12 | * 资源服务器的配置 13 | * 14 | * 授权中心即是授权服务器,又是资源服务器 15 | * 但是他只对外提供查询的操作,不提供修改操作 16 | * 修改操作需要作为另外一套资源服务器来进行控制 17 | * 18 | * {@link cn.edu.gzmu.authserver.config.WebSecurityConfig} 19 | * 安全相关的配置,此处的优先级高于 资源服务器 的优先级 20 | * 所以他会完全覆盖掉 资源服务器 中的额皮质 21 | * 为了统一,我们所有的权限都放在这里进行配置 22 | * 23 | * @author EchoCow 24 | * @date 2020/1/3 上午10:36 25 | */ 26 | package cn.edu.gzmu.authserver.config; -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/constant/AuthConstant.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.constant; 2 | 3 | /** 4 | * . 5 | * 6 | * @author EchoCow 7 | * @date 2020/1/2 下午8:07 8 | */ 9 | public interface AuthConstant { 10 | /** 11 | * 客户端信息. 12 | */ 13 | String CLIENT_DETAILS = "clientDetails"; 14 | /** 15 | * 学期. 16 | */ 17 | String SEMESTER = "semester"; 18 | /** 19 | * 学生. 20 | */ 21 | String STUDENT = "student"; 22 | /** 23 | * 教师. 24 | */ 25 | String TEACHER = "teacher"; 26 | /** 27 | * 数据. 28 | */ 29 | String SYS_DATA = "sysData"; 30 | /** 31 | * 资源. 32 | */ 33 | String SYS_RES = "sysRes"; 34 | /** 35 | * 角色. 36 | */ 37 | String SYS_ROLE = "sysRole"; 38 | /** 39 | * 用户. 40 | */ 41 | String SYS_USER = "sysUser"; 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/ClientDetailsRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | 4 | import cn.edu.gzmu.authserver.base.BaseRepository; 5 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 6 | import cn.edu.gzmu.authserver.model.entity.ClientDetails; 7 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 8 | 9 | import java.util.Optional; 10 | 11 | /** 12 | * Semester Repository 13 | * 14 | * @author echo 15 | * @version 1.0 16 | * @date 2019-5-23 17:38:13 17 | */ 18 | @RepositoryRestResource(path = AuthConstant.CLIENT_DETAILS) 19 | public interface ClientDetailsRepository extends BaseRepository { 20 | 21 | /** 22 | * 通过 client id 查询 23 | * 24 | * @param clientId client id 25 | * @return ClientDetails 26 | */ 27 | Optional findFirstByClientId(String clientId); 28 | 29 | } -------------------------------------------------------------------------------- /src/test/java/cn/edu/gzmu/authserver/PasswordTest.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 5 | import org.springframework.security.crypto.password.PasswordEncoder; 6 | 7 | import java.util.Base64; 8 | 9 | /** 10 | * @author echo 11 | * @version 1.0.0 12 | * @date 19-6-11 下午5:42 13 | */ 14 | class PasswordTest { 15 | 16 | private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12); 17 | 18 | @Test 19 | void password() { 20 | System.out.println(passwordEncoder.encode("lesson-cloud-secret")); 21 | System.out.println(passwordEncoder.encode("secret")); 22 | System.out.println(passwordEncoder.encode("1997")); 23 | } 24 | 25 | @Test 26 | void base64() { 27 | String s = Base64.getEncoder().encodeToString("client:secret".getBytes()); 28 | System.out.println(s); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/properties/SmsConfig.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.properties; 2 | 3 | 4 | import lombok.Data; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @author echo 10 | * @version 1.0 11 | * @date 19-5-7 17:31 12 | */ 13 | @Data 14 | @Component 15 | @ConfigurationProperties(prefix = "application.sms") 16 | public class SmsConfig { 17 | /** 18 | * app id 19 | */ 20 | private String appId; 21 | /** 22 | * app key 23 | */ 24 | private String appKey; 25 | /** 26 | * 注册/登录 模板 27 | */ 28 | private String actionTemplate; 29 | /** 30 | * 短信验证码长度 31 | */ 32 | private Integer codeLength = 4; 33 | /** 34 | * 验证码有效期 35 | */ 36 | private Integer codeExpireIn = 60; 37 | /** 38 | * 是否开发模式 39 | */ 40 | private Boolean dev = false; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/SysDataRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseRepository; 4 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 5 | import cn.edu.gzmu.authserver.model.entity.SysData; 6 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 7 | import org.springframework.data.rest.core.annotation.RestResource; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * SysData Repository 13 | * 14 | * @author echo 15 | * @version 1.0 16 | * @date 2019-5-23 17:38:13 17 | */ 18 | @RepositoryRestResource(path = AuthConstant.SYS_DATA) 19 | public interface SysDataRepository extends BaseRepository { 20 | 21 | /** 22 | * 通过父级id和类型查询 23 | * 24 | * @param parentId 父级id 25 | * @param type 类型 26 | * @return 结果 27 | */ 28 | @RestResource(path = "/byParentIdAndType") 29 | List findAllByParentIdAndType(Long parentId, Integer type); 30 | 31 | } -------------------------------------------------------------------------------- /src/main/resources/templates/common/common.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.exception; 2 | 3 | import cn.edu.gzmu.authserver.auth.sms.SmsUserDetailsService; 4 | import org.springframework.security.core.AuthenticationException; 5 | import org.springframework.security.core.userdetails.User; 6 | 7 | /** 8 | * 如果 {@link SmsUserDetailsService} 实现无法通过其 sms 设备号找到{@link User},则抛出此异常。 9 | * 10 | * @author echo 11 | * @version 1.0 12 | * @date 19-4-20 13:40 13 | */ 14 | public class ResourceNotFoundException extends AuthenticationException { 15 | /** 16 | * 构造自定义信息的 ResourceNotFoundException 。 17 | * 18 | * @param msg 细节信息 19 | */ 20 | public ResourceNotFoundException(String msg) { 21 | super(msg); 22 | } 23 | 24 | /** 25 | * 构造自定义信息异常的 ResourceNotFoundException 。 26 | * 27 | * @param msg 细节信息 28 | * @param t 异常 29 | */ 30 | public ResourceNotFoundException(String msg, Throwable t) { 31 | super(msg, t); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | 4 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 5 | import org.springframework.web.context.request.ServletWebRequest; 6 | 7 | /** 8 | * 验证码资源处理 9 | * 10 | * @author echo 11 | * @version 1.0 12 | * @date 19-4-16 22:08 13 | */ 14 | public interface ValidateCodeRepository { 15 | 16 | /** 17 | * 保存 18 | * 19 | * @param request 请求 20 | * @param code 验证码 21 | * @param type 类型 22 | */ 23 | void save(ServletWebRequest request, ValidateCode code, ValidateCodeType type); 24 | 25 | /** 26 | * 获取 27 | * 28 | * @param request 请求 29 | * @param type 类型 30 | * @return 验证码 31 | */ 32 | ValidateCode get(ServletWebRequest request, ValidateCodeType type); 33 | 34 | /** 35 | * 移除 36 | * 37 | * @param request 请求 38 | * @param type 类型 39 | */ 40 | void remove(ServletWebRequest request, ValidateCodeType type); 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/exception/ValidateCodeException.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.exception; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | 5 | /** 6 | * 验证码异常 7 | * 8 | * @author echo 9 | * @version 1.0 10 | * @date 19-4-14 11:14 11 | */ 12 | public class ValidateCodeException extends AuthenticationException { 13 | public ValidateCodeException(){ 14 | super("验证码异常"); 15 | } 16 | 17 | /** 18 | * Constructs an {@code AuthenticationException} with the specified message and root 19 | * cause. 20 | * 21 | * @param msg the detail message 22 | * @param t the root cause 23 | */ 24 | public ValidateCodeException(String msg, Throwable t) { 25 | super(msg, t); 26 | } 27 | 28 | /** 29 | * Constructs an {@code AuthenticationException} with the specified message and no 30 | * root cause. 31 | * 32 | * @param msg the detail message 33 | */ 34 | public ValidateCodeException(String msg) { 35 | super(msg); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/projection/TeacherOrdinary.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity.projection; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.Teacher; 4 | import org.springframework.data.rest.core.config.Projection; 5 | 6 | /** 7 | * 教师投影. 8 | * 9 | * @author EchoCow 10 | * @date 2020/1/2 下午10:06 11 | */ 12 | @SuppressWarnings("unused") 13 | @Projection(name = "ordinary", types = { Teacher.class }) 14 | public interface TeacherOrdinary { 15 | 16 | /** 17 | * ID 18 | * @return ID 19 | */ 20 | Long getId(); 21 | 22 | /** 23 | * 学校id 24 | * @return 学校id 25 | */ 26 | Long getSchoolId(); 27 | 28 | /** 29 | * 学院id 30 | * @return 学院id 31 | */ 32 | Long getCollegeId(); 33 | 34 | /** 35 | * 专业id 36 | * @return 专业id 37 | */ 38 | Long getDepId(); 39 | 40 | /** 41 | * 获取名字 42 | * @return 名字 43 | */ 44 | String getName(); 45 | 46 | /** 47 | * 性别 48 | * @return 性别 49 | */ 50 | String getGender(); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/sms/SmsCodeProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.sms; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import cn.edu.gzmu.authserver.validate.ValidateCode; 5 | import cn.edu.gzmu.authserver.validate.ValidateCodeSender; 6 | import cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeProcessor; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.context.request.ServletWebRequest; 11 | 12 | /** 13 | * @author echo 14 | * @version 1.0 15 | * @date 19-4-14 14:08 16 | */ 17 | @RequiredArgsConstructor 18 | @Component("smsValidateCodeProcessor") 19 | public class SmsCodeProcessor extends AbstractValidateCodeProcessor { 20 | 21 | private final @NonNull ValidateCodeSender smsCodeSender; 22 | 23 | @Override 24 | protected void send(ServletWebRequest request, ValidateCode validateCode) { 25 | smsCodeSender.send(request.getParameter(SecurityConstants.GRANT_TYPE_SMS), validateCode); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/email/EmailCodeProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.email; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import cn.edu.gzmu.authserver.validate.ValidateCode; 5 | import cn.edu.gzmu.authserver.validate.ValidateCodeSender; 6 | import cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeProcessor; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.context.request.ServletWebRequest; 11 | 12 | /** 13 | * @author echo 14 | * @version 1.0 15 | * @date 19-5-21 23:52 16 | */ 17 | @Component("emailValidateCodeProcessor") 18 | @RequiredArgsConstructor 19 | public class EmailCodeProcessor extends AbstractValidateCodeProcessor { 20 | private final @NonNull ValidateCodeSender mailCodeSender; 21 | 22 | @Override 23 | protected void send(ServletWebRequest request, ValidateCode validateCode) { 24 | mailCodeSender.send(request.getParameter(SecurityConstants.GRANT_TYPE_EMAIL), validateCode); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/email/EmailCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.email; 2 | 3 | import cn.edu.gzmu.authserver.model.properties.EmailProperties; 4 | import cn.edu.gzmu.authserver.util.RandomCode; 5 | import cn.edu.gzmu.authserver.validate.ValidateCode; 6 | import cn.edu.gzmu.authserver.validate.ValidateCodeGenerator; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.context.request.ServletWebRequest; 11 | 12 | /** 13 | * @author echo 14 | * @version 1.0 15 | * @date 19-5-21 23:52 16 | */ 17 | @Component("emailValidateCodeGenerator") 18 | @RequiredArgsConstructor 19 | public class EmailCodeGenerator implements ValidateCodeGenerator { 20 | private final @NonNull EmailProperties emailProperties; 21 | 22 | @Override 23 | public ValidateCode generate(ServletWebRequest request) { 24 | return new ValidateCode(RandomCode.random(emailProperties.getCodeLength(), emailProperties.getOnlyNumber()), 25 | emailProperties.getCodeExpireIn()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 gzmuSoft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/constant/UserStatus.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.constant; 2 | 3 | /** 4 | * 用户状态. 5 | * 6 | * @author EchoCow 7 | * @date 2020/1/1 下午9:16 8 | */ 9 | public enum UserStatus { 10 | /** 11 | * 正常 12 | */ 13 | NORMAL, 14 | /** 15 | * 锁定 16 | */ 17 | LOCKED, 18 | /** 19 | * 是否启用 20 | */ 21 | ENABLE; 22 | 23 | /** 24 | * 是否正常 25 | * 26 | * @param status 状态 27 | * @return 结果 28 | */ 29 | public static Boolean isNormal(UserStatus status) { 30 | return UserStatus.NORMAL.equals(status); 31 | } 32 | 33 | /** 34 | * 是否锁定 35 | * 36 | * @param status 状态 37 | * @return 结果 38 | */ 39 | public static Boolean isLocked(UserStatus status) { 40 | return UserStatus.LOCKED.equals(status); 41 | } 42 | 43 | /** 44 | * 是否启用 45 | * 46 | * @param status 状态 47 | * @return 结果 48 | */ 49 | public static Boolean isEnable(UserStatus status) { 50 | return UserStatus.ENABLE.equals(status); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/cn/edu/gzmu/authserver/util/SubMailUtilsTest.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit.jupiter.SpringExtension; 10 | 11 | /** 12 | * @author echo 13 | * @version 1.0 14 | * @date 19-4-14 16:23 15 | */ 16 | @SpringBootTest 17 | @ExtendWith(SpringExtension.class) 18 | @DisplayName(":iphone: 手机短信测试") 19 | class SubMailUtilsTest { 20 | 21 | @Autowired 22 | private SubMailUtils subMailUtils; 23 | 24 | @Test 25 | @DisplayName(":iphone: 向指定手机号发送一条短信") 26 | void application() { 27 | JSONObject jsonObject = new JSONObject(); 28 | jsonObject.put("action", "登录"); 29 | jsonObject.put("code", "123456"); 30 | jsonObject.put("time", "123"); 31 | subMailUtils.sendActionMessage("13765308262", jsonObject); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/JwkEndpoint.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import com.nimbusds.jose.jwk.JWKSet; 4 | import com.nimbusds.jose.jwk.RSAKey; 5 | import lombok.AllArgsConstructor; 6 | import lombok.NonNull; 7 | import net.minidev.json.JSONObject; 8 | import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpoint; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | 12 | import java.security.KeyPair; 13 | import java.security.interfaces.RSAPublicKey; 14 | 15 | /** 16 | * . 17 | * 18 | * @author EchoCow 19 | * @date 2019/12/28 下午10:33 20 | */ 21 | @FrameworkEndpoint 22 | @AllArgsConstructor 23 | public class JwkEndpoint { 24 | 25 | private final @NonNull KeyPair keyPair; 26 | 27 | @GetMapping("/.well-known/jwks.json") 28 | @ResponseBody 29 | public JSONObject getKey() { 30 | RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); 31 | RSAKey key = new RSAKey.Builder(publicKey).build(); 32 | return new JWKSet(key).toJSONObject(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/sms/SmsCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.sms; 2 | 3 | import cn.edu.gzmu.authserver.model.properties.SmsConfig; 4 | import cn.edu.gzmu.authserver.util.RandomCode; 5 | import cn.edu.gzmu.authserver.validate.ValidateCode; 6 | import cn.edu.gzmu.authserver.validate.ValidateCodeGenerator; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.context.request.ServletWebRequest; 11 | 12 | /** 13 | * @author echo 14 | * @version 1.0 15 | * @date 19-4-14 14:11 16 | */ 17 | @RequiredArgsConstructor 18 | @Component("smsValidateCodeGenerator") 19 | public class SmsCodeGenerator implements ValidateCodeGenerator { 20 | 21 | private final @NonNull SmsConfig smsConfig; 22 | 23 | /** 24 | * 生成验证码 25 | * 26 | * @param request 请求 27 | * @return 生成结果 28 | */ 29 | @Override 30 | public ValidateCode generate(ServletWebRequest request) { 31 | return new ValidateCode( 32 | RandomCode.random(smsConfig.getCodeLength(), true), 33 | smsConfig.getCodeExpireIn()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/projection/StudentOrdinary.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity.projection; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.Student; 4 | import org.springframework.data.rest.core.config.Projection; 5 | 6 | /** 7 | * 学生投影. 8 | * 9 | * @author EchoCow 10 | * @date 2020/1/2 下午10:06 11 | */ 12 | @SuppressWarnings("unused") 13 | @Projection(name = "ordinary", types = { Student.class }) 14 | public interface StudentOrdinary { 15 | 16 | /** 17 | * ID 18 | * @return ID 19 | */ 20 | Long getId(); 21 | 22 | /** 23 | * 学校id 24 | * @return 学校id 25 | */ 26 | Long getSchoolId(); 27 | 28 | /** 29 | * 学院id 30 | * @return 学院id 31 | */ 32 | Long getCollegeId(); 33 | 34 | /** 35 | * 专业id 36 | * @return 专业id 37 | */ 38 | Long getSpecialtyId(); 39 | 40 | /** 41 | * 获取名字 42 | * @return 名字 43 | */ 44 | String getName(); 45 | 46 | /** 47 | * 学号 48 | * @return 学号 49 | */ 50 | String getNo(); 51 | 52 | /** 53 | * 性别 54 | * @return 性别 55 | */ 56 | String getGender(); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/email/EmailCodeRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.email; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 5 | import cn.edu.gzmu.authserver.model.exception.ValidateCodeException; 6 | import cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeRepository; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.springframework.web.context.request.ServletWebRequest; 9 | 10 | /** 11 | * @author EchoCow 12 | * @version 1.0 13 | * @date 19-5-22 9:02 14 | * @deprecated 过于复杂的实现方式,标记过时 15 | */ 16 | //@Component("emailValidateCodeRepository") 17 | @Deprecated 18 | public class EmailCodeRepository extends AbstractValidateCodeRepository { 19 | @Override 20 | protected String buildKey(ServletWebRequest request, ValidateCodeType type) { 21 | String email = request.getHeader(SecurityConstants.GRANT_TYPE_EMAIL); 22 | if (StringUtils.isBlank(email)) { 23 | throw new ValidateCodeException("请求中不存在邮箱号"); 24 | } 25 | return "code:" + type.toString().toLowerCase() + ":" + email; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/sms/SmsCodeRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.sms; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 5 | import cn.edu.gzmu.authserver.model.exception.ValidateCodeException; 6 | import cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeRepository; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.springframework.web.context.request.ServletWebRequest; 9 | 10 | /** 11 | * @author EchoCow 12 | * @version 1.0 13 | * @date 19-4-17 11:04 14 | * @deprecated 过于复杂的实现方式,标记过时 15 | */ 16 | //@Component("smsValidateCodeRepository") 17 | @Deprecated 18 | public class SmsCodeRepository extends AbstractValidateCodeRepository { 19 | 20 | @Override 21 | protected String buildKey(ServletWebRequest request, ValidateCodeType type) { 22 | String deviceId = request.getHeader(SecurityConstants.GRANT_TYPE_SMS); 23 | if (StringUtils.isBlank(deviceId)) { 24 | throw new ValidateCodeException("请求中不存在设备号"); 25 | } 26 | return "code:" + type.toString().toLowerCase() + ":" + deviceId; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/AuthorizationController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import org.springframework.security.oauth2.provider.AuthorizationRequest; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.SessionAttributes; 7 | import org.springframework.web.servlet.ModelAndView; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author EchoCow 13 | * @date 19-8-4 下午1:26 14 | */ 15 | @Controller 16 | @SessionAttributes("authorizationRequest") 17 | public class AuthorizationController { 18 | @GetMapping("/oauth/confirm_access") 19 | public ModelAndView getAccessConfirmation(Map model) { 20 | AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest"); 21 | ModelAndView view = new ModelAndView(); 22 | view.setViewName("authorization"); 23 | view.addObject("clientId", authorizationRequest.getClientId()); 24 | // 传递 scope 过去,Set 集合 25 | view.addObject("scopes", authorizationRequest.getScope()); 26 | return view; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/sms/SmsUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.sms; 2 | 3 | import cn.edu.gzmu.authserver.model.exception.ResourceNotFoundException; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | 6 | /** 7 | * 加载用户 sms 特定数据的核心接口。 8 | *

他在整个框架中将会作为用户数据加载。

9 | * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider 10 | * DaoAuthenticationProvider}. 11 | * 12 | *

13 | * 该接口只需要一个只读方法,这简化了对数据访问策略的支持。 14 | * 15 | * @author echo 16 | * @version 1.0 17 | * @date 19-4-20 13:30 18 | * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider 19 | * @see org.springframework.security.core.userdetails.UserDetailsService 20 | * @see UserDetails 21 | */ 22 | public interface SmsUserDetailsService { 23 | 24 | /** 25 | * 根据用户名找到用户。在实际实现中,只用查询手机号即可,对于手机号的验证需要对 {@link cn.edu.gzmu.authserver.validate.sms} 进行具体公职 26 | * 如何找寻用户具体取决于实现实例的配置方式。 27 | *

28 | * 在这种情况下,返回的 UserDetails 对象的用户名是可能是不是用户 sms 设备号。 29 | * 30 | * @param sms sms 设备号 31 | * @return 用户认证信息 32 | * @throws ResourceNotFoundException 用户名未找到 33 | */ 34 | UserDetails loadUserBySms(String sms) throws ResourceNotFoundException; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/Semester.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.ToString; 7 | import lombok.experimental.Accessors; 8 | import org.hibernate.annotations.Where; 9 | 10 | import javax.persistence.Entity; 11 | import javax.persistence.Table; 12 | import javax.persistence.Transient; 13 | import java.io.Serializable; 14 | 15 | /** 16 | * semester 17 | * 18 | * @author echo 19 | * @version 1.0 20 | * @date 2019-5-27 10:59:08 21 | */ 22 | @Data 23 | @ToString(callSuper = true) 24 | @Table(name = "semester") 25 | @Entity(name = "semester") 26 | @Where(clause = "is_enable = true") 27 | @EqualsAndHashCode(callSuper = true) 28 | @Accessors(chain = true) 29 | public class Semester extends BaseEntity implements Serializable { 30 | 31 | /** 32 | * 学校编号 33 | */ 34 | @javax.validation.constraints.NotNull(message = "schoolId 学校编号 为必填项") 35 | private Long schoolId; 36 | 37 | /** 38 | * 起始日期 39 | */ 40 | private java.time.LocalDate startDate; 41 | 42 | /** 43 | * 结束日期 44 | */ 45 | private java.time.LocalDate endDate; 46 | 47 | /** 48 | * 学校 49 | */ 50 | @Transient 51 | private SysData school; 52 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/email/EmailUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.email; 2 | 3 | import cn.edu.gzmu.authserver.model.exception.ResourceNotFoundException; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | 6 | /** 7 | * 加载用户 sms 特定数据的核心接口。 8 | *

他在整个框架中将会作为用户数据加载。

9 | * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider 10 | * DaoAuthenticationProvider}. 11 | * 12 | *

13 | * 该接口只需要一个只读方法,这简化了对数据访问策略的支持。 14 | * 15 | * @author EchoCow 16 | * @version 1.0 17 | * @date 19-4-20 13:30 18 | * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider 19 | * @see org.springframework.security.core.userdetails.UserDetailsService 20 | * @see UserDetails 21 | */ 22 | public interface EmailUserDetailsService { 23 | 24 | 25 | /** 26 | * 根据用户名找到用户。在实际实现中,只用查询手机号即可,对于手机号的验证需要对 {@link cn.edu.gzmu.authserver.validate.email} 进行具体公职 27 | * 如何找寻用户具体取决于实现实例的配置方式。 28 | *

29 | * 在这种情况下,返回的 UserDetails 对象的用户名是可能是不是用户 email 设备号。 30 | * 31 | * @param email email 设备号 32 | * @return 用户认证信息 33 | * @throws ResourceNotFoundException 用户名未找到 34 | */ 35 | UserDetails loadUserByEmail(String email) throws ResourceNotFoundException; 36 | } 37 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: '授权服务器', 3 | description: '文档说明', 4 | port: 1111, 5 | base: '/authorization-server/', 6 | head: [ 7 | ['link', {rel: 'apple-touch-icon', href: '/apple-touch-icon.png'}], 8 | ['link', {rel: 'icon', href: '/favicon-32x32.png'}], 9 | ['link', {rel: 'manifest', href: '/manifest.json'}], 10 | ['meta', {name: 'theme-color', content: '#ffffff'}] 11 | ], 12 | plugins: { 13 | '@vuepress/pwa': { 14 | serviceWorker: true, 15 | updatePopup: { 16 | message: '内容已经更新啦~', 17 | buttonText: '跟我走!' 18 | } 19 | }, 20 | '@vuepress/back-to-top': true 21 | }, 22 | themeConfig: { 23 | repo: 'gzmuSoft/authorization-server', 24 | editLinkText: '帮助我们改进此文档!', 25 | editLinks: true, 26 | docsDir: 'docs', 27 | nav: [ 28 | {text: '主页', link: '/'}, 29 | {text: '操作手册', link: '/description/'}, 30 | {text: '技术架构', link: '/technology/'} 31 | ], 32 | sidebar: { 33 | '/description/': [ 34 | ['', '文档说明'], 35 | ['/description/OAuth2', 'OAuth2'] 36 | ] 37 | }, 38 | sidebarDepth: 2, 39 | lastUpdated: 'Last Updated' 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/SysData.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.ToString; 7 | import lombok.experimental.Accessors; 8 | import org.hibernate.annotations.Where; 9 | 10 | import javax.persistence.Entity; 11 | import javax.persistence.Table; 12 | import javax.persistence.Transient; 13 | import javax.validation.constraints.Size; 14 | import java.io.Serializable; 15 | 16 | /** 17 | * @author echo 18 | * @version 1.0.0 19 | * @date 19-6-11 下午5:29 20 | */ 21 | @Data 22 | @ToString(callSuper = true) 23 | @Table(name = "sys_data") 24 | @Entity(name = "sys_data") 25 | @Where(clause = "is_enable = true") 26 | @EqualsAndHashCode(callSuper = true) 27 | @Accessors(chain = true) 28 | public class SysData extends BaseEntity implements Serializable { 29 | 30 | /** 31 | * 0,代表无上级,即:学校 32 | */ 33 | private java.lang.Long parentId; 34 | 35 | /** 36 | * 简介 37 | */ 38 | @Size(max = 2048, message = "brief 不能大于 2048 位") 39 | private java.lang.String brief; 40 | 41 | /** 42 | * 0:学校,1:学院,2:系部,3:专业,4:班级,5:性别,6:学历,7:学位,8:教师毕业专业,9:民族,10:研究方向,11:职称 43 | */ 44 | private java.lang.Integer type; 45 | 46 | @Transient 47 | private SysData parent; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 发送了点小错误 5 | 6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

404 找不到页面

17 |

~~~

18 | 点击返回 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 |
29 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/.vuepress/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "贵州民族大学授权服务器", 3 | "short_name": "授权服务器", 4 | "description": "授权服务器说明文档", 5 | "theme_color": "#2196f3", 6 | "background_color": "#2196f3", 7 | "display": "standalone", 8 | "start_url": "index.html", 9 | "icons": [ 10 | { 11 | "src": "images/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "images/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "images/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "images/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "images/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "images/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "images/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "images/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ], 51 | "splash_pages": null 52 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeConfig.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import cn.edu.gzmu.authserver.model.properties.EmailProperties; 4 | import cn.edu.gzmu.authserver.model.properties.SmsConfig; 5 | import cn.edu.gzmu.authserver.util.EmailUtils; 6 | import cn.edu.gzmu.authserver.util.SubMailUtils; 7 | import cn.edu.gzmu.authserver.validate.email.EmailCodeSender; 8 | import cn.edu.gzmu.authserver.validate.sms.SmsCodeSender; 9 | import lombok.NonNull; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | /** 15 | * @author echo 16 | * @version 1.0 17 | * @date 19-4-14 14:17 18 | */ 19 | @Configuration 20 | @RequiredArgsConstructor 21 | public class ValidateCodeConfig { 22 | 23 | private final @NonNull SubMailUtils subMailUtils; 24 | private final @NonNull SmsConfig smsConfig; 25 | private final @NonNull EmailUtils emailUtils; 26 | private final @NonNull EmailProperties emailProperties; 27 | 28 | @Bean 29 | public ValidateCodeSender smsCodeSender() { 30 | return new SmsCodeSender(subMailUtils, smsConfig); 31 | } 32 | 33 | @Bean 34 | public ValidateCodeSender mailCodeSender() { 35 | return new EmailCodeSender(emailUtils, emailProperties); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/StudentRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | 4 | import cn.edu.gzmu.authserver.base.BaseRepository; 5 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 6 | import cn.edu.gzmu.authserver.model.entity.Student; 7 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 8 | import org.springframework.data.rest.core.annotation.RestResource; 9 | 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | /** 14 | * Student Repository 15 | * 16 | * @author echo 17 | * @version 1.0 18 | * @date 2019-5-23 17:38:13 19 | */ 20 | @RepositoryRestResource(path = AuthConstant.STUDENT) 21 | public interface StudentRepository extends BaseRepository { 22 | 23 | /** 24 | * 通过用户 id 查询学生 25 | * 26 | * @param userId 用户 id 27 | * @return 结果 28 | */ 29 | @RestResource(path = "byUserId") 30 | Optional findFirstByUserId(Long userId); 31 | 32 | /** 33 | * 查询在指定 user ids 的学生信息 34 | * 35 | * @param userIds 用户 ids 36 | * @return 学生信息 37 | */ 38 | @RestResource(path = "byUserIds") 39 | List findAllByUserIdIn(List userIds); 40 | 41 | /** 42 | * 通过班级id查询学生 43 | * 44 | * @param classId 班级id 45 | * @return 学生 46 | */ 47 | List findAllByClassesId(Long classId); 48 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/constant/SecurityConstants.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.constant; 2 | 3 | /** 4 | * @author echo 5 | * @version 1.0 6 | * @date 19-4-14 16:17 7 | */ 8 | public interface SecurityConstants { 9 | 10 | /** 11 | * 理验证码的 url 前缀 12 | */ 13 | String VALIDATE_CODE_URL_PREFIX = "/code"; 14 | /** 15 | * 手机验证码登录请求 url 16 | */ 17 | String LOGIN_PROCESSING_URL_SMS = "/oauth/sms"; 18 | /** 19 | * 需要验证短信验证码的注册请求 url 20 | */ 21 | String REGISTER_PROCESSING_URL_EMAIL = "/auth/register"; 22 | /** 23 | * 发送短信验证码或验证短信验证码时,手机的参数名称 24 | */ 25 | String GRANT_TYPE_SMS = "sms"; 26 | /** 27 | * 发送邮箱验证码或验证短信验证码时,邮箱的参数名称 28 | */ 29 | String GRANT_TYPE_EMAIL = "email"; 30 | /** 31 | * 公共角色 32 | */ 33 | String ROLE_PUBLIC = "ROLE_PUBLIC"; 34 | /** 35 | * 管理员角色 36 | */ 37 | String ROLE_ADMIN = "ROLE_ADMIN"; 38 | /** 39 | * 未授权角色 40 | */ 41 | String ROLE_NO_AUTH = "ROLE_NO_AUTH"; 42 | /** 43 | * 教师角色 44 | */ 45 | String ROLE_TEACHER = "ROLE_TEACHER"; 46 | /** 47 | * 学生角色 48 | */ 49 | String ROLE_STUDENT = "ROLE_STUDENT"; 50 | /** 51 | * 未登录 52 | */ 53 | String ROLE_NO_LOGIN = "ROLE_NO_LOGIN"; 54 | /** 55 | * 匿名 56 | */ 57 | String ROLE_ANONYMOUS = "ROLE_ANONYMOUS"; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/sms/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 新增的 sms 授权方式 3 | * 由于 spring security oauth2 并没有提供基于短信的认证方式,我们必须自己实现。 4 | *

5 | * 对于所有的请求,我们只会拦截 /oauth/sms 请求 6 | * 通过 {@link cn.edu.gzmu.authserver.auth.sms.SmsAuthenticationFilter} 过滤器进行拦截 7 | * 获取到需要的参数后,将他封装为我们自己的 {@link org.springframework.security.authentication.AbstractAuthenticationToken} 8 | * 实现类 {@link cn.edu.gzmu.authserver.auth.sms.SmsAuthenticationToken} 9 | * 将其交给 {@link org.springframework.security.authentication.AuthenticationManager} 10 | * 他将会检索所有已经实现 {@link org.springframework.security.authentication.AuthenticationProvider} 的子类 11 | *

12 | * 我们需要提供一个他的子类 {@link cn.edu.gzmu.authserver.auth.sms.SmsAuthenticationProvider} 13 | * 并实现接口中所有的方法,使用它来校验授权与用户信息。 14 | * 依然会去请求数据库,我通过实现自己写的 {@link cn.edu.gzmu.authserver.auth.sms.SmsUserDetailsService} 接口 15 | * 让 {@link cn.edu.gzmu.authserver.service.impl.UserDetailsServiceImpl} 实现其所有方法 16 | * 通过它就可以获取到用户信息。 17 | *

18 | * 最后我们需要将他交给登录成功处理器 {@link cn.edu.gzmu.authserver.auth.sms.SmsSuccessHandler} 19 | * 进行构建完整的 token 信息 20 | * 21 | * @author EchoCow 22 | * @version 1.0 23 | * @date 19-4-20 15:27 24 | * @date 19-7-31 10:12 25 | * @deprecated 过于复杂的配置方式,标记过时 26 | * 参见 Spring Security Oauth2 从零到一完整实践(五) 自定义授权模式(手机、邮箱等) 27 | */ 28 | package cn.edu.gzmu.authserver.auth.sms; -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/SysRes.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.ToString; 7 | import lombok.experimental.Accessors; 8 | import org.hibernate.annotations.Where; 9 | 10 | import javax.persistence.Entity; 11 | import javax.persistence.Table; 12 | import javax.validation.constraints.Size; 13 | import java.io.Serializable; 14 | 15 | /** 16 | * @author echo 17 | * @version 1.0.0 18 | * @date 19-6-11 下午5:27 19 | */ 20 | @Data 21 | @Table(name = "sys_res") 22 | @Entity(name = "sys_res") 23 | @Where(clause = "is_enable = true") 24 | @ToString(callSuper = true) 25 | @EqualsAndHashCode(callSuper = true) 26 | @Accessors(chain = true) 27 | public class SysRes extends BaseEntity implements Serializable { 28 | 29 | /** 30 | * 路径 31 | */ 32 | @Size(max = 55, message = "url 不能大于 55 位") 33 | private java.lang.String url; 34 | 35 | /** 36 | * 描述 37 | */ 38 | @Size(max = 55, message = "describe 不能大于 55 位") 39 | private java.lang.String describe; 40 | 41 | /** 42 | * 方法 43 | */ 44 | @Size(max = 55, message = "method 不能大于 55 位") 45 | private java.lang.String method; 46 | 47 | /** 48 | * 请求资源的权限域 49 | */ 50 | @Size(max = 55, message = "scopes 不能大于 55 位") 51 | private java.lang.String scopes; 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/constant/EntityType.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.constant; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public enum EntityType { 7 | /** 8 | * 管理员 9 | */ 10 | ADMIN("ROLE_ADMIN"), 11 | /** 12 | * 教师 13 | */ 14 | TEACHER("ROLE_TEACHER"), 15 | /** 16 | * 学生 17 | */ 18 | STUDENT("ROLE_STUDENT"), 19 | /** 20 | * 系部管理员 21 | */ 22 | DEPARTMENT_ADMIN("ROLE_DEPARTMENT_ADMIN"); 23 | 24 | private String value; 25 | 26 | @Contract(pure = true) 27 | EntityType(String name) { 28 | value = name; 29 | } 30 | 31 | @Contract(pure = true) 32 | public String value() { 33 | return value; 34 | } 35 | 36 | @Contract(pure = true) 37 | public static boolean isStudent(@NotNull String name) { 38 | return name.contains(STUDENT.value); 39 | } 40 | 41 | @Contract(pure = true) 42 | public static boolean isTeacher(@NotNull String name) { 43 | return name.contains(TEACHER.value); 44 | } 45 | 46 | @Contract(pure = true) 47 | public static boolean isAdmin(@NotNull String name) { 48 | return name.contains(ADMIN.value); 49 | } 50 | 51 | @Contract(pure = true) 52 | public static boolean isDepartmentAdmin(@NotNull String name) { 53 | return name.contains(DEPARTMENT_ADMIN.value); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import cn.edu.gzmu.authserver.filter.ApiNumberFilter; 4 | import lombok.NonNull; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.security.config.annotation.SecurityConfigurerAdapter; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.web.DefaultSecurityFilterChain; 9 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * 验证码安全配置 14 | *

15 | * 添加过滤器 16 | * 17 | * @author EchoCow 18 | * @version 1.0 19 | * @date 19-4-14 16:19 20 | */ 21 | @Component 22 | @RequiredArgsConstructor 23 | public class ValidateCodeSecurityConfig 24 | extends SecurityConfigurerAdapter { 25 | 26 | private final @NonNull ValidateCodeGrantTypeFilter validateCodeGrantTypeFilter; 27 | private final @NonNull ApiNumberFilter apiNumberFilter; 28 | 29 | @Override 30 | public void configure(HttpSecurity http) { 31 | http 32 | .addFilterBefore(validateCodeGrantTypeFilter, UsernamePasswordAuthenticationFilter.class) 33 | .addFilterBefore(apiNumberFilter, UsernamePasswordAuthenticationFilter.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/SysRole.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.ToString; 7 | import lombok.experimental.Accessors; 8 | import org.hibernate.annotations.Where; 9 | import org.springframework.security.core.GrantedAuthority; 10 | 11 | import javax.persistence.*; 12 | import javax.validation.constraints.Size; 13 | import java.io.Serializable; 14 | 15 | /** 16 | * @author echo 17 | * @version 1.0.0 18 | * @date 19-6-11 下午5:27 19 | */ 20 | @Data 21 | @Table(name = "sys_role") 22 | @Entity(name = "sys_role") 23 | @Where(clause = "is_enable = true") 24 | @ToString(callSuper = true) 25 | @EqualsAndHashCode(callSuper = true) 26 | @Accessors(chain = true) 27 | public class SysRole extends BaseEntity implements GrantedAuthority, Serializable { 28 | 29 | /** 30 | * 描述 31 | */ 32 | @Size(max = 128, message = "des 不能大于 128 位") 33 | private java.lang.String des; 34 | 35 | /** 36 | * 图标 37 | */ 38 | @Size(max = 55, message = "iconCls 不能大于 55 位") 39 | private java.lang.String iconCls; 40 | 41 | /** 42 | * 父角色编号 43 | */ 44 | @javax.validation.constraints.NotNull(message = "parentId 父角色编号 为必填项") 45 | private java.lang.Long parentId; 46 | 47 | @Override 48 | public String getAuthority() { 49 | return getName(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/cn/edu/gzmu/authserver/util/EmailUtilsTest.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit.jupiter.SpringExtension; 9 | 10 | import java.util.HashMap; 11 | import java.util.concurrent.Future; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | 15 | /** 16 | * @author Japoul 17 | * @author echo 18 | * @version 1.0 19 | * @date 2019-05-21 16:45 20 | */ 21 | @SpringBootTest 22 | @ExtendWith(SpringExtension.class) 23 | @DisplayName(":email: 邮件测试") 24 | class EmailUtilsTest { 25 | 26 | @Autowired 27 | private EmailUtils emailUtils; 28 | 29 | @Test 30 | @DisplayName(":email: 异步的发送一封邮件") 31 | void application() { 32 | HashMap values = new HashMap<>(); 33 | values.put("code", RandomCode.random(6, false)); 34 | values.put("time", 10); 35 | String toEmail = "lizhongyue248@163.com"; 36 | String type = "注册"; 37 | String subject = "云课程注册邮件"; 38 | String template = "registerTemplate.html"; 39 | Future res = emailUtils.sendTemplateMail(toEmail, type, subject, template, values); 40 | assertTrue(res.isDone()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/util/RandomCode.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * 随机生成 验证码 7 | * 8 | * @author echo 9 | * @version 1.0 10 | * @date 19-5-20 15:45 11 | */ 12 | public class RandomCode { 13 | private static final char[] MORE_CHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); 14 | private static final Random RANDOM = new Random(); 15 | // 今天依旧是一个人写代码的日子,只是一个普通的周一,所以晚上我们去和代码约会吧。 16 | 17 | /** 18 | * 随机生成验证码 19 | * 20 | * @param length 长度 21 | * @param end 结束长度 22 | * @return 结果 23 | */ 24 | private static String random(Integer length, Integer end) { 25 | StringBuilder result = new StringBuilder(); 26 | for (int i = 0; i < length; i++) { 27 | result.append(MORE_CHAR[RANDOM.nextInt(end)]); 28 | } 29 | return result.toString(); 30 | } 31 | 32 | /** 33 | * 随机生成验证码 34 | * 35 | * @param length 长度 36 | * @param onlyNum 是否只要数字 37 | * @return 结果 38 | */ 39 | public static String random(Integer length, Boolean onlyNum) { 40 | return onlyNum ? random(length, 10) : random(length, MORE_CHAR.length); 41 | } 42 | 43 | /** 44 | * 随机验证码 45 | * 46 | * @param length 长度 47 | * @return 结果 48 | */ 49 | public static String random(Integer length) { 50 | return random(length, false); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCode.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * @author echo 10 | * @version 1.0 11 | * @date 19-4-14 11:23 12 | */ 13 | @Data 14 | public class ValidateCode implements Serializable { 15 | /** 16 | * 验证码 17 | */ 18 | private String code; 19 | /** 20 | * 过期时间 21 | */ 22 | private LocalDateTime expireTime; 23 | /** 24 | * 有效期 25 | */ 26 | private int expireIn; 27 | /** 28 | * 构造 29 | */ 30 | public ValidateCode() { 31 | } 32 | 33 | /** 34 | * 几秒后过期 35 | * 36 | * @param code 验证码 37 | * @param expireIn 过期时间,单位/秒 38 | */ 39 | public ValidateCode(String code, int expireIn) { 40 | this.code = code; 41 | this.expireTime = LocalDateTime.now().plusSeconds(expireIn); 42 | this.expireIn = expireIn; 43 | } 44 | 45 | /** 46 | * 构造 47 | * 48 | * @param code 验证码 49 | * @param expireTime 过期时间 50 | */ 51 | public ValidateCode(String code, LocalDateTime expireTime) { 52 | this.code = code; 53 | this.expireTime = expireTime; 54 | } 55 | 56 | /** 57 | * 是否已经过期 58 | * 59 | * @return 结果 60 | */ 61 | public boolean expired() { 62 | return LocalDateTime.now().isAfter(expireTime); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import lombok.NonNull; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import org.springframework.web.context.request.ServletWebRequest; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | /** 15 | * 动态获取验证码 16 | * 17 | * @author echo 18 | * @version 1.0 19 | * @date 19-4-14 13:59 20 | */ 21 | @RestController 22 | @RequiredArgsConstructor 23 | public class ValidateCodeController { 24 | 25 | private final @NonNull ValidateCodeProcessorHolder validateCodeProcessorHolder; 26 | 27 | /** 28 | * 通过 type 进行查询到对对应的处理器 29 | * 同时创建验证码 30 | * 31 | * @param request 请求 32 | * @param response 响应 33 | * @param type 验证码类型 34 | * @throws Exception 异常 35 | */ 36 | @GetMapping(SecurityConstants.VALIDATE_CODE_URL_PREFIX + "/{type}") 37 | public void creatCode(HttpServletRequest request, HttpServletResponse response, 38 | @PathVariable String type) throws Exception { 39 | validateCodeProcessorHolder.findValidateCodeProcessor(type) 40 | .create(new ServletWebRequest(request, response)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/constant/SysDataEnum.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.constant; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | /** 6 | * 数据类型枚举 7 | * 8 | * @author echo 9 | * @version 1.0 10 | * @date 19-5-21 20:46 11 | */ 12 | public enum SysDataEnum { 13 | /** 14 | * 学校 15 | */ 16 | SCHOOL(1), 17 | /** 18 | * 学院 19 | */ 20 | COLLEGE(2), 21 | /** 22 | * 系部 23 | */ 24 | DEPARTMENTS(3), 25 | /** 26 | * 专业 27 | */ 28 | PROFESSION(4), 29 | /** 30 | * 班级 31 | */ 32 | CLASSES(5), 33 | /** 34 | * 学历 35 | */ 36 | EDUCATION(6), 37 | /** 38 | * 学位 39 | */ 40 | DEGREE(7), 41 | /** 42 | * 教师毕业专业 43 | */ 44 | TEACHER_GRADUATION_MAJOR(8), 45 | /** 46 | * 民族 47 | */ 48 | NATION(9), 49 | /** 50 | * 研究方向 51 | */ 52 | RESEARCH_DIRECTION(10), 53 | /** 54 | * 职称 55 | */ 56 | JOB_TITLE(11); 57 | 58 | private int type; 59 | 60 | SysDataEnum(int type) { 61 | this.type = type; 62 | } 63 | 64 | public static boolean match(@NotNull SysDataEnum sysDataEnum, @NotNull int type) { 65 | return sysDataEnum.getType() == type; 66 | } 67 | 68 | public int getType() { 69 | return type; 70 | } 71 | 72 | public void setType(int type) { 73 | this.type = type; 74 | } 75 | 76 | public boolean match(int type) { 77 | return match(this, type); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/ClientRegistrationService.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.ClientDetails; 4 | import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; 5 | import org.springframework.security.oauth2.provider.NoSuchClientException; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 自定义客户端服务. 11 | * 12 | * @author EchoCow 13 | * @date 2019/12/23 下午11:20 14 | */ 15 | public interface ClientRegistrationService { 16 | 17 | /** 18 | * 添加客户端 19 | * 20 | * @param clientDetails 客户端 21 | * @throws ClientAlreadyExistsException 客户端已存在异常 22 | * @throws NoSuchClientException 找不到客户端异常 23 | */ 24 | void saveOrUpdateClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException, NoSuchClientException; 25 | 26 | /** 27 | * 更新客户端蜜意奥 28 | * 29 | * @param clientId 客户端 ID 30 | * @param clientSecret 客户端 密钥 31 | * @throws NoSuchClientException 未找到异常 32 | */ 33 | void updateClientSecret(String clientId, String clientSecret) throws NoSuchClientException; 34 | 35 | /** 36 | * 移除客户端 37 | * 38 | * @param clientId 客户端 ID 39 | * @throws NoSuchClientException 未找到异常 40 | */ 41 | void removeClientDetails(String clientId) throws NoSuchClientException; 42 | 43 | /** 44 | * 获取所有客户端 45 | * 46 | * @return 客户端 47 | */ 48 | List listClientDetails(); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8888 3 | 4 | spring: 5 | profiles: 6 | active: dev 7 | data: 8 | rest: 9 | base-path: /api 10 | default-page-size: 10 11 | sort-param-name: id 12 | main: 13 | allow-bean-definition-overriding: true 14 | jpa: 15 | open-in-view: true 16 | show-sql: true 17 | database-platform: org.hibernate.dialect.PostgreSQL9Dialect 18 | mail: 19 | host: smtp.exmail.qq.com 20 | port: 465 21 | username: lessonCloud@japoul.cn 22 | password: lessonC2019 23 | properties: 24 | mail: 25 | debug: false 26 | smtp: 27 | auth: true 28 | starttls: 29 | required: true 30 | socketFactory: 31 | class: javax.net.ssl.SSLSocketFactory 32 | test-connection: false 33 | application: 34 | name: gzmu-auth 35 | session: 36 | redis: 37 | namespace: gzmu-auth 38 | store-type: redis 39 | management: 40 | endpoints: 41 | web: 42 | exposure: 43 | include: "*" 44 | application: 45 | security: 46 | access-token-validity-seconds: 60 47 | sms: 48 | dev: true 49 | app-id: 35223 50 | app-key: e7eede1811867421882df861a20ed26e 51 | action-template: RhXa91 52 | email: 53 | dev: true 54 | logging: 55 | level: 56 | org.springframework.boot.actuate.endpoint.EndpointId: error 57 | org.springframework.context.support.[PostProcessorRegistrationDelegate$BeanPostProcessorChecker]: warn 58 | file: 59 | name: logs/auth-server.log 60 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/config/Oauth2ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.config; 2 | 3 | 4 | import cn.edu.gzmu.authserver.validate.ValidateCodeSecurityConfig; 5 | import lombok.NonNull; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 10 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 11 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 12 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 13 | 14 | /** 15 | * @author EchoCow 16 | * @version 1.0.0 17 | * @date 19-6-11 下午5:06 18 | */ 19 | @Configuration 20 | @RequiredArgsConstructor 21 | @EnableResourceServer 22 | public class Oauth2ResourceServerConfig extends ResourceServerConfigurerAdapter { 23 | private final @NonNull JwtTokenStore jwtTokenStore; 24 | 25 | @Override 26 | public void configure(ResourceServerSecurityConfigurer resources) { 27 | resources.tokenStore(jwtTokenStore); 28 | } 29 | 30 | @Override 31 | public void configure(HttpSecurity http) throws Exception { 32 | 33 | http 34 | .authorizeRequests() 35 | .anyRequest() 36 | .authenticated(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/handler/AccessDeniedExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.handler; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.security.access.AccessDeniedException; 8 | import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; 9 | import org.springframework.security.web.access.AccessDeniedHandler; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | 15 | /** 16 | * @author EchoCow 17 | * @date 2019/8/4 下午10:14 18 | */ 19 | @Slf4j 20 | @RequiredArgsConstructor 21 | public class AccessDeniedExceptionHandler extends OAuth2AccessDeniedHandler implements AccessDeniedHandler { 22 | 23 | @Override 24 | public void handle(HttpServletRequest request, HttpServletResponse response, 25 | AccessDeniedException accessDeniedException) throws IOException { 26 | JSONObject result = new JSONObject(); 27 | result.put("error", accessDeniedException.getClass().getSimpleName()); 28 | result.put("error_description", accessDeniedException.getLocalizedMessage()); 29 | log.debug("Access Denied Failed!"); 30 | response.setContentType("application/json;charset=utf-8"); 31 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 32 | response.getWriter().write(result.toJSONString()); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/OauthController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.servlet.ModelAndView; 9 | import org.springframework.web.servlet.view.RedirectView; 10 | 11 | import java.security.Principal; 12 | import java.util.Objects; 13 | 14 | /** 15 | * @author EchoCow 16 | * @date 19-7-14 下午3:21 17 | */ 18 | @Controller 19 | @RequestMapping("/oauth") 20 | @RequiredArgsConstructor 21 | public class OauthController { 22 | 23 | @GetMapping("/login") 24 | public String loginView() { 25 | return "login"; 26 | } 27 | 28 | @GetMapping("/logout") 29 | public ModelAndView logoutView( 30 | @RequestParam("redirect_url") String redirectUrl, 31 | @RequestParam(name = "client_id", required = false) String clientId, 32 | Principal principal) { 33 | if (Objects.isNull(principal)) { 34 | return new ModelAndView(new RedirectView(redirectUrl)); 35 | } 36 | ModelAndView view = new ModelAndView(); 37 | view.setViewName("logout"); 38 | view.addObject("user", principal.getName()); 39 | view.addObject("redirectUrl", redirectUrl); 40 | view.addObject("clientId", clientId); 41 | return view; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/cn/edu/gzmu/authserver/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; 8 | import org.springframework.test.context.junit.jupiter.SpringExtension; 9 | import org.springframework.web.method.HandlerMethod; 10 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 11 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 12 | 13 | import java.util.Map; 14 | 15 | 16 | @ExtendWith(SpringExtension.class) 17 | @SpringBootTest 18 | class ApplicationTests { 19 | 20 | @Autowired 21 | private RequestMappingHandlerMapping requestMappingHandlerMapping; 22 | 23 | @Autowired 24 | private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping; 25 | 26 | @Test 27 | void contextLoads() { 28 | Map handlerMethods1 = frameworkEndpointHandlerMapping.getHandlerMethods(); 29 | handlerMethods1.forEach((requestMappingInfo, handlerMethod) -> System.out.println(requestMappingInfo.toString() + handlerMethod)); 30 | Map handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); 31 | handlerMethods.forEach((requestMappingInfo, handlerMethod) -> System.out.println(requestMappingInfo.toString() + handlerMethod)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeProcessorHolder.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 4 | import cn.edu.gzmu.authserver.model.exception.ValidateCodeException; 5 | import lombok.NonNull; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * 验证码处理分发 13 | * 14 | * 通过传递过来的类型,从已经依赖注入容器中搜寻符合名称的组件。 15 | * 直接通过名称获取对应的 {@link ValidateCodeProcessor} 实现类 16 | * 17 | * @author echo 18 | * @version 1.0 19 | * @date 19-4-14 16:22 20 | */ 21 | @Component 22 | @RequiredArgsConstructor 23 | public class ValidateCodeProcessorHolder { 24 | private final @NonNull Map validateCodeProcessors; 25 | 26 | /** 27 | * 通过验证码类型查找 28 | * 29 | * @param type 验证码类型 30 | * @return 验证码处理器 31 | */ 32 | ValidateCodeProcessor findValidateCodeProcessor(ValidateCodeType type) { 33 | return findValidateCodeProcessor(type.getParamNameOnValidate().toLowerCase()); 34 | } 35 | 36 | /** 37 | * 通过验证码名称查找 38 | * 39 | * @param type 名称 40 | * @return 验证码处理器 41 | */ 42 | ValidateCodeProcessor findValidateCodeProcessor(String type) { 43 | String name = type.toLowerCase() + ValidateCodeProcessor.class.getSimpleName(); 44 | ValidateCodeProcessor processor = validateCodeProcessors.get(name); 45 | if (processor == null){ 46 | throw new ValidateCodeException("验证码处理器" + name + "不存在"); 47 | } 48 | return processor; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/email/EmailCodeSender.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.email; 2 | 3 | import cn.edu.gzmu.authserver.model.properties.EmailProperties; 4 | import cn.edu.gzmu.authserver.util.EmailUtils; 5 | import cn.edu.gzmu.authserver.validate.ValidateCode; 6 | import cn.edu.gzmu.authserver.validate.ValidateCodeSender; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.time.Duration; 12 | import java.util.HashMap; 13 | 14 | /** 15 | * @author echo 16 | * @version 1.0 17 | * @date 19-5-21 23:52 18 | */ 19 | @Slf4j 20 | @RequiredArgsConstructor 21 | public class EmailCodeSender implements ValidateCodeSender { 22 | 23 | private final @NonNull EmailUtils emailUtils; 24 | private final @NonNull EmailProperties emailProperties; 25 | 26 | @Override 27 | public void send(String receive, ValidateCode code) { 28 | HashMap variables = new HashMap<>(1); 29 | variables.put("code", code.getCode()); 30 | variables.put("time", Duration.ofSeconds(code.getExpireIn()).toMinutes()); 31 | if (emailProperties.getDev()) { 32 | log.info("向 {} 发送邮箱登录验证码 {},有效期 {} 分钟。", receive, code.getCode(), 33 | Duration.ofSeconds(code.getExpireIn()).toMinutes()); 34 | } else { 35 | log.debug("向 {} 发送邮箱登录验证码 {},有效期 {} 分钟。", receive, code.getCode(), 36 | Duration.ofSeconds(code.getExpireIn()).toMinutes()); 37 | emailUtils.sendTemplateMail(receive, "登录", 38 | "[贵州民族大学]欢迎登录", "registerTemplate.html", variables); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/handler/AuthLogoutSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.handler; 2 | 3 | import cn.edu.gzmu.authserver.auth.Oauth2Helper; 4 | import lombok.AllArgsConstructor; 5 | import lombok.NonNull; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; 11 | import org.springframework.stereotype.Component; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | /** 18 | * 退出登录处理器. 19 | * 20 | * @author EchoCow 21 | * @date 2020/1/2 下午3:11 22 | */ 23 | @Slf4j 24 | @Component 25 | @AllArgsConstructor 26 | public class AuthLogoutSuccessHandler implements LogoutSuccessHandler { 27 | private final @NonNull Oauth2Helper oauth2Helper; 28 | 29 | @Override 30 | public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { 31 | String redirectUrl = request.getParameter("redirectUrl"); 32 | if (StringUtils.isBlank(redirectUrl)) { 33 | redirectUrl = "/oauth/login"; 34 | } 35 | String clientId = request.getParameter("clientId"); 36 | if (StringUtils.isNoneBlank(clientId)) { 37 | oauth2Helper.safeLogout(clientId, authentication); 38 | } 39 | response.setStatus(HttpStatus.FOUND.value()); 40 | response.sendRedirect(redirectUrl); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/resources/templates/logout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 确认退出吗? 5 | 6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 确认退出当前应用吗? 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 确认退出 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/impl/SysRoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service.impl; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import cn.edu.gzmu.authserver.model.entity.SysRole; 5 | import cn.edu.gzmu.authserver.repository.SysRoleRepository; 6 | import cn.edu.gzmu.authserver.service.SysRoleService; 7 | import lombok.AllArgsConstructor; 8 | import lombok.NonNull; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * . 17 | * 18 | * @author EchoCow 19 | * @date 2020/1/1 下午10:22 20 | */ 21 | @Service 22 | @AllArgsConstructor 23 | public class SysRoleServiceImpl implements SysRoleService { 24 | private @NonNull SysRoleRepository sysRoleRepository; 25 | 26 | @Override 27 | public Set findAllByRoles(Set roles) { 28 | List roleIds = roles.stream() 29 | .map(BaseEntity::getId) 30 | .collect(Collectors.toList()); 31 | roles.addAll(sysRoleRepository.searchAllRoleByIds(roleIds)); 32 | return roles; 33 | } 34 | 35 | @Override 36 | public Set findAllByUser(Long userId) { 37 | Set sysRoles = sysRoleRepository.searchAllByUserId(userId); 38 | List roleIds = sysRoles.stream() 39 | .map(BaseEntity::getId) 40 | .collect(Collectors.toList()); 41 | return sysRoleRepository.searchAllRoleByIds(roleIds); 42 | } 43 | 44 | @Override 45 | public Set findAllByRes(Long resId) { 46 | return sysRoleRepository.searchAllByResId(resId); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 提供验证码类型的所有服务,提供一套完整的发送、验证服务。 3 | *

4 | * 获取验证码: 5 | * 对于 sms 服务,将会自动匹配 /code/sms 请求,通过 {@link cn.edu.gzmu.authserver.validate.ValidateCodeProcessorHolder} 6 | * 获取对应的验证码处理器,处理其继承于 {@link cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeProcessor} 7 | * 提供了一套默认完整的生成、保存操作,具体查看其注释。 8 | * 遵循单一职责原则,每个接口只提供一个方法,因此对于每个验证码应该分别有一下接口的实现类: 9 | *

10 | * {@link cn.edu.gzmu.authserver.validate.ValidateCodeSender} 验证码发送方式 11 | * 对于不同的验证方式,发送验证码的方式也是不同的。 12 | *

13 | * {@link cn.edu.gzmu.authserver.validate.ValidateCodeGenerator} 验证码的生成规则 14 | * 对于不同的验证方式,验证码的生成规则也是不同的。 15 | *

16 | * {@link cn.edu.gzmu.authserver.validate.ValidateCodeRepository} 在 redis 中,验证码存储的键 17 | * 对于不同的验证方式,验证码存储的键应该有不同的键。可以通过继承抽象类 {@link cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeRepository} 18 | * 其提供了一套默认的处理方式,但是你必须实现 buildKey 方法进行自定义键生成策略。 19 | *

20 | * {@link cn.edu.gzmu.authserver.validate.ValidateCodeProcessor} 验证码处理器 21 | * 此方法由 抽象类 {@link cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeProcessor} 实现,不同的验证码 22 | * 的行为可以通过继承此类进行修改 23 | *

24 | * 验证验证码: 25 | * 默认的验证方式由抽象类 {@link cn.edu.gzmu.authserver.validate.impl.AbstractValidateCodeProcessor} 进行维护 26 | * 默认情况下主要通过 {@link cn.edu.gzmu.authserver.model.constant.ValidateCodeType} 的枚举作为键来获取请求体中对应的验证码 27 | * 验证方法为 validate ,子类可以自由覆盖并修改验证规则。 28 | * 29 | * 现在一有两种验证码实现,参见 {@link cn.edu.gzmu.authserver.validate.email} 和 {@link cn.edu.gzmu.authserver.validate.sms} 30 | * 31 | * @author EchoCow 32 | * @version 1.0 33 | * @date 19-4-20 15:05 34 | * @date 19-5-22 11:53 35 | * @date 19-7-31 10:54 标记过于复杂的实现为过时状态 36 | */ 37 | package cn.edu.gzmu.authserver.validate; -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/sms/SmsAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.sms; 2 | 3 | import cn.edu.gzmu.authserver.service.impl.UserDetailsServiceImpl; 4 | import lombok.Setter; 5 | import org.springframework.security.authentication.AuthenticationProvider; 6 | import org.springframework.security.authentication.InternalAuthenticationServiceException; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | 11 | /** 12 | * 授权提供者 13 | * 14 | * 15 | * @author EchoCow 16 | * @version 1.0 17 | * @date 19-4-14 15:54 18 | * @deprecated 过于复杂的配置方式,标记过时 19 | */ 20 | @Setter 21 | @Deprecated 22 | public class SmsAuthenticationProvider implements AuthenticationProvider { 23 | 24 | private UserDetailsServiceImpl userDetailsService; 25 | 26 | @Override 27 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 28 | SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication; 29 | UserDetails user = userDetailsService.loadUserBySms(authenticationToken.getPrincipal().toString()); 30 | if (user == null) { 31 | throw new InternalAuthenticationServiceException("无效认证"); 32 | } 33 | SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(user, user.getAuthorities()); 34 | authenticationResult.setDetails(authenticationToken.getDetails()); 35 | return authenticationResult; 36 | } 37 | 38 | @Override 39 | public boolean supports(Class authentication) { 40 | return SmsAuthenticationToken.class.isAssignableFrom(authentication); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/TeacherRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseRepository; 4 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 5 | import cn.edu.gzmu.authserver.model.entity.Teacher; 6 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 7 | import org.springframework.data.rest.core.annotation.RestResource; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | /** 13 | * Teacher Repository 14 | * 15 | * @author echo 16 | * @version 1.0 17 | * @date 2019-5-23 17:38:13 18 | */ 19 | @RepositoryRestResource(path = AuthConstant.TEACHER) 20 | public interface TeacherRepository extends BaseRepository { 21 | 22 | /** 23 | * 通过用户 id 查询教师 24 | * 25 | * @param userId 用户 id 26 | * @return 结果 27 | */ 28 | @RestResource(path = "byUserId") 29 | Optional findFirstByUserId(Long userId); 30 | 31 | /** 32 | * 查询在指定 user ids 的教师信息 33 | * 34 | * @param userIds 用户 ids 35 | * @return 学生信息 36 | */ 37 | @RestResource(path = "byUserIds") 38 | List findAllByUserIdIn(List userIds); 39 | 40 | /** 41 | * 通过 DepId 查询 42 | * 43 | * @param depId depId 44 | * @return 列表 45 | */ 46 | @RestResource(path = "byDepId") 47 | List findAllByDepId(Long depId); 48 | 49 | /** 50 | * 通过 SchoolId 查询 51 | * 52 | * @param schoolId schoolId 53 | * @return 列表 54 | */ 55 | @RestResource(path = "bySchoolId") 56 | List findAllBySchoolId(Long schoolId); 57 | 58 | /** 59 | * 通过 CollegeId 查询 60 | * 61 | * @param collegeId CollegeId 62 | * @return 列表 63 | */ 64 | @RestResource(path = "byCollegeId") 65 | List findAllByCollegeId(Long collegeId); 66 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/impl/AbstractValidateCodeRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.impl; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 4 | import cn.edu.gzmu.authserver.validate.ValidateCode; 5 | import cn.edu.gzmu.authserver.validate.ValidateCodeRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.web.context.request.ServletWebRequest; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * 验证码资源类 14 | * 15 | * @author EchoCow 16 | * @version 1.0 17 | * @date 19-4-16 22:18 18 | * @deprecated 过于复杂的实现,标记过时 19 | */ 20 | @Deprecated 21 | public abstract class AbstractValidateCodeRepository implements ValidateCodeRepository { 22 | 23 | @Autowired 24 | private RedisTemplate redisTemplate; 25 | 26 | @Override 27 | public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType type) { 28 | redisTemplate.opsForValue().set(buildKey(request, type), code, code.getExpireIn(), TimeUnit.SECONDS); 29 | } 30 | 31 | @Override 32 | public ValidateCode get(ServletWebRequest request, ValidateCodeType type) { 33 | return redisTemplate.opsForValue().get(buildKey(request, type)); 34 | } 35 | 36 | @Override 37 | public void remove(ServletWebRequest request, ValidateCodeType type) { 38 | redisTemplate.delete(buildKey(request, type)); 39 | } 40 | 41 | /** 42 | * 构建 redis 的 key 值,需要子类实现。 43 | *

44 | * 对于每种不同的验证码类型,都应该有不同的 key 的构建方式. 45 | * 请求中的不同的参数应该分别获取不同的属性 46 | * 47 | * @param request 需要构建的请求体, 48 | * @param type 验证码类型。 49 | * @return key 50 | */ 51 | protected abstract String buildKey(ServletWebRequest request, ValidateCodeType type); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/sms/SmsCodeSender.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.sms; 2 | 3 | import cn.edu.gzmu.authserver.model.properties.SmsConfig; 4 | import cn.edu.gzmu.authserver.util.SubMailUtils; 5 | import cn.edu.gzmu.authserver.validate.ValidateCode; 6 | import cn.edu.gzmu.authserver.validate.ValidateCodeConfig; 7 | import cn.edu.gzmu.authserver.validate.ValidateCodeSender; 8 | import com.alibaba.fastjson.JSONObject; 9 | import lombok.NonNull; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.time.Duration; 14 | 15 | /** 16 | * 验证码发送 17 | *

18 | * 对于他的注入,请在 {@link ValidateCodeConfig} 中进行配置 19 | * 使用 CGLIB 增强 20 | * 21 | * @author echo 22 | * @version 1.0 23 | * @date 19-4-14 14:13 24 | */ 25 | @Slf4j 26 | @RequiredArgsConstructor 27 | public class SmsCodeSender implements ValidateCodeSender { 28 | 29 | private final @NonNull SubMailUtils subMailUtils; 30 | private final @NonNull SmsConfig smsConfig; 31 | 32 | /** 33 | * 发送验证码 34 | * 35 | * @param receive 手机号 36 | * @param code 验证码 37 | */ 38 | @Override 39 | public void send(String receive, ValidateCode code) { 40 | JSONObject jsonObject = new JSONObject(); 41 | jsonObject.put("action", "登录"); 42 | jsonObject.put("code", code.getCode()); 43 | jsonObject.put("time", Duration.ofSeconds(code.getExpireIn()).toMinutes()); 44 | if (smsConfig.getDev()) { 45 | log.info("向 {} 发送登录验证码 {},有效期 {} 分钟", receive, code.getCode(), 46 | Duration.ofSeconds(code.getExpireIn()).toMinutes()); 47 | } else { 48 | log.debug("向 {} 发送登录验证码 {},有效期 {} 分钟", receive, code.getCode(), 49 | Duration.ofSeconds(code.getExpireIn()).toMinutes()); 50 | subMailUtils.sendActionMessage(receive, jsonObject); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/sms/SmsAuthenticationToken.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.sms; 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 | /** 10 | * token 配置 11 | * 12 | * 13 | * @author EchoCow 14 | * @version 1.0 15 | * @date 19-4-14 15:47 16 | * @deprecated 过于复杂的配置方式,标记过时 17 | */ 18 | @Deprecated 19 | public class SmsAuthenticationToken extends AbstractAuthenticationToken { 20 | 21 | private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; 22 | 23 | private final Object principal; 24 | 25 | SmsAuthenticationToken(Object phone) { 26 | super(null); 27 | this.principal = phone; 28 | setAuthenticated(false); 29 | } 30 | 31 | SmsAuthenticationToken(Object principal, Collection authorities) { 32 | super(authorities); 33 | this.principal = principal; 34 | super.setAuthenticated(true); 35 | } 36 | 37 | @Override 38 | public Object getCredentials() { 39 | return null; 40 | } 41 | 42 | @Override 43 | public Object getPrincipal() { 44 | return this.principal; 45 | } 46 | 47 | @Override 48 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { 49 | if (isAuthenticated) { 50 | throw new IllegalArgumentException( 51 | "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); 52 | } 53 | super.setAuthenticated(false); 54 | } 55 | 56 | @Override 57 | public void eraseCredentials() { 58 | super.eraseCredentials(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/util/MapUtils.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.SysUser; 4 | import com.alibaba.fastjson.JSONObject; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * . 11 | * 12 | * @author EchoCow 13 | * @date 2020/5/5 下午8:07 14 | */ 15 | public class MapUtils { 16 | 17 | private MapUtils() { 18 | } 19 | 20 | private final static String AVATAR = "avatar"; 21 | private final static String IMAGE = "image"; 22 | private final static String PHONE = "phone"; 23 | private final static String EMAIL = "email"; 24 | private final static String NAME = "name"; 25 | private final static String ID = "id"; 26 | 27 | public static JSONObject userBaseJson(SysUser user) { 28 | final JSONObject userObject = new JSONObject(); 29 | userObject.put(ID, user.getId()); 30 | userObject.put(IMAGE, user.getImage()); 31 | userObject.put(AVATAR, user.getAvatar()); 32 | userObject.put(PHONE, user.getPhone()); 33 | userObject.put(EMAIL, user.getEmail()); 34 | return userObject; 35 | } 36 | 37 | public static List userBaseList(List users, List entity) { 38 | final List result = new ArrayList<>(users.size()); 39 | for (int i = 0; i < users.size(); i++) { 40 | final JSONObject user = users.get(i); 41 | final JSONObject teacher = entity.get(i); 42 | final JSONObject r = new JSONObject(); 43 | r.put(ID, user.getLong(ID)); 44 | r.put(IMAGE, user.getString(IMAGE)); 45 | r.put(AVATAR, user.getString(AVATAR)); 46 | r.put(PHONE, user.getString(PHONE)); 47 | r.put(EMAIL, user.getString(EMAIL)); 48 | r.put(NAME, teacher.getString(NAME)); 49 | result.add(r); 50 | } 51 | return result; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/impl/ValidateCodeRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.impl; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 4 | import cn.edu.gzmu.authserver.model.exception.ValidateCodeException; 5 | import cn.edu.gzmu.authserver.validate.ValidateCode; 6 | import cn.edu.gzmu.authserver.validate.ValidateCodeRepository; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.util.StringUtils; 12 | import org.springframework.web.context.request.ServletWebRequest; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * 验证码资源实现类 18 | * 19 | * @author EchoCow 20 | * @date 2019/7/31 上午10:36 21 | */ 22 | @Component 23 | @RequiredArgsConstructor 24 | public class ValidateCodeRepositoryImpl implements ValidateCodeRepository { 25 | 26 | private final @NonNull RedisTemplate redisTemplate; 27 | 28 | @Override 29 | public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType type) { 30 | redisTemplate.opsForValue().set(buildKey(request, type), code, code.getExpireIn(), TimeUnit.SECONDS); 31 | } 32 | 33 | @Override 34 | public ValidateCode get(ServletWebRequest request, ValidateCodeType type) { 35 | return redisTemplate.opsForValue().get(buildKey(request, type)); 36 | } 37 | 38 | @Override 39 | public void remove(ServletWebRequest request, ValidateCodeType type) { 40 | redisTemplate.delete(buildKey(request, type)); 41 | } 42 | 43 | /** 44 | * 构建 redis 存储时的 key 45 | * 46 | * @param request 请求 47 | * @param type 类型 48 | * @return key 49 | */ 50 | private String buildKey(ServletWebRequest request, ValidateCodeType type) { 51 | String deviceId = request.getParameter(type.getParamNameOnValidate().toLowerCase()); 52 | if (StringUtils.isEmpty(deviceId)) { 53 | throw new ValidateCodeException("请求中不存在 " + type); 54 | } 55 | return "code:" + type + ":" + deviceId; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/resources/templates/authorization.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 确认您的授权信息 5 | 6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 确认应用的授权信息 17 | 18 |
19 | 20 |
21 | 22 | 23 | 当前应用将会获取您的以下权限: 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 确认授权 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 |
48 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/handler/AuthFailureHandler.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.handler; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.data.redis.core.ValueOperations; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.util.StringUtils; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | import java.net.URLEncoder; 17 | import java.util.Optional; 18 | 19 | /** 20 | * 登录失败处理器 21 | * 22 | * @author echo 23 | * @version 1.0 24 | * @date 19-4-14 10:51 25 | */ 26 | @Slf4j 27 | @Component 28 | @RequiredArgsConstructor 29 | public class AuthFailureHandler implements AuthenticationFailureHandler { 30 | private final RedisTemplate longRedisTemplate; 31 | private static final String LOGIN_FAILURE_API_NUMBER = "login_failure_api_number"; 32 | 33 | @Override 34 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, 35 | AuthenticationException exception) throws IOException { 36 | log.debug("Login failed!"); 37 | ValueOperations operations = longRedisTemplate.opsForValue(); 38 | Long number = Optional.ofNullable(operations.get(LOGIN_FAILURE_API_NUMBER)).orElse(0L); 39 | operations.set(LOGIN_FAILURE_API_NUMBER, number + 1); 40 | String username = request.getParameter("username"); 41 | if (StringUtils.hasText(username)) { 42 | final String key = "failure:" + username; 43 | final Long userLoginSuccess = Optional.ofNullable(operations.get(key)).orElse(0L); 44 | operations.set(key, userLoginSuccess + 1); 45 | } 46 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 47 | response.sendRedirect("/oauth/login?error=" 48 | + URLEncoder.encode(exception.getLocalizedMessage(), "UTF-8")); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/sms/SmsAuthenticationSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.sms; 2 | 3 | import cn.edu.gzmu.authserver.service.impl.UserDetailsServiceImpl; 4 | import lombok.NonNull; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.config.annotation.SecurityConfigurerAdapter; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.web.DefaultSecurityFilterChain; 10 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 11 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 12 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 13 | 14 | /** 15 | * sms 授权配置 16 | * 17 | * 18 | * @author EchoCow 19 | * @version 1.0 20 | * @date 19-4-14 16:02 21 | * @deprecated 过于复杂的配置方式,标记过时 22 | */ 23 | //@Component 24 | @Deprecated 25 | @RequiredArgsConstructor 26 | public class SmsAuthenticationSecurityConfig 27 | extends SecurityConfigurerAdapter { 28 | 29 | private final @NonNull AuthenticationSuccessHandler smsSuccessHandler; 30 | private final @NonNull AuthenticationFailureHandler authFailureHandle; 31 | private final @NonNull UserDetailsServiceImpl userDetailsService; 32 | 33 | 34 | @Override 35 | public void configure(HttpSecurity http) { 36 | // 过滤器链 37 | SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter(); 38 | smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); 39 | smsAuthenticationFilter.setAuthenticationSuccessHandler(smsSuccessHandler); 40 | smsAuthenticationFilter.setAuthenticationFailureHandler(authFailureHandle); 41 | 42 | // 授权提供者 43 | SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider(); 44 | smsAuthenticationProvider.setUserDetailsService(userDetailsService); 45 | 46 | http.authenticationProvider(smsAuthenticationProvider) 47 | .addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/base/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.base; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.experimental.Accessors; 6 | import org.springframework.data.annotation.CreatedBy; 7 | import org.springframework.data.annotation.CreatedDate; 8 | import org.springframework.data.annotation.LastModifiedBy; 9 | import org.springframework.data.annotation.LastModifiedDate; 10 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 11 | 12 | import javax.persistence.*; 13 | import javax.validation.constraints.Size; 14 | import java.time.LocalDateTime; 15 | 16 | /** 17 | * @author echo 18 | * @version 1.0.0 19 | * @date 19-6-11 下午5:24 20 | */ 21 | @Getter 22 | @Setter 23 | @MappedSuperclass 24 | @Accessors(chain = true) 25 | @EntityListeners(AuditingEntityListener.class) 26 | public class BaseEntity { 27 | /** 28 | * id 主键 29 | */ 30 | @Id 31 | @GeneratedValue(strategy = GenerationType.IDENTITY) 32 | private java.lang.Long id; 33 | 34 | /** 35 | * 名称 36 | */ 37 | @Size(max = 30, message = "name 长度不能超过 30") 38 | private String name; 39 | 40 | /** 41 | * 全称 42 | */ 43 | @Size(max = 55, message = "spell 长度不能超过 55") 44 | private String spell; 45 | 46 | /** 47 | * 排序 48 | */ 49 | private Integer sort; 50 | 51 | /** 52 | * 创建时间 53 | */ 54 | @CreatedDate 55 | @Column(name = "create_time", nullable = false, columnDefinition = "datetime not null default now() comment '创建时间'") 56 | private LocalDateTime createTime; 57 | 58 | /** 59 | * 创建用户 60 | */ 61 | @CreatedBy 62 | @Column(name = "create_user") 63 | private String createUser; 64 | 65 | /** 66 | * 修改时间 67 | */ 68 | @LastModifiedDate 69 | @Column(name = "modify_time", nullable = false, columnDefinition = "datetime not null default now() comment '修改时间'") 70 | private LocalDateTime modifyTime; 71 | 72 | /** 73 | * 修改用户 74 | */ 75 | @LastModifiedBy 76 | @Column(name = "modify_user") 77 | private String modifyUser; 78 | 79 | /** 80 | * 备注 81 | */ 82 | @Size(max = 255, message = "remark 长度不能超过 255") 83 | private String remark; 84 | 85 | /** 86 | * 是否启用 87 | */ 88 | private Boolean isEnable = true; 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/res/AuthAccessDecisionManager.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.res; 2 | 3 | import org.springframework.security.access.AccessDecisionManager; 4 | import org.springframework.security.access.AccessDeniedException; 5 | import org.springframework.security.access.ConfigAttribute; 6 | import org.springframework.security.authentication.InsufficientAuthenticationException; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Collection; 11 | import java.util.Objects; 12 | 13 | import static cn.edu.gzmu.authserver.model.constant.SecurityConstants.*; 14 | 15 | /** 16 | * 授权决策 17 | * 18 | * @author EchoCow 19 | * @date 2019/8/6 下午2:03 20 | */ 21 | @Component 22 | public class AuthAccessDecisionManager implements AccessDecisionManager { 23 | @Override 24 | public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { 25 | for (ConfigAttribute configAttribute : configAttributes) { 26 | String needRole = configAttribute.getAttribute(); 27 | if (ROLE_NO_AUTH.equals(needRole)) { 28 | throw new AccessDeniedException("权限不足"); 29 | } 30 | // 如果是 ROLE_NO_LOGIN 资源,放行 31 | if (ROLE_NO_LOGIN.equals(needRole)) { 32 | return; 33 | } 34 | // 如果是 ROLE_PUBLIC 资源且不是匿名用户,放行 35 | if (ROLE_PUBLIC.equals(needRole) 36 | && !roleCondition(authentication, ROLE_ANONYMOUS)) { 37 | return; 38 | } 39 | // 符合条件的,放行 40 | if (roleCondition(authentication, needRole)) { 41 | return; 42 | } 43 | } 44 | throw new AccessDeniedException("权限不足"); 45 | } 46 | 47 | @Override 48 | public boolean supports(ConfigAttribute attribute) { 49 | return true; 50 | } 51 | 52 | @Override 53 | public boolean supports(Class clazz) { 54 | return true; 55 | } 56 | 57 | private Boolean roleCondition(Authentication authentication, String role) { 58 | return authentication.getAuthorities().stream() 59 | .anyMatch(authority -> 60 | Objects.nonNull(authority.getAuthority()) 61 | && authority.getAuthority().equalsIgnoreCase(role)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/sms/SmsAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.sms; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.security.authentication.AuthenticationServiceException; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 9 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | /** 15 | * 授权过滤器 16 | * 17 | * 18 | * @author EchoCow 19 | * @version 1.0 20 | * @date 19-4-14 15:44 21 | * @deprecated 过于复杂的配置方式,标记过时 22 | */ 23 | @Deprecated 24 | public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter { 25 | 26 | SmsAuthenticationFilter() { 27 | super(new AntPathRequestMatcher(SecurityConstants.LOGIN_PROCESSING_URL_SMS, "POST")); 28 | } 29 | 30 | @Override 31 | public Authentication attemptAuthentication(HttpServletRequest request, 32 | HttpServletResponse response) throws AuthenticationException { 33 | if (!HttpMethod.POST.matches(request.getMethod())) { 34 | throw new AuthenticationServiceException( 35 | "Authentication method not supported: " + request.getMethod()); 36 | } 37 | String phone = obtainSms(request); 38 | phone = phone == null ? "" : phone.trim(); 39 | SmsAuthenticationToken authRequest = new SmsAuthenticationToken(phone); 40 | setDetails(request, authRequest); 41 | return this.getAuthenticationManager().authenticate(authRequest); 42 | } 43 | 44 | /** 45 | * 获取请求中的 sms 值 46 | * 47 | * @param request 正在为其创建身份验证请求 48 | * @return 请求中的 sms 值 49 | */ 50 | private String obtainSms(HttpServletRequest request) { 51 | return request.getHeader(SecurityConstants.GRANT_TYPE_SMS); 52 | } 53 | 54 | /** 55 | * 提供以便子类可以配置放入 authentication request 的 details 属性的内容 56 | * 57 | * @param request 正在为其创建身份验证请求 58 | * @param authRequest 应设置其详细信息的身份验证请求对象 59 | */ 60 | private void setDetails(HttpServletRequest request, 61 | SmsAuthenticationToken authRequest) { 62 | authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/SysUser.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import cn.edu.gzmu.authserver.model.constant.UserStatus; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.ToString; 8 | import lombok.experimental.Accessors; 9 | import org.hibernate.annotations.Where; 10 | 11 | import javax.persistence.*; 12 | import javax.validation.constraints.Size; 13 | import java.io.Serializable; 14 | import java.util.Set; 15 | 16 | import static javax.persistence.EnumType.STRING; 17 | 18 | /** 19 | * @author echo 20 | * @version 1.0.0 21 | * @date 19-6-11 下午5:26 22 | */ 23 | @Data 24 | @Table(name = "sys_user") 25 | @Entity(name = "sys_user") 26 | @Where(clause = "is_enable = true") 27 | @ToString(callSuper = true) 28 | @EqualsAndHashCode(callSuper = true) 29 | @Accessors(chain = true) 30 | public class SysUser extends BaseEntity implements Serializable { 31 | 32 | /** 33 | * 密码 34 | */ 35 | @javax.validation.constraints.NotNull(message = "password 密码 为必填项") 36 | @Size(max = 255, message = "password 不能大于 255 位") 37 | @com.fasterxml.jackson.annotation.JsonIgnore 38 | private java.lang.String password; 39 | 40 | /** 41 | * 1:正常、2:锁定一小时、3:禁用 42 | */ 43 | @Enumerated(STRING) 44 | @javax.validation.constraints.NotNull(message = "status 1:正常、2:锁定一小时、3:禁用 为必填项") 45 | private UserStatus status; 46 | 47 | /** 48 | * 图标 49 | */ 50 | @Size(max = 255, message = "image 不能大于 255 位") 51 | private java.lang.String image; 52 | 53 | /** 54 | * 头像 55 | */ 56 | @Size(max = 255, message = "avatar 不能大于 255 位") 57 | private java.lang.String avatar; 58 | 59 | /** 60 | * 电子邮箱 61 | */ 62 | @javax.validation.constraints.NotNull(message = "email 电子邮箱 为必填项") 63 | @Size(max = 255, message = "email 不能大于 255 位") 64 | @javax.validation.constraints.Email(message = "email不合法,请输入正确的邮箱地址") 65 | private java.lang.String email; 66 | 67 | /** 68 | * 联系电话 69 | */ 70 | @javax.validation.constraints.NotNull(message = "phone 联系电话 为必填项") 71 | @Size(max = 20, message = "phone 不能大于 20 位") 72 | private java.lang.String phone; 73 | 74 | /** 75 | * 在线状态 1-在线 0-离线 76 | */ 77 | private java.lang.Boolean onlineStatus; 78 | 79 | /** 80 | * 学生信息 81 | */ 82 | @Transient 83 | private Student student; 84 | 85 | /** 86 | * 教师信息 87 | */ 88 | @Transient 89 | private Teacher teacher; 90 | 91 | /** 92 | * 角色信息 93 | */ 94 | @Transient 95 | private Set roles; 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/util/VerifyParameter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 11 | 12 | 13 | /** 14 | * 参数验证注解,对于 controller 的参数校验 15 | *

16 | * 当参数在请求体内,只有一个参数且为 {@link JSONObject} 类型时可用 17 | *

18 | * 验证表达式如下: 19 | * 字段名称 | 条件表达式 |# 提示信息 20 | *

21 | * 条件表达式中,对于范围使用 - 进行分割,例如 2-5 22 | * 对于值直接写即可,例如 {@code equal} 例如 email|xxx@163.com|#邮箱不能为空 23 | * 对于 {@code required} 和 {@code number} 不需要填写条件表达式 24 | * 25 | * @author echo 26 | * @version 1.0 27 | * @date 19-5-22 14:54 28 | */ 29 | @Target(METHOD) 30 | @Retention(RUNTIME) 31 | @Documented 32 | public @interface VerifyParameter { 33 | 34 | /** 35 | * 同 required 36 | * 37 | * @return required 38 | */ 39 | String[] value() default ""; 40 | 41 | /** 42 | * 验证的参数 43 | *

44 | * 表达式如下 45 | * age|#age为必填项 : age 只能为数字 提示信息 age为必填项 46 | * 47 | * @return 需要验证的字段 48 | */ 49 | String[] required() default ""; 50 | 51 | /** 52 | * 验证只能为数字 53 | *

54 | * 表达式如下 55 | * age|#age只能为数字 : age 只能为数字 提示信息 age只能为数字 56 | * 57 | * @return 需要验证的字段 58 | */ 59 | String[] number() default ""; 60 | 61 | /** 62 | * 需要验证大小的字段 63 | *

64 | * 表达式如下 65 | * username|1-5|#username长度只能为5 : username 的长度范围为 1 - 5 提示信息 username长度只能为 5 66 | * 67 | * @return 需要验证的字段 68 | */ 69 | String[] size() default ""; 70 | 71 | /** 72 | * 需要验证最大值的字段 73 | *

74 | * 表达式如下 75 | * age|5|#age最大值为5 : age 字段最大为 5 提示信息为 age 最大值为 5 76 | * 77 | * @return 需要验证的字段 78 | */ 79 | String[] max() default ""; 80 | 81 | /** 82 | * 需要验证最小值的字段 83 | *

84 | * 表达式如下 85 | * age|5|#age最小值为5: age 字段最小为 5 提示信息为 age 最小值为 5 86 | * 87 | * @return 需要验证的字段 88 | */ 89 | String[] min() default ""; 90 | 91 | /** 92 | * 需要验证范围的字段 93 | *

94 | * 必须为数字,格式如下 95 | * age|1-5|#age不在指定范围内 : age 字段范围为 1-5 提示信息为 age 不在指定范围内 96 | * 97 | * @return 需要验证的字段 98 | */ 99 | String[] range() default ""; 100 | 101 | /** 102 | * 需要相等的字段 103 | *

104 | * 表达式如下 105 | * age|1|#age不能为空 : age 字段必须为 1 提示信息为 age 不能为空 106 | * email|xxx@163.com|#邮箱不能为空 : email 字段必须为 xxx@163.com 提示信息为 邮箱 不能为空 107 | * 108 | * @return 需要验证的字段 109 | */ 110 | String[] equal() default ""; 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/grant/EmailTokenGranter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.grant; 2 | 3 | import cn.edu.gzmu.authserver.auth.email.EmailUserDetailsService; 4 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 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.Objects; 14 | 15 | /** 16 | * 邮箱登录 17 | * 18 | * @author EchoCow 19 | * @date 2019/7/30 下午1:33 20 | */ 21 | public class EmailTokenGranter extends AbstractTokenGranter { 22 | private static final String GRANT_TYPE = SecurityConstants.GRANT_TYPE_EMAIL; 23 | private EmailUserDetailsService emailUserDetailsService; 24 | 25 | /** 26 | * 构造方法提供一些必要的注入的参数 27 | * 通过这些参数来完成我们父类的构建 28 | * 29 | * @param tokenServices tokenServices 30 | * @param clientDetailsService clientDetailsService 31 | * @param oAuth2RequestFactory oAuth2RequestFactory 32 | * @param emailUserDetailsService emailUserDetailsService 33 | */ 34 | public EmailTokenGranter(AuthorizationServerTokenServices tokenServices, 35 | ClientDetailsService clientDetailsService, 36 | OAuth2RequestFactory oAuth2RequestFactory, 37 | EmailUserDetailsService emailUserDetailsService) { 38 | super(tokenServices, clientDetailsService, oAuth2RequestFactory, GRANT_TYPE); 39 | this.emailUserDetailsService = emailUserDetailsService; 40 | } 41 | 42 | /** 43 | * 在这里查询我们用户,构建用户的授权信息 44 | * 45 | * @param client 客户端 46 | * @param tokenRequest tokenRequest 47 | * @return OAuth2Authentication 48 | */ 49 | @Override 50 | protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { 51 | String email = tokenRequest.getRequestParameters().getOrDefault(GRANT_TYPE, ""); 52 | UserDetails userDetails = emailUserDetailsService.loadUserByEmail(email); 53 | if (Objects.isNull(userDetails)) { 54 | throw new UsernameNotFoundException("用户不存在"); 55 | } 56 | Authentication user = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), 57 | userDetails.getPassword(), userDetails.getAuthorities()); 58 | return new OAuth2Authentication(tokenRequest.createOAuth2Request(client), user); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/base/BaseRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.base; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 8 | import org.springframework.data.jpa.repository.Modifying; 9 | import org.springframework.data.jpa.repository.Query; 10 | import org.springframework.data.repository.NoRepositoryBean; 11 | import org.springframework.data.repository.query.Param; 12 | import org.springframework.data.rest.core.annotation.RestResource; 13 | 14 | import javax.transaction.Transactional; 15 | import java.util.List; 16 | 17 | /** 18 | * 基类 19 | * 20 | * @param 实体类 21 | * @param 主键类型 22 | * @author echo 23 | * @version 1.0 24 | * @date 2019-4-11 11:59:46 25 | */ 26 | @NoRepositoryBean 27 | @SuppressWarnings({"all", "uncheck"}) 28 | public interface BaseRepository extends JpaRepository, JpaSpecificationExecutor { 29 | 30 | /** 31 | * 获取所有 32 | * 33 | * @return list 34 | */ 35 | @RestResource(path = "all", rel = "all") 36 | @Query(value = "select * from #{#entityName}", nativeQuery = true) 37 | List searchAll(); 38 | 39 | /** 40 | * 查询所有数据 41 | * 42 | * @param pageable 分页 43 | * @return 结果 44 | */ 45 | @Query(value = "select * from #{#entityName} ", countQuery = "select count(*) from #{#entityName}", nativeQuery = true) 46 | Page findAllExist(Pageable pageable); 47 | 48 | 49 | /** 50 | * 通过 id 列表查询 51 | * 52 | * @param ids id 列表 53 | * @return 结果 54 | */ 55 | @RestResource(path = "byIds") 56 | @Query(value = "select * from #{#entityName} where id in (:ids) and is_enable = true ", nativeQuery = true) 57 | List searchAllByIds(@NotNull @Param("ids") List ids); 58 | 59 | 60 | /** 61 | * 通过 id 列表查询 62 | * 63 | * @param ids id 列表 64 | * @param pageable 分页对象 65 | * @return 结果 66 | */ 67 | @RestResource(path = "byIdsPage", rel = "byIdsPage") 68 | @Query(value = "select * from #{#entityName} where id in (:ids) and is_enable = true ", 69 | countQuery = "select count(*) from #{#entityName}", nativeQuery = true) 70 | Page searchAllByIds(@NotNull @Param("ids") List ids, Pageable pageable); 71 | 72 | 73 | /** 74 | * 删除多个数据. 75 | * 76 | * @param ids ids 77 | */ 78 | @Modifying 79 | @RestResource(path = "deleteByIds") 80 | @Transactional(rollbackOn = Exception.class) 81 | @Query(value = "update #{#entityName} set is_enable = false where id in (:ids)", nativeQuery = true) 82 | int deleteExistByIds(@NotNull @Param("ids") List ids); 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/SysRoleRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | 4 | import cn.edu.gzmu.authserver.base.BaseRepository; 5 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 6 | import cn.edu.gzmu.authserver.model.entity.SysRole; 7 | import org.springframework.data.jpa.repository.Query; 8 | import org.springframework.data.repository.query.Param; 9 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 10 | import org.springframework.data.rest.core.annotation.RestResource; 11 | 12 | import java.util.List; 13 | import java.util.Optional; 14 | import java.util.Set; 15 | 16 | /** 17 | * SysRole Repository 18 | * 19 | * @author echo 20 | * @version 1.0 21 | * @date 2019-5-7 11:05:31 22 | */ 23 | @RepositoryRestResource(path = AuthConstant.SYS_ROLE) 24 | public interface SysRoleRepository extends BaseRepository { 25 | 26 | /** 27 | * 通过角色名查询 28 | * 29 | * @param name 名 30 | * @return 角色 31 | */ 32 | @RestResource(path = "byName") 33 | Optional findFirstByName(String name); 34 | 35 | /** 36 | * 通过 id 列表查询 37 | * 38 | * @param ids id 列表 39 | * @return 结果 40 | */ 41 | Set findByIdIn(List ids); 42 | 43 | /** 44 | * 角色 45 | * 46 | * @param userId 用户 id 47 | * @return 结果 48 | */ 49 | @RestResource(path = "byUserId") 50 | @Query(value = "select r.* from sys_user_role sur, sys_role r " + 51 | "where sur.user_id = (:userId) and r.id = sur.role_id and r.is_enable = true", 52 | nativeQuery = true) 53 | Set searchAllByUserId(@Param("userId") Long userId); 54 | 55 | /** 56 | * 角色 57 | * 58 | * @param resId 资源 id 59 | * @return 结果 60 | */ 61 | @RestResource(path = "byResId") 62 | @Query(value = "select r.* from sys_role_res srr, sys_role r " + 63 | "where srr.res_id = (:resId) and r.id = srr.role_id and r.is_enable = true", 64 | nativeQuery = true) 65 | Set searchAllByResId(@Param("resId") Long resId); 66 | 67 | /** 68 | * 获取当前角色的所有相关角色 69 | * 70 | * @param roleIds 角色 ids 71 | * @return 结果 72 | */ 73 | @RestResource(path = "byRoleId") 74 | @Query(value = "WITH RECURSIVE cte as (" + 75 | " SELECT * " + 76 | " FROM sys_role " + 77 | " WHERE id in (:roleIds) AND is_enable = true" + 78 | " UNION ALL " + 79 | " SELECT r.*" + 80 | " FROM sys_role r " + 81 | " JOIN cte c ON c.parent_id = r.id" + 82 | " WHERE r.is_enable = true" + 83 | ") " + 84 | "SELECT DISTINCT * " + 85 | "FROM cte", nativeQuery = true) 86 | Set searchAllRoleByIds(@Param("roleIds") List roleIds); 87 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/grant/SmsTokenGranter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth.grant; 2 | 3 | import cn.edu.gzmu.authserver.auth.sms.SmsUserDetailsService; 4 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 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.Map; 14 | import java.util.Objects; 15 | 16 | /** 17 | * 手机登录 18 | * 19 | * @author EchoCow 20 | * @date 2019/7/30 下午1:33 21 | */ 22 | public class SmsTokenGranter extends AbstractTokenGranter { 23 | private static final String GRANT_TYPE = SecurityConstants.GRANT_TYPE_SMS; 24 | private SmsUserDetailsService smsUserDetailsService; 25 | 26 | /** 27 | * 构造方法提供一些必要的注入的参数 28 | * 通过这些参数来完成我们父类的构建 29 | * 30 | * @param tokenServices tokenServices 31 | * @param clientDetailsService clientDetailsService 32 | * @param oAuth2RequestFactory oAuth2RequestFactory 33 | * @param smsUserDetailsService smsUserDetailsService 34 | */ 35 | public SmsTokenGranter(AuthorizationServerTokenServices tokenServices, 36 | ClientDetailsService clientDetailsService, 37 | OAuth2RequestFactory oAuth2RequestFactory, 38 | SmsUserDetailsService smsUserDetailsService) { 39 | super(tokenServices, clientDetailsService, oAuth2RequestFactory, GRANT_TYPE); 40 | this.smsUserDetailsService = smsUserDetailsService; 41 | } 42 | 43 | /** 44 | * 在这里查询我们用户,构建用户的授权信息 45 | * 46 | * @param client 客户端 47 | * @param tokenRequest tokenRequest 48 | * @return OAuth2Authentication 49 | */ 50 | @Override 51 | protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { 52 | Map params = tokenRequest.getRequestParameters(); 53 | String sms = params.getOrDefault(GRANT_TYPE, ""); 54 | UserDetails userDetails = smsUserDetailsService.loadUserBySms(sms); 55 | if (Objects.isNull(userDetails)) { 56 | throw new UsernameNotFoundException("用户不存在"); 57 | } 58 | Authentication user = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), 59 | userDetails.getPassword(), userDetails.getAuthorities()); 60 | return new OAuth2Authentication(tokenRequest.createOAuth2Request(client), user); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/AuthTokenEnhancer.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.SysRole; 4 | import cn.edu.gzmu.authserver.model.entity.SysUser; 5 | import cn.edu.gzmu.authserver.service.SysUserService; 6 | import com.alibaba.fastjson.JSONObject; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.core.userdetails.User; 11 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 12 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 13 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 14 | import org.springframework.security.oauth2.provider.token.TokenEnhancer; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.stream.Collectors; 20 | 21 | import static cn.edu.gzmu.authserver.model.constant.SecurityConstants.*; 22 | 23 | /** 24 | * 令牌增强,用于扩展 25 | * 26 | * @author echo 27 | * @date 19-6-19 下午3:53 28 | */ 29 | @Component 30 | @RequiredArgsConstructor 31 | public class AuthTokenEnhancer implements TokenEnhancer { 32 | 33 | private final @NonNull SysUserService sysUserService; 34 | 35 | @Override 36 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { 37 | User user = (User) authentication.getPrincipal(); 38 | final Map additionalInfo = new HashMap<>(6); 39 | SysUser sysUser = sysUserService.findByName(user.getUsername()); 40 | additionalInfo.put("user_name", user.getUsername()); 41 | additionalInfo.put("authorities", sysUser.getRoles().stream().map(SysRole::getAuthority).collect(Collectors.toList())); 42 | additionalInfo.put("roles", sysUser.getRoles().stream() 43 | .map(r -> { 44 | JSONObject role = new JSONObject(); 45 | role.put("id", r.getId()); 46 | role.put("name", r.getName()); 47 | return role; 48 | }).collect(Collectors.toList()) 49 | ); 50 | additionalInfo.put("sub", user.getUsername()); 51 | additionalInfo.put("user_id", sysUser.getId()); 52 | additionalInfo.put("iat", (System.currentTimeMillis()) / 1000L); 53 | additionalInfo.put("nbf", (System.currentTimeMillis()) / 1000L); 54 | additionalInfo.put("is_student", user.getAuthorities().stream() 55 | .map(GrantedAuthority::getAuthority).anyMatch(ROLE_STUDENT::equals)); 56 | additionalInfo.put("is_teacher", user.getAuthorities().stream() 57 | .map(GrantedAuthority::getAuthority).anyMatch(ROLE_TEACHER::equals)); 58 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); 59 | return accessToken; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/repository/SysUserRepository.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.repository; 2 | 3 | 4 | import cn.edu.gzmu.authserver.base.BaseRepository; 5 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 6 | import cn.edu.gzmu.authserver.model.entity.SysUser; 7 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 8 | import org.springframework.data.rest.core.annotation.RestResource; 9 | 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | 14 | /** 15 | * SysUser Repository 16 | * 17 | * @author echo 18 | * @version 1.0 19 | * @date 2019-5-7 11:05:31 20 | */ 21 | @RepositoryRestResource(path = AuthConstant.SYS_USER) 22 | public interface SysUserRepository extends BaseRepository { 23 | 24 | /** 25 | * Find All By Ids 26 | * 27 | * @param ids Ids 28 | * @return all 29 | */ 30 | List findAllByIdIn(List ids); 31 | 32 | /** 33 | * 通过名称查询用户,并非模糊查询。 34 | * 每次获取均需要通过非空判断。 35 | * 36 | * @param name 名称 37 | * @return 结果 38 | */ 39 | @RestResource(exported = false) 40 | Optional findFirstByName(String name); 41 | 42 | /** 43 | * 通过手机号查询用户,并非模糊查询。 44 | * 每次获取均需要通过非空判断。 45 | * 46 | * @param phone 手机号 47 | * @return 结果 48 | */ 49 | @RestResource(exported = false) 50 | Optional findFirstByPhone(String phone); 51 | 52 | /** 53 | * 通过邮箱号查询用户,并非模糊查询。 54 | * 每次获取均需要通过非空判断。 55 | * 56 | * @param email 邮箱号 57 | * @return 结果 58 | */ 59 | @RestResource(exported = false) 60 | Optional findFirstByEmail(String email); 61 | 62 | /** 63 | * 通过用户名、手机号、邮箱号查询用户,并非模糊查询。 64 | * 每次获取均需要通过非空判断。 65 | * 66 | * @param name 用户名 67 | * @param phone 手机号 68 | * @param email 邮箱号 69 | * @return 结果 70 | */ 71 | @RestResource(exported = false) 72 | Optional findFirstByNameOrPhoneOrEmail(String name, String phone, String email); 73 | 74 | 75 | /** 76 | * 通过用户名、手机号、邮箱号查询是否存在 77 | * 78 | * @param name 用户名 79 | * @param phone 手机号 80 | * @param email 邮箱号 81 | * @return 结果 82 | */ 83 | @RestResource(path = "exist") 84 | Boolean existsByNameOrPhoneOrEmail(String name, String phone, String email); 85 | 86 | /** 87 | * 通过名称查询是否存在 88 | * 89 | * @param name 名称 90 | * @return 结果 91 | */ 92 | @RestResource(path = "existByName") 93 | Boolean existsByName(String name); 94 | 95 | /** 96 | * 通过手机号查询是否存在 97 | * 98 | * @param phone phone 99 | * @return 结果 100 | */ 101 | @RestResource(path = "existByPhone") 102 | Boolean existsByPhone(String phone); 103 | 104 | /** 105 | * 通过邮箱查询是否存在 106 | * 107 | * @param email email 108 | * @return 结果 109 | */ 110 | @RestResource(path = "existByEmail") 111 | Boolean existsByEmail(String email); 112 | 113 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/filter/ApiNumberFilter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.filter; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.springframework.core.annotation.Order; 6 | import org.springframework.data.redis.core.ListOperations; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.data.redis.core.ValueOperations; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.util.AntPathMatcher; 11 | import org.springframework.web.filter.OncePerRequestFilter; 12 | 13 | import javax.servlet.FilterChain; 14 | import javax.servlet.ServletException; 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | import java.time.LocalDate; 19 | import java.util.Optional; 20 | 21 | /** 22 | * Oauth fitler. 23 | * 24 | * @author EchoCow 25 | * @date 2020/5/17 上午8:03 26 | */ 27 | @Component 28 | @RequiredArgsConstructor 29 | @Order(Integer.MIN_VALUE) 30 | public class ApiNumberFilter extends OncePerRequestFilter { 31 | private static final String OAUTH_API_NUMBER = "oauth_api_number"; 32 | private static final String AUTHORIZATION_SERVER_API_NUMBER = "authorization_server_api_number"; 33 | private static final String AUTHORIZATION_SERVER_API_URL = "authorization_server_api_url"; 34 | private static final String AUTHORIZATION_SERVER_DATA_API_NUMBER = "authorization_server_data_api_number"; 35 | private final AntPathMatcher oauthPathMatcher = new AntPathMatcher("/oauth/**"); 36 | private final RedisTemplate longRedisTemplate; 37 | private final RedisTemplate stringRedisTemplate; 38 | 39 | @Override 40 | protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, 41 | @NotNull FilterChain filterChain) throws ServletException, IOException { 42 | final String requestUrl = request.getServletPath(); 43 | ValueOperations operations = longRedisTemplate.opsForValue(); 44 | if (oauthPathMatcher.isPattern(requestUrl)) { 45 | final Long number = Optional.ofNullable(operations.get(OAUTH_API_NUMBER)).orElse(0L); 46 | operations.set(OAUTH_API_NUMBER, number + 1); 47 | } 48 | final Long number = Optional.ofNullable(operations.get(AUTHORIZATION_SERVER_API_NUMBER)).orElse(0L); 49 | operations.set(AUTHORIZATION_SERVER_API_NUMBER, number + 1); 50 | final String dateKey = AUTHORIZATION_SERVER_DATA_API_NUMBER + "=" + LocalDate.now().toString(); 51 | final Long dataNumber = Optional.ofNullable(operations.get(dateKey)).orElse(0L); 52 | operations.set(dateKey, dataNumber + 1); 53 | ListOperations stringOperations = stringRedisTemplate.opsForList(); 54 | stringOperations.leftPush(AUTHORIZATION_SERVER_API_URL, requestUrl); 55 | filterChain.doFilter(request, response); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/auth/Oauth2Helper.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.auth; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.Student; 4 | import cn.edu.gzmu.authserver.model.entity.SysUser; 5 | import cn.edu.gzmu.authserver.model.entity.Teacher; 6 | import cn.edu.gzmu.authserver.repository.StudentRepository; 7 | import cn.edu.gzmu.authserver.repository.TeacherRepository; 8 | import lombok.NonNull; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.core.GrantedAuthority; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 14 | import org.springframework.security.oauth2.provider.token.TokenStore; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.stream.Collectors; 18 | 19 | import static cn.edu.gzmu.authserver.model.constant.SecurityConstants.*; 20 | 21 | /** 22 | * @author EchoCow 23 | * @date 2019/8/6 下午12:41 24 | */ 25 | @Component 26 | @RequiredArgsConstructor 27 | public class Oauth2Helper { 28 | private final @NonNull StudentRepository studentRepository; 29 | private final @NonNull TeacherRepository teacherRepository; 30 | private final @NonNull TokenStore tokenStore; 31 | 32 | public Student student() { 33 | if (noRole(ROLE_STUDENT)) { 34 | throw new UsernameNotFoundException("找不到当前用户的学生信息"); 35 | } 36 | SysUser details = (SysUser) SecurityContextHolder.getContext().getAuthentication().getDetails(); 37 | return studentRepository.findFirstByUserId(details.getId()).orElseThrow( 38 | () -> new UsernameNotFoundException("找不到当前用户的学生信息")); 39 | } 40 | 41 | public Teacher teacher() { 42 | if (noRole(ROLE_TEACHER)) { 43 | throw new UsernameNotFoundException("找不到当前用户的教师信息"); 44 | } 45 | SysUser details = (SysUser) SecurityContextHolder.getContext().getAuthentication().getDetails(); 46 | return teacherRepository.findFirstByUserId(details.getId()).orElseThrow( 47 | () -> new UsernameNotFoundException("找不到当前用户的教师信息")); 48 | } 49 | 50 | public boolean noRole(String roleName) { 51 | return !SecurityContextHolder.getContext().getAuthentication().getAuthorities() 52 | .stream().map(GrantedAuthority::getAuthority) 53 | .collect(Collectors.toList()) 54 | .contains(roleName); 55 | } 56 | 57 | 58 | /** 59 | * 如果携带了 clientId,清除 token 60 | * ——> 一处退出,处处退出 61 | * 62 | * @param clientId clientId 63 | * @param authentication authentication 64 | */ 65 | public void safeLogout(String clientId, Authentication authentication) { 66 | tokenStore 67 | .findTokensByClientIdAndUserName(clientId, authentication.getName()) 68 | .forEach(oAuth2AccessToken -> { 69 | tokenStore.removeAccessToken(oAuth2AccessToken); 70 | tokenStore.removeRefreshToken(oAuth2AccessToken.getRefreshToken()); 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.NonNull; 5 | import lombok.RequiredArgsConstructor; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.springframework.http.HttpEntity; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.bind.annotation.RestController; 14 | import org.springframework.web.method.HandlerMethod; 15 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 16 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.util.Map; 20 | 21 | /** 22 | * api 获取当前授权服务器的 api 信息 23 | * 24 | * @author EchoCow 25 | * @date 2019/8/7 上午10:19 26 | */ 27 | @RestController 28 | @RequiredArgsConstructor 29 | public class HomeController { 30 | 31 | private final @NonNull PasswordEncoder passwordEncoder; 32 | private final @NonNull HttpServletRequest request; 33 | private final @NonNull RequestMappingHandlerMapping requestMappingHandlerMapping; 34 | private final @NonNull FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping; 35 | 36 | @GetMapping("/") 37 | public HttpEntity home() { 38 | Map endpointHandlerMethods = frameworkEndpointHandlerMapping.getHandlerMethods(); 39 | Map applicationHandlerMethods = requestMappingHandlerMapping.getHandlerMethods(); 40 | JSONObject result = new JSONObject(); 41 | endpointHandlerMethods.forEach((key, value) -> 42 | result.put(value.getMethod().getName(), getBaseUrl() + getPath(key))); 43 | applicationHandlerMethods.forEach((key, value) -> 44 | result.put(value.getMethod().getName(), getBaseUrl() + getPath(key))); 45 | return ResponseEntity.ok(result); 46 | } 47 | 48 | @GetMapping("/encrypt") 49 | public HttpEntity encrypt(@RequestParam String password) { 50 | JSONObject result = new JSONObject(); 51 | result.put("password", password); 52 | result.put("encrypt", passwordEncoder.encode(password)); 53 | return ResponseEntity.ok(result); 54 | } 55 | 56 | private String getBaseUrl() { 57 | StringBuffer url = request.getRequestURL(); 58 | if (StringUtils.endsWith(url, "/")) { 59 | return StringUtils.substringBeforeLast(url.toString(), "/"); 60 | } 61 | return url.toString(); 62 | } 63 | 64 | private String getPath(RequestMappingInfo requestMappingInfo) { 65 | return requestMappingInfo.getPatternsCondition() 66 | .getPatterns() 67 | .stream() 68 | .findFirst() 69 | .orElse(""); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import cn.edu.gzmu.authserver.model.entity.Student; 4 | import cn.edu.gzmu.authserver.model.entity.SysData; 5 | import cn.edu.gzmu.authserver.model.entity.SysUser; 6 | import cn.edu.gzmu.authserver.service.AuthService; 7 | import cn.edu.gzmu.authserver.util.VerifyParameter; 8 | import com.alibaba.fastjson.JSONObject; 9 | import lombok.NonNull; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.http.HttpEntity; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.security.access.prepost.PreAuthorize; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import javax.validation.constraints.NotNull; 20 | import java.security.Principal; 21 | 22 | /** 23 | * 授权信息 24 | * 25 | * @author echo 26 | * @version 1.0 27 | * @date 19-4-16 20:46 28 | */ 29 | @RestController 30 | @RequestMapping("/auth") 31 | @RequiredArgsConstructor 32 | public class AuthController { 33 | 34 | private final @NonNull AuthService authService; 35 | private final @NonNull PasswordEncoder passwordEncoder; 36 | 37 | @PostMapping("/register") 38 | @VerifyParameter( 39 | required = { 40 | "user.name#用户名称不能为空!", 41 | "student.id#学生id为必填项!", 42 | "student.name#学生名称为必填项!", 43 | "user.email#用户邮箱为必填项!", 44 | "user.phone#用户手机号为必填项!", 45 | "school.id#学校为必填项!" 46 | }, 47 | equal = {"school.type|1#选择的数据类型必须为学校类型!"} 48 | ) 49 | public HttpEntity register(@NotNull @RequestBody JSONObject params) { 50 | return ResponseEntity.status(HttpStatus.CREATED).body( 51 | authService.register( 52 | params.getObject("user", SysUser.class), 53 | params.getObject("student", Student.class), 54 | params.getObject("school", SysData.class) 55 | ) 56 | ); 57 | } 58 | 59 | @GetMapping("/me") 60 | @PreAuthorize("isFullyAuthenticated()") 61 | public HttpEntity me(Principal principal) { 62 | if (!(principal instanceof OAuth2Authentication)) { 63 | return ResponseEntity.badRequest().build(); 64 | } 65 | OAuth2Authentication authentication = (OAuth2Authentication) principal; 66 | SysUser details = (SysUser) authentication.getDetails(); 67 | return ResponseEntity.ok(authService.userDetails(details.getId())); 68 | } 69 | 70 | @GetMapping("/password") 71 | public HttpEntity password(@RequestParam String password) { 72 | return ResponseEntity.ok(passwordEncoder.encode(password)); 73 | } 74 | 75 | @GetMapping("/match") 76 | public HttpEntity match(@RequestParam String password, @RequestParam String encode) { 77 | return ResponseEntity.ok(passwordEncoder.matches(password, encode)); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/util/SubMailUtils.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | import cn.edu.gzmu.authserver.model.properties.SmsConfig; 4 | import com.alibaba.fastjson.JSONObject; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.http.HttpResponse; 7 | import org.apache.http.client.HttpClient; 8 | import org.apache.http.client.methods.HttpPost; 9 | import org.apache.http.entity.StringEntity; 10 | import org.apache.http.impl.client.HttpClients; 11 | import org.apache.http.util.EntityUtils; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.scheduling.annotation.Async; 14 | import org.springframework.scheduling.annotation.AsyncResult; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.io.IOException; 18 | import java.util.concurrent.Future; 19 | 20 | /** 21 | * sub mail 工具类 22 | * 23 | * @author echo 24 | * @version 1.0 25 | * @date 19-5-7 11:31 26 | */ 27 | @Slf4j 28 | @Component 29 | public class SubMailUtils { 30 | 31 | private final SmsConfig smsConfig; 32 | private static final HttpClient HTTP_CLIENT = HttpClients.createDefault(); 33 | private static final String X_SEND = "https://api.mysubmail.com/message/xsend"; 34 | private static final String MULTI_X_SEND = "https://api.mysubmail.com/message/multixsend"; 35 | 36 | @Autowired 37 | public SubMailUtils(SmsConfig smsConfig) { 38 | this.smsConfig = smsConfig; 39 | } 40 | 41 | /** 42 | * 发送一条信息 43 | * 44 | * @param to 接收人 45 | * @param vars 模板变量 46 | */ 47 | @Async 48 | public Future sendActionMessage(String to, JSONObject vars) { 49 | HttpPost httpPost = new HttpPost(X_SEND); 50 | JSONObject jsonParam = appInfo(smsConfig.getActionTemplate()); 51 | jsonParam.put("to", to); 52 | jsonParam.put("vars", vars); 53 | httpPost.setEntity(entityBuilder(jsonParam.toJSONString())); 54 | HttpResponse resp; 55 | try { 56 | resp = HTTP_CLIENT.execute(httpPost); 57 | String response = EntityUtils.toString(resp.getEntity(), "UTF-8"); 58 | log.debug(response); 59 | JSONObject result = JSONObject.parseObject(response); 60 | String res = String.format("向 %s 发送短信结果: %s", to, resp.getStatusLine().getStatusCode() == 200 && 61 | "success".equals(result.getString("status")) ? "成功" : "失败"); 62 | log.debug(res); 63 | return new AsyncResult<>(res); 64 | } catch (IOException e) { 65 | log.error(e.getMessage()); 66 | return new AsyncResult<>("短信发送失败:" + e.getMessage()); 67 | } 68 | } 69 | 70 | private JSONObject appInfo(String project) { 71 | JSONObject param = new JSONObject(); 72 | param.put("appid", smsConfig.getAppId()); 73 | param.put("signature", smsConfig.getAppKey()); 74 | param.put("project", project); 75 | return param; 76 | } 77 | 78 | private StringEntity entityBuilder(String param) { 79 | StringEntity entity = new StringEntity(param, "UTF-8"); 80 | entity.setContentEncoding("UTF-8"); 81 | entity.setContentType("application/json"); 82 | return entity; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 贵州民族大学——欢迎登录 5 | 6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 欢迎登录 17 | 18 | 21 | 22 | 23 |

24 |
25 | 26 | 27 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 忘记密码 37 | 38 | 登录 39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/util/EmailUtils.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.util; 2 | 3 | import cn.edu.gzmu.authserver.model.properties.EmailConfig; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.mail.SimpleMailMessage; 6 | import org.springframework.mail.javamail.JavaMailSender; 7 | import org.springframework.mail.javamail.MimeMessageHelper; 8 | import org.springframework.scheduling.annotation.Async; 9 | import org.springframework.scheduling.annotation.AsyncResult; 10 | import org.springframework.stereotype.Component; 11 | import org.thymeleaf.TemplateEngine; 12 | import org.thymeleaf.context.Context; 13 | 14 | import javax.mail.MessagingException; 15 | import javax.mail.internet.MimeMessage; 16 | import java.util.Map; 17 | import java.util.concurrent.Future; 18 | 19 | /** 20 | * @author Japoul 21 | * @version 1.0 22 | * @date 2019-05-21 14:45 23 | */ 24 | @Component 25 | @Async 26 | @Slf4j 27 | public class EmailUtils { 28 | 29 | private final JavaMailSender javaMailSender; 30 | private final EmailConfig emailConfig; 31 | private final TemplateEngine templateEngine; 32 | 33 | public EmailUtils(JavaMailSender javaMailSender, EmailConfig emailConfig, TemplateEngine templateEngine) { 34 | this.javaMailSender = javaMailSender; 35 | this.emailConfig = emailConfig; 36 | this.templateEngine = templateEngine; 37 | } 38 | 39 | /** 40 | * 简单文字邮件发送 41 | * 42 | * @param toEmail 接收者邮箱 43 | * @param subject 邮件主题 44 | * @param content 邮件内容 45 | */ 46 | public void sendSimpleMail(String toEmail, String subject, String content) { 47 | SimpleMailMessage message = new SimpleMailMessage(); 48 | message.setFrom(emailConfig.getUsername()); 49 | message.setTo(toEmail); 50 | message.setSubject(subject); 51 | message.setText(content); 52 | javaMailSender.send(message); 53 | } 54 | 55 | /** 56 | * 发送带模板的邮件 57 | * 58 | * @param toEmail 接收者邮箱 59 | * @param type 邮件类型 60 | * @param subject 邮件主题 61 | * @param template 邮件模板名称(默认资源路径下) 62 | * @param variables 模板内变量集合 63 | */ 64 | public Future sendTemplateMail(String toEmail, String type, String subject, 65 | String template, Map variables) { 66 | MimeMessage message = javaMailSender.createMimeMessage(); 67 | Context context = new Context(); 68 | variables.forEach(context::setVariable); 69 | context.setVariable("type", type); 70 | String content = templateEngine.process(template, context); 71 | try { 72 | MimeMessageHelper messageHelper = new MimeMessageHelper(message, true); 73 | messageHelper.setFrom(emailConfig.getUsername()); 74 | messageHelper.setTo(toEmail); 75 | messageHelper.setSubject(subject); 76 | messageHelper.setText(content, true); 77 | javaMailSender.send(message); 78 | log.debug("向 {} 发送 {} 邮件成功", toEmail, type); 79 | return new AsyncResult<>("邮件发送成功"); 80 | } catch (MessagingException e) { 81 | e.printStackTrace(); 82 | log.warn("向 {} 发送 {} 邮件失败", toEmail, type); 83 | return new AsyncResult<>("邮件发送失败"); 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | true 12 | ${LOG_PATH}/info/${LOG_INFO_FILE}} 13 | 14 | 15 | ERROR 16 | 17 | DENY 18 | 19 | ACCEPT 20 | 21 | 22 | 23 | ${LOG_PATH}/info/info.%d{yyyy-MM-dd}.%i.log.gz 24 | 25 | 10MB 26 | 27 | 10GB 28 | 29 | 30 30 | 31 | 32 | 33 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 34 | 35 | UTF-8 36 | 37 | 38 | 39 | 40 | 41 | 42 | true 43 | ${LOG_PATH}/error/${LOG_ERROR_FILE} 44 | 45 | ERROR 46 | 47 | 48 | 49 | ${LOG_PATH}/error/error.%d{yyyy-MM-dd}.%i.log.gz 50 | 51 | 10MB 52 | 53 | 10GB 54 | 55 | 30 56 | 57 | 58 | 59 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 60 | 61 | UTF-8 62 | 63 | 64 | 65 | 66 | %boldYellow(%d{yyyy-MM-dd HH:mm:ss.SSS}) %boldCyan(%-5level) --- %highlight([%thread]) %magenta(%logger{50}) - %msg%n 67 | UTF-8 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/TeacherController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 4 | import cn.edu.gzmu.authserver.model.entity.SysUser; 5 | import cn.edu.gzmu.authserver.model.entity.Teacher; 6 | import cn.edu.gzmu.authserver.model.exception.ResourceException; 7 | import cn.edu.gzmu.authserver.repository.SysUserRepository; 8 | import cn.edu.gzmu.authserver.repository.TeacherRepository; 9 | import cn.edu.gzmu.authserver.util.MapUtils; 10 | import com.alibaba.fastjson.JSONObject; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.rest.webmvc.RepositoryRestController; 13 | import org.springframework.http.HttpEntity; 14 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | 20 | import java.util.Comparator; 21 | import java.util.List; 22 | import java.util.stream.Collectors; 23 | 24 | import static org.springframework.http.ResponseEntity.ok; 25 | 26 | /** 27 | * @author EchoCow 28 | * @date 2019/8/4 下午8:49 29 | */ 30 | @RepositoryRestController 31 | @RequiredArgsConstructor 32 | @RequestMapping(AuthConstant.TEACHER) 33 | public class TeacherController { 34 | 35 | private final SysUserRepository sysUserRepository; 36 | private final TeacherRepository teacherRepository; 37 | private final static String AVATAR = "avatar"; 38 | private final static String IMAGE = "image"; 39 | private final static String PHONE = "phone"; 40 | private final static String EMAIL = "email"; 41 | private final static String NAME = "name"; 42 | private final static String ID = "id"; 43 | 44 | @GetMapping("/id/{id}") 45 | public HttpEntity userId(@PathVariable Long id) { 46 | final SysUser user = sysUserRepository.findById(id) 47 | .orElseThrow(() -> new UsernameNotFoundException("找不到当前用户信息")); 48 | final Teacher teacher = teacherRepository.findFirstByUserId(id) 49 | .orElseThrow(() -> new UsernameNotFoundException("找不到当前用户的教师信息")); 50 | final JSONObject result = new JSONObject(); 51 | result.put(NAME, teacher.getName()); 52 | result.put(IMAGE, user.getImage()); 53 | result.put(AVATAR, user.getAvatar()); 54 | result.put(PHONE, user.getPhone()); 55 | result.put(EMAIL, user.getEmail()); 56 | return ok(result); 57 | } 58 | 59 | @GetMapping("/ids") 60 | public HttpEntity userIds(@RequestParam List ids) { 61 | final List users = sysUserRepository.findAllByIdIn(ids) 62 | .stream().sorted(Comparator.comparingLong(SysUser::getId)) 63 | .map(MapUtils::userBaseJson).collect(Collectors.toList()); 64 | final List teachers = teacherRepository.findAllByUserIdIn(ids) 65 | .stream().sorted(Comparator.comparingLong(Teacher::getUserId)) 66 | .map(t -> { 67 | final JSONObject teacher = new JSONObject(); 68 | teacher.put(ID, t.getUserId()); 69 | teacher.put(NAME, t.getName()); 70 | return teacher; 71 | }).collect(Collectors.toList()); 72 | if (users.size() != teachers.size()) { 73 | throw new ResourceException("资源错误"); 74 | } 75 | return ok(MapUtils.userBaseList(users, teachers)); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/Teacher.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.ToString; 7 | import lombok.experimental.Accessors; 8 | import org.hibernate.annotations.Where; 9 | 10 | import javax.persistence.Entity; 11 | import javax.persistence.Table; 12 | import javax.persistence.Transient; 13 | import javax.validation.constraints.Size; 14 | import java.io.Serializable; 15 | 16 | /** 17 | * @author echo 18 | * @version 1.0.0 19 | * @date 19-6-11 下午5:27 20 | */ 21 | @Data 22 | @ToString(callSuper = true) 23 | @Table(name = "teacher") 24 | @Entity(name = "teacher") 25 | @Where(clause = "is_enable = true") 26 | @EqualsAndHashCode(callSuper = true) 27 | @Accessors(chain = true) 28 | public class Teacher extends BaseEntity implements Serializable { 29 | 30 | /** 31 | * 用户编号 32 | */ 33 | private java.lang.Long userId; 34 | 35 | /** 36 | * 学校编号 37 | */ 38 | private java.lang.Long schoolId; 39 | 40 | /** 41 | * 学院编号 42 | */ 43 | private java.lang.Long collegeId; 44 | 45 | /** 46 | * 系部编号 47 | */ 48 | private java.lang.Long depId; 49 | 50 | /** 51 | * 性别 52 | */ 53 | @Size(max = 255, message = "gender 不能大于 255 位") 54 | private java.lang.String gender; 55 | 56 | /** 57 | * 出生日期 58 | */ 59 | @javax.validation.constraints.Past 60 | private java.time.LocalDate birthday; 61 | 62 | /** 63 | * 民族 64 | */ 65 | private java.lang.Long nation; 66 | 67 | /** 68 | * 学位 69 | */ 70 | @Size(max = 255, message = "degree 不能大于 255 位") 71 | private java.lang.Long degree; 72 | 73 | /** 74 | * 最后学历 75 | */ 76 | @Size(max = 255, message = "academic 不能大于 255 位") 77 | private java.lang.Long academic; 78 | 79 | /** 80 | * 最后学历毕业时间 81 | */ 82 | private java.time.LocalDate graduationDate; 83 | 84 | /** 85 | * 最后学历所学专业 86 | */ 87 | @Size(max = 255, message = "major 不能大于 255 位") 88 | private java.lang.String major; 89 | 90 | /** 91 | * 最后学历毕业院校 92 | */ 93 | @Size(max = 255, message = "graduateInstitution 不能大于 255 位") 94 | private java.lang.String graduateInstitution; 95 | 96 | /** 97 | * 主要研究方向 98 | */ 99 | @Size(max = 255, message = "majorResearch 不能大于 255 位") 100 | private java.lang.String majorResearch; 101 | 102 | /** 103 | * 个人简历 104 | */ 105 | @Size(max = 2048, message = "resume 不能大于 2048 位") 106 | private java.lang.String resume; 107 | 108 | /** 109 | * 参加工作时间 110 | */ 111 | private java.time.LocalDate workDate; 112 | 113 | /** 114 | * 职称 115 | */ 116 | @Size(max = 255, message = "profTitle 不能大于 255 位") 117 | private java.lang.Long profTitle; 118 | 119 | /** 120 | * 职称评定时间 121 | */ 122 | private java.time.LocalDate profTitleAssDate; 123 | 124 | /** 125 | * 是否学术学科带头人 126 | */ 127 | private java.lang.Boolean isAcademicLeader = false; 128 | 129 | /** 130 | * 所属学科门类 131 | */ 132 | @Size(max = 255, message = "subjectCategory 不能大于 255 位") 133 | private java.lang.String subjectCategory; 134 | 135 | /** 136 | * 身份证号码 137 | */ 138 | @Size(max = 18, message = "idNumber 不能大于 18 位") 139 | private java.lang.String idNumber; 140 | 141 | @Transient 142 | private SysData school; 143 | 144 | @Transient 145 | private SysData college; 146 | 147 | @Transient 148 | private SysData dep; 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/service/impl/ClientDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.service.impl; 2 | 3 | import cn.edu.gzmu.authserver.service.ClientRegistrationService; 4 | import cn.edu.gzmu.authserver.model.entity.ClientDetails; 5 | import cn.edu.gzmu.authserver.repository.ClientDetailsRepository; 6 | import lombok.NonNull; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; 11 | import org.springframework.security.oauth2.provider.ClientDetailsService; 12 | import org.springframework.security.oauth2.provider.ClientRegistrationException; 13 | import org.springframework.security.oauth2.provider.NoSuchClientException; 14 | 15 | import java.util.List; 16 | import java.util.Objects; 17 | 18 | /** 19 | * 自定义 jdbc 读取客户端信息. 20 | * 21 | * @author EchoCow 22 | * @version 1.0 23 | * @date 2019/12/23 下午10:07 24 | */ 25 | @Slf4j 26 | @RequiredArgsConstructor 27 | public class ClientDetailsServiceImpl implements ClientDetailsService, ClientRegistrationService { 28 | 29 | private final @NonNull ClientDetailsRepository clientDetailsRepository; 30 | private final @NonNull PasswordEncoder passwordEncoder; 31 | private static final String NO_CLIENT = "No client found with id = "; 32 | 33 | @Override 34 | public org.springframework.security.oauth2.provider.ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { 35 | org.springframework.security.oauth2.provider.ClientDetails clientDetails = clientDetailsRepository 36 | .findFirstByClientId(clientId).orElseThrow(() -> new NoSuchClientException(NO_CLIENT + clientId)) 37 | .buildSpringClientDetails(); 38 | log.debug("获取客户端信息:{}", clientDetails); 39 | return clientDetails; 40 | } 41 | 42 | @Override 43 | public void saveOrUpdateClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException, NoSuchClientException { 44 | if (Objects.isNull(clientDetails.getId())) { 45 | log.debug("添加客户端信息:{}", clientDetails); 46 | clientDetails.setClientSecret(passwordEncoder.encode(clientDetails.getClientSecret())); 47 | } else { 48 | log.debug("更新客户端信息:{}", clientDetails); 49 | ClientDetails client = clientDetailsRepository.findById(clientDetails.getId()) 50 | .orElseThrow(() -> new NoSuchClientException(NO_CLIENT + clientDetails.getId())); 51 | clientDetails.setClientSecret(client.getClientSecret()); 52 | } 53 | clientDetailsRepository.save(clientDetails); 54 | } 55 | 56 | @Override 57 | public void updateClientSecret(String clientId, String clientSecret) throws NoSuchClientException { 58 | ClientDetails client = clientDetailsRepository 59 | .findFirstByClientId(clientId).orElseThrow(() -> new NoSuchClientException(NO_CLIENT + clientId)); 60 | client.setClientSecret(passwordEncoder.encode(clientSecret)); 61 | clientDetailsRepository.save(client); 62 | } 63 | 64 | @Override 65 | public void removeClientDetails(String clientId) throws NoSuchClientException { 66 | ClientDetails client = clientDetailsRepository 67 | .findFirstByClientId(clientId).orElseThrow(() -> new NoSuchClientException(NO_CLIENT + clientId)); 68 | client.setIsEnable(false); 69 | clientDetailsRepository.save(client); 70 | } 71 | 72 | @Override 73 | public List listClientDetails() { 74 | return clientDetailsRepository.findAll(); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/controller/SysUserController.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.controller; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.AuthConstant; 4 | import cn.edu.gzmu.authserver.model.entity.Student; 5 | import cn.edu.gzmu.authserver.model.entity.SysUser; 6 | import cn.edu.gzmu.authserver.model.entity.Teacher; 7 | import cn.edu.gzmu.authserver.repository.StudentRepository; 8 | import cn.edu.gzmu.authserver.repository.SysUserRepository; 9 | import cn.edu.gzmu.authserver.repository.TeacherRepository; 10 | import cn.edu.gzmu.authserver.util.MapUtils; 11 | import com.alibaba.fastjson.JSONObject; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.data.rest.webmvc.RepositoryRestController; 14 | import org.springframework.http.HttpEntity; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | 20 | import java.util.*; 21 | import java.util.stream.Collectors; 22 | 23 | import static java.util.Comparator.comparingLong; 24 | import static org.springframework.http.ResponseEntity.notFound; 25 | import static org.springframework.http.ResponseEntity.ok; 26 | 27 | /** 28 | * . 29 | * 30 | * @author EchoCow 31 | * @date 2020/4/17 下午1:18 32 | */ 33 | @RequiredArgsConstructor 34 | @RepositoryRestController 35 | @RequestMapping(AuthConstant.SYS_USER) 36 | public class SysUserController { 37 | 38 | private final StudentRepository studentRepository; 39 | private final TeacherRepository teacherRepository; 40 | private final SysUserRepository sysUserRepository; 41 | private final static String NAME = "name"; 42 | private final static String ID = "id"; 43 | 44 | /** 45 | * 获取用户的实体信息 46 | * 47 | * @param id id 48 | * @return 实体 49 | */ 50 | @GetMapping("/id/{id}") 51 | public HttpEntity userId(@PathVariable Long id) { 52 | Optional student = studentRepository.findFirstByUserId(id); 53 | if (student.isPresent()) { 54 | return ok().body(student.get().setIdNumber(null)); 55 | } 56 | Optional teacher = teacherRepository.findFirstByUserId(id); 57 | if (teacher.isPresent()) { 58 | return ok().body(teacher.get().setIdNumber(null)); 59 | } 60 | return notFound().build(); 61 | } 62 | 63 | @GetMapping("/info") 64 | public HttpEntity userInfoIds(@RequestParam List ids) { 65 | List users = sysUserRepository.findAllByIdIn(ids) 66 | .stream().sorted(comparingLong(SysUser::getId)) 67 | .map(MapUtils::userBaseJson).collect(Collectors.toList()); 68 | List students = studentRepository.findAllByUserIdIn(ids).stream() 69 | .map(s -> { 70 | final JSONObject student = new JSONObject(); 71 | student.put(ID, s.getUserId()); 72 | student.put(NAME, s.getName()); 73 | return student; 74 | }).collect(Collectors.toList()); 75 | List teachers = teacherRepository.findAllByUserIdIn(ids).stream() 76 | .map(t -> { 77 | final JSONObject teacher = new JSONObject(); 78 | teacher.put(ID, t.getUserId()); 79 | teacher.put(NAME, t.getName()); 80 | return teacher; 81 | }).collect(Collectors.toList()); 82 | teachers.addAll(students); 83 | teachers.sort(Comparator.comparing(e -> e.getLong(ID))); 84 | return ok(MapUtils.userBaseList(users, teachers)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeFilter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 5 | import cn.edu.gzmu.authserver.model.exception.ValidateCodeException; 6 | import lombok.NonNull; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.http.HttpMethod; 10 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.util.AntPathMatcher; 13 | import org.springframework.util.StringUtils; 14 | import org.springframework.web.context.request.ServletWebRequest; 15 | import org.springframework.web.filter.OncePerRequestFilter; 16 | 17 | import javax.servlet.FilterChain; 18 | import javax.servlet.ServletException; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.Set; 25 | 26 | /** 27 | * 验证码过滤器。 28 | * 29 | *

继承于 {@link OncePerRequestFilter} 确保在一次请求只通过一次filter

30 | *

需要配置指定拦截路径,默认拦截 POST 请求

31 | * 32 | * @author EchoCow 33 | * @version 1.0 34 | * @date 19-4-14 10:56 35 | * @deprecated 更换验证方式而过时 36 | */ 37 | @Slf4j 38 | //@Component 39 | @Deprecated 40 | @RequiredArgsConstructor 41 | public class ValidateCodeFilter extends OncePerRequestFilter { 42 | 43 | private final @NonNull AuthenticationFailureHandler authFailureHandle; 44 | private final @NonNull ValidateCodeProcessorHolder validateCodeProcessorHolder; 45 | private Map urlMap = new HashMap<>(); 46 | private AntPathMatcher antPathMatcher = new AntPathMatcher(); 47 | 48 | 49 | @Override 50 | public void afterPropertiesSet() throws ServletException { 51 | super.afterPropertiesSet(); 52 | // 路径拦截 53 | urlMap.put(SecurityConstants.LOGIN_PROCESSING_URL_SMS, ValidateCodeType.SMS); 54 | urlMap.put(SecurityConstants.REGISTER_PROCESSING_URL_EMAIL, ValidateCodeType.EMAIL); 55 | } 56 | 57 | @Override 58 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 59 | FilterChain filterChain) throws ServletException, IOException { 60 | ValidateCodeType validateCodeType = getValidateCodeType(request); 61 | if (validateCodeType != null) { 62 | try { 63 | log.debug("请求需要验证!验证请求:" + request.getRequestURI() + "验证类型:" + validateCodeType); 64 | validateCodeProcessorHolder.findValidateCodeProcessor(validateCodeType) 65 | .validate(new ServletWebRequest(request, response)); 66 | log.debug("验证码通过!"); 67 | } catch (ValidateCodeException e) { 68 | // 授权失败处理器接受处理 69 | authFailureHandle.onAuthenticationFailure(request, response, e); 70 | return; 71 | } 72 | } 73 | // 放行 74 | filterChain.doFilter(request, response); 75 | } 76 | 77 | /** 78 | * 获取验证码类型 79 | * 80 | * @param request 请求 81 | * @return 验证码类型 82 | */ 83 | private ValidateCodeType getValidateCodeType(HttpServletRequest request) { 84 | if (StringUtils.endsWithIgnoreCase(request.getMethod(), HttpMethod.POST.name())) { 85 | Set urls = urlMap.keySet(); 86 | for (String url : urls) { 87 | if (antPathMatcher.match(url, request.getRequestURI())) { 88 | return urlMap.get(url); 89 | } 90 | } 91 | } 92 | return null; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/ValidateCodeGrantTypeFilter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.SecurityConstants; 4 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 5 | import cn.edu.gzmu.authserver.model.exception.ValidateCodeException; 6 | import lombok.NonNull; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.security.oauth2.common.util.OAuth2Utils; 12 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 13 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 14 | import org.springframework.security.web.util.matcher.RequestMatcher; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.web.context.request.ServletWebRequest; 17 | import org.springframework.web.filter.OncePerRequestFilter; 18 | 19 | import javax.servlet.FilterChain; 20 | import javax.servlet.ServletException; 21 | import javax.servlet.http.HttpServletRequest; 22 | import javax.servlet.http.HttpServletResponse; 23 | import java.io.IOException; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | import java.util.Objects; 27 | 28 | /** 29 | * 验证码过滤器。 30 | * 31 | *

继承于 {@link OncePerRequestFilter} 确保在一次请求只通过一次filter

32 | *

需要配置指定拦截路径,默认拦截 POST 请求

33 | * 34 | * @author EchoCow 35 | * @version 1.0 36 | * @date 19-4-14 10:56 37 | */ 38 | @Slf4j 39 | @Component 40 | @RequiredArgsConstructor 41 | public class ValidateCodeGrantTypeFilter extends OncePerRequestFilter { 42 | 43 | private final @NonNull AuthenticationFailureHandler authFailureHandle; 44 | private final @NonNull ValidateCodeProcessorHolder validateCodeProcessorHolder; 45 | private Map typeMap = new HashMap<>(); 46 | private RequestMatcher requestMatcher = new AntPathRequestMatcher("/oauth/token", HttpMethod.POST.name()); 47 | 48 | 49 | @Override 50 | public void afterPropertiesSet() throws ServletException { 51 | super.afterPropertiesSet(); 52 | // 类型匹配 53 | typeMap.put(SecurityConstants.GRANT_TYPE_SMS, ValidateCodeType.SMS); 54 | typeMap.put(SecurityConstants.GRANT_TYPE_EMAIL, ValidateCodeType.EMAIL); 55 | } 56 | 57 | 58 | @Override 59 | protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, 60 | @NotNull FilterChain filterChain) throws ServletException, IOException { 61 | if (requestMatcher.matches(request)) { 62 | ValidateCodeType validateCodeType = getGrantType(request); 63 | if (Objects.nonNull(validateCodeType)) { 64 | try { 65 | log.debug("请求需要验证!验证请求:" + request.getRequestURI() + "验证类型:" + validateCodeType); 66 | validateCodeProcessorHolder.findValidateCodeProcessor(validateCodeType) 67 | .validate(new ServletWebRequest(request, response)); 68 | log.debug("验证码通过!"); 69 | } catch (ValidateCodeException e) { 70 | // 授权失败处理器接受处理 71 | log.debug("验证失败!"); 72 | authFailureHandle.onAuthenticationFailure(request, response, e); 73 | return; 74 | } 75 | } 76 | } 77 | // 放行 78 | filterChain.doFilter(request, response); 79 | } 80 | 81 | /** 82 | * 获取验证码类型 83 | * 84 | * @param request 请求 85 | * @return 验证码类型 86 | */ 87 | private ValidateCodeType getGrantType(HttpServletRequest request) { 88 | return typeMap.get(request.getParameter(OAuth2Utils.GRANT_TYPE)); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/model/entity/Student.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.model.entity; 2 | 3 | import cn.edu.gzmu.authserver.base.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.ToString; 7 | import lombok.experimental.Accessors; 8 | import org.hibernate.annotations.Where; 9 | 10 | import javax.persistence.Entity; 11 | import javax.persistence.Table; 12 | import javax.persistence.Transient; 13 | import javax.validation.constraints.Size; 14 | import java.io.Serializable; 15 | 16 | /** 17 | * @author echo 18 | * @version 1.0.0 19 | * @date 19-6-11 下午5:27 20 | */ 21 | @Data 22 | @ToString(callSuper = true) 23 | @Table(name = "student") 24 | @Entity(name = "student") 25 | @Where(clause = "is_enable = true") 26 | @EqualsAndHashCode(callSuper = true) 27 | @Accessors(chain = true) 28 | public class Student extends BaseEntity implements Serializable { 29 | 30 | /** 31 | * 用户编号 32 | */ 33 | private java.lang.Long userId; 34 | 35 | /** 36 | * 学校编号 37 | */ 38 | @javax.validation.constraints.NotNull(message = "schoolId 学校编号 为必填项") 39 | private java.lang.Long schoolId; 40 | 41 | /** 42 | * 学院编号 43 | */ 44 | @javax.validation.constraints.NotNull(message = "collegeId 学院编号 为必填项") 45 | private java.lang.Long collegeId; 46 | 47 | /** 48 | * 系部编号 49 | */ 50 | @javax.validation.constraints.NotNull(message = "depId 系部编号 为必填项") 51 | private java.lang.Long depId; 52 | 53 | /** 54 | * 专业编号 55 | */ 56 | @javax.validation.constraints.NotNull(message = "specialtyId 专业编号 为必填项") 57 | private java.lang.Long specialtyId; 58 | 59 | /** 60 | * 班级编号 61 | */ 62 | @javax.validation.constraints.NotNull(message = "classesId 班级编号 为必填项") 63 | private java.lang.Long classesId; 64 | 65 | /** 66 | * 学号 67 | */ 68 | @javax.validation.constraints.NotNull(message = "no 学号 为必填项") 69 | @Size(max = 20, message = "no 不能大于 20 位") 70 | private java.lang.String no; 71 | 72 | /** 73 | * 性别 74 | */ 75 | @javax.validation.constraints.NotNull(message = "gender 性别 为必填项") 76 | @Size(max = 255, message = "gender 不能大于 255 位") 77 | private java.lang.String gender; 78 | 79 | /** 80 | * 身份证号码 81 | */ 82 | @javax.validation.constraints.NotNull(message = "idNumber 身份证号码 为必填项") 83 | @Size(max = 18, message = "idNumber 不能大于 18 位") 84 | private java.lang.String idNumber; 85 | 86 | /** 87 | * 出生日期 88 | */ 89 | @javax.validation.constraints.Past 90 | private java.time.LocalDate birthday; 91 | 92 | /** 93 | * 入校时间 94 | */ 95 | private java.time.LocalDate enterDate; 96 | 97 | /** 98 | * 最后学历 99 | */ 100 | private java.lang.Long academic; 101 | 102 | /** 103 | * 最后学历毕业时间 104 | */ 105 | private java.time.LocalDate graduationDate; 106 | 107 | /** 108 | * 学制 109 | */ 110 | private java.lang.Integer studyYear; 111 | 112 | /** 113 | * 最后学历毕业院校 114 | */ 115 | @Size(max = 255, message = "graduateInstitution 不能大于 255 位") 116 | private java.lang.String graduateInstitution; 117 | 118 | /** 119 | * 最后学历所学专业(若最后学历是高中,则不需要填写 120 | * 若最后学历是大专,则需要填写) 121 | */ 122 | @Size(max = 255, message = "originalMajor 不能大于 255 位") 123 | private java.lang.String originalMajor; 124 | 125 | /** 126 | * 个人简历 127 | */ 128 | @Size(max = 2048, message = "resume 不能大于 2048 位") 129 | private java.lang.String resume; 130 | 131 | /** 132 | * 谢谢哦啊跑 133 | */ 134 | @Transient 135 | private SysData school; 136 | 137 | /** 138 | * 学院 139 | */ 140 | @Transient 141 | private SysData college; 142 | 143 | /** 144 | * 系部 145 | */ 146 | @Transient 147 | private SysData dep; 148 | 149 | /** 150 | * 专业 151 | */ 152 | @Transient 153 | private SysData specialty; 154 | 155 | /** 156 | * 班级 157 | */ 158 | @Transient 159 | private SysData classes; 160 | 161 | /** 162 | * 用户 163 | */ 164 | @Transient 165 | private SysUser user; 166 | } 167 | -------------------------------------------------------------------------------- /docs/description/OAuth2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: OAuth2 3 | --- 4 | 5 | > 在使用之前,您还是需要对 OAuth2 开放授权协议具有一定的认识。 6 | 7 | ## 简述 8 | 9 | 在大多数情况之下,此协议是作为第三方客户端进行接入的时候进行使用,例如 QQ 的 OAuth2 授权码流程如下: 10 | 11 | 1. 在第三方客户端应用(例如某个社交网站,域名为 `example.com` )点击 “使用 QQ 帐号进行登录”。 12 | 2. 跳转到 QQ 登录的网页,此时域名为 `qq.com`,也就是到了 QQ 的 OAuth 授权服务器之上了,这个时候输入帐号密码就是在他的网页上面进行输入的就是安全的。 13 | 3. 在 `qq.com` 登录成功以后,他会跳转到第三方客户端指定的一个地址,我们称之为 **回调地址**,同时携带一个 **授权码** 的参数(大多数情况下是 url 参数)。 14 | 4. 我们在回调地址之中获取到 **授权码** 参数,然后携带这个参数以及我们第三方客户端的信息(一般是客户端id和客户端密钥)去请求指定的接口获取用户的个人信息或者登录凭证。 15 | 16 | 这样一个 OAuth2 的一个第三方客户端进行授权操作就完成了。在这个过程中有如下几点好处: 17 | 18 | 1. 保护用户账户信息安全:输入用户帐号密码的过程是在应用中完成的而不是第三方客户端那里完成的(即使用 QQ 登录时,帐号密码的输入是在 `qq.com` 的域名下完成的) 19 | 2. 当有新的客户端接入时变得极其容易,只需要添加客户端的信息即可。 20 | 3. 有效管理客户端权限:例如某些客户端只能够读取用户信息而不能够修改用户信息,完全可以通过使用客户端 id 进行具体的接口、权限管理 21 | 22 | ## 权限控制 23 | 24 | 那么在 OAuth2 我们如何来进行用户的权限管理和权限鉴定呢?在一般情况下,可以用通过 `scope` 来完成这么一个过程。 25 | 26 | > `scope` 可以简单可以复杂也可以完全没有。 27 | 28 | ### 不使用 scope 29 | 30 | 不使用 scope 意味着你所有的资源都是开放的,在这种情况下一般都是只提供 **读取** 接口,例如允许某个第三方客户端读取用户信息、角色信息等。 31 | 32 | - 好处:简单、快捷 33 | - 坏处:只能读取无法修改 34 | 35 | ### 简单 scope 36 | 37 | 简单的 `scope` 一般就只提供几种描述动作的操作符,最为常见的是 `READ`、`WRITE`、`ALL`;通过第三方客户端接入,每个客户端可以获取到不同的权限。 38 | 39 | 例如: A 应用可以修改用户的信息,其申请的 `scope` 应该为 `WRITE`,B 应用只能够读取用户信息,其申请的 `scope` 应该位 `READ`。 40 | 41 | - 好处:简单的资源操作控制 42 | - 坏处:没有对资源的具体操作限制 43 | 44 | ### 复杂的 scope 45 | 46 | 复杂的 `scope` 就涉及到对于某个资源的详细控制,可以把它看成 `ACL` 权限控制。 47 | 48 | - 比如 `message:READ` 表示对 `message` 的资源具有 `READ` 权限 49 | - 比如 `user:WRITE` 表示对 `user` 的资源具有 `WRITE` 权限 50 | 51 | 这样的权限控制基本覆盖大多数的使用情况 52 | 53 | - 好处:详细的资源权限控制 54 | - 坏处:实现具有一定难度,不可动态修改,灵活性较低 55 | 56 | ### RBAC 动态模型 57 | 58 | 在我们的应用中,涉及到了一个最为重要的东西:**角色(ROLE)**。我们使用 **RBAC(Role-based access control)** 进行我们的权限管理。 59 | 60 | ![rbac](/images/rbac.png) 61 | 62 | 所以在使用 `scope` 的情况对我们来说是不适用的,这种情况下我们如何去管理权限呢?我更希望使用 OAuth2 来完全的管理我们的用户角色等信息而不是再去引入一些其他的东西,我将它与 RBAC 授权模型进行搭配使用。如何做呢? 63 | 64 | 1. 授权服务器的资源只提供 **读** 权限,所有的客户端只能够读取用户的信息,而不能够修改任何信息。 65 | 2. 下发令牌时,提供用户用的 `scope` 信息以外的 `ROLE` 信息。 66 | 3. 提供一个独立客户端专门来进行修改授权服务器的资源,即:**授权中心**。 67 | 68 | 角色我们会给客户端,那么图中的 **资源** 应该是什么呢?那应该是第三方客户端的资源,由第三方客户端自己掌控。 69 | 70 | 换句话说,第三方客户端可以很自由的选择权限控制方式。 71 | 72 | 1. 使用 `scope`:我们不使用 `scope` 但是不代表第三方客户端不能使用,在客户端足够简单的情况下,可以使用 `scope` 来对你的应用进行权限控制。 73 | 2. 使用 `role`:在应用使用的时候,我们会下发给客户端用户的角色信息,客户端完全可以使用他进行 RBAC 权限控制 74 | 3. 使用自己的用户系统:如果你想完全脱离我们的应用使用,完全可以在得到我们给予的用户信息以后,自己创建用户并使用一套新的用户管理系统,这个也是 OAuth2 最为本质的使用方式。 75 | 76 | ## 授权模式 77 | 78 | 前面我们提到的 QQ 的栗子就是授权模式中的授权码模式。在我们的授权服务器中提供四种授权模式: 79 | 80 | 1. 授权码模式 81 | 2. 密码模式 82 | 3. 手机验证码模式 83 | 4. 邮箱验证码模式 84 | 85 | 对于不同的模式 86 | 87 | - 安全级别: 1 > 3 > 4 > 2 88 | - 复杂级别: 1 > 3 = 4 > 2 89 | - 上手难度: 1 > 2 = 3 = 4 90 | - 推荐指数: 1 > 2 > 3 = 4 91 | 92 | ### 授权码模式 93 | 94 | ```text 95 | 96 | +----------+ 97 | | Resource | 98 | | Owner | 99 | | | 100 | +----------+ 101 | ^ 102 | | 103 | (B) 104 | +----|-----+ Client Identifier +---------------+ 105 | | -+----(A)-- & Redirection URI ---->| | 106 | | User- | | Authorization | 107 | | Agent -+----(B)-- User authenticates --->| Server | 108 | | | | | 109 | | -+----(C)-- Authorization Code ---<| | 110 | +-|----|---+ +---------------+ 111 | | | ^ v 112 | (A) (C) | | 113 | | | | | 114 | ^ v | | 115 | +---------+ | | 116 | | |>---(D)-- Authorization Code ---------' | 117 | | Client | & Redirection URI | 118 | | | | 119 | | |<---(E)----- Access Token -------------------' 120 | +---------+ (w/ Optional Refresh Token) 121 | 122 | 123 | Note: The lines illustrating steps (A), (B), and (C) are broken into 124 | two parts as they pass through the user-agent. 125 | 126 | ``` -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzmu/authserver/validate/impl/AbstractValidateCodeProcessor.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzmu.authserver.validate.impl; 2 | 3 | import cn.edu.gzmu.authserver.model.constant.ValidateCodeType; 4 | import cn.edu.gzmu.authserver.validate.ValidateCode; 5 | import cn.edu.gzmu.authserver.validate.ValidateCodeGenerator; 6 | import cn.edu.gzmu.authserver.validate.ValidateCodeProcessor; 7 | import cn.edu.gzmu.authserver.validate.ValidateCodeRepository; 8 | import cn.edu.gzmu.authserver.model.exception.ValidateCodeException; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.context.request.ServletWebRequest; 12 | 13 | import java.util.Map; 14 | 15 | /** 16 | * 默认抽象的验证码处理器,不同类型的验证码必须继承此类。 17 | *

18 | * 提供了一套默认完整的生成、保存操作,对于不同的类型会有不同的发送操作,因此子类必须实现 19 | * 抽象 send 方法 20 | * 具体请参阅每个方法的详细注释 21 | * 22 | * @author EchoCow 23 | * @version 1.0 24 | * @date 19-4-14 11:37 25 | */ 26 | public abstract class AbstractValidateCodeProcessor 27 | implements ValidateCodeProcessor { 28 | 29 | private static final String CODE = "code"; 30 | 31 | @Autowired 32 | private ValidateCodeRepository validateCodeRepository; 33 | 34 | /** 35 | * 收集系统中所有的 {@link ValidateCodeGenerator} 接口实现。 36 | */ 37 | @Autowired 38 | private Map validateCodeGenerators; 39 | 40 | /** 41 | * 验证码创建逻辑 42 | * 43 | * @param request 请求 44 | * @throws Exception 异常 45 | */ 46 | @Override 47 | public void create(ServletWebRequest request) throws Exception { 48 | C generate = generate(request); 49 | save(request, generate); 50 | send(request, generate); 51 | } 52 | 53 | /** 54 | * 生成验证码 55 | * 56 | * @param request 请求 57 | * @return 验证码 58 | */ 59 | @SuppressWarnings("unchecked") 60 | private C generate(ServletWebRequest request) throws ValidateCodeException { 61 | String generatorName = getComponentName(ValidateCodeGenerator.class); 62 | ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName); 63 | if (validateCodeGenerator == null) { 64 | throw new ValidateCodeException("验证码生成器" + generatorName + "不存在。"); 65 | } 66 | return (C) validateCodeGenerator.generate(request); 67 | } 68 | 69 | /** 70 | * 验证器验证验证码 71 | * 72 | * @param request 被验证的请求 73 | */ 74 | @Override 75 | @SuppressWarnings("unchecked") 76 | public void validate(ServletWebRequest request) { 77 | ValidateCodeType validateCodeType = getValidateCodeType(); 78 | C code = (C) validateCodeRepository.get(request, validateCodeType); 79 | if (code == null) { 80 | throw new ValidateCodeException("获取验证码失败,请检查输入是否正确或重新发送!"); 81 | } 82 | if (!StringUtils.equalsIgnoreCase(code.getCode(), request.getParameter(CODE))) { 83 | throw new ValidateCodeException("验证码不正确,请重新输入!"); 84 | } 85 | // 暂时不清除验证码 86 | // validateCodeRepository.remove(request, validateCodeType); 87 | } 88 | 89 | /** 90 | * 保存验证码 91 | * 92 | * @param request 请求 93 | * @param validateCode 验证码 94 | */ 95 | private void save(ServletWebRequest request, C validateCode) { 96 | validateCodeRepository.save(request, validateCode, getValidateCodeType()); 97 | } 98 | 99 | /** 100 | * 发送验证码,由子类实现 101 | * 102 | * @param request 请求 103 | * @param validateCode 验证码 104 | * @throws Exception 异常 105 | */ 106 | protected abstract void send(ServletWebRequest request, C validateCode) throws Exception; 107 | 108 | /** 109 | * 根据请求 url 获取验证码类型 110 | * 111 | * @return 结果 112 | */ 113 | private ValidateCodeType getValidateCodeType() { 114 | String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor"); 115 | return ValidateCodeType.valueOf(type.toUpperCase()); 116 | } 117 | 118 | @SuppressWarnings("all") 119 | private String getComponentName(Class component) { 120 | return getValidateCodeType().toString().toLowerCase() + component.getSimpleName(); 121 | } 122 | } 123 | --------------------------------------------------------------------------------