2021最新Spring Security知识梳理

一、SpringSecurity 框架简介

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。 正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。

1、用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。

2、用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

整理了spring全家桶学习笔记,spring家族成员的学习资料都系统整理好了,需要的话可以直接点击领取

二、SpringSecurity与shiro

1、SpringSecurity特点

(1)和 Spring 无缝整合。

(2)全面的权限控制。

(3)专门为 Web 开发而设计。

  • 旧版本不能脱离 Web 环境使用。
  • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独
  • 引入核心模块就可以脱离 Web 环境。
  • 重量级

2、shiro特点

Apache 旗下的轻量级权限控制框架。

(1)轻量级

Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。

(2)通用性

好处:不局限于 Web 环境,可以脱离 Web 环境使用。

缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。

3、SpringSecurity与shiro总结

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,SpringSecurity 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。因此,一般来说,常见的安全管理技术栈的组合是这样的:

(1)SSM + Shiro

(2)Spring Boot/Spring Cloud + Spring Security

以上只是一个推荐的组合而已,如果单纯从技术上来说,无论怎么组合,都是可以运行的。

三、Spring Security过滤器

1、Spring Security中常见过滤器

  1. FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。
  2. ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常。
  3. UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户

名,密码。

2、15种过滤器

SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的 15 个过滤器进行说明:

(1) WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。

(2) SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将SecurityContextHolder 中的信息清除,例如Session 中维护一个用户的安全信息就是这个过滤器处理的。

(3) HeaderWriterFilter:用于将头信息加入响应中。

(4) CsrfFilter:用于处理跨站请求伪造。

(5)LogoutFilter:用于处理退出登录。

(6)UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的 usernameParameter 和 passwordParameter 两个参数的值进行修改。

(7)DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。

(8)BasicAuthenticationFilter:检测和处理 http basic 认证。

(9)RequestCacheAwareFilter:用来处理请求的缓存。

(10)SecurityContextHolderAwareRequestFilter:主要是包装请求对象 request。

(11)AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在Authentication 对象,如果不存在为其提供一个匿名 Authentication。

(12)SessionManagementFilter:管理 session 的过滤器

(13)ExceptionTranslationFilter:处理 AccessDeniedException 和AuthenticationException 异常。

(14)FilterSecurityInterceptor:可以看做过滤器链的出口。

(15)RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie,用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。

3、SpringSecurity 基本流程

Spring Security 采取过滤链实现认证与授权,只有当前过滤器通过,才能进入下一个过滤器:

绿色部分是认证过滤器,需要我们自己配置,可以配置多个认证过滤器。认证过滤器可以使用 Spring Security 提供的认证过滤器,也可以自定义过滤器(例如:短信验证)。认证过滤器要在 configure(HttpSecurity http)方法中配置,没有配置不生效。

下面会重点介绍以下三个过滤器: UsernamePasswordAuthenticationFilter 过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证。ExceptionTranslationFilter 过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)。FilterSecurityInterceptor 过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并 由 ExceptionTranslationFilter 过滤器进行捕获和处理。

4、SpringSecurity 认证流程

认证流程是在 UsernamePasswordAuthenticationFilter 过滤器中处理的,具体流程如下所示:

四、PasswordEncoder 接口

// 表示把参数按照特定的解析规则进行解析
String encode(CharSequence rawPassword);// 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹
配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个
参数表示存储的密码。
boolean matches(CharSequence rawPassword, String encodedPassword);// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回
false。默认返回 false。
default boolean upgradeEncoding(String encodedPassword) {return false;
}复制代码

BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。 BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认 10。

五、SpringBoot整合Spring Security入门

1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.guor</groupId><artifactId>securityProject</artifactId><version>0.0.1-SNAPSHOT</version><name>securityProject</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--lombok用来简化实体类--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
复制代码

2、application.properties

server.port=8111
#spring.security.user.name=root
#spring.security.user.password=root#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
复制代码

