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 extends GrantedAuthority> 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 |
19 | {{nameText}}
20 |
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 | 
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 |
--------------------------------------------------------------------------------