Spring Security 进阶篇配置对象版学习

配套资料,免费下载
链接:https://pan.baidu.com/s/1EINPwP4or0Nuj8BOEPsIyw
提取码:kbue
复制这段内容后打开百度网盘手机App,操作更方便哦

第一章 Spring Security的概述

1.1、框架概述

Spring Security 是 Spring 家族中的一个安全管理框架,Spring Security 的两大核心功能就是认证(authentication)和授权(authorization)。

1.2、常用术语

  • 认证 :你是什么人。
  • 授权 :你能做什么。
  • 用户 :主要包括用户名称、用户密码和当前用户所拥有的角色信息,可用于实现认证操作。
  • 角色 :主要包括角色名称、角色描述和当前角色所拥有的权限信息,可用于实现授权操作。

1.3、常用单词

  • 认证 :authentication
  • 授权 :authorization
  • 用户 :user
  • 角色 :role
  • 登录 :login
  • 注销 :logout

1.4、环境准备

打开基础代码:

请在配套资料中,找到spring boot专用基础代码,使用Idea打开spring-boot-security,这只是一个很普通的spring boot + mybatis项目,如果你有spring boot + mybatis项目的基础,相信你一定能看得懂,我们从左侧的菜单栏可以看到有四个部分,其中“产品管理”、“订单管理”虽然可以进行添加和查询所有,但是,这两个功能并没有和数据库交互,为了防止污染数据库表,让大家看起来很乱,所以我就使用map结构在service层进行了数据模拟;“用户管理”和“角色管理”我已经实现了最基础的增加和查询所有的功能,因为这并不是我们学习的重点,所以一些基本的配置和页面编写我就帮大家完成了。

我们正好趁此机会看着下边的页面,以这个项目为基础来进行学习Spring Security,最终实现的效果就是:

  • zhangsan:作为产品采购员,只能访问产品管理模块
  • lisi:作为财务管理员,只能访问订单管理模块
  • wangwu:作为系统管理员,可以访问所有模块,并可以对zhangsan和lisi进行访问权限管理

image-20210116142642925

修改数据连接:

因为这个基础代码是可以在我的电脑上运行的,我安装了mysql 5.5idea 2020.1,当你打开了基础项目的时候,并不一定能运行,请你检查运行环境是否和我保持一致,其中最重要的就是mysql,请最好不要安装mysql 8.0,如果你有一定的基础并且完全有能力克服此问题,你可以忽略我所说的以上这些。接下来,我们需要修改数据库的连接,这一步很重要,找到application.yaml文件,根据自己的实际情况修改密码,其余的就不用动了。

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 密码

导入基础数据:

请在配套资料中,找到spring boot专用基础代码,将test.sql运行到自己的mysql去,为了防止破坏大家的数据库,我在这里采用公共的test数据库来进行数据保存。

一切准备就绪:

当以上所有工作都准备完成以后,意味着,你可以启动项目并进行简单的查看了,启动后在浏览器输入http://localhost:8080/进行访问。

第二章 Spring Security的基本使用

2.1、导入所需依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.2、创建配置对象

因为我们已经引入了spring-boot-starter-security,默认会帮我们自动配置好Spring Security的所有配置,我们要是想要修改默认配置,只要重写里边的指定方法即可。我们创建指定配置对象,用于修改默认配置:com.caochenlei.config.SecurityConfig

@Configuration
@EnableWebSecurity//开启Spring Security对WebMVC的支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //请将对Spring Security的配置方法写在这个类中
    
}

2.3、使用默认账户

当我们做完以上工作,我们就可以使用Spring Security的功能了,当你启动你的项目,在地址栏输入:http://localhost:8080,如果能够正常运行,那么,你将会看到如下界面:

image-20210118173258796

默认Spring Boot会帮你自动配置好所有功能,包括账户和密码,那么问题来了,账户和密码是多少?默认的账户是user,而默认的密码必须看控制台:

image-20210118173444287

对了你没看错,这么一长串都是密码,直接复制,然后粘贴到密码文本框中,点击登录即可。

image-20210118173612267

2.4、配置登录用户

我们也看到了,使用Spring Boot帮我们配置好的账户和密码,难免有些不方便,我们如何自己指定用户和密码以及用户和密码所对应的角色呢,那么就需要用到配置类了,在配置类中,加入下边这段代码。

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("user").password("{noop}123456").roles("USER");
    auth.inMemoryAuthentication().withUser("admin").password("{noop}123456").roles("ADMIN");
}

这里一定要注意,角色前边千万不能加前缀ROLE_,否则你会连工程都起不来,这是规定。重新启动项目,访问项目首页,试试新配制的用户和密码好不好用。

2.5、退出当前登录

