易水通用组件

易水通用组件

Posted by yishuifengxiao on 2019-07-24

在日常开发过程中,发现有一个重要的组件会被经常使用到,但是又没有一个比较好用的功能集合,在开发项目是需要反复配置,造成了大量不必要的重复性简单劳动,因此对日常使用到功能进行了一个通用封装,形成了【易水风萧通用组件】,方便后期项目开发。
易水风萧通用组件主要包含以下一些常用功能:

  • swagger-ui文档
  • 全局跨域支持
  • 全局异常捕获
  • 通用辅助工具
  • 验证码功能
  • spring security
  • oauth2
  • spring social (QQ登录 、微信登录)

在使用 易水风萧通用组件 之前,需要先在项目的pom依赖里加入以下配置:

1
2
3
4
5
<dependency>
<groupId>com.yishuifengxiao.common</groupId>
<artifactId>common-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

注意:在使用时请参考 https://mvnrepository.com/artifact/com.yishuifengxiao.common/common-spring-boot-starter 将版本号替换为最新版本。

本文档针对于3.0.0及后续版本,由于3.0.0版本更新内容较多,对于历史版本本说明文档可能会有较大出入

一 swagger-ui文档

1.1 快速启动

在配置文件中加入以下配置即可快速开启swagger-ui功能。

1
yishuifengxiao.swagger.base-package= 需要扫描的控制器代码的路径

加入上述配置后,即可通过

http://ip:port/doc.html

或者

http://ip:port/swagger-ui.html

查看自己的swagger-ui文档了。

此外,也可以通过http://ip:port/v2/api-docs查看元数据

这里只是简化了swagger-ui的扫描注解,对于软件开发过程中必须swagger-ui其他API注解任然不可省略。

下面是一个简单的swagger-ui配置文档示例

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
@Api(value = "【测试接口】测试接口", tags = {"测试接口"})
@Valid
@Controller
@RequestMapping
@Slf4j
public class WebConftroller {

@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "登录的用户名"),
@ApiImplicitParam(name = "loginIp", value = "登录ip"),
@ApiImplicitParam(name = "pass", value = "登录结果,true表示成功,false失败"),
@ApiImplicitParam(name = "pageSize", value = "分页大小,分页的大小不能小于1,默认值为20"),
@ApiImplicitParam(name = "pageNum", value = "当前页的页码,页码的大小不能小于1,默认值为1")})
@ApiOperation(value = "分页查询登录记录", notes = "分页查询登录记录")
@GetMapping("/demo")
@ResponseBody
public Response<String> findPage(
HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "username", required = false) String username,
@RequestParam(value = "loginIp", required = false) String loginIp,
@RequestParam(value = "pass", required = false) Boolean pass,
@RequestParam(name = "pageSize", defaultValue = "20", required = false) Integer pageSize,
@RequestParam(name = "pageNum", defaultValue = "1", required = false) Integer pageNum) {


return Response.suc();

}

}

特别鸣谢: 此项功能中的doc.html界面中功能使用到了刀哥的 swagger-bootstrap-ui 中的功能 ,在此特别感谢 刀哥 的大力支持,关于swagger-bootstrap-ui的详细说明请参见刀哥的 swagger-bootstrap-ui文档

1.2 常规配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# swagger-ui文档的标题
yishuifengxiao.swagger.title=API接口文档
# swagger-ui文档描述
yishuifengxiao.swagger.description=易水风萧 接口说明文档
#swagger-ui 项目服务的url
yishuifengxiao.swagger.terms-of-service-url=http://www.yishuifengxiao.com/
# swagger-ui 文档分组的名字
yishuifengxiao.swagger.group-name=default
# swagger-ui 文档版本
yishuifengxiao.swagger.version=1.0.0
# 项目联系人名字
yishuifengxiao.swagger.contact-user=yishuifengxiao
# 项目联系的url
yishuifengxiao.swagger.contact-url=http://www.yishuifengxiao.com/
# 项目联系邮箱
yishuifengxiao.swagger.contact-email=zhiyubujian@163.com

以上常规配置都有缺省默认值,用户在使用 易水风萧通用组件 时,如果没有特别需要,使用默认配置即可。

1.3 进阶配置

一般情况下,使用swagger-ui的常规配置即可满足日常开发需要,但是在某些情况下,可能需要一些高级配置。如,需要通过在所有的API接口上批量加上一个默认参数,此时即可用通用组件的高级配置功能了。

1
2
3
4
5
yishuifengxiao.swagger.contact.auths[0].name=Authorization
yishuifengxiao.swagger.contact.auths[0].description=自定义必填请求头
yishuifengxiao.swagger.contact.auths[0].modelRef=string
yishuifengxiao.swagger.contact.auths[0].parameterType=header
yishuifengxiao.swagger.contact.auths[0].required=false

上述示例配置在API文档中的所有请求中批量添加了一个参数名为Authorization的请求头参数。

yishuifengxiao.swagger.contact.auths是一个数组,可以添加多个配置,更多详细配置可参见参见swagger-ui的 ParameterBuilder 用法配置

二 全局跨域配置

快速启动

在 系统的配置文件增加一下配置

1
yishuifengxiao.cors.enable=true

