156 Star 673 Fork 193

编程界的小学生/common-security

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

common-security

目前包含如下几大模块:

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如下:

    <img id="imageCode" src="" onclick="getImageCode()" />
    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

    com.chentongwei.security.code.geetest.id=f4fac365b3ccb82ed68cc8c1ab6346f6
    com.chentongwei.security.code.geetest.key=c91ca7c31cf0d8a40c5cf5714624bfb2
    com.chentongwei.security.code.geetest.newfailback=true

    html+js

    <div class="row cl">
        <div id="captcha" class="formControls col-xs-8 col-xs-offset-3">
            <p id="wait">正在加载验证码......</p>
        </div>
    </div>
    <button id="popup-submit">测试登录</button>
    
    $.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如下:

    # 需要图形验证码拦截的url
    com.chentongwei.security.code.image.url=/login
    # 需要极验验证码拦截的url
    com.chentongwei.security.code.geetest.url=/login, /hello

    PS:在我们对如上配置的url进行访问的时候,会进行验证码的校验操作。

    比如:我们在/login登录接口上做了验证码配置,则直接传递用户名密码请求login的话是会被拦截报错的,会提示验证码不能为空等字样。

    具体拦截如下内容:

    验证码的值不能为空
    验证码不存在,请刷新页面重试
    验证码已过期
    验证码不匹配
  • 4、直接配置文件里面配置上哪些url无需权限即可,若不配置,则默认拦截全部url(意味着只有登录才可以访问)。

    # 无需登录即可访问的url
    com.chentongwei.security.authorize.permitUrls=/hello, /hello2

4、补充

4.1、配置项

图形验证码(默认长度4、宽67、高23、有效期1分钟)

# 图形验证码长度
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分钟)

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分钟)

# 短信验证码长度
com.chentongwei.security.code.sms.length=6
# 短信验证码过期时间
com.chentongwei.security.code.sms.expireIn=120
# 需要短信验证码拦截的url
com.chentongwei.security.code.sms.url=/mobile

前后分离配置(默认不分离,session存储)

# redis为前后分离,验证码存储采用redis,默认不写是session的方式,前后不分离。
# 若采取的是redis方式,千万记得在验证码接口参数中像header里传递deviceId,这个在前面demo中有注释详细说明
com.chentongwei.security.code.repository=redis

限制IP访问接口次数(默认不对任何接口做限制,若配了Url,则默认是3s内最多访问10次)

# 需要设置同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权限配置

# 无需登录即可访问的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:

{"code":401,"msg":"Unauthorized","data":null}

REDIRECT / JSON(默认JSON)

# 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

@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

@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

public class MySmsCodeSender implements SmsCodeSender {
    @Override
    public void send(String mobile, String code) {
        System.out.println("send.......");
    }
}

认证成功/失败处理器

成功处理器

PS:

自定义成功处理器,前提只有一个,bean的id为authenticationSuccessHandler

@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

@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

# 自定义核心配置,false:自定义;true:采用我的默认配置,缺省值为true
com.chentongwei.security.core.config.enable=false

这样就可以自定义SpringSecurity的配置了。

2、自定义的SpringSecurity配置类:

建议至少包含我默认的ValidateSecurityCoreConfig类里的注入的bean,而你只增加注入的bean和修改configure方法,因为我注入的bean有成功失败处理器,验证码校验器,短信配置等,这些功能你都不用的话,那就别用此框架啦!!!

还有一个非常关键的是:

@Autowired
private AuthorizeConfigManager authorizeConfigManager;

// 一定要放到最后,是因为config方法里最后做了其他任何方法都需要身份认证才能访问。
// 放到前面的话,后面在加载.antMatchers(getPermitUrls()).permitAll()的时候也会被认为无权限,
// 因为前面已经做了其他任何方法都需要身份认证才能访问,SpringSecurity是有先后顺序的。
authorizeConfigManager.config(http);