3、SecurityConfig

package com.guor.security.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String password = passwordEncoder.encode("123");auth.inMemoryAuthentication().withUser("zs").password(password).roles("admin");}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}
}
复制代码
package com.guor.security.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;import javax.sql.DataSource;@Configuration
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;//注入数据源@Autowiredprivate DataSource dataSource;//配置对象@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {//退出http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();//配置没有权限访问跳转自定义页面http.exceptionHandling().accessDeniedPage("/unauth.html");http.formLogin()   //自定义自己编写的登录页面.loginPage("/on.html")  //登录页面设置.loginProcessingUrl("/user/login")   //登录访问路径.defaultSuccessUrl("/success.html").permitAll()  //登录成功之后,跳转路径.failureUrl("/unauth.html").and().authorizeRequests().antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证//当前登录用户,只有具有admins权限才可以访问这个路径//1 hasAuthority方法// .antMatchers("/test/index").hasAuthority("admins")//2 hasAnyAuthority方法// .antMatchers("/test/index").hasAnyAuthority("admins,manager")//3 hasRole方法   ROLE_sale.antMatchers("/test/index").hasRole("sale").anyRequest().authenticated().and().rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(60)//设置有效时长,单位秒.userDetailsService(userService);// .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());// .and().csrf().disable();  //关闭csrf防护}
}
复制代码

4、启动类

package com.guor.security;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;@SpringBootApplication
@MapperScan("com.guor.security.mapper")
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
public class SecurityProjectApplication {public static void main(String[] args) {SpringApplication.run(SecurityProjectApplication.class, args);}}
复制代码

5、User

package com.guor.security.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
public class User {private Integer id;private String username;private String password;
}
复制代码

6、UserService

package com.guor.security.service;import com.guor.security.entity.User;
import com.guor.security.mapper.UsersMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//调用usersMapper方法,根据用户名查询数据库QueryWrapper<Users> wrapper = new QueryWrapper();// where username=?wrapper.eq("username",username);User user = userMapper.selectOne(wrapper);//判断if(user == null) {//数据库没有用户名,认证失败throw  new UsernameNotFoundException("用户名不存在!");}List<GrantedAuthority> auths =AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");//从查询数据库返回users对象,得到用户名和密码,返回return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auths);}
}
复制代码

7、UserMapper

package com.guor.security.mapper;import com.guor.security.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper<User> {
}
复制代码

8、UserController