注意: 默认开启了跨域支持,如需关闭将值设置为 false 即可

三 全局全局异常捕获

全局异常捕获不需要显式开启,在项目中加入了易水风萧通用组件后会自动启动。

四 通用辅助工具

通用辅助工具是基于ApplicationContextAware的接口扩展,通过ApplicationContextAware注入spring 上下文,让用户无需再在各个类中显式注入其他的引用依赖,能快速地通过 ApplicationContext 获取到对象的示例。

本工具里常用的方法签名有

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
  /**
* 将源对象里的属性赋值给目标对象
*
* @param <S> 源对象
* @param <T> 目标对象
* @param source 源对象
* @param target 目标对象
* @return
*/
<S, T> T copy(S source, T target)

/**
* 对参数进行非空和空格处理,并对undefined值的数据进行过滤
*
* @param str
* @return
*/
String undefined(String str)

/**
* 对传入的参数进行非空处理
*
* @param t 传入的参数
* @return 处理后的参数
*/
<T> T convert(T t)

/**
* 根据class获取实例对象
*
* @param <T>
* @param clazz
* @return
*/
<T> T getBean(Class<? extends T> clazz)

五 验证码功能

在日常开发过程中,经常需要使用到验证码功能,在易水风萧通用工具中对常见的 图形验证码、邮箱验证码、短信验证码做一个通用封装。在这三种类型的验证码中,目前仅对图形验证码和邮箱验证码做了缺省性实现,由于短信验证码中短信发送的特殊性,需要用户在开发过程中完成短信发送功能的具体实现。

5.1 快速启动

将验证码工具注入到需要使用到验证码的地方

注入代码如下:

1
2
@Autowired
private CodeProcessorHolder codeProcessorHolder;

5.2 图形验证码

5.2.1 生成图形验证码

在注入一个验证码工具后,通过以下代码即可快速生成一个图形验证码。
具体的示例代码如下:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/code/image")
@ResponseBody
public Response<String> image(HttpServletRequest request, HttpServletResponse response){
try {
codeProcessorHolder.findValidateCodeProcessor(CodeType.IMAGE).create(new ServletWebRequest(request,response));
} catch (ValidateException e) {
return Response.error(e.getMessage());
}
return Response.suc();
}

在以上代码后,用户即可通过

http://ip:port/code/image?image=唯一的随机值

获取图形验证码了。

在上述请求中,用户应该将image参数存储起来,因为在校验验证码时需要用到。

5.2.2 验证图形验证码

验证图形验证码的示例代码如下:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/image/validate")
@ResponseBody
public Response<String> validate(HttpServletRequest request, HttpServletResponse response){
try {
codeProcessorHolder.findValidateCodeProcessor(CodeType.IMAGE).validate(new ServletWebRequest(request,response));
} catch (ValidateException e) {
return Response.error(e.getMessage());
}
return Response.suc();
}

用户在验证图形验证码时,即可用过

http://ip:port/image/validate?image=唯一的随机值&image_code=验证码内容

来判断验证码是否正确。

5.2.3 进阶配置

【注意】上述请求中image参数的值应该与请求中图形验证码的url中的image参数的值保持一致。

特殊提示:对于前后端部署在一起的单体应用,上述两个请求中的image参数都可以省略掉。

针对于图形验证码,组件对图形验证码的生成做一个默认实现,如果生成的图形验证码的内容不满足用户需要,用户可以自定义一个名为 imageCodeGenerator 的实例注入到spring中即可。

实例代码如下

1
2
3
4
5
6
7
8
@Component("imageCodeGenerator")
public class ImageCodeGenerator implements CodeGenerator{

@Override
public ImageCode generate(ServletWebRequest servletWebRequest){
//实现自己的图形验证码的生成逻辑
}
}

除此之外,图形验证码相关的配置有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 验证码的长度,默认为4
yishuifengxiao.code.image.length=4
# 验证码的失效时间,单位秒,默认为300s
yishuifengxiao.code.image.expireIn=300
# 验证码是否包含字母,默认包含
yishuifengxiao.code.image.isContainLetter=true
# 验证码是否包含数字,默认包含
yishuifengxiao.code.image.isContainNumber=true
# 验证码的请求参数
yishuifengxiao.code.image.codeKey=image
# 验证码对应的值的参数
yishuifengxiao.code.image.codeValue=image_code
# 图形验证码的图形宽度
yishuifengxiao.code.image.width=70
# 图形验证码的图形高度
yishuifengxiao.code.image.height=28

注意:在上述请求中,验证码的请求参数中的image参数由yishuifengxiao.code.image.codeKey属性,image_code参数由yishuifengxiao.code.image.codeValue属性决定。在某些极端情况下,可以通过这两个配置修改请求参数。

5.3 邮件验证码

在使用邮箱验证码时,需要先进行下述配置:

  1. 在项目中导入邮件发送相关的依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  1. 在项目的配置文件中加入邮件发送相关的配置属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring.mail.default-encoding=UTF-8
spring.mail.host=邮箱服务器
spring.mail.username=完整的邮箱地址
spring.mail.password=密码
spring.mail.port=465
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=3000
spring.mail.properties.mail.smtp.writetimeout=5000
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.smtp.socketFactory.fallback=false
spring.mail.properties.mail.smtp.socketFactory.port=465
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

