# common-security
**Repository Path**: dingjunjun_codeSpace/common-security
## Basic Information
- **Project Name**: common-security
- **Description**: SpringBoot通用的权限处理框架(权限神器),每个模块都可以单独使用,针对Spring-Security进行二次封装,使其变得更简洁,可扩展性更强。引入此包无需再重复引入SpringSecurity的依赖,真正的达到了零配置、高灵活的特性!
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 193
- **Created**: 2023-02-07
- **Last Updated**: 2023-02-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# common-security
**目前包含如下几大模块:**
````java
common-security-core
common-security-validate
common-security-browser
common-security-app
````
****
## 一、common-security-validate
## 1、是什么?
**通用的验证码处理。基于SpringSecurity技术。高扩展、高可用、配置简单、灵活,支持两种模式:前后端不分离(session存储数据)和前后端分离(比如:web前后端分离或者APP形式,采取redis存储数据)。**
## 2、怎么用?
- **1、后续所说的所有的配置项都配置到`security.properties`文件中。**
- **2、导入jar包后,在你自己SpringBoot项目启动类上添加如下注解**
`@ComponentScan(basePackages = {"com.chentongwei.security", "你自己的包名"})`
## 3、具体用法?
**场景:**
我们的系统都会带有登录功能,而登录功能常用的无非就三种:表单登录、短信登录、第三方登录。
表单登录的话大多都会带有验证码功能,而验证码(主流的有图形验证码也有极验证)我们需要写接口生成,也需要写一些判断来验证是否正确,短信登录的话我们也需要验证短信验证码是否匹配等操作。还有URL安全问题,比如我不知道你密码,但是我却知道你的访问url,那么我直接敲url,应该会让我们跳转到登录页去等。
**问题:**
- 1、我们需要自己写验证码生成接口。
- 2、如果网站登录要极验证,发表评论要图形验证码,我们需要写两套验证码接口,还需要与极验证SDK做整合。
- 3、登录(或其他需要验证码的接口)的时候我们需要验证验证码是否为空、是否匹配、是否过期等等操作。
- 4、一些URL未登录的时候无法被访问,返回401没有权限状态码,我们需要作此限制。
**解答**
`common-security-validate`能完美解决以上全部问题:
- 1、直接调用`code/image`接口即可获取图形验证码,返回的是base64编码后的。Demo如下:
```javascript
window.onload = function () {
getImageCode();
}
function getImageCode() {
$.ajax({
url: "code/image",
type: "get",
/**
* 为什么要在headers里传个deviceId参数?
* 若你是前后端不分离(采取session共享数据),则无需传递此header里的参数。
* 若你是前后端分离(采取redis共享数据),则需要在header里传递deviceId参数,
* 若你是APP,则此参数建议取设备id,若你是web前后分离,则可以从后台获取一个然后放到缓存中,并放到此处。它标记着一个唯一的用户。
*/
headers: {
"deviceId": "123"
},
success: function (data) {
$("#imageCode").attr("src", "data:image/jpeg;base64," + data);
}
});
}
```
- 2、上面介绍了图形验证码,这里看下极验证:
2.1、首先去极验官网注册账号并创建项目,然后获取到id和key。
2.2、然后在配置文件里配置好,并直接调用`code/geetest`接口即可获取极验验证码。Demo如下:
`security.properties`
```properties
com.chentongwei.security.code.geetest.id=f4fac365b3ccb82ed68cc8c1ab6346f6
com.chentongwei.security.code.geetest.key=c91ca7c31cf0d8a40c5cf5714624bfb2
com.chentongwei.security.code.geetest.newfailback=true
```
`html+js`
```javascript
$.ajax({
// 获取id,challenge,success(是否启用failback)
url: "code/geetest?t=" + (new Date()).getTime(), // 加随机数防止缓存
type: "get",
dataType: "json",
/**
* 为什么要在headers里传个deviceId参数?
* 若你是前后端不分离(采取session共享数据),则无需传递此header里的参数。
* 若你是前后端分离(采取redis共享数据),则需要在header里传递deviceId参数,
* 若你是APP,则此参数建议取设备id,若你是web前后分离,则可以从后台获取一个然后放到缓存中,并放到此处。它标记着一个唯一的用户。
*/
headers: {
"deviceId": "123"
},
success: function (data) {
console.log("data:" , data);
// 使用initGeetest接口
// 参数1:配置参数
// 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
initGeetest({
gt: data.gt,
challenge: data.challenge,
product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
offline: !data.success // 表示用户后台检测极验服务器是否宕机,与SDK配合,用户一般不需要关注
}, handlerPopup);
}
});
// 代码详细说明
var handlerPopup = function (captchaObj) {
console.info(captchaObj)
// 注册提交按钮事件,比如在登陆页面的登陆按钮
$("#popup-submit").click(function () {
// 此处省略在登陆界面中,获取登陆数据的一些步骤
// 先校验是否点击了验证码
var validate = captchaObj.getValidate();
if (!validate) {
alert('请先完成验证!');
return;
}
// 提交验证码信息,比如登陆页面,你需要提交登陆信息,用户名和密码等登陆数据
$.ajax({
url: "login",
type: "post",
// dataType: "json",
data: {
// 用户名和密码等其他数据,自己获取,不做演示
username:"13123",
password:"123456",
// 验证码数据,这些数据不用自己获取
// 这是二次验证所需的三个值
// 当然,你也可以直接设置验证码单独校验,省略其他信息
geetest_challenge: validate.geetest_challenge,
geetest_validate: validate.geetest_validate,
geetest_seccode: validate.geetest_seccode
},
headers: {
"deviceId": "123"
},
// 这里是正确返回处理结果的处理函数
// 假设你就返回了1,2,3
// 当然,正常情况是返回JSON数据
success: function (result) {
alert("登陆成功!" + result);
}
});
});
// 将验证码加到id为captcha的元素里
// 验证码将会在下面指定的元素中显示出来
captchaObj.appendTo("#captcha");
captchaObj.onReady(function () {
$("#wait").hide();
});
};
```
- 3、校验全都封装好了,你只需要在配置文件里配置上需要验证码的url即可。Demo如下:
```properties
# 需要图形验证码拦截的url
com.chentongwei.security.code.image.url=/login
# 需要极验验证码拦截的url
com.chentongwei.security.code.geetest.url=/login, /hello
```
> PS:在我们对如上配置的url进行访问的时候,会进行验证码的校验操作。
>
> 比如:我们在/login登录接口上做了验证码配置,则直接传递用户名密码请求login的话是会被拦截报错的,会提示验证码不能为空等字样。
>
> 具体拦截如下内容:
>
> ```
> 验证码的值不能为空
> 验证码不存在,请刷新页面重试
> 验证码已过期
> 验证码不匹配
> ```
- 4、直接配置文件里面配置上哪些url无需权限即可,若不配置,则默认拦截全部url(意味着只有登录才可以访问)。
```properties
# 无需登录即可访问的url
com.chentongwei.security.authorize.permitUrls=/hello, /hello2
```
## 4、补充
### 4.1、配置项
**图形验证码(默认长度4、宽67、高23、有效期1分钟)**
```properties
# 图形验证码长度
com.chentongwei.security.code.image.length=6
# 图形验证码宽度
com.chentongwei.security.code.image.width=150
# 图形验证码高度
com.chentongwei.security.code.image.height=80
# 图形验证码过期时间(s)
com.chentongwei.security.code.image.expireIn=120
# 需要图形验证码拦截的url
com.chentongwei.security.code.image.url=/login
```
**极验证(默认有效期1分钟)**
```properties
com.chentongwei.security.code.geetest.id=f4fac365b3ccb82ed68cc8c1ab6346f6
com.chentongwei.security.code.geetest.key=c91ca7c31cf0d8a40c5cf5714624bfb2
com.chentongwei.security.code.geetest.newfailback=true
# 极验验证码过期时间(s)
com.chentongwei.security.code.geetest.expireIn=120
# 需要极验验证码拦截的url
com.chentongwei.security.code.geetest.url=/login, /hello
```
**短信验证码(默认长度6、有效期1分钟)**
```properties
# 短信验证码长度
com.chentongwei.security.code.sms.length=6
# 短信验证码过期时间
com.chentongwei.security.code.sms.expireIn=120
# 需要短信验证码拦截的url
com.chentongwei.security.code.sms.url=/mobile
```
**前后分离配置(默认不分离,session存储)**
```properties
# redis为前后分离,验证码存储采用redis,默认不写是session的方式,前后不分离。
# 若采取的是redis方式,千万记得在验证码接口参数中像header里传递deviceId,这个在前面demo中有注释详细说明
com.chentongwei.security.code.repository=redis
```
**限制IP访问接口次数(默认不对任何接口做限制,若配了Url,则默认是3s内最多访问10次)**
```properties
# 需要设置同ip在一定时间内最多可访问同url几次的url路径
com.chentongwei.security.authorize.ipValidateUrl=/hello, /hello2
# 同ip在一定时间内最多可访问同url几次的时间配置(秒)
com.chentongwei.security.authorize.ipValidateSeconds=5
# 同ip在一定时间内最多可访问同url几次的次数配置
com.chentongwei.security.authorize.ipValidateCount=5
```
**URL权限配置**
```properties
# 无需登录即可访问的url
com.chentongwei.security.authorize.permitUrls=/hello,/hello2
# 若出现401状态码,则跳转到固定page
com.chentongwei.security.authorize.unAuthorizePage=/default-login.html
```
> PS:
>
> 1、默认拦截全部url
>
> 2、访问没权限的url,返回401状态码,若配置了unAuthorizePage,则跳转到unAuthorizePage页面,若没配置unAuthorizePage,则返回如下JSON:
>
> ```json
> {"code":401,"msg":"Unauthorized","data":null}
> ```
**REDIRECT / JSON(默认JSON)**
```properties
# REDIRECT/JSON 认证成功/失败后重定向到页面还是返回JSON
com.chentongwei.security.authentication.loginType=REDIRECT
# REDIRECT的方式,认证失败后跳转到的页面url(默认URL是及其不友好的),默认,此配置JSON方式无效
com.chentongwei.security.authentication.loginErrorPage=http://www.tucaole.cn
# REDIRECT的方式,认证成功后跳转到的页面url(默认URL是上一次访问的URL),此配置JSON方式无效
com.chentongwei.security.authentication.loginSuccessPage=http://www.tucaole.cn
```
### 4.2、扩展项
**图形验证码**
> PS:若你不喜欢我生成的图形验证码,比如嫌弃我的条纹太少了,样式太丑了神马的,完全可以自定义一个验证码接口,放心,即使是你自定义的,我的验证一样会生效。
>
> 自定义接口前提:
>
> 1、bean的id为`imageValidateCodeGenerator`
>
> 2、实现`com.chentongwei.security.validate.code.ValidateCodeGenerator`
>
> ```java
> @Component("imageValidateCodeGenerator")
> public class ImageValidateCodeGenerator implements ValidateCodeGenerator {
>
> private final Logger logger = LoggerFactory.getLogger(getClass());
>
> @Override
> public ValidateCode generate(ServletWebRequest request) {
> logger.info("自定义的验证码生成逻辑....");
> return new ValidateCode();
> }
> }
> ```
**极验证验证码**
> PS:若你不喜欢我生成的极验验证码完全可以自定义一个验证码接口,放心,即使是你自定义的,我的验证一样会生效。
>
> 自定义接口前提:
>
> 1、bean的id为`geetestValidateCodeGenerator`
>
> 2、实现`com.chentongwei.security.validate.code.ValidateCodeGenerator`
>
> ```java
> @Component("geetestValidateCodeGenerator")
> public class GeetestValidateCodeGenerator implements ValidateCodeGenerator {
> private final Logger logger = LoggerFactory.getLogger(getClass());
>
> @Override
> public ValidateCode generate(ServletWebRequest request) {
> logger.info("自定义的验证码生成逻辑....");
> return new ValidateCode();
> }
> }
> ```
**短信验证码**
> PS:我默认的短信验证码生成完全没用,我就在控制台输出了一串数字,因为这个短信平台的厂商不同,实现方式不同,所以我提供一个接口,你们自行实现自己对接的厂商即可。放心,即使是你自定义的,我的验证一样会生效。
>
> 自定义接口前提:
>
> 1、实现`com.chentongwei.security.validate.code.sms.SmsCodeSender`
>
> ```java
> public class MySmsCodeSender implements SmsCodeSender {
> @Override
> public void send(String mobile, String code) {
> System.out.println("send.......");
> }
> }
> ```
**认证成功/失败处理器**
**成功处理器**
> PS:
>
> 自定义成功处理器,前提只有一个,bean的id为`authenticationSuccessHandler`
>
> ```java
> @Component("authenticationSuccessHandler")
> public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
>
> private Logger logger = LoggerFactory.getLogger(getClass());
>
> @Override
> public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
> logger.info("验证成功!");
> }
> }
> ```
**失败处理器**
> PS:
>
> 自定义失败处理器,前提只有一个,bean的id为`authenticationFailureHandler`
>
> ```java
> @Component("authenticationFailureHandler")
> public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
>
> private Logger logger = LoggerFactory.getLogger(getClass());
>
> @Override
> public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
> logger.info("验证失败!");
> }
> }
> ```
**UserDetailsService**
> PS:用过SpringSecurity的人一定知道UserDetailsService,我内置了一个DefaultUserDetailsService,没任何用处,唯一用途就是启动的时候若没有写UserDetailsService的实现,不会出错,但是大家一定要写UserDetailsService的实现,来完成自己的登录业务逻辑,我的只是为了启动不报错而生的!!
**SpringSecurity的核心配置**
> PS:我内置了一个叫`ValidateSecurityCoreConfig`的核心配置,里面只有一些基础的配置,比如:成功失败处理器,接口权限等。没有包含session、logout等等一堆东西,若我的满足不了你们的需求,则大可自定义。
>
> 自定义配置前提:
>
> 1、需要在配置文件里指定enable=false,缺省值是true
>
> ```properties
> # 自定义核心配置,false:自定义;true:采用我的默认配置,缺省值为true
> com.chentongwei.security.core.config.enable=false
> ```
>
> 这样就可以自定义SpringSecurity的配置了。
>
> 2、自定义的SpringSecurity配置类:
>
> 建议至少包含我默认的`ValidateSecurityCoreConfig`类里的注入的bean,而你只增加注入的bean和修改configure方法,因为我注入的bean有成功失败处理器,验证码校验器,短信配置等,这些功能你都不用的话,那就别用此框架啦!!!
>
> 还有一个非常关键的是:
>
> ```java
> @Autowired
> private AuthorizeConfigManager authorizeConfigManager;
>
> // 一定要放到最后,是因为config方法里最后做了其他任何方法都需要身份认证才能访问。
> // 放到前面的话,后面在加载.antMatchers(getPermitUrls()).permitAll()的时候也会被认为无权限,
> // 因为前面已经做了其他任何方法都需要身份认证才能访问,SpringSecurity是有先后顺序的。
> authorizeConfigManager.config(http);
> ```
>
> 若我不想自定义配置类,我想在你这配置类里追加几个`HttpSecurity`类里的方法能行吗?
>
> 能行,在你项目中写个类实现`com.chentongwei.security.core.authorize.AuthorizeConfigProvider`即可,并且**指定@Order(Integer.MAX_VALUE - 100)**,其他什么都不用管。会自动生效!!!
>
> 比如
>
> ```java
> @Component
> @Order(Integer.MAX_VALUE - 100)
> public class DemoAuthorizeConfigProvider implements AuthorizeConfigProvider {
> @Override
> public void config(HttpSecurity httpConfig) {
> httpSecurity.authorizeRequests().antMatchers("/hello2").hasRole("admin");
> }
> }
> ```
>
> 还有一个需要注意的地方,就是再比如:如果我们既要自定义`httpSecurity.authorizeRequests().antMatchers`又要自定义`config.anyRequest()`,这时候不能写到一个类(比如上面的`DemoAuthorizeConfigProvider`)里面,这时候要写两个类,一个是上面的那个类,还需要单独写一个类里面单独去定义`httpSecurity.authorizeRequests().anyRequest()`,并且指定`@Order(Integer.MAX_VALUE)`,比如:
>
> ```java
> @Component
> @Order(Integer.MAX_VALUE)
> public class DemoAnyRequestAuthorizeConfigProvider implements AuthorizeConfigProvider {
> @Override
> public void config(HttpSecurity httpSecurity) {
> httpSecurity.authorizeRequests().anyRequest().access("@rbacService.hasPermission(request,authentication)");
> }
> }
> ```
>
> 总结注意如下四点:
>
> 1、实现AuthorizeConfigProvider接口。
>
> 2、若要配置非`httpSecurity.authorizeRequests().anyRequest`(比如:`httpSecurity.authorizeRequests().antMatchers`)的时候,需要指定`@Order(Integer.MAX_VALUE - 100)`;而配置`httpSecurity.authorizeRequests().anyRequest`的时候,需要指定`@Order(Integer.MAX_VALUE)`。
>
> 3、自定义`httpSecurity.authorizeRequests().anyRequest`和非`httpSecurity.authorizeRequests().anyRequest`需要写两个不同的类,并指定不同的@Order。
>
> 4、除了内置的几个接口(如:验证码、登录等)不需要认证身份即可访问,其他的接口都是需要身份认证才可访问(`httpSecurity.authorizeRequests().anyRequest().authenticated();`)。
****
## 二、common-security-browser
## 1、是什么?
**基于`common-security-validate`,并在它基础上增加了登出操作、记住我、session管理以及是否允许iframe操作;适用于前后端不分离(session存储数据)。**
## 2、怎么用?
- **1、后续所说的所有的配置项都配置到`security.properties`文件中。**
- **2、导入jar包后,在你自己SpringBoot项目启动类上添加如下注解**
`@ComponentScan(basePackages = {"com.chentongwei.security", "你自己的包名"})`
## 3、具体用法?
> PS:上面的`common-security-validate`里的所有配置全都可以使用。并在原基础上增加了如下三部分。
- **3.1、登出**
```properties
# 退出登录接口,缺省值为/logout
com.chentongwei.security.logout.logoutUrl=/logout2
# 缺省值:/default-login.html,是跳转到URL还是返回JSON,只要这里配置了URL(并且不为缺省值),那就是跳转到URL
com.chentongwei.security.logout.logoutSuccessUrl=/login.html
```
> PS:默认登出接口为/logout,这个可自定义,而且不管定义成什么,直接配置文件里写一下就行了,无需真的写一个logout控制器接口,内部原理会先将记住我的表里的当前用户删除掉,并清除JSESSIONID。
>
>
>
> 登出后默认是返回如下JSON数据,若配置了`logoutSuccessUrl`的话,则登出成功后跳转到`logoutSuccessUrl`对应的value值。
>
> ```json
> {"code":200,"msg":"退出成功"}
> ```
>
>
>
> 示例:
>
> ```html
>
> 退出
> ```
- **3.2、记住我**
> PS:前提:需要建立一张用于记住我的表,这个表是SpringSecurity的记住我功能必须要建立的。建表语句如下:
>
> ```sql
> CREATE TABLE persistent_logins (
> username VARCHAR (64) NOT NULL,
> series VARCHAR (64) PRIMARY KEY,
> token VARCHAR (64) NOT NULL,
> last_used TIMESTAMP NOT NULL
> )
> ```
*记住我配置项:*
```properties
# 记住我时长(缺省值3600s)
# 注意:需要提前建表
com.chentongwei.security.rememberme.seconds=7200
```
> PS:记住我功能简述:
>
> 一般前后不分离的项目我们都会将用户信息存到session,意味着服务器重启的时候就需要每个用户都重新登录才可以,而记住我就是做了持久化,记住我后,会将`username`,`token`等信息放到刚才那张表中,然后首次登陆会根据`token`去表里查询是否存在对应的信息,以及是否超时等,若符合要求则直接登陆。第一次验证成功后会存到`cookie`中,所以并不是每次都查询表,这样一来也不会产生性能问题。
>
>
>
> 若你用了记住我功能,则复选框的`name`必须是`remember-me` 。
>
> 示例:
>
> `记住我`
- **3.3、session管理**
```properties
# session失效/被踢掉时跳转的地址,默认不配置,不配置则代表返回JSON格式
com.chentongwei.security.session.sessionInvalidUrl=/login.html
# 同一个用户在系统中最大的session数,默认1
com.chentongwei.security.session.maximumSessions=1
```
> PS:
>
> 1、默认session失效后返回如下JSON数据,若配置了`sessionInvalidUrl`则session失效后会跳转到对应的value。
>
> ```json
> {"code":601,"msg":"session已失效"}
> ```
>
> 2、默认一个用户在系统中最大的登录数为1,当有多个登录数时,会把上一个登录的人给踢掉,踢掉后默认返回如下JSON数据,配置了`sessionInvalidUrl`则session失效后会跳转到对应的value。若想一个账号在系统中最大登录数为多个,则只需修改`maximumSessions`即可。
>
> ```json
> {"code":601,"msg":"session已失效可能是您的账号在别处登录导致的"}
> ```
- **3.4、其他配置**
```properties
# 1:放开frame的拦截权限。缺省值;0:不允许frame嵌套
com.chentongwei.security.frame.disableStatus=1
```
> PS:默认不允许iframe嵌套,防止csrf攻击。若有需要,则可以配置`disableStatus=1`即可。
****
## 三、common-security-app
## 1、是什么?
**基于`common-security-validate`,并在它基础上增加了JWT、登出操作、用户并发登录管理等操作;适用于前后端分离(redis存储数据)。**
## 2、怎么用?
- **1、后续所说的所有的配置项都配置到`security.properties`文件中。**
- **2、导入jar包后,在你自己SpringBoot项目启动类上添加如下注解**
`@ComponentScan(basePackages = {"com.chentongwei.security", "你自己的包名"})`
## 3、具体用法?
> PS:上面的`common-security-validate`里的所有配置全都可以使用。并在原基础上增加了如下三部分。
>
> 用户登录后会在Header里返回JWT生成的Token。此后任何需要认证才可访问的接口都需要携带此Token在Header中。
- **3.1、JWT**
```properties
# JWT签名密钥,默认为defaultSecret
com.chentongwei.security.jwt.secret=tucaole
# JWT过期时间(秒),默认为3600s,一小时
com.chentongwei.security.jwt.expiration=120
# md5Key,每个token都对应一个唯一的md5key,默认值为randomKey
com.chentongwei.security.jwt.md5Key=tucaolemd5
# GET请求是否需要进行Authentication请求头校验,true:默认校验;false:不拦截GET请求
com.chentongwei.security.jwt.preventsGetMethod=false
```
> PS:只是SpringSecurity整合的JWT,并没有融入Oauth2。
- **3.2、自动刷新Token**
```properties
# 判断token还剩余多少秒到期后自动刷新Token,并放到header里。默认是60s,-1为不刷新
com.chentongwei.security.jwt.autoRefreshTokenExpiration=60
```
- **3.3、并发登录**
```properties
# 判断是否开启允许多人同账号同时在线,若不允许的话则将上一个人T掉,默认false,不T掉,允许多人登录,true:T掉
com.chentongwei.security.jwt.preventsLogin=true
```
- **3.4、退出登录**
```properties
# 内置了退出接口:/jwtLogout
```
- **3.5、认证成功/失败处理器重写**
```properties
# 是否要覆盖认证成功处理器,若要则指定enable=false。并且bean的name为“authenticationSuccessHandler”
com.chentongwei.security.app.success.handler.enable=false
# 是否要覆盖认证失败处理器,若要则指定enable=false。并且bean的name为“authenticationFailureHandler”
com.chentongwei.security.app.failure.handler.enable=false
```