package com.guor.security.controller;import com.guor.security.entity.User;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@RestController
@RequestMapping("/test")
public class UserController {@GetMapping("hello")public String hello() {return "hello security";}@GetMapping("index")public String index() {return "hello index";}@GetMapping("update")//@Secured({"ROLE_sale","ROLE_manager"})//@PreAuthorize("hasAnyAuthority('admins')")@PostAuthorize("hasAnyAuthority('admins')")public String update() {System.out.println("update......");return "hello update";}@GetMapping("getAll")@PostAuthorize("hasAnyAuthority('admins')")@PostFilter("filterObject.username == 'admin1'")public List<Users> getAllUser(){ArrayList<Users> list = new ArrayList<>();list.add(new Users(11,"admin1","6666"));list.add(new Users(21,"admin2","888"));System.out.println(list);return list;}}复制代码

六、微服务认证与授权实现思路

1、如果是基于 Session,那么 Spring-security 会对 cookie 里的 sessionid 进行解析,找到服务器存储的 session 信息,然后判断当前用户是否符合请求的要求。

2、如果是 token,则是解析出 token,然后将当前请求加入到 Spring-security 管理的权限信息中去。

如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为 key,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带到 header 请求头中,Spring-security 解析 header 头获取 token 信息,解析 token 获取当前用户名,根据用户名就可以从 redis中获取权限列表,这样 Spring-security 就能够判断当前请求是否有权限访问。

七、微服务代码实例

1、父工程pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><modules><module>common</module><module>infrastructure</module><module>service</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.atguigu</groupId><artifactId>acl_parent</artifactId><packaging>pom</packaging><version>0.0.1-SNAPSHOT</version><name>acl_parent</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><mybatis-plus.version>3.0.5</mybatis-plus.version><velocity.version>2.0</velocity.version><swagger.version>2.7.0</swagger.version><jwt.version>0.7.0</jwt.version><fastjson.version>1.2.28</fastjson.version><gson.version>2.8.2</gson.version><json.version>20170516</json.version><cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version></properties><dependencyManagement><dependencies><!--Spring Cloud--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--mybatis-plus 持久层--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>${velocity.version}</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version></dependency><!--swagger ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger.version}</version></dependency><!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>${json.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
复制代码

2、common模块

common模块pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>acl_parent</artifactId><groupId>com.atguigu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>common</artifactId><packaging>pom</packaging><modules><module>service_base</module><module>spring_security</module></modules><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><scope>provided </scope></dependency><!--lombok用来简化实体类:需要安装lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided </scope></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><scope>provided </scope></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><scope>provided </scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency></dependencies></project>
复制代码

3、common模块 -> SpringSecurity子模块

(1)核心配置类

Spring Security 的核心配置就是继承 WebSecurityConfigurerAdapter 并注解@EnableWebSecurity 的配置。这个配置指明了用户名密码的处理方式、请求路径、登录登出控制等和安全相关的配置

package com.atguigu.security.config;import com.atguigu.security.filter.TokenAuthFilter;
import com.atguigu.security.filter.TokenLoginFilter;
import com.atguigu.security.security.DefaultPasswordEncoder;
import com.atguigu.security.security.TokenLogoutHandler;
import com.atguigu.security.security.TokenManager;
import com.atguigu.security.security.UnauthEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private DefaultPasswordEncoder defaultPasswordEncoder;private UserDetailsService userDetailsService;@Autowiredpublic TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {this.userDetailsService = userDetailsService;this.defaultPasswordEncoder = defaultPasswordEncoder;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}/*** 配置设置* @param http* @throws Exception*///设置退出的地址和token,redis操作地址@Overrideprotected void configure(HttpSecurity http) throws Exception {http.exceptionHandling().authenticationEntryPoint(new UnauthEntryPoint())//没有权限访问.and().csrf().disable().authorizeRequests().anyRequest().authenticated().and().logout().logoutUrl("/admin/acl/index/logout")//退出路径.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();}//调用userDetailsService和密码处理@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);}//不进行认证的路径,可以直接访问@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/api/**");}
}
复制代码

(2)实体类

package com.atguigu.security.entity;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "微信openid")private String username;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty(value = "昵称")private String nickName;@ApiModelProperty(value = "用户头像")private String salt;@ApiModelProperty(value = "用户签名")private String token;}
复制代码

(3)过滤器

package com.atguigu.security.filter;import com.atguigu.security.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;public class TokenAuthFilter extends BasicAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenAuthFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {super(authenticationManager);this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {//获取当前认证成功用户权限信息UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);//判断如果有权限信息,放到权限上下文中if(authRequest != null) {SecurityContextHolder.getContext().setAuthentication(authRequest);}chain.doFilter(request,response);}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//从header获取tokenString token = request.getHeader("token");if(token != null) {//从token获取用户名String username = tokenManager.getUserInfoFromToken(token);//从redis获取对应权限列表List<String> permissionValueList = (List<String>)redisTemplate.opsForValue().get(username);Collection<GrantedAuthority> authority = new ArrayList<>();for(String permissionValue : permissionValueList) {SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);authority.add(auth);}return new UsernamePasswordAuthenticationToken(username,token,authority);}return null;}}复制代码
package com.atguigu.security.filter;import com.atguigu.security.entity.SecurityUser;
import com.atguigu.security.entity.User;
import com.atguigu.security.security.TokenManager;
import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private AuthenticationManager authenticationManager;public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {this.authenticationManager = authenticationManager;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;this.setPostOnly(false);this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));}//1 获取表单提交用户名和密码@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {//获取表单提交数据try {User user = new ObjectMapper().readValue(request.getInputStream(), User.class);return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));} catch (IOException e) {e.printStackTrace();throw new RuntimeException();}}//2 认证成功调用的方法@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)throws IOException, ServletException {//认证成功,得到认证成功之后用户信息SecurityUser user = (SecurityUser)authResult.getPrincipal();//根据用户名生成tokenString token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());//把用户名称和用户权限列表放到redisredisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());//返回tokenResponseUtil.out(response, R.ok().data("token",token));}//3 认证失败调用的方法protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)throws IOException, ServletException {ResponseUtil.out(response, R.error());}
}复制代码