如果不加入以上配置,在使用邮件验证码时会提示 【验证码处理器不存在】,且不能正确发送邮件验证码。

【注意】在阿里云ECS服务器上,25端口默认是关闭的,因此最好配置465端口

5.3.1 发送邮件验证码

邮件验证码的发送代码与图形验证码的发送方法基本一致,只需要将CodeType.IMAGE改成CodeType.EMAIL即可。

1
2
3
4
5
6
7
8
9
10
@GetMapping("/code/email")
@ResponseBody
public Response<String> email(HttpServletRequest request, HttpServletResponse response){
try {
codeProcessorHolder.findValidateCodeProcessor(CodeType.EMAIL).create(new ServletWebRequest(request,response));
} catch (ValidateException e) {
return Response.error(e.getMessage());
}
return Response.suc();
}

加入上述配置以后,即可通过

http://ip:port/code/email?email=目标邮箱地址

发送邮箱验证码了。

5.3.2 验证邮箱验证码

示例代码如下:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/email/validate")
@ResponseBody
public Response<String> validate(HttpServletRequest request, HttpServletResponse response){
try {
codeProcessorHolder.findValidateCodeProcessor(CodeType.EMAIL).validate(new ServletWebRequest(request,response));
} catch (ValidateException e) {
return Response.error(e.getMessage());
}
return Response.suc();
}

加入上述配置以后,即可通过

http://ip:port/email/validate?email=目标邮箱地址&email_code=验证码

来验证了。

5.3.3 进阶配置

在邮件验证码功能中,组件对邮箱验证码做了一个缺省性实现,在用户对邮件验证码的格式有自定义需求时,可以通过在spring中注入一个名为emailCodeSender的实例来实现自己的模板内容。

示例代码如下

1
2
3
4
5
6
7
8
@Component("emailCodeSender")
public class EmailCodeSender implements CodeSender<EmailCode> {

@Override
public void send(String target, EmailCode emailCode, CodeType codeType){
//实现自己的发送逻辑
}
}

除此之外,组件还提供了一些额外的配置

1
2
3
4
5
6
7
8
9
10
11
12
# 验证码的长度,默认为4
yishuifengxiao.code.email.length=4
# 验证码的失效时间,单位秒,默认为1800s
yishuifengxiao.code.email.expireIn=1800
# 验证码是否包含字母,默认包含
yishuifengxiao.code.email.isContainLetter=true
# 验证码是否包含数字,默认包含
yishuifengxiao.code.email.isContainNumber=true
# 验证码的请求参数
yishuifengxiao.code.email.codeKey=email
# 验证码对应的值的参数
yishuifengxiao.code.email.codeValue=email_code

类似地,验证码的请求参数中的email参数由yishuifengxiao.code.email.codeKeya属性,email_code参数由yishuifengxiao.code.email.codeValue属性决定。

5.4 短信验证码

由于短信验证码的特殊性,不同的短信提供商有不同的发送接口,因此在使用短信验证码功能之前,需要完成自己的名为 smsCodeSender短信发送器,并将其注入到spring之中。

示例代码如下

1
2
3
4
5
6
7
8
@Component("smsCodeSender")
public class SmsCodeSender implements CodeSender<SmsCode> {

@Override
public void send(String target, EmailCode emailCode, CodeType codeType){
//实现自己的发送逻辑
}
}

5.4.1 发送短信验证码

在完成前置配置之后,即可发送短信验证码了。

示例代码如下

1
2
3
4
5
6
7
8
9
10
@GetMapping("/code/sms")
@ResponseBody
public Response<String> sms(HttpServletRequest request, HttpServletResponse response){
try {
codeProcessorHolder.findValidateCodeProcessor(CodeType.SMS).create(new ServletWebRequest(request,response));
} catch (ValidateException e) {
return Response.error(e.getMessage());
}
return Response.suc();
}

加入上述配置以后,即可通过

http://ip:port/code/sms?phone=目标手机号

发送邮箱验证码了。

5.4.2 验证短信验证码

示例代码如下:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/sms/validate")
@ResponseBody
public Response<String> validate(HttpServletRequest request, HttpServletResponse response){
try {
codeProcessorHolder.findValidateCodeProcessor(CodeType.SMS).validate(new ServletWebRequest(request,response));
} catch (ValidateException e) {
return Response.error(e.getMessage());
}
return Response.suc();
}

加入上述配置以后,即可通过

http://ip:port/sms/validate?phone=目标手机号&phone_code=验证码

来验证了。

5.4.3 进阶配置

1
2
3
4
5
6
7
8
9
10
11
12
# 验证码的长度,默认为4
yishuifengxiao.code.sms.length=4
# 验证码的失效时间,单位秒,默认为300s
yishuifengxiao.code.sms.expireIn=300
# 验证码是否包含字母,默认包含
yishuifengxiao.code.sms.isContainLetter=true
# 验证码是否包含数字,默认包含
yishuifengxiao.code.sms.isContainNumber=true
# 验证码的请求参数
yishuifengxiao.code.sms.codeKey=phone
# 验证码对应的值的参数
yishuifengxiao.code.sms.codeValue=phone_code