如果想要注销,只要在浏览器地址访问:http://localhost:8080/logout就可以了,为了功能完整,请你打开main.html,第16行,修改注销地址为以下这段代码:

<ul class="navbar-nav px-3">
    <li class="nav-item text-nowrap">
        <a class="btn btn-danger btn-sm" th:href="@{/logout}">注销</a>
    </li>
</ul>

2.6、开放内嵌框架

当你使用用户user密码123456登录的时候,默认就会进入到权限管理系统的后台首页,但是当你点击各个功能模块的时候,会发现localhost拒绝了我们的连接请求。其实这个问题还是挺常见的一个问题,项目中如果用到iframe嵌入网页,然后用到Spring Security,请求就会被拦截,如果你打开F12开发者控制台,你可能就会发现这样一句报错:Refused to display 'http://localhost:8080/user/add' in a frame because it set 'X-Frame-Options' to 'deny'.

image-20210116175251143

Spring Security下,X-Frame-Options默认为DENY,非Spring Security环境下,X-Frame-Options的默认大多也是DENY,这种情况下,浏览器拒绝当前页面加载任何Frame页面,设置含义如下:

  • DENY:浏览器拒绝当前页面加载任何frame页面

  • SAMEORIGIN:frame页面的地址只能为同源域名下的页面

  • ALLOW-FROM:origin为允许frame加载的页面地址

既然清楚了问题的来源,那我们也就好解决这个问题了,有两种解决办法,第一种就是我们关掉Spring Security对frame的拦截;另外一种就是将X-Frame-Options设置为SAMEORIGIN,也就是只能是我们同域名下的请求访问,当然了,这种拦截机制肯定是为了保证系统的安全性,如果关掉了,有点太可惜了,我在这里给出两种解决方案的配置,但是我会采用第二种,而不是第一种的关闭。

第一种:

@Override
protected void configure(HttpSecurity http) throws Exception {
    //关闭X-Frame-Options响应头
    http.headers().frameOptions().disable();
}

第二种:

@Override
protected void configure(HttpSecurity http) throws Exception {
    //设置X-Frame-Options响应头为SAMEORIGIN
    http.headers().frameOptions().sameOrigin();
}

2.7、指定登录页面

虽然默认的登录页面还不错,往往项目中的静态页面已经是前端开发好的,包括登录页面,我们想要使用自己的登录界面该怎么办?我们不妨转换一下思维,使用自带的页面,我们先打开源码,看看他是怎么写的,按照他的这个模式,我们模仿着写到自己的登录界面中不就好了,为了节约大家的时间,我就在下边贴出来了关键部分,你也可以自己打开尝试,如下所示:

image-20210116182619671

我们会发现,他的这个登录页面没有什么特别的,就是一个form表单,里边有两个文本框,一个是账号,一个是密码,还有最下边多了一个特殊的hidden隐藏域,这个隐藏域他是为了防止csrf跨站破坏的,这个值每一次启动项目都不一样,是一个动态值,他是为了标识当前请求一定是我们自己的请求,而不是别的网站仿造的请求,我们的所有请求都需要携带上这个标签上边的value值,我们也称这个值为token值,如果使用的是thymeleaf,那么form action会帮我们自动加上csrf 隐藏域,这样我们不用什么特殊处理也就可以登录了,我们找到我们工程中的login.html,里边是一个空的html,请把以下代码复制进入。下边是我们自己定义的一个登录页面。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>自定义登录页</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}">
</head>
<body>
<div class="container mt-4">
    <form th:action="@{/login}" method="post">
        <div class="form-group">
            <label for="username">用户:</label>
            <input type="text" class="form-control" id="username" name="username" placeholder="请输入用户" required>
        </div>
        <div class="form-group">
            <label for="password">密码:</label>
            <input type="text" class="form-control" id="password" name="password" placeholder="请输入密码" required>
        </div>
        <div class="form-group form-check">
            <input type="checkbox" class="form-check-input" id="autoLogin">
            <label class="form-check-label" for="autoLogin">自动登录</label>
        </div>
        <button type="submit" class="btn btn-primary">登录</button>
    </form>
</div>
<script th:src="@{js/jquery-3.5.1.min.js}"></script>
<script th:src="@{js/bootstrap.bundle.min.js}"></script>
</body>
</html>

我们编写好自己的登录页面,还得需要告诉Spring Security你登录的时候不要使用你自己的登录界面了使用我的,我们只需要在配置对象中编写如下配置即可,然后重新启动项目,我们来看一看效果,是不是可以了。

