list = new ArrayList<>();
- for(GrantedAuthority authority: authorities){
- list.add(authority.getAuthority());
- }
- log.info("=== authority:" + list);
- return list.toString();
- }
-}
-```
-
-#### 4. web界面
-
-界面有4个:
-
-- login.html: 登录界面,所有人都可以访问
-- home.html: 主页面,普通用户和管理员可以访问
-- admin.html: 管理员页面,只有管理员可以访问
-- access_denied.html: 访问被拒绝页面,权限不足时会跳转到该页面;比如普通用户访问admin.html时
-
-login.html
-
-```html
-
-
-
-
- Spring Security
-
-
-
-
- Invalid username and password.
-
-
- You have been logged out
-
-
-
-
-```
-
-
-
-home.html
-
-```html
-
-
-
-
- Spring Security Home
-
-
-
- 欢迎
- 你的权限是
- admin页面
- 退出
-
-
-```
-
-admin.html
-
-```html
-
-
-
-
- Spring Security Admin
-
-
-
- 欢迎
- 你的权限是
- 退出
-
-
-```
-
-access_denied.html
-
-```html
-
-
-
-
- Access Denied
-
-
-
- 没有权限访问页面
- 你的权限是
- 退出
-
-
-```
-
-#### 5. 启动运行
-
-访问 http://localhost:8088,会自动跳转到login界面,如下:
-
-![image-20210603143352317](https://i.loli.net/2021/06/03/1ryvFKGR4wTZ8WP.png)
-
-这里先用普通用户的身份来登录,javalover/123456,登陆后进入主页:可以看到,权限是普通用户
-
-![image-20210603143453932](https://i.loli.net/2021/06/03/K24JhxYNAzC1o6L.png)
-
-这时点击`admin页面`就会提示权限不足,如下:
-
-![image-20210603143531292](https://i.loli.net/2021/06/03/3wEOuIRl2iQb8se.png)
-
-此时点击退出,又重新回到登录界面:并附有提示【已退出登录】
-
-![image-20210603143631810](https://i.loli.net/2021/06/03/8HwOQKTMyz5hXiI.png)
-
-最后用管理账户登录,admin/123456,登录进入主页:可以看到,权限是管理员
-
-![image-20210603143901708](https://i.loli.net/2021/06/03/Kvy1TUJPqn5OErc.png)
-
-这时点击`admin页面`,就会正常显示:
-
-![image-20210603144003914](https://i.loli.net/2021/06/03/acVsUEmMuFPpGAy.png)
-
-## 总结
-
-SpringSecurity的表单登录认证,总的来说代码不是很多,因为很多功能SpringSecurity都是自带的(比如登录、登出、权限不足等),我们只需要根据自己的需求来修改一些配置就可以了
-
-
-
-源码地址:[**demo-spring-security-login-form**](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-login-form)
diff --git a/demo-spring-security/demo-spring-security-login-redirect/README.md b/demo-spring-security/demo-spring-security-login-redirect/README.md
deleted file mode 100644
index 3fc9a8e..0000000
--- a/demo-spring-security/demo-spring-security-login-redirect/README.md
+++ /dev/null
@@ -1,250 +0,0 @@
-## 简介
-
-通常一个后台管理系统,会包含不同的角色和权限;
-
-然后不同的角色,登录后会根据权限的不同,跳转到不同的界面;
-
-这里我们还是设定有两个角色:普通用户和管理员;
-
-- 普通用户:登录成功跳转到 home 页面;
-- 管理员:登录成功跳转到 admin 页面;
-
-## 目录
-
-- 基本配置
-- 配置用户和角色
-- 创建处理器类
-- 配置处理器
-- 修改控制器
-- 运行
-
-## 正文
-
-### 1. 基本配置
-
-先配置一个默认的登录成功界面,如下所示:
-
-```java
-@Configuration
-@EnableWebSecurity
-@Slf4j
-public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .formLogin()
- .loginPage("/login")
- // 登录成功跳转的页面,第二个参数true表示每次登录成功都是跳转到home,如果false则表示跳转到登录之前访问的页面
- .defaultSuccessUrl("/home.html", true)
- // ... 其他配置
-
- }
-}
-```
-
-这样当用户登录成功后,会默认跳转到home.html界面;
-
-但是本节我们要做的就是修改这个地方,使得不同角色跳转不到不同的界面;
-
-下面开始进入主体
-
-### 2. 配置用户和角色
-
-这里我们用 全局配置:
-
-```java
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
- // 数据没有持久化,只是保存在内存中
- auth.inMemoryAuthentication()
- .withUser("javalover").password(passwordEncoder().encode("123456")).roles("USER")
- .and()
- .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");
- }
-```
-
-这里配置了两个用户:
-
-- 普通用户:javalover
-- 管理员:admin
-
-### 3. 创建处理器类
-
-第一步中的配置是:两个角色登录后,默认都是跳转到`home.html`界面;
-
-接下来就开始修改,使他们跳转到不同的界面;
-
-先定义一个处理器类,实现了`AuthenticationSuccessHandler`接口:
-
-```java
-public class MySimpleUrlAuthenticationSuccessHandler
- implements AuthenticationSuccessHandler {
-
-
- private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
-
- @Override
- public void onAuthenticationSuccess(HttpServletRequest request,
- HttpServletResponse response, Authentication authentication)
- throws IOException {
-
- String targetUrl = determineTargetUrl(authentication);
-
- if (response.isCommitted()) {
- logger.debug(
- "Response has already been committed. Unable to redirect to "
- + targetUrl);
- return;
- }
-
- redirectStrategy.sendRedirect(request, response, targetUrl);
- }
-
-}
-
-```
-
-覆写的`onAuthenticationSuccess`方法:登录成功会先到这个地方,然后我们就可以在这里控制下一步要跳转的界面(当然其他的一些操作也可以);
-
-
-
-`determineTargetUrl`方法:就是这篇文章的核心;
-
-它会根据不同的权限,获取到不同的跳转url,然后重定向;
-
-```java
-protected String determineTargetUrl(final Authentication authentication) {
-
- Map roleTargetUrlMap = new HashMap<>();
- roleTargetUrlMap.put("ROLE_USER", "/home");
- roleTargetUrlMap.put("ROLE_ADMIN", "/admin");
-
- final Collection extends GrantedAuthority> authorities = authentication.getAuthorities();
- for (final GrantedAuthority grantedAuthority : authorities) {
- String authorityName = grantedAuthority.getAuthority();
- if(roleTargetUrlMap.containsKey(authorityName)) {
- return roleTargetUrlMap.get(authorityName);
- }
- }
-
- throw new IllegalStateException();
-}
-```
-
-### 4. 配置处理器
-
-上面我们定义了一个处理器,用来控制登录成功后的跳转界面;
-
-这里我们将其配置到config类中;
-
-先在配置类中注入一个Bean:`AuthenticationSuccessHandler `,返回的是刚才创建的处理器类
-
-```java
-@Bean
-public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
- return new MySimpleUrlAuthenticationSuccessHandler();
-}
-```
-
-然后替换掉开头设置的跳转url参数,如下所示:
-
-```java
-@Override
-protected void configure(final HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .formLogin()
- .loginPage("/login")
- .successHandler(myAuthenticationSuccessHandler())
- // ...其他配置
-}
-```
-
-### 5. 修改控制器
-
-上面我们跳转home和admin,是通过控制器进行跳转的,下面我们配置一下控制器:
-
-```java
-@Controller
-@Slf4j
-public class SecurityController {
-
- @RequestMapping("/login")
- public String login(){
- log.info("=== login ===");
- return "login";
- }
-
- @RequestMapping("/home")
- public String home(Model model){
- model.addAttribute("user", getUsername());
- model.addAttribute("role", getAuthority());
- return "home";
- }
-
- @RequestMapping("/admin")
- public String admin(Model model){
- model.addAttribute("user", getUsername());
- model.addAttribute("role", getAuthority());
- return "admin";
- }
-
- @RequestMapping("/accessDenied")
- public String accessDenied(Model model){
- model.addAttribute("user", getUsername());
- model.addAttribute("role", getAuthority());
- return "access_denied";
- }
-
- // 获取当前登录的用户名
- private String getUsername(){
- return SecurityContextHolder.getContext().getAuthentication().getName();
- }
-
- // 获取当前登录的用户角色
- private String getAuthority(){
- Collection extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
- ArrayList list = new ArrayList<>();
- for(GrantedAuthority authority: authorities){
- list.add(authority.getAuthority());
- }
- log.info("=== authority:" + list);
- return list.toString();
- }
-}
-```
-
-### 6. 运行
-
-接下来我们就可以启动程序,访问`http://localhost:8090/`进行测试了
-
-> 前端代码就不贴了,就是三个界面:login,.html, home.html, admin.html。完整源码见文末
-
-- 普通用户登录:
-
-![image-20211119123413681](https://i.loli.net/2021/11/19/Zt6Ds9gBxhOqH2A.png)
-
-跳转到home
-
-![image-20211119123425793](https://i.loli.net/2021/11/19/GDZEaugkNMBIK97.png)
-
-- 管理员登录:
-
-![image-20211119123533315](https://i.loli.net/2021/11/19/jWm1UARyIxfNpuL.png)
-
-跳转到admin
-
-![image-20211119123541475](https://i.loli.net/2021/11/19/AJGNECQVonU6mse.png)
-
-##
-
-## 总结
-
-重定向的核心就是那个处理器中的`determineTargetUrl`方法,根据角色的不同,跳转到不同的界面;
-
-
-
-[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-login-redirect)
-
diff --git a/demo-spring-security/demo-spring-security-logout/README.md b/demo-spring-security/demo-spring-security-logout/README.md
deleted file mode 100644
index 7948f8d..0000000
--- a/demo-spring-security/demo-spring-security-logout/README.md
+++ /dev/null
@@ -1,168 +0,0 @@
-
-
-## 简介
-
-前面我们介绍了[表单登录的入门案例](https://juejin.cn/post/7030306851762176007);
-
-本篇介绍下**登出**的入门案例,代码基于表单登录的案例进行演示;
-
-代码地址见文末
-
-## 目录
-
-
-
-## 正文
-
-### 1. 基本配置
-
-最基本的登出配置如下所示:
-
-```java
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 登出 所有用户都可以访问
- .logout().permitAll();
- }
-```
-
-这里默认的登出url为`/logout`,通过在url中访问`http://localhost:8090/logout`就可以登出了。
-
-当然最方便的还是在界面中进行链接跳转,如下所示:
-
-```html
- 退出
-```
-
-### 2. 登出跳转
-
-**logoutSuccessUrl配置**:
-
-登出跳转成功后的默认界面是根路径,比如`http://localhost:8090/`;
-
-下面我们可以进行简单的配置,配置成自己指定的界面,如下所示:一般推荐将登出成功后跳转的链接设置为登录界面(习惯)
-
-```java
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 登出 所有用户都可以访问
- .logout().permitAll()
- .logoutSuccessUrl("/login");
- }
-```
-
-**logoutUrl配置:**
-
-登出跳转的默认url为`/logout`,比如`http://localhost:8090/logout`,如果登出成功,就跳转到上面配置的路径;
-
-配置如下所示:
-
-```java
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 登出 所有用户都可以访问
- .logout().permitAll()
- .logoutUrl("/logout");
- }
-```
-
-### 3. 更新缓存
-
-这里的缓存指的就是session和cookie;
-
-在登出之后,需要将session失效处理,并删除对应的cookie;
-
-对应的命令为:`invalidateHttpSession()` 和 `deleteCookies(...name)`;
-
-配置如下所示:
-
-> 其中删除的Cookies名称为`JSESSIONID`,这个就是前后端交互的一个凭证id,是在第一次前端请求后端时,后端返回的id;后续的请求后端会根据JSESSIONID来匹配对应的session
-
-```java
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 登出 所有用户都可以访问
- .logout().permitAll()
- .logoutUrl("/logout")
- .invalidateHttpSession(true)
- .deleteCookies("JSESSIONID");
- }
-```
-
-### 4. 登出处理器
-
-登出成功后,不仅可以设置特定的url,还可以执行一些自定义的操作;
-
-对应的命令为:`logoutSuccessHandler`
-
-比如我们需要记录登出时访问的最后一个界面,那么可以通过如下的代码来实现;
-
-先定义一个处理器:`CustomLogoutSuccessHandler.java`
-
-```java
-public class CustomLogoutSuccessHandler extends
- SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
-
- @Override
- public void onLogoutSuccess(
- HttpServletRequest request,
- HttpServletResponse response,
- Authentication authentication)
- throws IOException, ServletException {
-
- String refererUrl = request.getHeader("Referer");
- System.out.println("Logout from: " + refererUrl);
-
- super.onLogoutSuccess(request, response, authentication);
- }
-}
-```
-
-然后在配置中注入该处理器,通过方法注入,如下所示:
-
-```java
-public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
-
- @Bean
- public LogoutSuccessHandler logoutSuccessHandler(){
- return new CustomLogoutSuccessHandler();
- }
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 登出 所有用户都可以访问
- .logout()
- .permitAll()
- .logoutSuccessUrl("/login")
- .logoutUrl("/logout")
- .logoutSuccessHandler(logoutSuccessHandler());
- }
-
-}
-
-```
-
-这样我们在登出时,就可以看到控制台打印下面的内容:
-
-```bash
-Logout from: http://localhost:8090/home
-```
-
-## 总结
-
-本篇介绍了登出的相关配置和处理;
-
-配置有:
-
-- logoutUrl(): 登出链接配置
-- logoutSuccessUrl(): 登出成功后的跳转链接
-- invalidateHttpSession: 失效session
-- deleteCookies() : 删除对应cookie,多个cookieName逗号分隔
-- LogoutSuccessHandler:登出后执行的自定义操作
-
-[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-logout)
-
diff --git a/demo-spring-security/demo-spring-security-manually-login/README.md b/demo-spring-security/demo-spring-security-manually-login/README.md
deleted file mode 100644
index 59435af..0000000
--- a/demo-spring-security/demo-spring-security-manually-login/README.md
+++ /dev/null
@@ -1,177 +0,0 @@
-
-
-## 简介
-
-前面我们的SpringSecurity文章介绍的登录认证,不管是`form-login-auth`表单登录认证,还是`basic-auth`基本方式的认证,都是通过SpringSecurity系统自动进行的认证,我们并没用人工干预;
-
-比如表单登录认证,我们在表单中提交`login`的表单登录请求后,后台并没有自己写对应的处理器,而是由系统自动认证的;
-
-相应的,基本方式的认证也是直接把用户名密码写入header中,系统自动认证;
-
-那今天我们就来手动进行认证,以此熟悉下认证的过程
-
-## 目录
-
-## 正文
-
-### 1. 安全配置
-
-一如既往,还是简单配置一个用户,一个登录请求;
-
-不过这次的路径匹配多了一个自定义的manually-login:`.antMatchers("/manually-login").permitAll()`;
-
-这个就是我们在提交表单时要请求的路径,如果不开放,请求后会再次跳转到登录界面(因为没有权限);
-
-```java
-@Configuration
-@EnableWebSecurity
-@Slf4j
-public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
-
- // 认证相关操作
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
- log.info("=== SecurityConfiguration.authenticate ===");
- // 数据没有持久化,只是保存在内存中
- auth.inMemoryAuthentication()
- .withUser("javalover").password(passwordEncoder().encode("123456")).roles("USER");
- }
-
- // 授权相关操作
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- log.info("=== SecurityConfiguration.authorize ===");
- http
- // home 页面,ADMIN 和 USER 都可以访问
- .antMatchers("/home").hasAnyRole("USER", "ADMIN")
- // login 页面,所有用户都可以访问
- .antMatchers("/manually-login").permitAll()
- .antMatchers("/login").permitAll()
- .anyRequest().authenticated()
- .and()
- // 自定义登录表单
- .formLogin().loginPage("/login")
- // 登录成功跳转的页面,第二个参数true表示每次登录成功都是跳转到home,如果false则表示跳转到登录之前访问的页面
- .defaultSuccessUrl("/home", true)
- // 失败跳转的页面(比如用户名/密码错误),这里还是跳转到login页面,只是给出错误提示
- .failureUrl("/login?error=true")
- .and()
- // 登出 所有用户都可以访问
- .logout().permitAll()
- }
-
- // 定义一个密码加密器,这个BCrypt也是Spring默认的加密器
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
-}
-
-```
-
-### 2. 表单组件
-
-登录表单如下所示:这里我们跳转的路径改为了`manually-login`,之前是`login`;这样登录请求时,请求登录认证会由我们自己进行处理;
-
-```html
-
-```
-
-### 3. 控制器
-
-这个控制器里有一个`manuallyLogin`就是负责接收上面的登录请求,如下所示:
-
-```java
-@Controller
-@Slf4j
-public class SecurityController {
-
- @RequestMapping("/login")
- public String login(){
- log.info("=== login ===");
- return "login";
- }
-
- @Autowired
- AuthenticationManager authManager;
-
- @PostMapping(path="/manually-login", consumes={APPLICATION_FORM_URLENCODED_VALUE})
- public String manuallyLogin(HttpServletRequest request, String username, String password){
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
- Authentication authentication = authManager.authenticate(authenticationToken);
- SecurityContext context = SecurityContextHolder.getContext();
- context.setAuthentication(authentication);
- HttpSession session = request.getSession(true);
- session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
- System.out.println("是否认证通过:"+context.getAuthentication());
- return "redirect:/home" ;
- }
- // ...省略其他的
-
-}
-
-```
-
-AuthenticationManager会在安全配置中配置,下面会介绍;
-
-这里先介绍下manuallyLogin方法中的认证步骤:
-
-1. 首先构建一个`UsernamePasswordAuthenticationToken`,根据用户名和密码(这里的用户名和密码就是通过登录表单传入的)
- 1. `UsernamePasswordAuthenticationToken`类是`Authentication`的实现类;
- 2. 主要实现的功能就是通过用户名/密码来认证用户,构造函数实现
- 3. 然后通过`isAuthenticated`判断是否认证成功
-2. 然后通过`AuthenticationManager`对上面的`token`进行认证;这一步是关键,如果用户名密码错误或者状态异常,都会在这里报错;
-3. 认证通过后,将认证结果`Authentication`保存到`SecurityContext`上下文中(这个上下文主要工作就是负责认证信息的获取和设保存);
- 1. 这里如果不保存,那么认证就没意义了,后续的访问还是会重定向到登录界面;因为后续的权限检测都是通过这个上下文检测的
- 2. 这里的`SecurityContext`是线程安全的,也就是说不同用户访问的是不同的`SecurityContext`上下文,互不影响
-4. 保存到上下文后,就是session的相关操作,这里先获取session;
- 1. 获取的同时会生成JSESSIONID,如果getSession(false)则不会生成JSESSIONID;
- 2. 将上下文保存到session中
-5. 通过`context.getAuthentication()`验证是否认证通过
-6. 最后重定向到home页面;
-
-### 4. AuthenticationManager配置
-
-这个刚开始自己用方法注入了一个,但是死活不成功,报内部错误,大概意思就是用户状态异常;
-
-后来查了下,才知道原来`WebSecurityConfigurerAdapter`配置接口本身就有获取`AuthenticationManager`的方法,直接覆写然后注入@Bean就好了,如下所示:
-
-```java
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-```
-
-### 5. 实践
-
-启动程序,访问`http://localhost:8090/login`跳转到登录界面,输入`javalover/123456`发送表单请求到`manually-login`,认证通过重定向到`home`页面
-
-![image-20211123105928146](https://i.loli.net/2021/11/23/6zuweELWQJdpNnk.png)
-
-![image-20211123110142791](https://i.loli.net/2021/11/23/OW3pBoHXcDGIAQZ.png)
-
-后台打印的认证信息如下:Authenticated=true就说明认证通过,还有对应的权限信息
-
-```bash
-是否认证通过:UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=javalover, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]]
-```
-
-## 总结
-
-本篇主要通过手动认证的方式,熟悉了一下认证的过程:
-
-- 先构造一个认证对象,通过用户名/密码;
-- 再通过认证管理器进行认证请求,成功后返回认证信息(包括用户名、密码(不可见)、权限等信息),失败时会抛出各种异常;
-- 接下来将认证信息保存到上下文;
-- 最后将上下文保存到session中进行管理;
-
-
-
-[源码地址]()
diff --git a/demo-spring-security/demo-spring-security-remember-me/README.md b/demo-spring-security/demo-spring-security-remember-me/README.md
deleted file mode 100644
index e3632d9..0000000
--- a/demo-spring-security/demo-spring-security-remember-me/README.md
+++ /dev/null
@@ -1,154 +0,0 @@
-## 前言
-
-前面介绍了基于SpringSecurity的表单登录例子;
-
-本篇介绍怎么给表单登录添加一个**记住我**的功能;
-
-有了这个功能,那么在token失效后,系统会自动获取最新token,而不用重新登录;
-
-这里需要注意一点:这里的token并不是普通的token,而是JSESSIONID;这个JSESSIONID会在前端第一次请求后端时返回,以后这个JSESSION就是前后台通讯的凭证
-
-## 目录
-
-## 正文
-
-### 1. 安全配置
-
-这里我们做一个最简单的配置,如下所示:添加一个rememberMe()方法
-
-```java
-@Configuration
-@EnableWebSecurity
-@Slf4j
-public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
-
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
- // 数据没有持久化,只是保存在内存中
- auth.inMemoryAuthentication()
- .withUser("javalover").password(passwordEncoder().encode("123456")).roles("USER")
- .and()
- .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");
- }
- // 授权相关操作
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- log.info("=== SecurityConfiguration.authorize ===");
- http
- // 登出 所有用户都可以访问
- .logout().permitAll()
- .deleteCookies("JSESSIONID")
- .and()
- .rememberMe()
- // ...省略其他配置
- ;
- }
-
-
-}
-
-```
-
-可以看到,我们要做的只是提供一个rememberMe()方法,前台就可以在token失效后自动获取最新的token,而不用再重新输入用户名密码进行登录操作;
-
-### 2. 前端组件
-
-这里我们前端也尽可能简化,在之前的表单登录基础上,只增加一个复选框 rememberMe,代码如下:
-
-```html
-
-```
-
-### 3. 实践-不勾选rememberMe
-
-下面我们就可以基于上面的代码,进行一个简单的测试:看一下 勾选rememberMe和不勾选的差别
-
-> 完整代码见文末地址
-
-第一步:启动程序,界面如下所示:javalover/123456
-
-![image-20211122152245002](https://i.loli.net/2021/11/22/HJfNM4xW7p2z6PV.png)
-
-这里我们先不勾选**记住我**,点击**登录**,跳转到如下的主页:
-
-![image-20211122152555706](https://i.loli.net/2021/11/22/yMzTQc1YvmiuC5W.png)
-
-第二步:**接下来是重点**,这里我们删除本地cookie中的JSESSIONID,如下所示:F12->应用程序->cookies->JSESSION->右键-删除
-
-![image-20211122152731372](https://i.loli.net/2021/11/22/UJi4FWH9qo7kv6V.png)
-
-第三步:刷新页面,可以看到,自动跳转到登录页面,因为token失效了,前后端通讯的凭证没了:
-
-![image-20211122152901019](https://i.loli.net/2021/11/22/JazEfBAciKCy7xk.png)
-
-> 其实上面我们也可以不删除cookie,等着session失效(默认30分钟,可以在application.yml中配置:server.servlet.session.timeout=60,默认单位秒)
->
-> 后端的session都失效了,那session产生的JSESSIONID肯定也无效了;
-
-### 4. 实践-勾选rememberMe
-
-下面我们勾选**记住我**,重复上面的步骤,会发现在删除cookie中的JSESSIONID时,看到多了一个remember-me;
-
-![image-20211122153751334](https://i.loli.net/2021/11/22/x6aBAdEGWgvVT1t.png)
-
-其实这里我们可以换个角度来理解:虽然删了JSESSIONID,但是因为还有一个remember-me,所以前后端的通讯还是没有断开;
-
-所以此时我们刷新页面,还是停留在主页,不会跳转到登录界面;
-
-**但是如果我们把remember-me也一起删掉,那么结果很明显,还是会跳到登陆界面**。
-
-### 5. 更多配置
-
-**失效时间:**
-
-上面我们配置的remember-me,默认的token失效时间是两周,下面我们可以配置的短一点,比如一天:
-
-```java
-.logout().permitAll()
- .deleteCookies("JSESSIONID")
- .and()
- .rememberMe()
- .tokenValiditySeconds(86400)
- .and()
-```
-
-> 失效时间:严格意义上来说,上面这个失效时间 应该是remember-me的失效时间;
-
-这样的话,如果超过一天后,你再去删除JSESSIONID或者session失效,那么刷新页面还是会跳转到登录界面;
-
-**加密的密钥:**
-
-前面我们在调试界面看到的remember-me cookie值,它的值是由:MD5(用户名+过期时间+密码+密钥)合成的;
-
-这里的密钥我们可以自己配置,如下所示:
-
-```java
-.rememberMe()
- .key("privateKey")
- .tokenValiditySeconds(86400)
-```
-
-## 总结
-
-上面介绍了**rememberMe**的相关知识,了解了其实**rememberMe**就是:用新的通讯凭证`remember-me`来管理旧的通讯凭证`JSESSIONID`;
-
-当`JSESSIONID`被删除或者`session`过期时,如果`rememberMe cookie`还没过期(默认两周),那么系统就可以自动登录
-
-`remember-me`真实的过期时间可以在调试界面看到,如下所示:
-
-![image-20211122162553634](https://i.loli.net/2021/11/22/bQH12ZA3k8dy4nT.png)
-
diff --git a/demo-spring-security/demo-spring-security-session/README.md b/demo-spring-security/demo-spring-security-session/README.md
deleted file mode 100644
index f591834..0000000
--- a/demo-spring-security/demo-spring-security-session/README.md
+++ /dev/null
@@ -1,151 +0,0 @@
-
-
-## 简介
-
-前面我们介绍了基于SpringSecurity的两种认证方式:[表单认证](https://juejin.cn/post/7030306851762176007)和[基本认证](https://juejin.cn/post/7031077013393768484);
-
-本篇我们介绍下认证后如何获取用户的基本信息;
-
-这里的核心就是`SecurityContextHolder`类。
-
-## 目录
-
-1. 从SecurityContextHolder中获取
-2. 从控制器中获取
-3. 从自定义接口获取
-
-## 正文
-
-### 1. 从SecurityContextHolder中获取
-
-代码如下所示:
-
-```java
-Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-if (!(authentication instanceof AnonymousAuthenticationToken)) {
- String currentUserName = authentication.getName();
- return currentUserName;
-}else{
- return "";
-}
-```
-
-这里我们加了一个校验,当获取的认证用户存在时我们才去访问;
-
-这种方法最大的好处就是方便,直接通过静态方法就可以获取;
-
-但是缺点也很明显,其中最大的缺点就是不方便测试;
-
-### 2. 从控制器中获取
-
-通过控制器来获取用户信息,就很灵活了;
-
-- 我们可以通过Principal参数获取:这里的Principal其实就是一个实体类,用来代表用户,可以是个人,也可以是公司
-
-```java
-@GetMapping("/userinfo-principal")
-public String userinfoByPrincipal(Principal principal) {
- return principal.getName();
-}
-```
-
-- 也可以通过Authentication参数获取:
-
-```java
-@GetMapping("/userinfo-authentication")
-public String userinfoByAuthentication(Authentication authentication) {
- return authentication.getName();
-}
-```
-
-上面我们主要获取了用户名,用户的其他信息也是可以获取的;
-
-这里我们可以试着获取用户的权限:通过 UserDetails 类来获取:
-
-```java
-@GetMapping("/userinfo-authentication")
-public String userinfoByAuthentication(Authentication authentication) {
- UserDetails userDetails = (UserDetails) authentication.getPrincipal();
- System.out.println("User has authorities: " + userDetails.getAuthorities());
- return authentication.getName();
-}
-```
-
-> 需要注意的是, Principal 是不能转换成UserDetails的,因为他俩之间没关系;
->
-> 而这里的authentication.getPrincipal()返回的是一个Object对象,在请求接口时,Object传入的是一个UserDetails对象,所以获取时可以通过UserDetails强转;
-
-通过debug我们可以看到,在访问`/userinfo-authentication`时,getPrincipal返回的实际上就是一个User对象(User实现了UserDetails),所以转换是没问题的
-
-![image-20211116172359093](https://i.loli.net/2021/11/16/xogBnKGP5kcCeIJ.png)
-
-- 还可以通过HttpServletRequest获取:这个其实本质还是通过 Principal 获取
-
-```java
-@GetMapping("/userinfo-request")
-public String userinfoByRequest(HttpServletRequest request) {
- Principal principal = request.getUserPrincipal();
- return principal.getName();
-}
-```
-
-### 3. 自定义接口获取
-
-前面我们体验了从控制器获取,这种方式有点局限,因为如果想在其他类中获取,就无能为力了;
-
-其实更灵活的方式是自己定义一个Bean,然后在需要的地方注入来获取;
-
-这里其实是对第一种方式的升级,将 SecurityContextHolder包装到一个Bean中,然后在需要的地方进行注入即可;
-
-接口类:**IAuthenticationFacade**
-
-```java
-public interface IAuthenticationFacade {
- Authentication getAuthentication();
-}
-```
-
-实现类:**AuthenticationFacade**
-
-```java
-@Component
-public class AuthenticationFacade implements IAuthenticationFacade {
-
- @Override
- public Authentication getAuthentication() {
- return SecurityContextHolder.getContext().getAuthentication();
- }
-}
-```
-
-这样一来,我们就可以在需要的地方通过@Autowired注入IAuthenticationFacade即可:
-
-```java
-@RestController
-public class UserController {
-
- @Autowired
- IAuthenticationFacade authenticationFacade;
-
- @GetMapping("/userinfo-custom-interface")
- public String userinfoByCustomInterface() {
- Authentication authentication = authenticationFacade.getAuthentication();
- return authentication.getName();
- }
-
-}
-```
-
-可以看到,这次我们没有用到任何的参数,只通过自定义的Bean来获取,这样不仅充分利用了Spring的依赖注入功能,还使得获取信息变得更加灵活。
-
-## 总结
-
-上面介绍了三种获取方式:
-
-1. 直接通过 SecurityContextHolder 的静态方法获取
-2. 从控制器中获取:通过各种参数,比如Principal、Authentication、HttpServletRequest
-3. 从自定义的Bean中获取:该方法是对方法1的升级,通过将 SecurityContextHolder包装到 Bean 中,使得获取信息更加灵活
-
-
-
-[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-userinfo)
diff --git a/demo-spring-security/demo-spring-security-userinfo/README.md b/demo-spring-security/demo-spring-security-userinfo/README.md
deleted file mode 100644
index f591834..0000000
--- a/demo-spring-security/demo-spring-security-userinfo/README.md
+++ /dev/null
@@ -1,151 +0,0 @@
-
-
-## 简介
-
-前面我们介绍了基于SpringSecurity的两种认证方式:[表单认证](https://juejin.cn/post/7030306851762176007)和[基本认证](https://juejin.cn/post/7031077013393768484);
-
-本篇我们介绍下认证后如何获取用户的基本信息;
-
-这里的核心就是`SecurityContextHolder`类。
-
-## 目录
-
-1. 从SecurityContextHolder中获取
-2. 从控制器中获取
-3. 从自定义接口获取
-
-## 正文
-
-### 1. 从SecurityContextHolder中获取
-
-代码如下所示:
-
-```java
-Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-if (!(authentication instanceof AnonymousAuthenticationToken)) {
- String currentUserName = authentication.getName();
- return currentUserName;
-}else{
- return "";
-}
-```
-
-这里我们加了一个校验,当获取的认证用户存在时我们才去访问;
-
-这种方法最大的好处就是方便,直接通过静态方法就可以获取;
-
-但是缺点也很明显,其中最大的缺点就是不方便测试;
-
-### 2. 从控制器中获取
-
-通过控制器来获取用户信息,就很灵活了;
-
-- 我们可以通过Principal参数获取:这里的Principal其实就是一个实体类,用来代表用户,可以是个人,也可以是公司
-
-```java
-@GetMapping("/userinfo-principal")
-public String userinfoByPrincipal(Principal principal) {
- return principal.getName();
-}
-```
-
-- 也可以通过Authentication参数获取:
-
-```java
-@GetMapping("/userinfo-authentication")
-public String userinfoByAuthentication(Authentication authentication) {
- return authentication.getName();
-}
-```
-
-上面我们主要获取了用户名,用户的其他信息也是可以获取的;
-
-这里我们可以试着获取用户的权限:通过 UserDetails 类来获取:
-
-```java
-@GetMapping("/userinfo-authentication")
-public String userinfoByAuthentication(Authentication authentication) {
- UserDetails userDetails = (UserDetails) authentication.getPrincipal();
- System.out.println("User has authorities: " + userDetails.getAuthorities());
- return authentication.getName();
-}
-```
-
-> 需要注意的是, Principal 是不能转换成UserDetails的,因为他俩之间没关系;
->
-> 而这里的authentication.getPrincipal()返回的是一个Object对象,在请求接口时,Object传入的是一个UserDetails对象,所以获取时可以通过UserDetails强转;
-
-通过debug我们可以看到,在访问`/userinfo-authentication`时,getPrincipal返回的实际上就是一个User对象(User实现了UserDetails),所以转换是没问题的
-
-![image-20211116172359093](https://i.loli.net/2021/11/16/xogBnKGP5kcCeIJ.png)
-
-- 还可以通过HttpServletRequest获取:这个其实本质还是通过 Principal 获取
-
-```java
-@GetMapping("/userinfo-request")
-public String userinfoByRequest(HttpServletRequest request) {
- Principal principal = request.getUserPrincipal();
- return principal.getName();
-}
-```
-
-### 3. 自定义接口获取
-
-前面我们体验了从控制器获取,这种方式有点局限,因为如果想在其他类中获取,就无能为力了;
-
-其实更灵活的方式是自己定义一个Bean,然后在需要的地方注入来获取;
-
-这里其实是对第一种方式的升级,将 SecurityContextHolder包装到一个Bean中,然后在需要的地方进行注入即可;
-
-接口类:**IAuthenticationFacade**
-
-```java
-public interface IAuthenticationFacade {
- Authentication getAuthentication();
-}
-```
-
-实现类:**AuthenticationFacade**
-
-```java
-@Component
-public class AuthenticationFacade implements IAuthenticationFacade {
-
- @Override
- public Authentication getAuthentication() {
- return SecurityContextHolder.getContext().getAuthentication();
- }
-}
-```
-
-这样一来,我们就可以在需要的地方通过@Autowired注入IAuthenticationFacade即可:
-
-```java
-@RestController
-public class UserController {
-
- @Autowired
- IAuthenticationFacade authenticationFacade;
-
- @GetMapping("/userinfo-custom-interface")
- public String userinfoByCustomInterface() {
- Authentication authentication = authenticationFacade.getAuthentication();
- return authentication.getName();
- }
-
-}
-```
-
-可以看到,这次我们没有用到任何的参数,只通过自定义的Bean来获取,这样不仅充分利用了Spring的依赖注入功能,还使得获取信息变得更加灵活。
-
-## 总结
-
-上面介绍了三种获取方式:
-
-1. 直接通过 SecurityContextHolder 的静态方法获取
-2. 从控制器中获取:通过各种参数,比如Principal、Authentication、HttpServletRequest
-3. 从自定义的Bean中获取:该方法是对方法1的升级,通过将 SecurityContextHolder包装到 Bean 中,使得获取信息更加灵活
-
-
-
-[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-userinfo)
diff --git a/demo-swagger3/README.md b/demo-swagger3/README.md
deleted file mode 100644
index fefb0ed..0000000
--- a/demo-swagger3/README.md
+++ /dev/null
@@ -1,259 +0,0 @@
-## 目录
-
-- 前言:什么是Swagger
-- 起步:(只需简单的3步)
- - 加载依赖
- - 添加注解@EnableOpenApi
- - 启动SpringBoot,访问Swagger后台界面
-- 配置:基于Java的配置
-- 注解:Swagger2 和 Swagger3做对比
-- 源码:
-- 问题:踩坑记录(后面再整理)
-
-## 前言
-
-**什么是Swagger:**
-
- Swagger 是最流行的 API 开发工具,它遵循 OpenAPI Specification(OpenAPI 规范,也简称 OAS)。
-
- 它最方便的地方就在于,API文档可以和服务端保持同步,即服务端更新一个接口,前端的API文档就可以实时更新,而且可以在线测试。
-
- 这样一来,Swagger就大大降低了前后端的沟通障碍,不用因为一个接口调不通而争论不休
-
-> 之前用的看云文档,不过这种第三方的都需要手动维护,还是不太方便
-
-## 起步
-
-1. 加载依赖
-
-```xml
-
- io.springfox
- springfox-boot-starter
- 3.0.0
-
-```
-
-2. 添加@EnableOpenApi注解
-
-```java
-@EnableOpenApi
-@SpringBootApplication
-public class SwaggerApplication {
- public static void main(String[] args) {
- SpringApplication.run(SwaggerApplication.class, args);
- }
-}
-```
-
-3. 启动项目,访问"http://localhost:8080/swagger-ui/index.html"
-
-![image-20210729112424407](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\主页.png)
-
-这样一个简单的Swagger后台接口文档就搭建完成了;
-
-下面我们说下配置和注解
-
-## 配置
-
-可以看到,上面那个界面中,默认显示了一个`basic-error-controller`接口分组,但是我们并没有写;
-
-通过在项目中查找我们发现,SpringBoot内部确实有这样一个控制器类,如下所示:
-
-![image-20210729113119350](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\BasicErrorController.png)
-
-这说明Swagger默认的配置,会自动把@Controller控制器类添加到接口文档中
-
-下面我们就自己配置一下,如下所示:
-
-```java
-import io.swagger.annotations.ApiOperation;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.oas.annotations.EnableOpenApi;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.Contact;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-
-@Configuration
-public class SwaggerConfig {
-
- @Bean
- public Docket createRestApi() {
- // 配置OAS 3.0协议
- return new Docket(DocumentationType.OAS_30)
- .apiInfo(apiInfo())
- .select()
- // 查找有@Tag注解的类,并生成一个对应的分组;类下面的所有http请求方法,都会生成对应的API接口
- // 通过这个配置,就可以将那些没有添加@Tag注解的控制器类排除掉
- .apis(RequestHandlerSelectors.withClassAnnotation(Tag.class))
- .paths(PathSelectors.any())
- .build();
- }
-
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("GPS Doc")
- .description("GPS Doc文档")
- .termsOfServiceUrl("http://www.javalover.com")
- .contact(new Contact("javalover", "http://www.javalover.cn", "1121263265@qq.com"))
- .version("2.0.0")
- .build();
- }
-
-}
-```
-
-这样上面那个`basic-error-controller`就看不见了
-
-## 注解
-
-我们先看下Swagger2中的注解,如下所示:
-
-- @Api:用在控制器类上,表示对类的说明
- - tags="说明该类的作用,可以在UI界面上看到的说明信息的一个好用注解"
- - value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
-
-- @ApiOperation:用在请求的方法上,说明方法的用途、作用
- - value="说明方法的用途、作用"
- - notes="方法的备注说明"
-
-- @ApiImplicitParams:用在请求的方法上,表示一组参数说明
- - @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面(标注一个指定的参数,详细概括参数的各个方面,例如:参数名是什么?参数意义,是否必填等)
- - name:属性值为方法参数名
- - value:参数意义的汉字说明、解释
- - required:参数是否必须传
- - paramType:参数放在哪个地方
- - header --> 请求参数的获取:@RequestHeader
- - query --> 请求参数的获取:@RequestParam
- - path(用于restful接口)--> 请求参数的获取:@PathVariable
- - dataType:参数类型,默认String,其它值dataType="Integer"
- - defaultValue:参数的默认值
-
-- @ApiResponses:用在请求的方法上,表示一组响应
- - @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
- - code:状态码数字,例如400
- - message:信息,例如"请求参数没填好"
- - response:抛出异常的类
-
-- @ApiModel:用于响应类上(POJO实体类),描述一个返回响应数据的信息(描述POJO类请求或响应的实体说明)
- (这种一般用在post接口的时候,使用@RequestBody接收JSON格式的数据的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候)
- - @ApiModelProperty:用在POJO属性上,描述响应类的属性说明
-- @ApiIgnore:使用该注解忽略这个某个API或者参数;
-
-上面这些是Swagger2的注解,下面我们看下Swagger3和它的简单对比
-
-![Swagger3注解](https://i.loli.net/2021/07/29/s62vJN5XLKdugER.png)
-
-接下来我们就用Swagger3的注解来写一个接口看下效果(其中穿插了Swagger2的注解)
-
-- 控制器UserController.java
-
-```java
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiImplicitParam;
-import io.swagger.annotations.ApiImplicitParams;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.v3.oas.annotations.Hidden;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Parameters;
-import io.swagger.v3.oas.annotations.enums.ParameterIn;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.*;
-import springfox.documentation.annotations.ApiIgnore;
-
-@Tag(name = "user-controller", description = "用户接口")
-@RestController
-public class UserController {
-
- // 忽略这个api
- @Operation(hidden = true)
- @GetMapping("/hello")
- public String hello(){
- return "hello";
- }
-
- @Operation(summary = "用户接口 - 获取用户详情")
- @GetMapping("/user/detail")
- // 这里的@Parameter也可以不加,Swagger会自动识别到这个name参数
- // 但是加@Parameter注解可以增加一些描述等有用的信息
- public User getUser(@Parameter(in = ParameterIn.QUERY, name = "name", description = "用户名") String name){
- User user = new User();
- user.setUsername(name);
- user.setPassword("123");
- return user;
- }
-
- @Operation(summary = "用户接口 - 添加用户")
- @PostMapping("/user/add")
- // 这里的user会被Swagger自动识别
- public User addUser(@RequestBody User user){
- System.out.println("添加用户");
- return user;
- }
-
-}
-
-```
-
-实体类User.java:
-
-```java
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema
-@Data
-public class User {
-
- @Schema(name = "username", description = "用户名", example = "javalover")
- private String username;
-
- @Schema(name = "password", description = "密码", example = "123456")
- private String password;
-
- // 隐藏这个属性,这样接口文档的请求参数中就看不到这个属性
- @Schema(hidden = true)
- private String email;
-
-}
-
-```
-
-启动后运行界面如下:
-
-- 首页展示:
-
-![image-20210729132629924](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\首页展示.png)
-
-- /user/add接口展示:
-
-![image-20210729132730799](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\user-add接口.png)
-
-- /user/detail接口展示
-
- ![image-20210729132849933](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\user-detail接口.png)
-
-## 问题
-
-目前只是简单地体验了下,其实里面还是有很多坑,等后面有空再整理解决,下面列举几个:
-
-- @Paramters参数无效
-- @ApiImplicitParamter的body属性无效
-- @Tag的name属性:如果name属性不是当前类名的小写连字符格式,则会被识别为一个单独的接口分组
-- 等等
-
-
-
-**最近整理了一份面试资料《Java面试题-校招版》附答案,无密码无水印,感兴趣的可以关注公众号回复“面试”领取。**
-
diff --git a/demo-task/README.md b/demo-task/README.md
deleted file mode 100644
index 829e262..0000000
--- a/demo-task/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# 定时任务:Spring自带的TaskScheduler接口
-
-## 简介
-
-Spring自带的`TaskScheduler`主要用来执行一些定时任务,比如每天的23点执行一次任务(固定时间点)、每隔10分钟执行一次任务等(固定频率)
-
-## 示例
-
-- 首先写一个定时任务
-
- `MyTask.java`
-
- ```java
- @Component
- public class MyTask {
-
- @Scheduled(cron = "*/5 * * * * *")
- public void task1(){
- System.out.println("this is task1");
- }
- }
-
- ```
-
-- 然后在主程序中添加注解`@EnableScheduling`
-
- ```java
- @SpringBootApplication
- @EnableScheduling
- public class TaskApplication {
- public static void main(String[] args) {
- SpringApplication.run(TaskApplication.class, args);
- }
- }
-
- ```
-
-- 最后启动程序,就可以看到控制台的任务执行情况,每隔5s打印一次
-
-## 知识点
-
-- 注解`@Scheduled`:设置定时任务,支持cron表达式、fixedRate固定频率触发等;
-- 注解`@EnableScheduling `:开启定时任务,可以加在主类或者配置类中
-- 配置线程池大小`spring.task.scheduling.pool.size=20`:默认是1
-
-## 参考
-
-- [@Scheduled](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-scheduled)注解
-
-- [cron表达式](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-cron-expression)
-
-- [@EnableScheduling](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support)注解
\ No newline at end of file
diff --git a/demo-upload/README.md b/demo-upload/README.md
deleted file mode 100644
index 221a187..0000000
--- a/demo-upload/README.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# 上传文件
-
-## 简介
-
-前端用vue写个简单界面,用来选择文件,进行上传
-
-后端用Spring Boot写个接口,用来接收文件
-
-> PS:注意跨域问题
-
-## 示例
-
-#### 前端:
-
-**文件上传页面** `Home.vue`
-
-```vue
-
-
-
-
-
+```
+
+下面我们启动vue`yarn run serve`,访问主界面`http://localhost:8080/`,点击主页:可以看到打印了 success
+
+![image-20211112175126444](https://i.loli.net/2021/11/12/JjARF12CPwNdXHY.png)
+
+
+
+同理,如果我们把HelloWorld.vue中的用户名/密码配置拿掉,那么就会打印fail,报401错误:
+
+![image-20211112175337702](https://i.loli.net/2021/11/12/CO8Qm5NAVtP2bwI.png)
+
+## 总结
+
+SpringSecurity的基本认证方式跟表单认证方式,后端的代码其实差不多,就是配置的地方不一样;
+
+这俩的核心都是通过用户名/密码的方式进行认证,只是适用的场景不同:
+
+- Form Login,表单登录认证,适用于单体应用
+
+- Basic Authentication,基本的http认证,适用于前后端分离的应用
+
+
+
+源码地址:[demo-spring-security-basic-auth](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-basic-auth)
diff --git a/demo-spring-security/demo-spring-security-basic-auth/web/basic-auth/README.md b/demo-spring-security/demo-spring-security-basic-auth/web/basic-auth/README.md
new file mode 100644
index 0000000..1b3ed18
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-basic-auth/web/basic-auth/README.md
@@ -0,0 +1,24 @@
+# basic-auth
+
+## Project setup
+```
+yarn install
+```
+
+### Compiles and hot-reloads for development
+```
+yarn serve
+```
+
+### Compiles and minifies for production
+```
+yarn build
+```
+
+### Lints and fixes files
+```
+yarn lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).
diff --git a/demo-spring-security/demo-spring-security-block-brute-force-auth/README.md b/demo-spring-security/demo-spring-security-block-brute-force-auth/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/demo-spring-security/demo-spring-security-jpa/README.md b/demo-spring-security/demo-spring-security-jpa/README.md
new file mode 100644
index 0000000..aadbba1
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-jpa/README.md
@@ -0,0 +1,3 @@
+## 简介
+基于JPA的SpringSecurity
+这一版跟之前的唯一不同就是,使用了JPA,将数据持久化了
diff --git a/demo-spring-security/demo-spring-security-login-form/README.md b/demo-spring-security/demo-spring-security-login-form/README.md
new file mode 100644
index 0000000..b280551
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-login-form/README.md
@@ -0,0 +1,314 @@
+
+
+## 简介
+
+SpringSecurity的认证机制有多种,比如基于用户名/密码的认证,基于OAuth2.0的认证(OAuth已废弃)。。。
+
+而基于用户名/密码的认证方式,又分多种,比如:
+
+- Form Login,表单登录认证(单体应用,比如SpringMVC)
+- Basic Authentication,基本的http认证(前后端分离应用)
+- 【已废弃】Digest Authentication,数字认证(已废弃,不再使用这种认证方式,因为它的加密方式不安全,比如md5加密等;现在比较安全的加密方式有BCrypt等)
+
+本节介绍的就是第一种:**表单登录的认证方式**
+
+## 目录
+
+1. maven配置
+2. security配置
+3. controller控制器
+4. web界面
+5. 启动运行
+
+## 正文
+
+在开始之前,需要先了解两个词
+
+- Authenticate认证:就是通过用户名/密码等方式,登入到系统,这个过程就是认证;类似于进入景区的大门
+- Authorize授权:就是登入到系统之后,校验用户是否有权限操作某个模块,这个过程就是授权;类似于进入景区后,各个收费区域,只有交了钱(有权限),才能进入指定区域;
+
+项目背景:Spring Boot + SpringMVC + Thymeleaf
+
+项目结构如下:
+
+![image-20210603141803128](https://i.loli.net/2021/06/03/FSsi8QHcnlxAyf3.png)
+
+#### 1.maven配置:
+
+```xml
+
+
+
+ spring-boot-demo
+ com.jalon
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+ demo-spring-security-login-form
+
+ 1.8
+ 2.4.3
+ 1.18.16
+ 1.8
+ 1.8
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+```
+
+#### 2.security配置
+
+这里面主要包含两部分:
+
+- authenticate 认证配置:主要配置用户名,密码,角色(这里基于内存来保存,为了简化)
+- authorize 授权配置:主要配置各个角色的权限,即可以访问哪些页面
+
+```java
+@Configuration
+@EnableWebSecurity
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ // 认证相关操作
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ // 数据没有持久化,只是保存在内存中
+ auth.inMemoryAuthentication()
+ .withUser("javalover").password(passwordEncoder().encode("123456")).roles("USER")
+ .and()
+ .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");
+ }
+
+ // 授权相关操作
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.authorizeRequests()
+ // admin页面,只有admin角色可以访问
+ .antMatchers("/admin").hasRole("ADMIN")
+ // home 页面,ADMIN 和 USER 都可以访问
+ .antMatchers("/home").hasAnyRole("USER", "ADMIN")
+ // login 页面,所有用户都可以访问
+ .antMatchers("/login").permitAll()
+ .anyRequest().authenticated()
+ .and()
+ // 自定义登录表单
+ .formLogin().loginPage("/login")
+ // 登录成功跳转的页面,第二个参数true表示每次登录成功都是跳转到home,如果false则表示跳转到登录之前访问的页面
+ .defaultSuccessUrl("/home", true)
+ // 失败跳转的页面(比如用户名/密码错误),这里还是跳转到login页面,只是给出错误提示
+ .failureUrl("/login?error=true")
+ .and()
+ .logout().permitAll()
+ .and()
+ // 权限不足时跳转的页面,即访问一个页面时没有对应的权限,会跳转到这个页面
+ .exceptionHandling().accessDeniedPage("/accessDenied");
+ }
+
+ // 定义一个密码加密器,这个BCrypt也是Spring默认的加密器
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+}
+```
+
+#### 3. controller控制器
+
+控制器主要任务就是处理请求,下面就是典型的MVC模式
+
+```java
+@Controller
+@Slf4j
+public class SecurityController {
+
+ @RequestMapping("/login")
+ public String login(){
+ log.info("=== login ===");
+ return "login";
+ }
+
+ @RequestMapping("/home")
+ public String home(Model model){
+ model.addAttribute("user", getUsername());
+ model.addAttribute("role", getAuthority());
+ return "home";
+ }
+
+ @RequestMapping("/admin")
+ public String admin(Model model){
+ model.addAttribute("user", getUsername());
+ model.addAttribute("role", getAuthority());
+ return "admin";
+ }
+
+ // 权限不足
+ @RequestMapping("/accessDenied")
+ public String accessDenied(Model model){
+ model.addAttribute("user", getUsername());
+ model.addAttribute("role", getAuthority());
+ return "access_denied";
+ }
+
+ // 获取当前登录的用户名
+ private String getUsername(){
+ return SecurityContextHolder.getContext().getAuthentication().getName();
+ }
+
+ // 获取当前登录的用户角色:因为有可能一个用户有多个角色,所以需遍历
+ private String getAuthority(){
+ Collection extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
+ ArrayList list = new ArrayList<>();
+ for(GrantedAuthority authority: authorities){
+ list.add(authority.getAuthority());
+ }
+ log.info("=== authority:" + list);
+ return list.toString();
+ }
+}
+```
+
+#### 4. web界面
+
+界面有4个:
+
+- login.html: 登录界面,所有人都可以访问
+- home.html: 主页面,普通用户和管理员可以访问
+- admin.html: 管理员页面,只有管理员可以访问
+- access_denied.html: 访问被拒绝页面,权限不足时会跳转到该页面;比如普通用户访问admin.html时
+
+login.html
+
+```html
+
+
+
+
+ Spring Security
+
+
+
+
+ Invalid username and password.
+
+
+ You have been logged out
+
+
+
+
+```
+
+
+
+home.html
+
+```html
+
+
+
+
+ Spring Security Home
+
+
+
+ 欢迎
+ 你的权限是
+ admin页面
+ 退出
+
+
+```
+
+admin.html
+
+```html
+
+
+
+
+ Spring Security Admin
+
+
+
+ 欢迎
+ 你的权限是
+ 退出
+
+
+```
+
+access_denied.html
+
+```html
+
+
+
+
+ Access Denied
+
+
+
+ 没有权限访问页面
+ 你的权限是
+ 退出
+
+
+```
+
+#### 5. 启动运行
+
+访问 http://localhost:8088,会自动跳转到login界面,如下:
+
+![image-20210603143352317](https://i.loli.net/2021/06/03/1ryvFKGR4wTZ8WP.png)
+
+这里先用普通用户的身份来登录,javalover/123456,登陆后进入主页:可以看到,权限是普通用户
+
+![image-20210603143453932](https://i.loli.net/2021/06/03/K24JhxYNAzC1o6L.png)
+
+这时点击`admin页面`就会提示权限不足,如下:
+
+![image-20210603143531292](https://i.loli.net/2021/06/03/3wEOuIRl2iQb8se.png)
+
+此时点击退出,又重新回到登录界面:并附有提示【已退出登录】
+
+![image-20210603143631810](https://i.loli.net/2021/06/03/8HwOQKTMyz5hXiI.png)
+
+最后用管理账户登录,admin/123456,登录进入主页:可以看到,权限是管理员
+
+![image-20210603143901708](https://i.loli.net/2021/06/03/Kvy1TUJPqn5OErc.png)
+
+这时点击`admin页面`,就会正常显示:
+
+![image-20210603144003914](https://i.loli.net/2021/06/03/acVsUEmMuFPpGAy.png)
+
+## 总结
+
+SpringSecurity的表单登录认证,总的来说代码不是很多,因为很多功能SpringSecurity都是自带的(比如登录、登出、权限不足等),我们只需要根据自己的需求来修改一些配置就可以了
+
+
+
+源码地址:[**demo-spring-security-login-form**](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-login-form)
diff --git a/demo-spring-security/demo-spring-security-login-redirect/README.md b/demo-spring-security/demo-spring-security-login-redirect/README.md
new file mode 100644
index 0000000..3fc9a8e
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-login-redirect/README.md
@@ -0,0 +1,250 @@
+## 简介
+
+通常一个后台管理系统,会包含不同的角色和权限;
+
+然后不同的角色,登录后会根据权限的不同,跳转到不同的界面;
+
+这里我们还是设定有两个角色:普通用户和管理员;
+
+- 普通用户:登录成功跳转到 home 页面;
+- 管理员:登录成功跳转到 admin 页面;
+
+## 目录
+
+- 基本配置
+- 配置用户和角色
+- 创建处理器类
+- 配置处理器
+- 修改控制器
+- 运行
+
+## 正文
+
+### 1. 基本配置
+
+先配置一个默认的登录成功界面,如下所示:
+
+```java
+@Configuration
+@EnableWebSecurity
+@Slf4j
+public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .formLogin()
+ .loginPage("/login")
+ // 登录成功跳转的页面,第二个参数true表示每次登录成功都是跳转到home,如果false则表示跳转到登录之前访问的页面
+ .defaultSuccessUrl("/home.html", true)
+ // ... 其他配置
+
+ }
+}
+```
+
+这样当用户登录成功后,会默认跳转到home.html界面;
+
+但是本节我们要做的就是修改这个地方,使得不同角色跳转不到不同的界面;
+
+下面开始进入主体
+
+### 2. 配置用户和角色
+
+这里我们用 全局配置:
+
+```java
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ // 数据没有持久化,只是保存在内存中
+ auth.inMemoryAuthentication()
+ .withUser("javalover").password(passwordEncoder().encode("123456")).roles("USER")
+ .and()
+ .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");
+ }
+```
+
+这里配置了两个用户:
+
+- 普通用户:javalover
+- 管理员:admin
+
+### 3. 创建处理器类
+
+第一步中的配置是:两个角色登录后,默认都是跳转到`home.html`界面;
+
+接下来就开始修改,使他们跳转到不同的界面;
+
+先定义一个处理器类,实现了`AuthenticationSuccessHandler`接口:
+
+```java
+public class MySimpleUrlAuthenticationSuccessHandler
+ implements AuthenticationSuccessHandler {
+
+
+ private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest request,
+ HttpServletResponse response, Authentication authentication)
+ throws IOException {
+
+ String targetUrl = determineTargetUrl(authentication);
+
+ if (response.isCommitted()) {
+ logger.debug(
+ "Response has already been committed. Unable to redirect to "
+ + targetUrl);
+ return;
+ }
+
+ redirectStrategy.sendRedirect(request, response, targetUrl);
+ }
+
+}
+
+```
+
+覆写的`onAuthenticationSuccess`方法:登录成功会先到这个地方,然后我们就可以在这里控制下一步要跳转的界面(当然其他的一些操作也可以);
+
+
+
+`determineTargetUrl`方法:就是这篇文章的核心;
+
+它会根据不同的权限,获取到不同的跳转url,然后重定向;
+
+```java
+protected String determineTargetUrl(final Authentication authentication) {
+
+ Map roleTargetUrlMap = new HashMap<>();
+ roleTargetUrlMap.put("ROLE_USER", "/home");
+ roleTargetUrlMap.put("ROLE_ADMIN", "/admin");
+
+ final Collection extends GrantedAuthority> authorities = authentication.getAuthorities();
+ for (final GrantedAuthority grantedAuthority : authorities) {
+ String authorityName = grantedAuthority.getAuthority();
+ if(roleTargetUrlMap.containsKey(authorityName)) {
+ return roleTargetUrlMap.get(authorityName);
+ }
+ }
+
+ throw new IllegalStateException();
+}
+```
+
+### 4. 配置处理器
+
+上面我们定义了一个处理器,用来控制登录成功后的跳转界面;
+
+这里我们将其配置到config类中;
+
+先在配置类中注入一个Bean:`AuthenticationSuccessHandler `,返回的是刚才创建的处理器类
+
+```java
+@Bean
+public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
+ return new MySimpleUrlAuthenticationSuccessHandler();
+}
+```
+
+然后替换掉开头设置的跳转url参数,如下所示:
+
+```java
+@Override
+protected void configure(final HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .formLogin()
+ .loginPage("/login")
+ .successHandler(myAuthenticationSuccessHandler())
+ // ...其他配置
+}
+```
+
+### 5. 修改控制器
+
+上面我们跳转home和admin,是通过控制器进行跳转的,下面我们配置一下控制器:
+
+```java
+@Controller
+@Slf4j
+public class SecurityController {
+
+ @RequestMapping("/login")
+ public String login(){
+ log.info("=== login ===");
+ return "login";
+ }
+
+ @RequestMapping("/home")
+ public String home(Model model){
+ model.addAttribute("user", getUsername());
+ model.addAttribute("role", getAuthority());
+ return "home";
+ }
+
+ @RequestMapping("/admin")
+ public String admin(Model model){
+ model.addAttribute("user", getUsername());
+ model.addAttribute("role", getAuthority());
+ return "admin";
+ }
+
+ @RequestMapping("/accessDenied")
+ public String accessDenied(Model model){
+ model.addAttribute("user", getUsername());
+ model.addAttribute("role", getAuthority());
+ return "access_denied";
+ }
+
+ // 获取当前登录的用户名
+ private String getUsername(){
+ return SecurityContextHolder.getContext().getAuthentication().getName();
+ }
+
+ // 获取当前登录的用户角色
+ private String getAuthority(){
+ Collection extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
+ ArrayList list = new ArrayList<>();
+ for(GrantedAuthority authority: authorities){
+ list.add(authority.getAuthority());
+ }
+ log.info("=== authority:" + list);
+ return list.toString();
+ }
+}
+```
+
+### 6. 运行
+
+接下来我们就可以启动程序,访问`http://localhost:8090/`进行测试了
+
+> 前端代码就不贴了,就是三个界面:login,.html, home.html, admin.html。完整源码见文末
+
+- 普通用户登录:
+
+![image-20211119123413681](https://i.loli.net/2021/11/19/Zt6Ds9gBxhOqH2A.png)
+
+跳转到home
+
+![image-20211119123425793](https://i.loli.net/2021/11/19/GDZEaugkNMBIK97.png)
+
+- 管理员登录:
+
+![image-20211119123533315](https://i.loli.net/2021/11/19/jWm1UARyIxfNpuL.png)
+
+跳转到admin
+
+![image-20211119123541475](https://i.loli.net/2021/11/19/AJGNECQVonU6mse.png)
+
+##
+
+## 总结
+
+重定向的核心就是那个处理器中的`determineTargetUrl`方法,根据角色的不同,跳转到不同的界面;
+
+
+
+[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-login-redirect)
+
diff --git a/demo-spring-security/demo-spring-security-logout/README.md b/demo-spring-security/demo-spring-security-logout/README.md
new file mode 100644
index 0000000..7948f8d
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-logout/README.md
@@ -0,0 +1,168 @@
+
+
+## 简介
+
+前面我们介绍了[表单登录的入门案例](https://juejin.cn/post/7030306851762176007);
+
+本篇介绍下**登出**的入门案例,代码基于表单登录的案例进行演示;
+
+代码地址见文末
+
+## 目录
+
+
+
+## 正文
+
+### 1. 基本配置
+
+最基本的登出配置如下所示:
+
+```java
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ // 登出 所有用户都可以访问
+ .logout().permitAll();
+ }
+```
+
+这里默认的登出url为`/logout`,通过在url中访问`http://localhost:8090/logout`就可以登出了。
+
+当然最方便的还是在界面中进行链接跳转,如下所示:
+
+```html
+ 退出
+```
+
+### 2. 登出跳转
+
+**logoutSuccessUrl配置**:
+
+登出跳转成功后的默认界面是根路径,比如`http://localhost:8090/`;
+
+下面我们可以进行简单的配置,配置成自己指定的界面,如下所示:一般推荐将登出成功后跳转的链接设置为登录界面(习惯)
+
+```java
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ // 登出 所有用户都可以访问
+ .logout().permitAll()
+ .logoutSuccessUrl("/login");
+ }
+```
+
+**logoutUrl配置:**
+
+登出跳转的默认url为`/logout`,比如`http://localhost:8090/logout`,如果登出成功,就跳转到上面配置的路径;
+
+配置如下所示:
+
+```java
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ // 登出 所有用户都可以访问
+ .logout().permitAll()
+ .logoutUrl("/logout");
+ }
+```
+
+### 3. 更新缓存
+
+这里的缓存指的就是session和cookie;
+
+在登出之后,需要将session失效处理,并删除对应的cookie;
+
+对应的命令为:`invalidateHttpSession()` 和 `deleteCookies(...name)`;
+
+配置如下所示:
+
+> 其中删除的Cookies名称为`JSESSIONID`,这个就是前后端交互的一个凭证id,是在第一次前端请求后端时,后端返回的id;后续的请求后端会根据JSESSIONID来匹配对应的session
+
+```java
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ // 登出 所有用户都可以访问
+ .logout().permitAll()
+ .logoutUrl("/logout")
+ .invalidateHttpSession(true)
+ .deleteCookies("JSESSIONID");
+ }
+```
+
+### 4. 登出处理器
+
+登出成功后,不仅可以设置特定的url,还可以执行一些自定义的操作;
+
+对应的命令为:`logoutSuccessHandler`
+
+比如我们需要记录登出时访问的最后一个界面,那么可以通过如下的代码来实现;
+
+先定义一个处理器:`CustomLogoutSuccessHandler.java`
+
+```java
+public class CustomLogoutSuccessHandler extends
+ SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
+
+ @Override
+ public void onLogoutSuccess(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Authentication authentication)
+ throws IOException, ServletException {
+
+ String refererUrl = request.getHeader("Referer");
+ System.out.println("Logout from: " + refererUrl);
+
+ super.onLogoutSuccess(request, response, authentication);
+ }
+}
+```
+
+然后在配置中注入该处理器,通过方法注入,如下所示:
+
+```java
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Bean
+ public LogoutSuccessHandler logoutSuccessHandler(){
+ return new CustomLogoutSuccessHandler();
+ }
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ // 登出 所有用户都可以访问
+ .logout()
+ .permitAll()
+ .logoutSuccessUrl("/login")
+ .logoutUrl("/logout")
+ .logoutSuccessHandler(logoutSuccessHandler());
+ }
+
+}
+
+```
+
+这样我们在登出时,就可以看到控制台打印下面的内容:
+
+```bash
+Logout from: http://localhost:8090/home
+```
+
+## 总结
+
+本篇介绍了登出的相关配置和处理;
+
+配置有:
+
+- logoutUrl(): 登出链接配置
+- logoutSuccessUrl(): 登出成功后的跳转链接
+- invalidateHttpSession: 失效session
+- deleteCookies() : 删除对应cookie,多个cookieName逗号分隔
+- LogoutSuccessHandler:登出后执行的自定义操作
+
+[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-logout)
+
diff --git a/demo-spring-security/demo-spring-security-manually-login/README.md b/demo-spring-security/demo-spring-security-manually-login/README.md
new file mode 100644
index 0000000..59435af
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-manually-login/README.md
@@ -0,0 +1,177 @@
+
+
+## 简介
+
+前面我们的SpringSecurity文章介绍的登录认证,不管是`form-login-auth`表单登录认证,还是`basic-auth`基本方式的认证,都是通过SpringSecurity系统自动进行的认证,我们并没用人工干预;
+
+比如表单登录认证,我们在表单中提交`login`的表单登录请求后,后台并没有自己写对应的处理器,而是由系统自动认证的;
+
+相应的,基本方式的认证也是直接把用户名密码写入header中,系统自动认证;
+
+那今天我们就来手动进行认证,以此熟悉下认证的过程
+
+## 目录
+
+## 正文
+
+### 1. 安全配置
+
+一如既往,还是简单配置一个用户,一个登录请求;
+
+不过这次的路径匹配多了一个自定义的manually-login:`.antMatchers("/manually-login").permitAll()`;
+
+这个就是我们在提交表单时要请求的路径,如果不开放,请求后会再次跳转到登录界面(因为没有权限);
+
+```java
+@Configuration
+@EnableWebSecurity
+@Slf4j
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ // 认证相关操作
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ log.info("=== SecurityConfiguration.authenticate ===");
+ // 数据没有持久化,只是保存在内存中
+ auth.inMemoryAuthentication()
+ .withUser("javalover").password(passwordEncoder().encode("123456")).roles("USER");
+ }
+
+ // 授权相关操作
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ log.info("=== SecurityConfiguration.authorize ===");
+ http
+ // home 页面,ADMIN 和 USER 都可以访问
+ .antMatchers("/home").hasAnyRole("USER", "ADMIN")
+ // login 页面,所有用户都可以访问
+ .antMatchers("/manually-login").permitAll()
+ .antMatchers("/login").permitAll()
+ .anyRequest().authenticated()
+ .and()
+ // 自定义登录表单
+ .formLogin().loginPage("/login")
+ // 登录成功跳转的页面,第二个参数true表示每次登录成功都是跳转到home,如果false则表示跳转到登录之前访问的页面
+ .defaultSuccessUrl("/home", true)
+ // 失败跳转的页面(比如用户名/密码错误),这里还是跳转到login页面,只是给出错误提示
+ .failureUrl("/login?error=true")
+ .and()
+ // 登出 所有用户都可以访问
+ .logout().permitAll()
+ }
+
+ // 定义一个密码加密器,这个BCrypt也是Spring默认的加密器
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+}
+
+```
+
+### 2. 表单组件
+
+登录表单如下所示:这里我们跳转的路径改为了`manually-login`,之前是`login`;这样登录请求时,请求登录认证会由我们自己进行处理;
+
+```html
+
+```
+
+### 3. 控制器
+
+这个控制器里有一个`manuallyLogin`就是负责接收上面的登录请求,如下所示:
+
+```java
+@Controller
+@Slf4j
+public class SecurityController {
+
+ @RequestMapping("/login")
+ public String login(){
+ log.info("=== login ===");
+ return "login";
+ }
+
+ @Autowired
+ AuthenticationManager authManager;
+
+ @PostMapping(path="/manually-login", consumes={APPLICATION_FORM_URLENCODED_VALUE})
+ public String manuallyLogin(HttpServletRequest request, String username, String password){
+ UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
+ Authentication authentication = authManager.authenticate(authenticationToken);
+ SecurityContext context = SecurityContextHolder.getContext();
+ context.setAuthentication(authentication);
+ HttpSession session = request.getSession(true);
+ session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
+ System.out.println("是否认证通过:"+context.getAuthentication());
+ return "redirect:/home" ;
+ }
+ // ...省略其他的
+
+}
+
+```
+
+AuthenticationManager会在安全配置中配置,下面会介绍;
+
+这里先介绍下manuallyLogin方法中的认证步骤:
+
+1. 首先构建一个`UsernamePasswordAuthenticationToken`,根据用户名和密码(这里的用户名和密码就是通过登录表单传入的)
+ 1. `UsernamePasswordAuthenticationToken`类是`Authentication`的实现类;
+ 2. 主要实现的功能就是通过用户名/密码来认证用户,构造函数实现
+ 3. 然后通过`isAuthenticated`判断是否认证成功
+2. 然后通过`AuthenticationManager`对上面的`token`进行认证;这一步是关键,如果用户名密码错误或者状态异常,都会在这里报错;
+3. 认证通过后,将认证结果`Authentication`保存到`SecurityContext`上下文中(这个上下文主要工作就是负责认证信息的获取和设保存);
+ 1. 这里如果不保存,那么认证就没意义了,后续的访问还是会重定向到登录界面;因为后续的权限检测都是通过这个上下文检测的
+ 2. 这里的`SecurityContext`是线程安全的,也就是说不同用户访问的是不同的`SecurityContext`上下文,互不影响
+4. 保存到上下文后,就是session的相关操作,这里先获取session;
+ 1. 获取的同时会生成JSESSIONID,如果getSession(false)则不会生成JSESSIONID;
+ 2. 将上下文保存到session中
+5. 通过`context.getAuthentication()`验证是否认证通过
+6. 最后重定向到home页面;
+
+### 4. AuthenticationManager配置
+
+这个刚开始自己用方法注入了一个,但是死活不成功,报内部错误,大概意思就是用户状态异常;
+
+后来查了下,才知道原来`WebSecurityConfigurerAdapter`配置接口本身就有获取`AuthenticationManager`的方法,直接覆写然后注入@Bean就好了,如下所示:
+
+```java
+ @Bean
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+```
+
+### 5. 实践
+
+启动程序,访问`http://localhost:8090/login`跳转到登录界面,输入`javalover/123456`发送表单请求到`manually-login`,认证通过重定向到`home`页面
+
+![image-20211123105928146](https://i.loli.net/2021/11/23/6zuweELWQJdpNnk.png)
+
+![image-20211123110142791](https://i.loli.net/2021/11/23/OW3pBoHXcDGIAQZ.png)
+
+后台打印的认证信息如下:Authenticated=true就说明认证通过,还有对应的权限信息
+
+```bash
+是否认证通过:UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=javalover, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]]
+```
+
+## 总结
+
+本篇主要通过手动认证的方式,熟悉了一下认证的过程:
+
+- 先构造一个认证对象,通过用户名/密码;
+- 再通过认证管理器进行认证请求,成功后返回认证信息(包括用户名、密码(不可见)、权限等信息),失败时会抛出各种异常;
+- 接下来将认证信息保存到上下文;
+- 最后将上下文保存到session中进行管理;
+
+
+
+[源码地址]()
diff --git a/demo-spring-security/demo-spring-security-remember-me/README.md b/demo-spring-security/demo-spring-security-remember-me/README.md
new file mode 100644
index 0000000..e3632d9
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-remember-me/README.md
@@ -0,0 +1,154 @@
+## 前言
+
+前面介绍了基于SpringSecurity的表单登录例子;
+
+本篇介绍怎么给表单登录添加一个**记住我**的功能;
+
+有了这个功能,那么在token失效后,系统会自动获取最新token,而不用重新登录;
+
+这里需要注意一点:这里的token并不是普通的token,而是JSESSIONID;这个JSESSIONID会在前端第一次请求后端时返回,以后这个JSESSION就是前后台通讯的凭证
+
+## 目录
+
+## 正文
+
+### 1. 安全配置
+
+这里我们做一个最简单的配置,如下所示:添加一个rememberMe()方法
+
+```java
+@Configuration
+@EnableWebSecurity
+@Slf4j
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ // 数据没有持久化,只是保存在内存中
+ auth.inMemoryAuthentication()
+ .withUser("javalover").password(passwordEncoder().encode("123456")).roles("USER")
+ .and()
+ .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");
+ }
+ // 授权相关操作
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ log.info("=== SecurityConfiguration.authorize ===");
+ http
+ // 登出 所有用户都可以访问
+ .logout().permitAll()
+ .deleteCookies("JSESSIONID")
+ .and()
+ .rememberMe()
+ // ...省略其他配置
+ ;
+ }
+
+
+}
+
+```
+
+可以看到,我们要做的只是提供一个rememberMe()方法,前台就可以在token失效后自动获取最新的token,而不用再重新输入用户名密码进行登录操作;
+
+### 2. 前端组件
+
+这里我们前端也尽可能简化,在之前的表单登录基础上,只增加一个复选框 rememberMe,代码如下:
+
+```html
+
+```
+
+### 3. 实践-不勾选rememberMe
+
+下面我们就可以基于上面的代码,进行一个简单的测试:看一下 勾选rememberMe和不勾选的差别
+
+> 完整代码见文末地址
+
+第一步:启动程序,界面如下所示:javalover/123456
+
+![image-20211122152245002](https://i.loli.net/2021/11/22/HJfNM4xW7p2z6PV.png)
+
+这里我们先不勾选**记住我**,点击**登录**,跳转到如下的主页:
+
+![image-20211122152555706](https://i.loli.net/2021/11/22/yMzTQc1YvmiuC5W.png)
+
+第二步:**接下来是重点**,这里我们删除本地cookie中的JSESSIONID,如下所示:F12->应用程序->cookies->JSESSION->右键-删除
+
+![image-20211122152731372](https://i.loli.net/2021/11/22/UJi4FWH9qo7kv6V.png)
+
+第三步:刷新页面,可以看到,自动跳转到登录页面,因为token失效了,前后端通讯的凭证没了:
+
+![image-20211122152901019](https://i.loli.net/2021/11/22/JazEfBAciKCy7xk.png)
+
+> 其实上面我们也可以不删除cookie,等着session失效(默认30分钟,可以在application.yml中配置:server.servlet.session.timeout=60,默认单位秒)
+>
+> 后端的session都失效了,那session产生的JSESSIONID肯定也无效了;
+
+### 4. 实践-勾选rememberMe
+
+下面我们勾选**记住我**,重复上面的步骤,会发现在删除cookie中的JSESSIONID时,看到多了一个remember-me;
+
+![image-20211122153751334](https://i.loli.net/2021/11/22/x6aBAdEGWgvVT1t.png)
+
+其实这里我们可以换个角度来理解:虽然删了JSESSIONID,但是因为还有一个remember-me,所以前后端的通讯还是没有断开;
+
+所以此时我们刷新页面,还是停留在主页,不会跳转到登录界面;
+
+**但是如果我们把remember-me也一起删掉,那么结果很明显,还是会跳到登陆界面**。
+
+### 5. 更多配置
+
+**失效时间:**
+
+上面我们配置的remember-me,默认的token失效时间是两周,下面我们可以配置的短一点,比如一天:
+
+```java
+.logout().permitAll()
+ .deleteCookies("JSESSIONID")
+ .and()
+ .rememberMe()
+ .tokenValiditySeconds(86400)
+ .and()
+```
+
+> 失效时间:严格意义上来说,上面这个失效时间 应该是remember-me的失效时间;
+
+这样的话,如果超过一天后,你再去删除JSESSIONID或者session失效,那么刷新页面还是会跳转到登录界面;
+
+**加密的密钥:**
+
+前面我们在调试界面看到的remember-me cookie值,它的值是由:MD5(用户名+过期时间+密码+密钥)合成的;
+
+这里的密钥我们可以自己配置,如下所示:
+
+```java
+.rememberMe()
+ .key("privateKey")
+ .tokenValiditySeconds(86400)
+```
+
+## 总结
+
+上面介绍了**rememberMe**的相关知识,了解了其实**rememberMe**就是:用新的通讯凭证`remember-me`来管理旧的通讯凭证`JSESSIONID`;
+
+当`JSESSIONID`被删除或者`session`过期时,如果`rememberMe cookie`还没过期(默认两周),那么系统就可以自动登录
+
+`remember-me`真实的过期时间可以在调试界面看到,如下所示:
+
+![image-20211122162553634](https://i.loli.net/2021/11/22/bQH12ZA3k8dy4nT.png)
+
diff --git a/demo-spring-security/demo-spring-security-session/README.md b/demo-spring-security/demo-spring-security-session/README.md
new file mode 100644
index 0000000..f591834
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-session/README.md
@@ -0,0 +1,151 @@
+
+
+## 简介
+
+前面我们介绍了基于SpringSecurity的两种认证方式:[表单认证](https://juejin.cn/post/7030306851762176007)和[基本认证](https://juejin.cn/post/7031077013393768484);
+
+本篇我们介绍下认证后如何获取用户的基本信息;
+
+这里的核心就是`SecurityContextHolder`类。
+
+## 目录
+
+1. 从SecurityContextHolder中获取
+2. 从控制器中获取
+3. 从自定义接口获取
+
+## 正文
+
+### 1. 从SecurityContextHolder中获取
+
+代码如下所示:
+
+```java
+Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+if (!(authentication instanceof AnonymousAuthenticationToken)) {
+ String currentUserName = authentication.getName();
+ return currentUserName;
+}else{
+ return "";
+}
+```
+
+这里我们加了一个校验,当获取的认证用户存在时我们才去访问;
+
+这种方法最大的好处就是方便,直接通过静态方法就可以获取;
+
+但是缺点也很明显,其中最大的缺点就是不方便测试;
+
+### 2. 从控制器中获取
+
+通过控制器来获取用户信息,就很灵活了;
+
+- 我们可以通过Principal参数获取:这里的Principal其实就是一个实体类,用来代表用户,可以是个人,也可以是公司
+
+```java
+@GetMapping("/userinfo-principal")
+public String userinfoByPrincipal(Principal principal) {
+ return principal.getName();
+}
+```
+
+- 也可以通过Authentication参数获取:
+
+```java
+@GetMapping("/userinfo-authentication")
+public String userinfoByAuthentication(Authentication authentication) {
+ return authentication.getName();
+}
+```
+
+上面我们主要获取了用户名,用户的其他信息也是可以获取的;
+
+这里我们可以试着获取用户的权限:通过 UserDetails 类来获取:
+
+```java
+@GetMapping("/userinfo-authentication")
+public String userinfoByAuthentication(Authentication authentication) {
+ UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+ System.out.println("User has authorities: " + userDetails.getAuthorities());
+ return authentication.getName();
+}
+```
+
+> 需要注意的是, Principal 是不能转换成UserDetails的,因为他俩之间没关系;
+>
+> 而这里的authentication.getPrincipal()返回的是一个Object对象,在请求接口时,Object传入的是一个UserDetails对象,所以获取时可以通过UserDetails强转;
+
+通过debug我们可以看到,在访问`/userinfo-authentication`时,getPrincipal返回的实际上就是一个User对象(User实现了UserDetails),所以转换是没问题的
+
+![image-20211116172359093](https://i.loli.net/2021/11/16/xogBnKGP5kcCeIJ.png)
+
+- 还可以通过HttpServletRequest获取:这个其实本质还是通过 Principal 获取
+
+```java
+@GetMapping("/userinfo-request")
+public String userinfoByRequest(HttpServletRequest request) {
+ Principal principal = request.getUserPrincipal();
+ return principal.getName();
+}
+```
+
+### 3. 自定义接口获取
+
+前面我们体验了从控制器获取,这种方式有点局限,因为如果想在其他类中获取,就无能为力了;
+
+其实更灵活的方式是自己定义一个Bean,然后在需要的地方注入来获取;
+
+这里其实是对第一种方式的升级,将 SecurityContextHolder包装到一个Bean中,然后在需要的地方进行注入即可;
+
+接口类:**IAuthenticationFacade**
+
+```java
+public interface IAuthenticationFacade {
+ Authentication getAuthentication();
+}
+```
+
+实现类:**AuthenticationFacade**
+
+```java
+@Component
+public class AuthenticationFacade implements IAuthenticationFacade {
+
+ @Override
+ public Authentication getAuthentication() {
+ return SecurityContextHolder.getContext().getAuthentication();
+ }
+}
+```
+
+这样一来,我们就可以在需要的地方通过@Autowired注入IAuthenticationFacade即可:
+
+```java
+@RestController
+public class UserController {
+
+ @Autowired
+ IAuthenticationFacade authenticationFacade;
+
+ @GetMapping("/userinfo-custom-interface")
+ public String userinfoByCustomInterface() {
+ Authentication authentication = authenticationFacade.getAuthentication();
+ return authentication.getName();
+ }
+
+}
+```
+
+可以看到,这次我们没有用到任何的参数,只通过自定义的Bean来获取,这样不仅充分利用了Spring的依赖注入功能,还使得获取信息变得更加灵活。
+
+## 总结
+
+上面介绍了三种获取方式:
+
+1. 直接通过 SecurityContextHolder 的静态方法获取
+2. 从控制器中获取:通过各种参数,比如Principal、Authentication、HttpServletRequest
+3. 从自定义的Bean中获取:该方法是对方法1的升级,通过将 SecurityContextHolder包装到 Bean 中,使得获取信息更加灵活
+
+
+
+[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-userinfo)
diff --git a/demo-spring-security/demo-spring-security-userinfo/README.md b/demo-spring-security/demo-spring-security-userinfo/README.md
new file mode 100644
index 0000000..f591834
--- /dev/null
+++ b/demo-spring-security/demo-spring-security-userinfo/README.md
@@ -0,0 +1,151 @@
+
+
+## 简介
+
+前面我们介绍了基于SpringSecurity的两种认证方式:[表单认证](https://juejin.cn/post/7030306851762176007)和[基本认证](https://juejin.cn/post/7031077013393768484);
+
+本篇我们介绍下认证后如何获取用户的基本信息;
+
+这里的核心就是`SecurityContextHolder`类。
+
+## 目录
+
+1. 从SecurityContextHolder中获取
+2. 从控制器中获取
+3. 从自定义接口获取
+
+## 正文
+
+### 1. 从SecurityContextHolder中获取
+
+代码如下所示:
+
+```java
+Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+if (!(authentication instanceof AnonymousAuthenticationToken)) {
+ String currentUserName = authentication.getName();
+ return currentUserName;
+}else{
+ return "";
+}
+```
+
+这里我们加了一个校验,当获取的认证用户存在时我们才去访问;
+
+这种方法最大的好处就是方便,直接通过静态方法就可以获取;
+
+但是缺点也很明显,其中最大的缺点就是不方便测试;
+
+### 2. 从控制器中获取
+
+通过控制器来获取用户信息,就很灵活了;
+
+- 我们可以通过Principal参数获取:这里的Principal其实就是一个实体类,用来代表用户,可以是个人,也可以是公司
+
+```java
+@GetMapping("/userinfo-principal")
+public String userinfoByPrincipal(Principal principal) {
+ return principal.getName();
+}
+```
+
+- 也可以通过Authentication参数获取:
+
+```java
+@GetMapping("/userinfo-authentication")
+public String userinfoByAuthentication(Authentication authentication) {
+ return authentication.getName();
+}
+```
+
+上面我们主要获取了用户名,用户的其他信息也是可以获取的;
+
+这里我们可以试着获取用户的权限:通过 UserDetails 类来获取:
+
+```java
+@GetMapping("/userinfo-authentication")
+public String userinfoByAuthentication(Authentication authentication) {
+ UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+ System.out.println("User has authorities: " + userDetails.getAuthorities());
+ return authentication.getName();
+}
+```
+
+> 需要注意的是, Principal 是不能转换成UserDetails的,因为他俩之间没关系;
+>
+> 而这里的authentication.getPrincipal()返回的是一个Object对象,在请求接口时,Object传入的是一个UserDetails对象,所以获取时可以通过UserDetails强转;
+
+通过debug我们可以看到,在访问`/userinfo-authentication`时,getPrincipal返回的实际上就是一个User对象(User实现了UserDetails),所以转换是没问题的
+
+![image-20211116172359093](https://i.loli.net/2021/11/16/xogBnKGP5kcCeIJ.png)
+
+- 还可以通过HttpServletRequest获取:这个其实本质还是通过 Principal 获取
+
+```java
+@GetMapping("/userinfo-request")
+public String userinfoByRequest(HttpServletRequest request) {
+ Principal principal = request.getUserPrincipal();
+ return principal.getName();
+}
+```
+
+### 3. 自定义接口获取
+
+前面我们体验了从控制器获取,这种方式有点局限,因为如果想在其他类中获取,就无能为力了;
+
+其实更灵活的方式是自己定义一个Bean,然后在需要的地方注入来获取;
+
+这里其实是对第一种方式的升级,将 SecurityContextHolder包装到一个Bean中,然后在需要的地方进行注入即可;
+
+接口类:**IAuthenticationFacade**
+
+```java
+public interface IAuthenticationFacade {
+ Authentication getAuthentication();
+}
+```
+
+实现类:**AuthenticationFacade**
+
+```java
+@Component
+public class AuthenticationFacade implements IAuthenticationFacade {
+
+ @Override
+ public Authentication getAuthentication() {
+ return SecurityContextHolder.getContext().getAuthentication();
+ }
+}
+```
+
+这样一来,我们就可以在需要的地方通过@Autowired注入IAuthenticationFacade即可:
+
+```java
+@RestController
+public class UserController {
+
+ @Autowired
+ IAuthenticationFacade authenticationFacade;
+
+ @GetMapping("/userinfo-custom-interface")
+ public String userinfoByCustomInterface() {
+ Authentication authentication = authenticationFacade.getAuthentication();
+ return authentication.getName();
+ }
+
+}
+```
+
+可以看到,这次我们没有用到任何的参数,只通过自定义的Bean来获取,这样不仅充分利用了Spring的依赖注入功能,还使得获取信息变得更加灵活。
+
+## 总结
+
+上面介绍了三种获取方式:
+
+1. 直接通过 SecurityContextHolder 的静态方法获取
+2. 从控制器中获取:通过各种参数,比如Principal、Authentication、HttpServletRequest
+3. 从自定义的Bean中获取:该方法是对方法1的升级,通过将 SecurityContextHolder包装到 Bean 中,使得获取信息更加灵活
+
+
+
+[源码地址](https://github.com/Jalon2015/spring-boot-demo/tree/master/demo-spring-security/demo-spring-security-userinfo)
diff --git a/demo-swagger3/README.md b/demo-swagger3/README.md
new file mode 100644
index 0000000..fefb0ed
--- /dev/null
+++ b/demo-swagger3/README.md
@@ -0,0 +1,259 @@
+## 目录
+
+- 前言:什么是Swagger
+- 起步:(只需简单的3步)
+ - 加载依赖
+ - 添加注解@EnableOpenApi
+ - 启动SpringBoot,访问Swagger后台界面
+- 配置:基于Java的配置
+- 注解:Swagger2 和 Swagger3做对比
+- 源码:
+- 问题:踩坑记录(后面再整理)
+
+## 前言
+
+**什么是Swagger:**
+
+ Swagger 是最流行的 API 开发工具,它遵循 OpenAPI Specification(OpenAPI 规范,也简称 OAS)。
+
+ 它最方便的地方就在于,API文档可以和服务端保持同步,即服务端更新一个接口,前端的API文档就可以实时更新,而且可以在线测试。
+
+ 这样一来,Swagger就大大降低了前后端的沟通障碍,不用因为一个接口调不通而争论不休
+
+> 之前用的看云文档,不过这种第三方的都需要手动维护,还是不太方便
+
+## 起步
+
+1. 加载依赖
+
+```xml
+
+ io.springfox
+ springfox-boot-starter
+ 3.0.0
+
+```
+
+2. 添加@EnableOpenApi注解
+
+```java
+@EnableOpenApi
+@SpringBootApplication
+public class SwaggerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(SwaggerApplication.class, args);
+ }
+}
+```
+
+3. 启动项目,访问"http://localhost:8080/swagger-ui/index.html"
+
+![image-20210729112424407](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\主页.png)
+
+这样一个简单的Swagger后台接口文档就搭建完成了;
+
+下面我们说下配置和注解
+
+## 配置
+
+可以看到,上面那个界面中,默认显示了一个`basic-error-controller`接口分组,但是我们并没有写;
+
+通过在项目中查找我们发现,SpringBoot内部确实有这样一个控制器类,如下所示:
+
+![image-20210729113119350](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\BasicErrorController.png)
+
+这说明Swagger默认的配置,会自动把@Controller控制器类添加到接口文档中
+
+下面我们就自己配置一下,如下所示:
+
+```java
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.oas.annotations.EnableOpenApi;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public Docket createRestApi() {
+ // 配置OAS 3.0协议
+ return new Docket(DocumentationType.OAS_30)
+ .apiInfo(apiInfo())
+ .select()
+ // 查找有@Tag注解的类,并生成一个对应的分组;类下面的所有http请求方法,都会生成对应的API接口
+ // 通过这个配置,就可以将那些没有添加@Tag注解的控制器类排除掉
+ .apis(RequestHandlerSelectors.withClassAnnotation(Tag.class))
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("GPS Doc")
+ .description("GPS Doc文档")
+ .termsOfServiceUrl("http://www.javalover.com")
+ .contact(new Contact("javalover", "http://www.javalover.cn", "1121263265@qq.com"))
+ .version("2.0.0")
+ .build();
+ }
+
+}
+```
+
+这样上面那个`basic-error-controller`就看不见了
+
+## 注解
+
+我们先看下Swagger2中的注解,如下所示:
+
+- @Api:用在控制器类上,表示对类的说明
+ - tags="说明该类的作用,可以在UI界面上看到的说明信息的一个好用注解"
+ - value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
+
+- @ApiOperation:用在请求的方法上,说明方法的用途、作用
+ - value="说明方法的用途、作用"
+ - notes="方法的备注说明"
+
+- @ApiImplicitParams:用在请求的方法上,表示一组参数说明
+ - @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面(标注一个指定的参数,详细概括参数的各个方面,例如:参数名是什么?参数意义,是否必填等)
+ - name:属性值为方法参数名
+ - value:参数意义的汉字说明、解释
+ - required:参数是否必须传
+ - paramType:参数放在哪个地方
+ - header --> 请求参数的获取:@RequestHeader
+ - query --> 请求参数的获取:@RequestParam
+ - path(用于restful接口)--> 请求参数的获取:@PathVariable
+ - dataType:参数类型,默认String,其它值dataType="Integer"
+ - defaultValue:参数的默认值
+
+- @ApiResponses:用在请求的方法上,表示一组响应
+ - @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
+ - code:状态码数字,例如400
+ - message:信息,例如"请求参数没填好"
+ - response:抛出异常的类
+
+- @ApiModel:用于响应类上(POJO实体类),描述一个返回响应数据的信息(描述POJO类请求或响应的实体说明)
+ (这种一般用在post接口的时候,使用@RequestBody接收JSON格式的数据的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候)
+ - @ApiModelProperty:用在POJO属性上,描述响应类的属性说明
+- @ApiIgnore:使用该注解忽略这个某个API或者参数;
+
+上面这些是Swagger2的注解,下面我们看下Swagger3和它的简单对比
+
+![Swagger3注解](https://i.loli.net/2021/07/29/s62vJN5XLKdugER.png)
+
+接下来我们就用Swagger3的注解来写一个接口看下效果(其中穿插了Swagger2的注解)
+
+- 控制器UserController.java
+
+```java
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+@Tag(name = "user-controller", description = "用户接口")
+@RestController
+public class UserController {
+
+ // 忽略这个api
+ @Operation(hidden = true)
+ @GetMapping("/hello")
+ public String hello(){
+ return "hello";
+ }
+
+ @Operation(summary = "用户接口 - 获取用户详情")
+ @GetMapping("/user/detail")
+ // 这里的@Parameter也可以不加,Swagger会自动识别到这个name参数
+ // 但是加@Parameter注解可以增加一些描述等有用的信息
+ public User getUser(@Parameter(in = ParameterIn.QUERY, name = "name", description = "用户名") String name){
+ User user = new User();
+ user.setUsername(name);
+ user.setPassword("123");
+ return user;
+ }
+
+ @Operation(summary = "用户接口 - 添加用户")
+ @PostMapping("/user/add")
+ // 这里的user会被Swagger自动识别
+ public User addUser(@RequestBody User user){
+ System.out.println("添加用户");
+ return user;
+ }
+
+}
+
+```
+
+实体类User.java:
+
+```java
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema
+@Data
+public class User {
+
+ @Schema(name = "username", description = "用户名", example = "javalover")
+ private String username;
+
+ @Schema(name = "password", description = "密码", example = "123456")
+ private String password;
+
+ // 隐藏这个属性,这样接口文档的请求参数中就看不到这个属性
+ @Schema(hidden = true)
+ private String email;
+
+}
+
+```
+
+启动后运行界面如下:
+
+- 首页展示:
+
+![image-20210729132629924](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\首页展示.png)
+
+- /user/add接口展示:
+
+![image-20210729132730799](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\user-add接口.png)
+
+- /user/detail接口展示
+
+ ![image-20210729132849933](D:\StudyData\github-project\tangyuanxueJava\【文章】\【SpringBoot】\知识点\后台接口文档管理Swagger3\user-detail接口.png)
+
+## 问题
+
+目前只是简单地体验了下,其实里面还是有很多坑,等后面有空再整理解决,下面列举几个:
+
+- @Paramters参数无效
+- @ApiImplicitParamter的body属性无效
+- @Tag的name属性:如果name属性不是当前类名的小写连字符格式,则会被识别为一个单独的接口分组
+- 等等
+
+
+
+**最近整理了一份面试资料《Java面试题-校招版》附答案,无密码无水印,感兴趣的可以关注公众号回复“面试”领取。**
+
diff --git a/demo-task/README.md b/demo-task/README.md
new file mode 100644
index 0000000..829e262
--- /dev/null
+++ b/demo-task/README.md
@@ -0,0 +1,52 @@
+# 定时任务:Spring自带的TaskScheduler接口
+
+## 简介
+
+Spring自带的`TaskScheduler`主要用来执行一些定时任务,比如每天的23点执行一次任务(固定时间点)、每隔10分钟执行一次任务等(固定频率)
+
+## 示例
+
+- 首先写一个定时任务
+
+ `MyTask.java`
+
+ ```java
+ @Component
+ public class MyTask {
+
+ @Scheduled(cron = "*/5 * * * * *")
+ public void task1(){
+ System.out.println("this is task1");
+ }
+ }
+
+ ```
+
+- 然后在主程序中添加注解`@EnableScheduling`
+
+ ```java
+ @SpringBootApplication
+ @EnableScheduling
+ public class TaskApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(TaskApplication.class, args);
+ }
+ }
+
+ ```
+
+- 最后启动程序,就可以看到控制台的任务执行情况,每隔5s打印一次
+
+## 知识点
+
+- 注解`@Scheduled`:设置定时任务,支持cron表达式、fixedRate固定频率触发等;
+- 注解`@EnableScheduling `:开启定时任务,可以加在主类或者配置类中
+- 配置线程池大小`spring.task.scheduling.pool.size=20`:默认是1
+
+## 参考
+
+- [@Scheduled](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-scheduled)注解
+
+- [cron表达式](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-cron-expression)
+
+- [@EnableScheduling](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support)注解
\ No newline at end of file
diff --git a/demo-upload/README.md b/demo-upload/README.md
new file mode 100644
index 0000000..221a187
--- /dev/null
+++ b/demo-upload/README.md
@@ -0,0 +1,94 @@
+# 上传文件
+
+## 简介
+
+前端用vue写个简单界面,用来选择文件,进行上传
+
+后端用Spring Boot写个接口,用来接收文件
+
+> PS:注意跨域问题
+
+## 示例
+
+#### 前端:
+
+**文件上传页面** `Home.vue`
+
+```vue
+
+
+
+
+