类似地,验证码的请求参数中的phone参数由yishuifengxiao.code.sms.codeKeya属性,phone_code参数由yishuifengxiao.code.sms.codeValue属性决定。

5.5 组件高级配置

在默认情况下,验证码是存在内存中的,由DefaultCodeRepository进行了一个默认的实现。但是在集群或分布式情况下,此方式显然是不合适的,因此可以加入redis,将验证码存储在redis之中。

具体实现的步骤如下:

1 在项目中加入redis相关的依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2 在配置文件中加入redis相关的配置

1
2
3
spring.redis.host=redis数据库
spring.redis.password=redis数据库密码
spring.redis.port=redis数据库端口

在完成上述配置后,组件会默认使用 RedisCodeRepository来管理验证码的存取。

总的来说,组件会自动判断系统中有无redis相关的实例加载信息,如果系统中存在redis,会默认使用redis进行验证码管理。

在使用 RedisCodeRepository进行验证码管理后,redis数据库会在有验证码生成时产生一个key的前缀为 validate_code_的记录,此记录信息会被自动清理,请勿手动删除该记录,否则会影响验证码组件的功能。

六 spring security功能

在使用spring security时,可以参考以下步骤

  1. 在项目中加入 spring security依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

2 在项目中加入以下代码

下面的代码用户应该保证能被 @ComponentScan扫描到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableWebSecurity
public class SecurityConfig extends AbstractSecurityConfig {

@Override
protected void configure(HttpSecurity http) throws Exception {
// 调用父类中的默认配置
applyAuthenticationConfig(http);

// 加入自定义的授权配置
authorizeConfigManager.config(http.authorizeRequests());
}

}

该代码的示例代码可参见 com.yishuifengxiao.common.security.SecurityConfig

  1. 实现 UserDetailsService 接口,完成自己的授权逻辑,并将其注入到spring 之中。

【特别注意】在用户未按照(3)中的步骤配置自己的授权逻辑时,组件会默认进行一个缺省实现。在缺省实现的情况下,用户能使用任意用户名配合密码(12345678)进行登录。

加入上述配置之后,只有组件中内置的默认路径能通过授权,访问其他的url都被重定向到 /index 这个地址,具体的配置及原因请参照后续说明。

6.1 基础配置

1
2
3
4
5
6
7
8
9
10
# 是否关闭cors保护,默认为false
yishuifengxiao.security.close-cors=false
# 关闭csrf功能,默认为true
yishuifengxiao.security.close-csrf=true
# 是否开启httpBasic访问,默认为true
yishuifengxiao.security.http-basic=true
# 资源名称,默认为yishuifengxiao
yishuifengxiao.security.realm-name=yishuifengxiao
# 加解密中需要使用的密钥
yishuifengxiao.security.secret-key=

在spring security的官方要求中,需要用户在spring security中注入一个 PasswordEncoder接口的实例,本组件默认采用的是 【易水工具组件】中DES加密,该加密工具是基于DES对称加密算法而来,在本组件中利用其进行加密时的密钥由yishuifengxiao.security.secret-key的值决定,用户也可以不配此值,使用系统缺省密钥值。

在使用本组件时,推荐使用组件内置的默认加密工具,在用户的系统有特殊的加密需求时,可以通过向spring 中注入一个 名为 passwordEncoderPasswordEncoder实例来实现自定义加密。

6.2 登陆表单配置

1
2
3
4
5
6
7
8
9
10
# 表单提交时 默认的用户名参数名字,默认为 username
yishuifengxiao.security.core.usernameParameter=username
# 表单提交时 默认的密码参数名字,默认为 pwd
yishuifengxiao.security.core.passwordParameter=pwd
# 权限拦截时默认的跳转地址,默认为/index
yishuifengxiao.security.core.redirectUrl=/index
# 默认的表单登陆时form表单请求的地址,默认为 /auth/form
yishuifengxiao.security.core.formActionUrl=/auth/form
# 默认的处理登出请求的URL的路径【即请求此URL即为退出操作】,默认为 /loginOut
yishuifengxiao.security.core.loginOutUrl=/loginOut

对于登陆表单配置相关的参数,系统均有默认值,如无特殊需求,用户可以采用组件的默认值。

在前面的配置中,当用户将spring security的基础设置配置完成后,访问非授权路径会被自动重定向到 /index ,这是由yishuifengxiao.security.core.redirectUrl这个配置决定的,当用户有特殊需求时,可以配置此属性来配置 非授权路径的重定向地址。

在表单登陆时,用户可以参考下面的表单配置

1
2
3
4
5
6
7
<form action="/auth/form" method="POST">

用户名 <input name="username" /> <br />
密码 <input name="pwd" /> <br />
<button>登陆</button>

</form>

登录表单中,action提交目标由yishuifengxiao.security.core.formActionUrl决定,用户名和密码的请求参数分别由 yishuifengxiao.security.core.usernameParameteryishuifengxiao.security.core.passwordParameter决定。

在完成配置后,用户即可通过表单登陆系统了,根据用户参数的正确与否,分别有登录成功处理和登陆失败处理进行不同的管理,关于这两处理器,在后续章节会有明确介绍。