@Override
protected void configure(HttpSecurity http) throws Exception {
    //设置X-Frame-Options响应头为SAMEORIGIN
    http.headers().frameOptions().sameOrigin();
    //放行不用权限的资源(去登录页面当然不需要用权限,否则你都看不到登录界面,还怎么登录,所以去登录界面必须放行)
    http.authorizeRequests().antMatchers("/toLogin").permitAll();
    //拦截需要权限的资源(拦截所有请求,要想访问,登录的账号必须拥有USER和ADMIN的角色才行)
    http.authorizeRequests().antMatchers("/**").hasAnyRole("USER", "ADMIN").anyRequest().authenticated();
    //设置自定义登录界面
    http.formLogin()//启用表单登录
        .loginPage("/toLogin")//登录页面地址,只要你还没登录,默认就会来到这里
        .loginProcessingUrl("/login")//登录处理程序,Spring Security内置控制器方法
        .usernameParameter("username")//登录表单form中用户名输入框input的name名,不修改的话默认是username
        .passwordParameter("password")//登录表单form中密码框输入框input的name名,不修改的话默认是password
        .defaultSuccessUrl("/main")//登录认证成功后默认转跳的路径
        //.successForwardUrl("/main")//登录成功跳转地址,使用的是请求转发
        .failureForwardUrl("/toLogin")//登录失败跳转地址,使用的是请求转发
        .permitAll();
}
@Controller
public class MainController {
    @RequestMapping("/main")
    public String main() {
        return "main";
    }

    //跳转到登录页的方法
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }
}

2.8、开放静态资源