(4)security

package com.atguigu.security.security;import com.atguigu.utils.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;@Component
public class DefaultPasswordEncoder implements PasswordEncoder {public DefaultPasswordEncoder() {this(-1);}public DefaultPasswordEncoder(int strength) {}//进行MD5加密@Overridepublic String encode(CharSequence charSequence) {return MD5.encrypt(charSequence.toString());}//进行密码比对@Overridepublic boolean matches(CharSequence charSequence, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(charSequence.toString()));}
}复制代码
package com.atguigu.security.security;import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//退出处理器
public class TokenLogoutHandler implements LogoutHandler {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {//1 从header里面获取token//2 token不为空,移除token,从redis删除tokenString token = request.getHeader("token");if(token != null) {//移除tokenManager.removeToken(token);//从token获取用户名String username = tokenManager.getUserInfoFromToken(token);redisTemplate.delete(username);}ResponseUtil.out(response, R.ok());}
}
复制代码
package com.atguigu.security.security;import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;import java.util.Date;@Component
public class TokenManager {//token有效时长private long tokenEcpiration = 24*60*60*1000;//编码秘钥private String tokenSignKey = "123456";//1 使用jwt根据用户名生成tokenpublic String createToken(String username) {String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration)).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}//2 根据token字符串得到用户信息public String getUserInfoFromToken(String token) {String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return userinfo;}//3 删除tokenpublic void removeToken(String token) { }
}
复制代码
package com.atguigu.security.security;import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class UnauthEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(httpServletResponse, R.error());}
}
复制代码

4、common模块 -> service_base

(1)RedisConfig

package com.atguigu.utils;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@EnableCaching //开启缓存
@Configuration  //配置类
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);template.setConnectionFactory(factory);//key序列化方式template.setKeySerializer(redisSerializer);//value序列化template.setValueSerializer(jackson2JsonRedisSerializer);//value hashmap序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
复制代码

(2)SwaggerConfig

package com.atguigu.utils;import com.google.common.base.Predicates;
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.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//.paths(Predicates.not(PathSelectors.regex("/admin/.*"))).paths(Predicates.not(PathSelectors.regex("/error.*"))).build();}private ApiInfo webApiInfo(){return new ApiInfoBuilder().title("网站-课程中心API文档").description("本文档描述了课程中心微服务接口定义").version("1.0").contact(new Contact("java", "http://atguigu.com", "1123@qq.com")).build();}
}
复制代码

(3)工具类

package com.atguigu.utils.utils;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}public static void main(String[] args) {System.out.println(MD5.encrypt("111111"));}}复制代码
package com.atguigu.utils.utils;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class ResponseUtil {public static void out(HttpServletResponse response, R r) {ObjectMapper mapper = new ObjectMapper();response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);try {mapper.writeValue(response.getWriter(), r);} catch (IOException e) {e.printStackTrace();}}
}
复制代码
package com.atguigu.utils.utils;import lombok.Data;
import java.util.HashMap;
import java.util.Map;//统一返回结果的类
@Data
public class R {private Boolean success;private Integer code;private String message;private Map<String, Object> data = new HashMap<String, Object>();//把构造方法私有private R() {}//成功静态方法public static R ok() {R r = new R();r.setSuccess(true);r.setCode(20000);r.setMessage("成功");return r;}//失败静态方法public static R error() {R r = new R();r.setSuccess(false);r.setCode(20001);r.setMessage("失败");return r;}public R success(Boolean success){this.setSuccess(success);return this;}public R message(String message){this.setMessage(message);return this;}public R code(Integer code){this.setCode(code);return this;}public R data(String key, Object value){this.data.put(key, value);return this;}public R data(Map<String, Object> map){this.setData(map);return this;}
}
复制代码