6.3 并发登陆管理

1
2
3
4
5
6
# 同一个用户在系统中的最大session数,默认1 
yishuifengxiao.security.session.maximumSessions=1
# 达到最大session时是否阻止新的登录请求,默认为false,不阻止,新的登录会将老的登录失效掉
yishuifengxiao.security.session.maxSessionsPreventsLogin=false
# session失效时跳转的地址
yishuifengxiao.security.session.sessionInvalidUrl=/session/invalid

当因多终端登录造成前置的登陆用户的登陆状态实现,可以实现 SessionInformationExpiredStrategy 接口并将其注入到spring之中,这样就可以记录由此导致的相关信息了。

6.4 记住我功能

1
2
3
4
# 记住我产生的token,默认为 yishuifengxiao
yishuifengxiao.security.remeberMe.key=yishuifengxiao
# 登陆时开启记住我的参数,默认为 rememberMe
yishuifengxiao.security.remeberMe.rememberMeParameter=rememberMe

6.5 授权资源管理

授权资源管理是本组件中的重要部分,在本组件中所有的授权资源由以下几部分组成

  1. 非管理资源
  2. 匿名资源
  3. 自定义授权逻辑资源
  4. 保护资源

也就是说 授权资源 = 非管理资源 + 匿名资源 + 自定义授权逻辑资源 + 保护资源

非管理资源

指的是不经过spring security管理,用户可以不经过授权直接无障碍访问的资源,在本组件中,默认 静态资源和swagger-ui请求路径为 非管理资源

匿名资源

指的是经过spring security管理,但是用户无需登录,通过匿名用户就能访问的资源。

自定义授权逻辑资源

指的的是需要程序根据设定自定决定是否给予访问权限的资源

保护资源

指的是需要用户登陆后才能访问的资源,该部分资源只要用户登陆成功后,无论其权限如何都能访问。

6.5.1 非管理资源

组件中内置的非管理资源的属性配置为

1
2
3
4
5
6
7
8
9
10
# 是否默认包含静态资源,默认为true
yishuifengxiao.security.ignore.containStaticResource=true
# 是否包含swagger-ui的资源,默认为true
yishuifengxiao.security.ignore.containSwaagerUiResource=true
# 是否包含actuator相关的路径,默认为true
yishuifengxiao.security.ignore.containActuator=true
# 是否包含webJars资源,默认为true
yishuifengxiao.security.ignore.containWebjars=true
# 是否包含所有的资源,默认为false
yishuifengxiao.security.ignore.containAll=false

【特别注意】请勿轻易将yishuifengxiao.security.ignore.containAll属性配置为true,否则组件会给系统中所有的资源给予访问权限。

对于其他需要自定义为非管理资源的路径资源,用户还可以通过以下配置进行扩展

1
2
yishuifengxiao.security.ignore.map.demo1=/aaa
yishuifengxiao.security.ignore.map.demo2=/cc/**,/dsd/**

在上面的示例配置中demo1,demo2由用户自行配置,可以配置成任意符合规范的值,这些配置属性的值即为需要配置的资源,多个授权资源间用半角逗号分开。

6.5.2 自定义授权逻辑资源

1
2
yishuifengxiao.security.custom.map.demo1=/aaa
yishuifengxiao.security.custom.map.demo2=/cc/**,/dsd/**

与非管理资源的扩展配置类似,配置中demo1,demo2由用户自行配置,可以配置成任意符合规范的值,这些配置属性的值即为需要配置的资源,多个授权资源间用半角逗号分开。

【特别注意】使用此功能需要用户配置自己的自定义授权提供器,假如用户没有进行配置,组件的缺省实现会将此部分资源直接方形。

自定义授权提供器的实例代码如下:

1
2
3
4
5
6
7
@Component("customAuthority")
public class CustomAuthorityImpl implements CustomAuthority {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication auth){
//执行自己的授权逻辑,true表示通过授权,false表示拒绝授权
}
}

6.5.3 匿名资源

由于此资源的功能与匿名资源类似,因此组件不提供用户自定义配置功能。
组件默认的匿名资源为

1
2
3
4
5
6
7
8
9
10
# 权限拦截时默认的跳转地址,默认为/index
yishuifengxiao.security.core.redirectUrl
# 默认的表单登陆时form表单请求的地址,默认为 /auth/form
yishuifengxiao.security.core.formActionUrl
# 默认的处理登出请求的URL的路径【即请求此URL即为退出操作】,默认为 /loginOut
yishuifengxiao.security.core.loginOutUrl
# /oauth/token
/oauth/token
# session失效时跳转的地址,默认为 /session/invalid
yishuifengxiao.security.session.sessionInvalidUrl=

6.5.4 保护资源

在系统中所有的资源,除了非管理资源配置、自定义授权逻辑资源、匿名资源之外,其他所有的资源都需要经过登陆之后才能让访问。

6.6 验证码拦截

首先需要按照 第五章【验证码功能】这一章节中发送验证码所有需要的相关的配置,然后进行如下配置

1
2
3
yishuifengxiao.security.code.filter.image=需要进行图形验证码验证的路径,多个路径之间用半角逗号隔开
yishuifengxiao.security.code.filter.sms=需要进行短信验证码验证的路径,多个路径之间用半角逗号隔开
yishuifengxiao.security.code.filter.email=需要进行邮件验证码验证的路径,多个路径之间用半角逗号隔开

进行上述配置之后,用户在请求相应的资源时将 第五章【验证码功能】进行验证码验证时需要携带的请求参数放在本请求之后即可。这样就无需使用 第五章【验证码功能】中的方法显式验证了。

此外,用户还可以用 yishuifengxiao.security.code.isFilterGet属性来修改是否过滤get方法。

6.7 短信登陆

短信登陆是与表单登陆并列的功能。在某些情况下,用户需要开发短信登陆系统功能,本组件也支持此方法。

快速开启

用户可以通过配置以下熟悉开启短信登陆功能

1
yishuifengxiao.security.code.smsLoginUrl=登陆地址

其他配置

1
2
# 短信登陆参数,默认为 mobile
yishuifengxiao.security.code.smsLoginParam=mobile

6.8 流程处理器

在本组件中,spring security涉及到的处理有 登陆成功处理、登陆失败处理、 退出成功处理器。
对于本组件中的流程处理器,在流程运行到处理器时,处理器一般有三种处理处理策略

  1. 返回json格式的数据
  2. 重定向到指定的url
  3. 由spring security根据上下文决定返回json格式的数据还是重定向

对于这几种情况,组件抽象了击中处理方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   /**
* 重定向
*/
REDIRECT("redirect"),
/**
* 返回JSON数据
*/
JSON("json"),
/**
* 采用内容协商的方式处理
*/
AUTO("auto"),
/**
* 原始的处理方式
*/
DEFAULT("default")