若我不想自定义配置类,我想在你这配置类里追加几个HttpSecurity类里的方法能行吗?

能行,在你项目中写个类实现com.chentongwei.security.core.authorize.AuthorizeConfigProvider即可,并且指定@Order(Integer.MAX_VALUE - 100),其他什么都不用管。会自动生效!!!

比如

@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),比如:

@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、登出

    # 退出登录接口,缺省值为/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值。

    {"code":200,"msg":"退出成功"}

    示例:

    <!-- 注意此处的/logout2就是上面配置文件所配置的logoutUrl,默认为/logout -->
    <a href="/logout2">退出</a>
  • 3.2、记住我

    PS:前提:需要建立一张用于记住我的表,这个表是SpringSecurity的记住我功能必须要建立的。建表语句如下:

    CREATE TABLE persistent_logins (
        username VARCHAR (64) NOT NULL,
        series VARCHAR (64) PRIMARY KEY,
        token VARCHAR (64) NOT NULL,
        last_used TIMESTAMP NOT NULL
    )

    记住我配置项:

    # 记住我时长(缺省值3600s)
    # 注意:需要提前建表
    com.chentongwei.security.rememberme.seconds=7200

    PS:记住我功能简述:

    一般前后不分离的项目我们都会将用户信息存到session,意味着服务器重启的时候就需要每个用户都重新登录才可以,而记住我就是做了持久化,记住我后,会将usernametoken等信息放到刚才那张表中,然后首次登陆会根据token去表里查询是否存在对应的信息,以及是否超时等,若符合要求则直接登陆。第一次验证成功后会存到cookie中,所以并不是每次都查询表,这样一来也不会产生性能问题。

    若你用了记住我功能,则复选框的name必须是remember-me 。

    示例:

    <input name="remember-me" type="checkbox" value="true" />记住我

  • 3.3、session管理

    # session失效/被踢掉时跳转的地址,默认不配置,不配置则代表返回JSON格式
    com.chentongwei.security.session.sessionInvalidUrl=/login.html
    # 同一个用户在系统中最大的session数,默认1
    com.chentongwei.security.session.maximumSessions=1

    PS:

    1、默认session失效后返回如下JSON数据,若配置了sessionInvalidUrl则session失效后会跳转到对应的value。

    {"code":601,"msg":"session已失效"}

    2、默认一个用户在系统中最大的登录数为1,当有多个登录数时,会把上一个登录的人给踢掉,踢掉后默认返回如下JSON数据,配置了sessionInvalidUrl则session失效后会跳转到对应的value。若想一个账号在系统中最大登录数为多个,则只需修改maximumSessions即可。

    {"code":601,"msg":"session已失效可能是您的账号在别处登录导致的"}
  • 3.4、其他配置

    # 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
# 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
# 判断token还剩余多少秒到期后自动刷新Token,并放到header里。默认是60s,-1为不刷新
com.chentongwei.security.jwt.autoRefreshTokenExpiration=60
  • 3.3、并发登录
# 判断是否开启允许多人同账号同时在线,若不允许的话则将上一个人T掉,默认false,不T掉,允许多人登录,true:T掉
com.chentongwei.security.jwt.preventsLogin=true
  • 3.4、退出登录
# 内置了退出接口:/jwtLogout
  • 3.5、认证成功/失败处理器重写
# 是否要覆盖认证成功处理器,若要则指定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
The MIT License (MIT) Copyright (c) 2018 编程界的小学生 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

SpringBoot通用的权限处理框架(权限神器),每个模块都可以单独使用,针对Spring-Security进行二次封装,使其变得更简洁,可扩展性更强。引入此包无需再重复引入SpringSecurity的依赖,真正的达到了零配置、高灵活的特性! 展开 收起
Java
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/geekerdream/common-security.git
git@gitee.com:geekerdream/common-security.git
geekerdream
common-security
common-security
master

搜索帮助

Dd8185d8 1850385 E526c682 1850385