5、gateway模块

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>infrastructure</artifactId><groupId>com.atguigu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>api_gateway</artifactId><dependencies><dependency><groupId>com.atguigu</groupId><artifactId>service_base</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--gson--><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies>
</project>
复制代码

(2)application.properties

# 端口号
server.port=8222
# 服务名
spring.application.name=service-gateway
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true# 配置路由规则
spring.cloud.gateway.routes[0].id=service-acl
# 设置路由uri  lb://注册服务名称
spring.cloud.gateway.routes[0].uri=lb://service-acl
# 具体路径规则
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**
复制代码

(3)解决跨域

package com.atguigu.gateway.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;@Configuration
public class CorsConfig {//解决跨域@Beanpublic CorsWebFilter corsWebFilter() {CorsConfiguration config = new CorsConfiguration();config.addAllowedMethod("*");config.addAllowedOrigin("*");config.addAllowedHeader("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());source.registerCorsConfiguration("/**",config);return new CorsWebFilter(source);}
}
复制代码

(4)启动类

package com.atguigu.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {public static void main(String[] args) {SpringApplication.run(ApiGatewayApplication.class, args);}
}
复制代码

6、service模块

(1)pom.xml

(2)application.properties

以菜单权限管理CRUD为例:

(3)Permission

(4)PermissionController

(5)PermissionService

(6)PermissionMapper

(7)PermissionMapper.xml

(8)PermissionHelper

(9)启动类

最后:

最近我整理了整套**《JAVA核心知识点总结》**,说实话 ,作为一名Java程序员,不论你需不需要面试都应该好好看下这份资料。拿到手总是不亏的~我的不少粉丝也因此拿到腾讯字节快手等公司的Offer

进[Java架构资源交流群] ,找管理员获取哦-!

rn.PathPatternParser;