最终我们启动后,发现确实来到了我们自己定义的登录页面了,说明我们之前的配置没有任何问题,但是,好像干干巴巴,啥样式都没有,这是为什么呢?如果你能看到样式,你清理一下浏览器缓存或者CTRL+F5强制刷新一下,就看不到了,至于原因,我们不难想到,刚才我们只是对跳转到登录页的请求进行了放行,而Spring Security默认是拦截所有请求,那肯定也包括静态资源css、js、img之类的,因此,静态资源是应该要被放行的,静态资源是不需要进行保护的,我们需要在SecurityConfig配置如下代码来放行静态资源。

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/css/**");
    web.ignoring().antMatchers("/img/**");
    web.ignoring().antMatchers("/js/**");
    web.ignoring().antMatchers("/favicon.ico");
}

image-20210116195022625

2.9、指定退出页面

当你现在想要退出登录,点击右上角咱们之前配置好的注销,你就会神奇的发现,好像不能退出了,这是因为,默认退出会直接跳转到/login自动生成的认证页面,现在,认证页面也就是登录页面,已经改成我们自己的登录页面了,你只要指定了登录页面了,那默认的登录页面自然就不会创建了,因此当你退出的时候也就会报404找不到异常。

image-20210118200209516

而我们想要解决这个问题,其实很简单,我们给退出指定一个退出页面,只需要加入以下这段配置,很类似我们配置登录页的时候的代码:

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...
    //设置自定义登出界面
    http.logout()//启用退出登录
        .logoutUrl("/logout")//退出处理程序,Spring Security内置控制器方法
        .logoutSuccessUrl("/toLogin")//退出成功跳转地址
        .invalidateHttpSession(true)//清除当前会话
        .deleteCookies("JSESSIONID")//删除当前Cookie
        .permitAll();
}

即使加上了指定退出页的配置,当你登录后,点击注销,还是报404找不到资源,如果大家是跟着一步一步走来的,那就应该见过下边这个页面,当你在地址栏也好,还是a标签中也好,只要请求路径是:http://localhost:8080/logout,你就会看见下边这个是否确认注销的页面,你输入的刚才那个请求并不是真正退出,他还会问你是不是要退出,只有当你点击了这个Log Out,才是真正退出,你看他的源码,他是向/logout发送了一个post请求,并且还携带了csrf这个隐藏域,那我们是不是就可以仿照他这种形式,修改一下我们自己的退出功能呢。

image-20210116202243002

找到main.html,把之前的a标签的get请求,换成form的post请求,并加上隐藏域csrf,csrf不用我们自己加,只要你是用的thymeleaf的form,他会帮我们加上,不信的话,可以启动工程,右键查看源代码,看看是不是会自动生成一个csrf隐藏域。

<ul class="navbar-nav px-3">
    <li class="nav-item text-nowrap">
        <form th:action="@{/logout}" method="post">
            <input class="btn btn-danger btn-sm" type="submit" value="退出">
        </form>
    </li>
</ul>

第三章 Spring Security的高级使用

3.1、深入跨站请求伪造

3.1、CSRF的概念

CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,你可以这样来理解:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

3.2、CSRF的原理

假设:其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,用户C为Web A网站的合法用户。

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

3.3、CSRF的防御

目前防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证(Spring Security采用);在 HTTP 头中自定义属性并验证。

(1)验证 HTTP Referer 字段

根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用户必须先登陆 bank.example,然后通过点击页面上的按钮来触发转账事件。这时,该转帐请求的 Referer 值就会是转账按钮所在的页面的 URL,通常是以 bank.example 域名开头的地址。而如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客自己的网站。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其 Referer 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。

这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。

然而,这种方法并非万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如 IE6 或 FF2,目前已经有一些方法可以篡改 Referer 值。如果 bank.example 网站支持 IE6 浏览器,黑客完全可以把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就可以通过验证,从而进行 CSRF 攻击。

即便是使用最新的浏览器,黑客无法篡改 Referer 值,这种方法仍然有问题。因为 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心 Referer 值会把组织内网中的某些信息泄露到外网中。因此,用户自己可以设置浏览器使其在发送请求时不再提供 Referer。当他们正常访问银行网站时,网站会因为请求没有 Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问。

(2)在请求地址中添加 token 并验证

CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。

该方法还有一个缺点是难以保证 token 本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。

在Spring Security中,“GET”,“HEAD”,“TRACE”,"OPTIONS"四类请求可以直接通过,并不会被CsrfFilter过滤器过滤,会被直接放行,但是对于其他过滤器该过滤的还是会过滤的,除去上面四类,包括POST都要被验证携带token才能通过。

(3)在 HTTP 头中自定义属性并验证

这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。

然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。

3.4、form表单如何添加token

如果您使用的是thymeleaf,如果使用的是thymeleaf,那么form action会帮我们自动加上csrf 隐藏域,我们不用特殊处理。

如果自己想要设置,我们也可以使用隐藏域自己设置,一般我们不会设置这个,默认就有你设置他干啥,参考代码如下:

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">

3.5、ajax请求如何添加token

如果您使用的是thymeleaf,则可以直接在head标签内加上一个隐藏域即可。

<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

image-20210118203706198

$(function () {
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $(document).ajaxSend(function(e, xhr, options) {
        xhr.setRequestHeader(header, token);
    });
});

3.6、文件上传避免 CSRF 拦截

请将MultipartFilter在Spring Security过滤器之前指定。MultipartFilter在Spring Security过滤器之前指定,这意味着任何人都可以在您的服务器上放置临时文件。但是,只有授权用户才能提交由您的应用程序所处理的文件。通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。具体配置代码如下:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        insertFilters(servletContext, new MultipartFilter());
    }
}

3.7、如何关闭 CSRF 防御机制

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...
    //关闭CSRF跨站点请求仿造保护
    http.csrf().disable();
}

3.2、完成网站自动登录

如果我想要关闭浏览器,下次再打开浏览器,权限管理系统会自动根据我上次的登录状态进行登录,这就是登录常用的“自动登录功能”,要想实现自动登录功能,我们需要实现两处关键配置就能使用了,具体操作如下:

打开 login.html 修改自动登录的name为remember-me,这是一个默认名称,可以修改,但是一般我们就叫这个名

<div class="form-group form-check">
    <input type="checkbox" class="form-check-input" id="autoLogin" name="remember-me">
    <label class="form-check-label" for="autoLogin">自动登录</label>
</div>

配置 SecurityConfig 开启自动登录功能

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...
    //开启记住我功能(自动登录)
    http.rememberMe()
        .rememberMeParameter("remember-me")//表单参数名,默认参数是remember-me
        .rememberMeCookieName("remember-me")//浏览器存的cookie名,默认是remember-me
        .tokenValiditySeconds(60*60*24*30);//保存30两天,默认是两周
}

打开 http://localhost:8080/ ,登录以后,我们在关闭浏览器,然后重新打开 http://localhost:8080/ ,发现仍然可以访问,并且这时候不需要登录,他是怎么做到的呢?其实,在登录成功以后会往当前网站的cookie中写入一个自动登录的token值,当我们下次启动的时候,只要这个cookie没有消失,Spring Security就能拿到这个cookie的中保存的token的值,然后帮我们自动登录认证。

image-20210117101018607

3.3、保存凭据到数据库

自动登录功能方便是大家看得见的,但是安全性却令人担忧。因为cookie毕竟是保存在客户端的,很容易盗取,而且 cookie的值还与用户名、密码这些敏感数据相关,虽然加密了,但是将敏感信息存在客户端,还是不太安全。那么这就要提醒喜欢使用此功能的,用完网站要及时手动退出登录,清空认证信息。 此外,Spring Security还提供了remember-me的另一种相对更安全的实现机制:在客户端的cookie中,仅保存一个无意义的加密串(与用户名、密码等敏感数据无关),然后在数据库中保存该加密串-用户信息的对应关系,自动登录时,用cookie中的加密串,到数据库表中验证,如果通过,自动登录才算通过。这样,自动登录功能的安全性就有了保证,因此,我们需要在数据库中创建一张用于保存自动登录信息的表,这张表是固定的,包括名称、字段等信息,都不能修改,否则会认识失败。

CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

接下来,我们需要配置一下,告诉Spring Security使用哪一个dataSource来操作这个表

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...
    //开启记住我功能(自动登录)
    http.rememberMe()
        .rememberMeParameter("remember-me")//表单参数名,默认参数是remember-me
        .rememberMeCookieName("remember-me")//浏览器存的cookie名,默认是remember-me
        .tokenValiditySeconds(60 * 60 * 24 * 30)//保存30两天,默认是两周
        .tokenRepository(persistentTokenRepository());//使用数据库存储token,防止重启服务器丢失数据,非常重要,没有他不能保存到数据库
}

//数据源是咱们默认配置的数据源,直接注入进来就行
@Autowired
private DataSource dataSource;

@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    return jdbcTokenRepository;
}

接下来,我们重新进行测试,发现也是可行的,并且这里给出了浏览器和数据库的截图信息:

image-20210117102128942

image-20210117102206126

3.4、展示当前登录用户

登录成功以后,如何显示出来当前登录成功的用户名呢?我们这里给出两种常用方法,他们都必须使用Spring Security的标签库,在使用thymeleaf渲染前端的html时,thymeleaf为SpringSecurity提供的标签属性,首先需要引入thymeleaf-extras-springsecurity5依赖支持。

(1)在pom 文件中的引入springsecurity的标签依赖thymeleaf-extras-springsecurity5。

<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

(2)在main.html文件里面导入标签所对应的名称空间。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

第一种:打开 main.html 修改第12行

<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">
    权限管理系统,您好:
    <span sec:authentication="principal.username"></span>
</a>

第二种:打开 `main.html 修改第12行

<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">
    权限管理系统,您好:
    <span sec:authentication="name"></span>
</a>

修改完成,看看页面能够显示当前用户:

image-20210117102944141

3.5、对接数据库中数据

我们现在已经在内存中(代码写死的就在内存中)配置好了两个用户(user、admin)以及他们所对应的角色,但是,在真实的企业开发中,这些信息显然是不能保存在配置文件中的,因为要动态添加删除用户以及角色,我们就需要使用数据库来保存,现在 Spring Security 默认是走的配置对象中的账户和密码,我们如何对接数据库中的数据呢?

第一步:实现自己的 SysUserDetailsService 接口继承 UserDetailsService

public interface SysUserDetailsService extends UserDetailsService {
    
}

第二步:实现自己的 SysUserDetailsService 接口的 loadUserByUsername 方法,方法传入一个字符串,代表当前登录的用户名

@Service
@Transactional
public class SysUserDetailsServiceImpl implements SysUserDetailsService {
    @Autowired
    private SysUserMapper sysUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名去数据库中查询指定用户,这就要保证数据库中的用户的名称必须唯一,否则将会报错
        SysUser sysUser = sysUserMapper.findUserByUsername(username);
        //如果没有查询到这个用户,说明数据库中不存在此用户,认证失败
        if (sysUser == null) {
            throw new UsernameNotFoundException("user not exist");
        }

        //获取该用户所对应的所有角色,当查询用户的时候级联查询其所关联的所有角色,用户与角色是多对多关系
        //如果这个用户没有所对应的角色,也就是一个空集合,那么在登录的时候会报 403 没有权限异常,切记这点
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        List<SysRole> sysRoles = sysUser.getSysRoles();
        for (SysRole sysRole : sysRoles) {
            authorities.add(new SimpleGrantedAuthority(sysRole.getName()));
        }

        //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证
        //org.springframework.security.core.userdetails.User实现了UserDetails对象,是SpringSecurity内置认证对象
        return new User(sysUser.getUsername(), "{noop}"+sysUser.getPassword(), authorities);
    }
}

第三步:修改配置文件 SecurityConfig 中的认证提供者换成咱们自己定义的,然后重新启动权限管理系统使用数据库中的账户登录即可。

@Autowired
private SysUserDetailsService sysUserDetailsServiceImpl;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(sysUserDetailsServiceImpl);
}

第四步:使用数据库所提供的账户进行登录测试。

image-20210117114114859

3.6、用户密码进行加密

第一步:配置加密对象,然后设置给咱们自己的认证提供者。

@SpringBootApplication
public class SpringBootSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootSecurityApplication.class, args);
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
@Autowired
private SysUserDetailsService sysUserDetailsServiceImpl;

@Autowired
private BCryptPasswordEncoder passwordEncoder;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(sysUserDetailsServiceImpl).passwordEncoder(passwordEncoder);
}

第二步:保存用户的时候,给用户的密码进行加密,修改SysUserServiceImpl

@Autowired
private BCryptPasswordEncoder passwordEncoder;

@Override
public void save(SysUser sysUser) {
    sysUser.setPassword(passwordEncoder.encode(sysUser.getPassword()));
    sysUserMapper.save(sysUser);
}

第三步:去掉 SysUserDetailsServiceImpl 中的{noop}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    ...
    ...
    //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证
    //org.springframework.security.core.userdetails.User实现了UserDetails对象,是SpringSecurity内置认证对象
    return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
}

第四步:手动修改数据库中的密码为加密后的密码,我们现在需要知道123456加密后的密文,需要手动生成,注意啊,每一次生成都不一样,但是都可以用

public class CreatePwd {
    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("123456");
        System.out.println(encode);
    }
}

image-20210117130824084

第五步:重新启动权限管理系统,分别使用zhangsan、lisi、wangwu进行登录测试,发现都可以正常进行登录,我在创建表的时候默认就给他们分配了权限。

但是注意:前方高能,你能够登录,但是肯定你点击左侧的菜单右侧会报 403 没有权限,还记得之前我说过,咱们在配置类中拦截的所有资源所对应的角色是不能带前缀ROLE_否则会出问题,这是框架内部的一个机制,你打开数据库,会发现目前所有的角色都是没有ROLE_前缀的,那不是对的吗,出错就出错在这里,在你对接数据库的时候,权限校验的时候,他会默认给你定义的角色加上ROLE_前缀,因此,你就应该知道,你为什么没有权限了,数据库中的角色可没有ROLE_,解决的方法就是给所有角色都加上前缀ROLE_,加完以后,你数据库中的效果应该如下:

image-20210119105831281

修改完成以后,重新启动,然后分别登录,你将会看到如下截图:

zhangsan:用户权限和产品权限

image-20210117132034187

lisi:用户权限和订单权限

image-20210117132047464

wangwu:所有权限

image-20210117132102343

为什么这三个账户都能登录成功,以下几个方面很重要:

  1. Spring Security已经配置了加密登录,我们手动把数据库中的123456改成了密文,登录的时候才可以保证认证成功

  2. 认证成功了可不一定能访问我们系统的资源,必须拥有相对应的角色,虽然角色是我们自己定义的,但是请你不要忘记,我们一开始,使用死的配置进行配置用户的时候,那个时候,只有拥有USERADMIN这样的用户才能访问系统资源,这就是为什么zhangsan和lisi必须要有USER角色了

    //拦截需要权限的资源
    http.authorizeRequests().antMatchers("/**").hasAnyRole("USER", "ADMIN").anyRequest().authenticated();
    

3.7、动态展示功能菜单

3.7.1、页面菜单动态展示

细心的你应该发现了,无论是zhangsan、lisi、wangwu中的哪一个人登录进去,左侧的菜单都是下边这个样子,完全没有实现我们的效果

image-20210117132943767

我们可以使用Spring Security提供的标签库来动态判断,只有拥有指定角色的人,才可以访问我们指定的功能模块,具体做法如下,找到main.html进行修改:

<ul class="nav flex-column">
    <li class="nav-item border-bottom" sec:authorize="hasAnyRole('ROLE_ADMIN','ROLE_PRODUCT')">
        <p><a href="#">产品管理</a></p>
        <ul>
            <li><a th:href="@{product/add}" target="container">添加产品</a></li>
            <li><a th:href="@{product/findAll}" target="container">产品列表</a></li>
        </ul>
    </li>
    <li class="nav-item border-bottom" sec:authorize="hasAnyRole('ROLE_ADMIN','ROLE_ORDER')">
        <p><a href="#">订单管理</a></p>
        <ul>
            <li><a th:href="@{order/add}" target="container">添加订单</a></li>
            <li><a th:href="@{order/findAll}" target="container">订单列表</a></li>
        </ul>
    </li>
    <li class="nav-item border-bottom" sec:authorize="hasAnyRole('ROLE_ADMIN')">
        <p><a href="#">用户管理</a></p>
        <ul>
            <li><a th:href="@{user/add}" target="container">添加用户</a></li>
            <li><a th:href="@{user/findAll}" target="container">用户列表</a></li>
        </ul>
    </li>
    <li class="nav-item border-bottom" sec:authorize="hasAnyRole('ROLE_ADMIN')">
        <p><a href="#">角色管理</a></p>
        <ul>
            <li><a th:href="@{role/add}" target="container">添加角色</a></li>
            <li><a th:href="@{role/findAll}" target="container">角色列表</a></li>
        </ul>
    </li>
</ul>

我们保存以后,重新启动权限管理系统,再次分别登录zhangsan、lisi、wangwu,看看左侧菜单栏发生了什么变化

zhangsan:

image-20210117133651412

lisi:

image-20210117133706875

wangwu:

image-20210117133724167

3.7.2、业务代码动态拦截

我们发现虽然界面上效果好像可以了,但是,难道就真的可以了吗?还有没有什么纰漏,我们假设一种场景,一个程序员,它使用zhangsan的账户登录系统后,闲来无事,他呢,自己又懂技术,想试试,在地址栏直接输入李四的订单页面,看看能不能进去,结果发现,进去了,这就是纰漏。

image-20210117134032756

我们上一步所实现的只是表面你所看到的,也就是视图上实现了不同用户可以看到不同的菜单,但是在控制器层并没有拦截住,这就是导致问题的根本原因,一般我们的解决办法就是在业务层(控制器层也可以,但是不推荐),给相对应的方法或者相应的类添加角色判断注解,只有拥有相应角色的用户才能访问该方法或者该类,在Spring Security中,一共支持三种注解都可以做到这个效果,而这三种注解的开启都是一个注解上进行开启,我接下来会把三个注解都打开,只使用第一种注解,其余两种会给大家注释掉,要记住,打开的哪个注解,就用哪个注解来限制访问,必须配套使用。这里演示三类注解,实际开发中,用一类即可!

在主启动类上添加以下配置

@SpringBootApplication
//三种任选其一,不必全开,全开也没事,一定要注意标签的对应关系
@EnableGlobalMethodSecurity(
        jsr250Enabled = true, //JSR-250注解
        prePostEnabled = true, //spring表达式注解
        securedEnabled = true //SpringSecurity注解,推荐使用
)
public class SpringBootSecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootSecurityApplication.class, args);
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

修改OrderServiceImpl:我们就以这个类为例进行讲解,其余剩下的所有的实现都需要标注,可以在方法上标注注解,也可以在类上标注注解

@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    ...
    ...

    @RolesAllowed({"ROLE_ADMIN", "ROLE_ORDER"})//JSR-250注解
    //@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_ORDER')")//spring表达式注解
    //@Secured({"ROLE_ADMIN","ROLE_ORDER"})//SpringSecurity注解
    @Override
    public void save(Order Order) {
        int size = orderMap.size();
        int id = ++size;
        Order.setId(id);
        orderMap.put(id, Order);
    }

    @RolesAllowed({"ROLE_ADMIN","ROLE_ORDER"})//SpringSecurity注解
    @Override
    public List<Order> findAll() {
        Collection<Order> Orders = orderMap.values();
        return new ArrayList<>(Orders);
    }
}

完成以后,重新启动权限管理系统,然后登录zhangsan,你再次输入lisi的添加订单地址,看看还能不能访问,你会发现,添加订单界面还在,但是当你点击提交挺订单的时候,就会 403 权限不足,如果你连界面都不想展示出来,请你在控制层上标注相对应的注解即可。

image-20210119113412550

3.8、权限不足异常处理

大家也发现了,每次权限不足都出现403页面,这个错误页面是Spring Boot自己生成的白页,非常的难看,很不友好,当出现403异常以后,如何跳转到我们自定义的页面,接下来,我将提供两种形式来解决,第一种是Spring Security提供的解决方式,第二种是Spring MVC提供的解决方式,在解决问题之前,我们先定义自己的403没有权限的页面,以及通过控制器方法跳转到403.html,以上这几种情况还可以配置404、500等错误页面的跳转,如有需要也可以自行配置。

在 templates 目录中创建 error 目录,在 error 目录中创建 403.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>没有权限</title>
</head>
<body>
<h3>403,没有权限</h3>
</body>
</html>

MainController 中添加跳转方法,代码如下:

//跳转到错误页的方法
@RequestMapping("/to403")
public String to403() {
    return "error/403";
}

以下几种方法任选其一使用即可,不必全部配置,推荐使用第二种Spring MVC提供的异常处理机制。

第一种:SecurityConfig中配置一下代码即可

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...   

    //异常处理,使用函数表达式的写法可以不用在单独写一个类,非常方便
    http.exceptionHandling()
        .accessDeniedHandler((request, response, ex) -> {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setHeader("Content-Type", "application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
            out.flush();
            out.close();
        });
}

image-20210119114734904

第二种:com.caochenlei.controller 中创建一个包 advice ,然后创建 ExceptionAdvice

@ControllerAdvice
public class ExceptionAdvice {
    //别导错类了:org.springframework.security.access.AccessDeniedException
    //只有出现AccessDeniedException异常才调转403.html页面
    @ExceptionHandler(AccessDeniedException.class)
    public String exceptionAdvice() {
        return "forward:/to403";
    }
}

image-20210117182105797

3.9、保证当前登录人数

有时候我们为了安全,也可以设置同一个账户,只能同时有一个人在线,我们只需要简单的配置就能实现。

第一种:单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...

    //单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线
    http.sessionManagement().maximumSessions(1).expiredUrl("/toLogin");
}

第二种:单用户登录,如果有一个登录了,同一个用户在其他地方不能登录

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...
    //单用户登录,如果有一个登录了,同一个用户在其他地方不能登录
    http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
}

3.10、开启或关闭CORS

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了对CORS的支持,就可以跨源通信。

3.10.1、开启CORS

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...
    //开启CORS
    http.cors();
}

3.10.2、关闭CORS

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    ...
    //关闭CORS
    http.cors().disable();
}

原文地址:https://caochenlei.blog.csdn.net

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Nacos 中的参数有很多,如:命名空间、分组名、服务名、保护阈值、服务路由类型、临时实例等,那这些参数都是什么意思?又该如何设置?接下来我们一起来盘它。 1.命名空间 在 Nacos 中通过命名空间(Namespace)+ 分组(Group)+服务名(Name)可以定位到一个唯一的服务实例。 命名
Nacos 支持两种 HTTP 服务请求,一个是 REST Template,另一个是 Feign Client。之前的文章咱们介绍过 Rest Template 的调用方式,主要是通过 Ribbon(负载均衡) + RestTemplate 实现 HTTP 服务调用的,请求的核心代码是这样的: @
Nacos 是 Spring Cloud Alibaba 中一个重要的组成部分,它提供了两个重要的功能:服务注册与发现和统一的配置中心功能。 服务注册与发现功能解决了微服务集群中,调用者和服务提供者连接管理和请求转发的功能,让程序的开发者无需过多的关注服务提供者的稳定性和健康程度以及调用地址,因为这
Spring Cloud Alibaba 是阿里巴巴提供的一站式微服务开发解决方案,目前已被 Spring Cloud 官方收录。而 Nacos 作为 Spring Cloud Alibaba 的核心组件之一,提供了两个非常重要的功能:服务注册中心(服务注册和发现)功能,和统一配置中心功能。 Nac
在 Nacos 的路由策略中有 3 个比较重要的内容:权重、保护阈值和就近访问。因为这 3 个内容都是彼此独立的,所以今天我们就单独拎出“保护阈值”来详细聊聊。 保护阈值 保护阈值(ProtectThreshold):为了防止因过多实例故障,导致所有流量全部流入剩余健康实例,继而造成流量压力将剩余健
前两天遇到了一个问题,Nacos 中的永久服务删除不了,折腾了一番,最后还是顺利解决了。以下是原因分析和解决方案,建议先收藏,以备不时之需。 临时实例和持久化实例是 Nacos 1.0.0 中新增了一个特性。临时实例和持久化实例最大的区别是健康检查的方式:临时实例使用客户端主动上报的健康检查模式,而
Spring Cloud Alibaba 技术体系中的 Nacos,提供了两个重要的功能:注册中心(服务注册与发现)功能和配置中心功能。 其中注册中心解决了微服务调用中,服务提供者和服务调用者的解耦,让程序开发者可以无需过多的关注服务提供者和调用者的运行细节,只需要通过 Nacos 的注册中心就可以
负载均衡通器常有两种实现手段,一种是服务端负载均衡器,另一种是客户端负载均衡器,而我们今天的主角 Ribbon 就属于后者——客户端负载均衡器。 服务端负载均衡器的问题是,它提供了更强的流量控制权,但无法满足不同的消费者希望使用不同负载均衡策略的需求,而使用不同负载均衡策略的场景确实是存在的,所以客
本篇文章为大家展示了如何解决Spring Cloud 服务冲突问题,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、背景...
本篇内容主要讲解“spring cloud服务的注册与发现怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“spri...
本篇内容介绍了“Dubbo怎么实现Spring Cloud服务治理 ”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处...
本篇内容主要讲解“SpringCloud相关面试题有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SpringCloud相...
如何分析Spring Cloud Ribbon、Spring Cloud Feign以及断路器,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希
这篇文章主要讲解了“springcloud微服务的组成部分有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“s...
这篇文章主要讲解了“SpringCloud的OpenFeign项目怎么创建”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习...
本篇内容主要讲解“spring cloud oauth3整合JWT后获取用户信息不全怎么办”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带...
怎样解析微服务架构SpringCloud,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。...
这篇文章主要介绍spring cloud中API网关的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、服务网关简介1、外观模式客户端...
本篇内容介绍了“Spring Cloud微服务的相关问题有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处...
本文小编为大家详细介绍“spring cloud config整合gitlab如何搭建分布式的配置中心”,内容详细,步骤清晰,细节处理妥当,希望这篇“spring cloud config整合gi...