对于击中情况下,最终的流程处理方式由项目配置和用户请求参数共同决定:

  1. 系统配置为 redirect 时,处理器的处理方式为 重定向到指定地址
  2. 系统配置为 json 时,处理器的处理方式为 返回JSON数据
  3. 系统配置为 default 时,处理器的处理方式为 由spring security根据上下文决定返回json格式的数据还是重定向
  4. 系统配置为 auto 时,处理器的处理方式为此时根据用户请求决定,这种配置下,又分为以下几种情况

1) 从请求头中通过制定的参数名(yishuifengxiao.security.handler.headerName属性决定)获取处理方式
2) 判断请求中Accept请求头是否包含了json关键字,如果包含则流程处理器的处理方式为 json
3) 从请求的url中获取指定参数(yishuifengxiao.security.handler.paramName属性决定)的值,根据参数获取处理方式
4) 当前三步中未获取到明确的处理方式时,设置默认的处理方式为 redirect

这些属性的系统配置如下:

1
2
3
4
# 默认的header名称 type
yishuifengxiao.security.handler.headerName=type
# 默认的从请求参数获取处理方法的参数的名称 yishuifengxiao
yishuifengxiao.security.handler.paramName=yishuifengxiao

6.8.1 登陆成功处理器

1
2
3
4
# 流程处理器处理方式
yishuifengxiao.security.handler.suc.returnType=default
# 登陆成功后重定向的地址
yishuifengxiao.security.handler.suc.redirectUrl=/

6.8.2 登陆失败处理器

1
2
3
4
# 流程处理器处理方式
yishuifengxiao.security.handler.fail.returnType=auto
# 登陆失败后重定向的地址,
yishuifengxiao.security.handler.fail.redirectUrl=/index

6.8.3 退出失败处理器

1
2
3
4
5
6
# 流程处理器处理方式
yishuifengxiao.security.handler.exit.returnType=redirect
# 登陆失败后重定向的地址,
yishuifengxiao.security.handler.exit.redirectUrl=/index
# 退出成功后需要删除的cookie的名字
yishuifengxiao.security.handler.exit.cookieName=JSESSIONID

七 OAuth2功能

使用oauth2功能首先需要按照第六章中的步骤开启spring security的相关功能。

快速启动

1 在项目中加入oauth相关的依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>${spring-security-oauth2-autoconfigure.version}</version>
</dependency>

2 在项目加入以下代码

1
2
3
4
   @Configuration
public class CustomOauth2Config extends OAuth2Config{

}

3 加上@EnableResourceServer@EnableAuthorizationServer注解

完全开启示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@EnableWebSecurity
@EnableResourceServer
@EnableAuthorizationServer
public class SecurityConfig extends AbstractSecurityConfig {

@Override
protected void configure(HttpSecurity http) throws Exception {
// 调用父类中的默认配置
applyAuthenticationConfig(http);

// 加入自定义的授权配置
authorizeConfigManager.config(http.authorizeRequests());
}

@Configuration
public class CustomOauth2Config extends OAuth2Config{

}

}

4 实现UserDetailsService接口,完成自己的认证逻辑

【特别注意】在用户未按照(4)中的步骤配置自己的授权逻辑时,组件会默认进行一个缺省实现。在缺省实现的情况下,用户能使用任意用户名配合密码(12345678)进行登录

7.1 四种授权方式

7.1.1 密码模式

1
2
3
4
5
6
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