@Configuration
public class CorsConfig {

//解决跨域
@Bean
public CorsWebFilter corsWebFilter() {CorsConfiguration config = new CorsConfiguration();config.addAllowedMethod("*");config.addAllowedOrigin("*");config.addAllowedHeader("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());source.registerCorsConfiguration("/**",config);return new CorsWebFilter(source);
}

}
复制代码


(4)启动类

package com.atguigu.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
复制代码


### 6、service模块(1)pom.xml(2)application.properties以菜单权限管理CRUD为例:(3)Permission(4)PermissionController(5)PermissionService (6)PermissionMapper(7)PermissionMapper.xml(8)PermissionHelper(9)启动类# 最后:最近我整理了整套**《JAVA核心知识点总结》**,说实话 ,作为一名Java程序员,不论你需不需要面试都应该好好看下这份资料。拿到手总是不亏的~我的不少粉丝也因此拿到腾讯字节快手等公司的Offer> <font color=Blue size=5> 进[[Java架构资源交流群](https://jq.qq.com/?_wv=1027&k=bH7VlCjB)] ,找管理员获取哦-!![](https://img-blog.csdnimg.cn/20210603175857532.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phdmFfY29sYQ==,size_16,color_FFFFFF,t_70#pic_center)

2021最新Spring Security知识梳理相关推荐

  1. 2021最新Spring教程(精简)

    spring 一.spring文件及注解 1.spring配置主要全部文件 pom.xml applicationContext.xml Main.java UserDao.java UserDaoI ...

  2. Spring Annotation知识梳理

    2019独角兽企业重金招聘Python工程师标准>>> Spring annotation:(目的:减少applicationContext.xml文件配置) 使用注解时需要添加扫描 ...

  3. 2021最新Spring Boot 面试题

    这是我面试了很多中大厂总结出来的高频面试题,如果对你有帮助,希望大家点点赞,你们的点赞是我创作的动力. 什么是 Spring Boot? Spring Boot是Spring开源组织下的子项目,是Sp ...

  4. 2021最新PHP教程知识大全

    一.PHP教程前言 PHP 是一种创建动态交互性站点的强有力的服务器端语言.尤其适用于 Web 开发并可嵌入 HTML 中去.它的语法利用了 C.Java 和 Perl,易于学习.该语言的主要目标是允 ...

  5. spring security安全管理梳理

  6. 2021最新 SpringBoot面试题精选(附刷题小程序)

    推荐使用小程序阅读 为了能让您更加方便的阅读 本文所有的面试题目均已整理至小程序<面试手册> 可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验! 文章目录 推荐使用小程序阅读 1. ...

  7. 2021最新 MySQL面试题精选(附刷题小程序)

    推荐使用小程序阅读 为了能让您更加方便的阅读 本文所有的面试题目均已整理至小程序<面试手册> 可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验! 最近梳理汇总了Java面试常遇到的 ...

  8. Spring面试题及答案(2021年Spring面试题大全带答案)

    最近梳理2021最新 Spring 面试题 全家桶[附答案解析],包含了 Java基础.Spring.SpringMVC.Redis.SpringCloud.设计模式.等多个类型. 今天这篇是关于 S ...

  9. 还不了解Oauth2协议?这篇文章从入门到入土让你了解Oauth2以及Spring Security OAuth2 的使用

    SpringSecurityOAuth2学习和实战 1.OAuth2概述 1.1 什么是OAuth2 OAuth(Open Authorization)是一个关于授权(authorization)的开 ...

最新文章

  1. 通过一个案例理解 JWT
  2. 网站漏洞修复公司处理网站被篡改跳转到其他网站的解决办法
  3. 前微博副总私房干货:微博平台与架构从零到IPO的打磨经验
  4. pandas的dataframe节省内存
  5. 堂妹问的一道暑假作业题,难住985的家长
  6. 能用来写安卓吗_iPad能代替笔记本吗,除了看剧还能用来做什么?
  7. 社区团购“九不得”:低价倾销、大数据“杀熟”被禁止
  8. DNS 教父怒喷 DNS-over-HTTPS!
  9. status的状态码
  10. 新安装 Ubuntu 12.10 需要做的 10 件事(转)
  11. vue3 main.js引入 axios_Vue3.0新特性探索
  12. 【Android】NanoHttpd学习(一)
  13. 粒子群算法(PSO) C
  14. 遗传算法优化SVM支持向量机分类预测的参数代码模型
  15. html2canvas给图片添加水印,canvas 为图片添加水印
  16. Linux下查看网络流量常用方法
  17. 近观香港,远看上海,反思深圳
  18. 2022-2027年中国安全仪表系统(SIS)行业发展监测及投资战略研究报告
  19. Qt 图像放大缩小拖动
  20. ppi协议源码 c语言,西门子PPI通讯协议

热门文章

  1. 拿R来画画(六):很漂亮的Cleveland点图
  2. 3D展示框架SDK全面开放
  3. ORACLE DataGuard环境搭建详细步骤(新方法)
  4. Android程序员该如何进阶?,2021Android面经
  5. Nvidia Jetson AGX Orin 初体验
  6. HTML中 <img>标签的用法
  7. JavaWeb之上传与下载
  8. 直方图均衡化取整怎么计算_玩转直方图处理之直方图均衡化、规定化
  9. [shell] find 指令的使用 (如:找到大于10M的文件)
  10. python自学第七天之字典的增删改查