在请求中,各参数的含义如下

  • grant_type:表示授权类型,此处的值固定为”password”,必选项。
  • username:表示用户名,必选项。
  • password:表示用户的密码,必选项。
  • scope:表示权限范围,可选项。
  • Authorization: 请求头参数 ,值是 clientId:clientSecret经过base64编码后的值

下面是一个响应的例子

1
2
3
4
5
6
7
8
{
"access_token": "BDF867DE69F05143C709",
"token_type": "bearer",
"refresh_token": "d7cda8fb15714209a9f9f3b039a0034f",
"expires_in": 43199,
"scope": "read write trust",
"client_id": "yishui"
}

7.1.2 客户端模式

1
2
3
4
5
6
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

在本请求中,各参数的含义如下

  • grant_type:表示授权类型,此处的值固定为”client_credentials”,必选项。
  • Authorization: 请求头参数 ,值是 clientId:clientSecret经过base64编码后的值

下面是一个响应的例子

1
2
3
4
5
6
7
{
"access_token": "BDF867DE69F05143D3BF",
"token_type": "bearer",
"expires_in": 43199,
"scope": "read write trust",
"client_id": "yishui"
}

同密码模式相比,客户端模式的响应中缺少了 refresh_token 参数

7.1.3 授权码模式

授权码模式首先需要保证spring security的登陆功能正常可用。只有开启spring security的登陆功能可用,才能开启授权码功能。

先访问一下请求

1
2
GET /oauth/authorize?response_type=code&client_id=yishui&state=xyz&redirect_uri=http://demo.yishuifengxiao.com/demo HTTP/1.1
Host: oauth2.yishuifengxiao.com

在本请求中,各参数的含义如下:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
  • client_id: 用户的client_id

在进行此请求时,假如用户没有登录,spring security会进行拦截,因此需要用户先进行登录。

在正常情况下,访问以上请求会被重定向到

http://demo.yishuifengxiao.com/demo?code=fsfsdf
&state=xyz

服务器回应客户端的URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

通过上面的请求得到了code以后,用户需要使用下面请求获取到授权码

1
2
3
4
5
6
7
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=fsfsdf
&redirect_uri=demo.yishuifengxiao.com/demo

在本请求中,各参数的含义如下:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。
  • code:表示上一步获得的授权码,必选项。
  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
  • client_id:表示客户端ID,必选项。

7.1.4 简化模式

1
2
3
GET /oauth/authorize?response_type=token&client_id=yishui&state=xyz
&redirect_uri=http://demo.com/demo HTTP/1.1
Host: server.example.com

在本请求中,各参数的含义如下:

  1. response_type:表示授权类型,此处的值固定为”token”,必选项。
  2. client_id:表示客户端的ID,必选项。
  3. redirect_uri:表示重定向的URI,可选项。
  4. scope:表示权限范围,可选项。
  5. state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

7.1.5 刷新token

1
2
3
4
5
6
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=sdff

请求中个参数的含义:

  • granttype:表示使用的授权模式,此处的值固定为”refreshtoken”,必选项。
  • refresh_token:表示早前收到的更新令牌,必选项。
  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。

7.2 access_token使用

通过7.1 中的方法获取到access_token之后,一般有两种使用方法

  • 将access_token做为请求参数携带在url参数上

http://demo.yishuifengxiao.com/user/123?access_token=获得到的access_token

  • 将access_token做为请求参数放在请求头中

在所有需要授权的请求的请求头里都携带上参数
Authorization=Bearer 获得到的access_token

在通用组件中,由于对access_token进行了深度处理,因此用户可以access_token通过易水工具包里的DES工具饭解析出token里携带的信息。

在解密时需要使用的密钥由 6.1中设置的yishuifengxiao.security.secret-key属性值决定。

下面是一个 access_token 的解密信息示例

1
2
3
4
5
6
7
8
9
{
"username": "yishui",
"clientId": "admin",
"roles": [
"ROLE_USER",
"admin"
],
"grantType": "password"
}

解密信息的各参数的解释:

  • username: 用户登录时使用到的用户名(在客户端模式下该值为空)
  • clientId: 用户登录时使用的clientId(在简化模式下该值为空)
  • roles: 此登录用户拥有的角色(即此用户的authorities)
  • grantType:access_token对应的授权类型

access_token反解析出用户信息仅限于本组件,原生的oauth2的access_token不支持此功能

7.3 授权资源管理

7.3.1 基础概念

oauth2中的授权资源与spring security中授权资源既有重合又有不同的地方。对于spring security中非管理资源自定义授权逻辑资源以及匿名资源的概念依然保持一致,但是这些资源的范围略有差异,并且增加 游离资源的概念。

非管理资源

与spring security中的非管理资源的概念一致,其范围也由spring security中的非管理资源配置决定

自定义授权逻辑资源

与spring security中的自定义授权逻辑资源的概念一致,其范围也由spring security中的自定义授权逻辑资源配置决定

匿名资源
与spring security中的匿名资源的概念一致,但oauth2中内置匿名资源的范围较小

目前仅包含以下两个部分,且不能由用户定义。

1
2
3
4
# 权限拦截时默认的跳转地址,默认为/index
yishuifengxiao.security.core.redirectUrl=/index
# session失效时跳转的地址
yishuifengxiao.security.session.sessionInvalidUrl=/session/invalid

保护资源
与spring security中的保护资源的概念一致

游离资源
需要经过spring security权限管理,但是不在oauth2权限管理范围之内,它依旧是spring security中保护资源的一部分。

在oauth2的资源体系中

1
2
所有资源  = 非游离资源 + 游离资源
= 非管理资源 + 自定义授权逻辑资源 + 匿名资源 + 保护资源 + 游离资源

7.3.2 游离资源

组件里默认游离资源资源为

  • /oauth/**
  • yishuifengxiao.security.handler.suc.redirectUrl=/
  • yishuifengxiao.security.core.formActionUrl=/auth/form
  • yishuifengxiao.security.core.loginOutUrl=/loginOut
  • yishuifengxiao.social.filterProcessesUrl+/+yishuifengxiao.social.qq.providerId
  • yishuifengxiao.social.filterProcessesUrl+/+yishuifengxiao.social.weixin.providerId

除了这些默认的系统配置外,用户还可以根据项目需要配置自己的资源路径

1
2
yishuifengxiao.security.oauth2.map.demo1=/aaa
yishuifengxiao.security.oauth2.map.demo2=/cc/**,/dsd/**

在上面的示例配置中demo1,demo2由用户自行配置,可以配置成任意符合规范的值,这些配置属性的值即为需要配置的资源,多个授权资源间用半角逗号分开。

7.4 流程处理器

与spring security相比,oauth2在spring security的基础上增加两个流程处理器 权限拒绝处理器资源异常处理器。他们的处理逻辑与spring security中流程处理器的逻辑完全一致,因此在这里不做过多介绍。

权限拒绝处理器 一般会在用户无权访问资源(如401)的情况下触发,其配置如下

1
2
yishuifengxiao.security.handler.denie.returnType=atuo
yishuifengxiao.security.handler.denie.redirectUrl=/index

资源异常处理器 一般在用户访问资源时忘记携带token或者携带的token信息已过期的情况下触发。

1
2
yishuifengxiao.security.handler.exception.returnType=atuo
yishuifengxiao.security.handler.exception.redirectUrl=/index

八 spring social

使用spring social功能首先需要按照第六章中的步骤开启spring security的相关功能。

8.1 快速启动

1 在项目的pom里增加spring social相关的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
      <dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-core</artifactId>
<version>${spring-social.version}</version>
</dependency>


<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
<version>${spring-social.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>${spring-social.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId>
<version>${spring-social.version}</version>
</dependency>

2 在项目的任意一个 @Configuration下增加 @EnableSocial 注解

3 将SpringSocialConfigurer的实例对象注入到spring security之中。

4 在属性配置文件增加相关的配置

一个完整的配置文件的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@EnableWebSecurity
@EnableSocial
public class SecurityConfig extends AbstractSecurityConfig {

@Autowired
private SpringSocialConfigurer socialSecurityConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 调用父类中的默认配置
applyAuthenticationConfig(http);

http.apply(socialSecurityConfig);

// 加入自定义的授权配置
authorizeConfigManager.config(http.authorizeRequests());
}


}

8.2 QQ登录

开启QQ登录首先需要在 QQ互联 申请账号和密码

接下来,在项目的配置文件里增加以下配置

1
2
3
4
5
6
7
8
9
10
# spring social拦截器拦截的标志
yishuifengxiao.social.filter-processes-url=/callback
# QQ登录的appId
yishuifengxiao.social.qq.appId=QQ互联上申请的appId
# QQ登录的appSecret
yishuifengxiao.social.qq.appSecret=QQ互联上申请的appId对应的appSecret
# QQ登录的成功后的跳转路径
yishuifengxiao.social.qq.registerUrl=/registerUrl
# QQ登录的服务提供商标志
yishuifengxiao.social.qq.providerId=qq

在完成上述配置 ,访问

http://ip:port/callback/qq

即可进行QQ登录流程了

注意:
访问的的url中的/callback/qq由yishuifengxiao.social.filter-processes-url和yishuifengxiao.social.qq.providerId两部分共同组成。

8.3 微信登录

配置微信登录只需要在上述基础上进行一下配置即可

1
2
3
4
5
6
7
8
# 微信登录的appId
yishuifengxiao.social.weixin.appId=微信开发平台上申请的appId
# 微信登录的appSecret
yishuifengxiao.social.weixin.appSecret=微信开发平台申请的appId对应的appSecret
# 微信登录的成功后的跳转路径
yishuifengxiao.social.weixin.registerUrl=/registerUrl
# 微信登录的服务提供商标志
yishuifengxiao.social.weixin.providerId=weixin

在完成上述配置 ,访问

http://ip:port/callback/weixin

即可进行微信登录流程了

8.4 账号绑定

在完成QQ登录或者微信登录后,就可将微信(QQ)用户与本系统的用户绑定了。

绑定的关键代码如下:

  1. 注入 ProviderSignInUtils 工具
1
2
 @Autowired
private ProviderSignInUtils providerSignInUtils

2 开始绑定

1
providerSignInUtils.doPostSignUp("本系统用户ID",new  ServletWebRequest(request));