SpringBoot后端开发常用工具详细介绍——SpringSecurity认证用户保证安全

简单的开始

创建SpringBoot项目

首先创建一个简单的springboot项目,假设端口为8888,添加controller控制层,并在其中添加TestController控制类,那么启动springboot项目之后,访localhost:8888/api/message页面会显示my first message

java 复制代码
@RestController
@RequestMapping("/api")
public TestController{
    @GetMapping("/messages")
    public String myMessage(){
        return "my first message";
    }
}

添加SpringSecurity的依赖

xml 复制代码
<dependencies>
	<!-- ... 其他依赖元素 ... -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
        <!--可以通过下面的内容进行版本指定-->
        <spring-security.version>6.2.0-SNAPSHOT</spring-security.version>
	</dependency>
</dependencies>

SpringSecurity认证登录

运行springboot项目后,在控制台输出窗口出现:

bash 复制代码
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336

尝试访问localhost:port任意后端接口地址,可以发现出现了登录窗口,

使用user: user password:8e557245-73e2-4286-969a-ff57fe326336 这里的密码就是控制台输出的密码。

这就是springsecurity的端口认证机制。

原理说明

Filter和FilterChain

当客户端向应用程序发送请求时,SpringSecurity会创建一系列的Filter来过滤请求,这样的Filter有多个,这些Filter构成了从客户端到Servlet的一个FilterChain,在通过FilterChain的过滤之后,这个请求才会被Servlet处理。

需要注意的是Filter 会影响下游的 Filter 实例,当匹配到一个Filter之后就不再匹配下面的Filter

流程如下所示。

过滤过程的伪代码

java 复制代码
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // 在过滤之前的动作
    chain.doFilter(request, response); // 进行过滤
    // 过滤之后的动作
}

FilterChainProxy和SecurityFilterChain

基本流程

当一个请求来临时,我们常常会有这样的动作,对于某一个请求接口,去查看对应的过滤器,比如对account相关的接口,我们就会给account制定对应的过滤器,当account相关的请求来临时,我们必然的就需要去通过account的过滤器去处理该请求。

FilterChainProxy就是这样的一个角色, 用来确定当前请求应该调用哪些 Spring Security Filter 实例。

SecurityFilterChain的作用就是将过滤器进行分类,用来被FilterChainProxy识别调用。

因此当设计多个接口过滤器时,基本架构如下图所示

举例说明

FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只有第一个匹配的 SecurityFilterChain 被调用。

  • 如果请求的URL是 /api/messages,它首先与 /api/**SecurityFilterChain0 模式匹配,所以只有 SecurityFilterChain0 被调用,尽管它也与 SecurityFilterChainn 匹配。
  • 如果请求的URL是 /messages,它与 /api/**SecurityFilterChain_0 模式不匹配,所以 FilterChainProxy 会继续顺序尝试下面的 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例相匹配,则调用 SecurityFilterChain_n

工作流程

是 否 是 否 是 否 客户端请求受保护资源 Spring Security 过滤器链拦截请求 UserDetailsServiceAutoConfiguration 配置 AuthenticationManager 需要认证? UsernamePasswordAuthenticationFilter 拦截 访问资源 DaoAuthenticationProvider 处理认证 使用自定义 UserDetailsService? 自定义 UserDetailsService 加载用户信息 InMemoryUserDetailsManager 加载默认用户信息 密码匹配? 创建 Authentication 对象 抛出 AuthenticationException 将 Authentication 对象放入 SecurityContextHolder 访问资源 重定向到登录页面或返回错误

1. 自动配置的过程

  1. UserDetailsServiceAutoConfiguration 类上的条件注解

    • @ConditionalOnClass(AuthenticationManager.class)

      ​ 确保 AuthenticationManager 类在类路径上。

    • @ConditionalOnBean(ObjectPostProcessor.class)

      ​ 确保 Spring 容器中存在 ObjectPostProcessor 的 Bean

    • @ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class })

      确保 Spring 容器中没有定义 AuthenticationManagerAuthenticationProviderUserDetailsService 的 Bean。

  2. 默认InMemoryUserDetailsManager 的Bean 的创建

    如果上述条件都满足,UserDetailsServiceAutoConfiguration 会创建一个 InMemoryUserDetailsManager 的 Bean 作为默认的用户详细信息服务管理器。这个管理器会在内存中创建一个用户,通常用户名为 "user",密码为随机生成的 UUID,这个角色为 "USER"。

    在创建 InMemoryUserDetailsManager 时,UserDetailsServiceAutoConfiguration 会检查 SecurityProperties 中定义的用户密码。如果密码是生成的,它会记录一条日志,显示使用的密码。同时,它还会检查密码是否已经使用某种算法进行了编码,如果没有,它会使用 {noop} 前缀,表示密码没有被编码。

    创建过程代码:可以简单浏览,之后自己创建配置时会借鉴到

    java 复制代码
        @Bean
        public InMemoryUserDetailsManager inMemoryUserDetailsManager(
            SecurityProperties properties,// springsecurity的配置文件,里面有默认的用户名,可以进入 SecurityProperties 查看详细数据
            ObjectProvider<PasswordEncoder> passwordEncoder) // 密码编码器
        {
            
            SecurityProperties.User user = properties.getUser(); // 配置文件中的用户
            List<String> roles = user.getRoles(); // 获取角色
            
            return new InMemoryUserDetailsManager(
                new UserDetails[]{
                    User.withUsername(user.getName()) // 账号
                        .password(		// 密码
                        	this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())
                    	)
                        .roles(StringUtils.toStringArray(roles)) //角色
                        .build()
                }
            );
        }
  3. 注册Bean

    最终,UserDetailsServiceAutoConfiguration 会将 InMemoryUserDetailsManager 注册为 Spring 应用上下文中的一个 Bean,这样 Spring Security 在认证时就可以使用这个默认的用户详细信息服务。

2. UserDetailsService的作用

我们通过上面InMemoryUserDetailsManager的类,可以分析得出

  • InMemoryUserDetailsManager实现了UserDetailsManager、UserDetailsPasswordService中的方法

  • UserDetailsManager继承UserDetailsService

因此InMemoryUserDetailsManager的关键就是UserDetailsManagerUserDetailsPasswordService以及实现自UserDetailsService中的方法

接口 方法 描述
UserDetailsManager void createUser(UserDetails user) 根据提供的用户详情创建一个新用户账号
void updateUser(UserDetails user) 更新指定的用户账号
void deleteUser(String username) 从系统中删除具有给定登录名的用户账号
void changePassword(String oldPassword, String newPassword) 修改用户账号的密码。这应该在持久的用户存储库中更改用户的密码(数据库、LDAP等)
boolean userExists(String username) 检查具有给定登录名的用户账号是否存在于系统中
UserDetails loadUserByUsername(String username) 根据用户名加载用户信息,此方法从 UserDetailsService 继承
UserDetailsPasswordService UserDetails updatePassword(UserDetails user, String newPassword) 更新用户密码。在用户登录成功后,如果检测到密码需要更新(例如,密码策略变更),则调用此方法

​ 而我们可以通过上面部分自动配置过程可以知道,假如Spring 容器中定义了 AuthenticationManagerAuthenticationProviderUserDetailsService 的 Bean,那么自动配置文件将不会生效。

3. AuthenticationManager的作用

在Spring Security中,AuthenticationManager 是一个核心接口,负责对用户的认证请求进行处理。它定义了一个 authenticate 方法,该方法接受一个 Authentication 对象作为参数,并返回一个完全认证过的 Authentication 对象。如果认证失败,则抛出 AuthenticationException

ProviderManagerAuthenticationManager 的一个常见实现,它使用一个 AuthenticationProvider 列表来处理认证请求。每个 AuthenticationProvider 都有机会对认证请求进行处理,如果一个 AuthenticationProvider 无法处理请求,ProviderManager 会尝试下一个。这个过程会一直持续,直到找到一个能够成功认证请求的 AuthenticationProvider,或者所有的 AuthenticationProvider 都尝试完毕。
找到匹配的 Provider 认证成功 认证失败 未找到匹配的 Provider 开始认证 AuthenticationManager ProviderManager 遍历 AuthenticationProvider 列表 Provider 进行认证 返回认证后的 Authentication 对象 抛出 AuthenticationException 抛出 ProviderNotFoundException 认证完成

4. 手动配置账号密码

1)创建配置类、用户管理器

因此我们创建自己的WebSecurityConfig类 ,在里面进行InMemoryUserDetailsManager的注入,并实现构造方法。这样我们就手动创建了自己的配置内容。

java 复制代码
@Configuration
public class WebSecurityConfig {
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
        return new InMemoryUserDetailsManager();
    }
}

当然我们里面还没有给InMemoryUserDetailsManager添加任何用户。

2)初始化用户

添加下面代码,在创建InMemoryUserDetailsManager时新建一个用户

java 复制代码
@Configuration
public class WebSecurityConfig {
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
        return new InMemoryUserDetailsManager(
            User.withUsername("user") // 用户名
            .password("{noop}password") // 密码,以{noop}开头的话代表不加密
            .roles("a") // 使用可变参数传递角色
            .build()
        );
    }
}

这样,当我们启动时,就可以根据上面的账号和密码进行登录

3)添加用户

当然我们也可以通过调用InMemoryUserDetailsManager中的createUser方法添加用户的方式,来初始化manager用户管理器,下面我们展示创建两个用户的过程。

java 复制代码
@Configuration
public class WebSecurityConfig {
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(new User("admin", "{noop}123456", List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))));
        manager.createUser(new User("user", "{noop}654321", List.of(new SimpleGrantedAuthority("ROLE_USER"))));
        return manager;
    }
}
4)认证过程

我们通过上面的内容已经知道了初始化用户 添加用户,同样的里面的updateUser deleteUser changePassword userExists方法也基本类似,不再赘述。

接下来需要弄懂的就是如何认证的呢,我们明明没有写这些相关的方法。

通过最开始的流程图,我们可以知道在配置好认证用户之后,之后程序对于每一个请求都会进行拦截。

请求拦截

AbstractAuthenticationProcessingFilter将请求拦截,并通过调用attemptAuthentication方法进行处理,而这个方法的具体实现存在于UsernamePasswordAuthenticationFilter

将请求进行拦截,然后交给授权管理器AuthenticationManager进行控制

授权管理器认证

进入authenticate()方法发现进入到一个AuthenticationManager接口中,而这个接口的实现类是ProviderManager

ProviderManager类中的authenticate方法委派认证工作给一个或多个AuthenticationProvider

验证用户是否存在

AuthenticationProvider仍为一个接口,其默认实现类为AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProviderauthenticate方法流程如下:

  • 开始认证:认证过程开始。
  • 检查Authentication类型 :确保传入的Authentication对象是UsernamePasswordAuthenticationToken类型。
  • 抛出异常:如果类型不匹配,抛出异常。
  • 确定用户名 :从Authentication对象中获取用户名。
  • 从Cahce缓存获取UserDetails :尝试从Cahce缓存中获取UserDetails对象。
  • Cahce未命中:如果Cahce未命中,从用户信息源(如数据库)检索用户信息。
  • 用户不存在 :如果用户不存在,根据配置抛出UsernameNotFoundExceptionBadCredentialsException
  • 用户存在:如果用户存在,校验用户状态(如账户是否过期、是否锁定等)。
  • 用户状态无效 :如果用户状态无效,抛出AuthenticationException
  • 执行额外的认证检查:执行任何额外的认证检查(如密码过期检查)。
  • 认证检查失败:如果认证检查失败,重新检索用户信息并再次执行检查。
  • 执行后置认证检查:执行认证成功后的后置检查。
  • 后置检查失败 :如果后置检查失败,抛出AuthenticationException
  • 检查是否使用缓存:检查认证过程中是否使用了缓存。
  • 使用了缓存:如果没有使用缓存,将用户信息放入缓存。
  • 创建认证成功的Authentication对象 :创建一个新的Authentication对象,表示认证成功。
  • 返回认证成功的Authentication对象 :返回认证成功的Authentication对象。

先看前半部分查看用户是否存在

在这里调用了retrieveUser方法来进行用户验证获取验证结果,这个方法在DaoAuthenticationProvider中进行验证,是否存在该用户。

DaoAuthenticationProvider中调用loadUserByUsername方法进行具体内容的验证,这个方法在前面UserDetailsService的作用 中看到过

验证密码是否正确

在完成用户存在验证后,我们继续看AbstractUserDetailsAuthenticationProvider类,在这个类中使用additionalAuthenticationChecks方法进行账号密码的验证。

具体内容的实现仍在在DaoAuthenticationProvider

5)请求拦截

上面我们可以知道UsernamePasswordAuthenticationFilter拦截器,拦截的只是login的请求,那对于之后的每一次请求是个什么样的流程呢

通过拦截每一次请求,接着验证是否被授权,因此我们之后在处理请求拦截时,可以同样采用这样的方式,进行借鉴

6)汇总

发送登录请求 检查请求 是 否 默认 成功 失败 是 否 客户端 DispatcherServlet Spring Security Filter Chain SecurityContextHolder UsernamePasswordAuthenticationFilter 请求路径和方法匹配? 提取用户名和密码 继续过滤器链 创建 UsernamePasswordAuthenticationToken 调用 AuthenticationManager AuthenticationManager 委派给 AuthenticationProvider DaoAuthenticationProvider 调用 UserDetailsService UserDetailsService 加载 UserDetails 密码验证 返回 Authentication 对象 抛出 AuthenticationException 设置 SecurityContextHolder 认证成功处理 AuthenticationSuccessHandler 继续过滤器链 AuthenticationFailureHandler 重定向或返回成功响应 重定向到登录页面或显示错误 客户端

7)关于加密的过程

很多配置都是通过大致流程,因此可以扩展到理解其他的一些配置项。

我们发现在上面密码验证时,是设置了编码器,那我们从来没有配置过DaoAuthenticationProvider,这里的密码加密器是怎么配置的呢?

DaoAuthenticationProvider构造方法设置加密器的位置添加断点。然后执行程序时不断进入断点。

进入了InitializeUserDetailsBeanManagerConfigurer

5. 结合数据库进行用户认证

数据库和上面配置过程不同的是:

手动配置

  • 首先创建springSecurity的用户
  • 在登录时对用户进行认证
  • 与前面创建的用户进行匹配

数据库配置

  • 不需要创建用户
  • 登录时直接与数据库中的用户进行匹配

经过上面的过程,我们可以知道,主要的过程就是DaoAuthenticationProvider创建时设置的UserDetailsService,可以控制用户的认证。

1)引入数据库

我们采用springdatajpa操作数据库

向pom中添加

xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

设置yml内容

yml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springsecurity
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update #自动生成数据库
    show-sql: true
1)创建实体类

创建好之后记得手动在数据库中添加一条数据用于测试

java 复制代码
@Data
@Entity
@Table(name = "sys_user")
public class User {
    @Column(name = "user_id", unique = true, nullable = false, insertable = false, updatable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int userId;

    @Column(name = "mobile")
    private String mobile;

    @Column(name = "pwd")
    private String password;

    @Column(name="identity")
    private int identity;

    @Column(name="nick_name")
    private String nickName;
}
2)创建Dao层
java 复制代码
@Repository
public interface UserDao extends JpaRepository<User,Integer> {
    User findByMobileAndPassword(String mobile,String pwd);
    User findByMobile(String mobile);
}
3)仿照InMemoryUserDetailsManager创建MyUserDetailsManager

我们上面知道了,要想控制账号密码的验证,我们就需要自己注入UserDetailsService,这样他就不会采用系统本身的验证方案了。

java 复制代码
@Component
public class MyUserDetailsManager implements UserDetailsService {
    @Resource
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.findByMobile(username);

        Collection<? extends GrantedAuthority> authorities = new ArrayList<>();

        return new org.springframework.security.core.userdetails.User(
                user.getMobile(),
                "{noop}"+user.getPassword(),// 这里{noop}前缀代表不进行加密,也就是匹配时与数据库中的明文相同即可
                true,
                true,
                true,
                true,
                authorities
        );
    }
}
4)进行登录测试

6.漏洞保护

6.1 csrf跨域保护请求禁用

如果不禁用csrf,那么所有的post请求均会被拒绝

java 复制代码
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends
  WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) {
  http
  .csrf(csrf -> csrf.disable());
  }
}

springsecurity实战应用

1. 构建项目

项目框架

配置文件

pom
xml 复制代码
<dependencies>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</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-web</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>
    <!--jwt依赖-->
    <dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt</artifactId>
       <version>0.9.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-jwt -->
    <dependency>
       <groupId>cn.hutool</groupId>
       <artifactId>hutool-jwt</artifactId>
       <version>5.8.27</version>
    </dependency>
    <dependency>
       <groupId>com.mysql</groupId>
       <artifactId>mysql-connector-j</artifactId>
       <scope>runtime</scope>
    </dependency>
    <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <optional>true</optional>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
    </dependency>
    <dependency>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-test</artifactId>
       <scope>test</scope>
    </dependency>
yml
yml 复制代码
server:
  port: 11012

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springsecurity
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

实体类

java 复制代码
@Data
@Entity
@Table(name = "sys_user")
public class User {
    @Column(name = "user_id", unique = true, nullable = false, insertable = false, updatable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int userId;

    @Column(name = "mobile")
    private String mobile;

    @Column(name = "pwd")
    private String password;

    @Column(name="identity")
    private int identity;

    @Column(name="nick_name")
    private String nickName;
}

在执行项目之后,会自动构建数据库,在构建好数据库之后

记得手动插入一条数据

dao层

java 复制代码
@Repository
public interface UserDao extends JpaRepository<User,Integer> {
    User findByMobileAndPassword(String mobile,String pwd);
    User findByMobile(String mobile);
}

服务层

java 复制代码
public interface UserService {
    String login(String username,String password);
}
java 复制代码
@Service
public class UserServiceImpl implements UserService {

    UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public String login(String username, String password) {
        User user = userDao.findByMobileAndPassword(username, password);
            if (user!= null) {
                return "login success"+user.getNickName();
            } else {
                return "login fail";
            }
    }
}

控制层

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/test")
    public String test() {

        return "tt";
    }
    @GetMapping("/login")
    public String login(@RequestParam(name = "account") String account, @RequestParam(name = "password") String password) {
        return userService.login(account, password);
    }
}

springSecurity

java 复制代码
WebSecurityConfig
java 复制代码
@Configuration
public class WebSecurityConfig {

    //加密器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 授权管理器
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }
    
    @Bean
    @Order(1)
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher("/user/login")
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().anonymous()  // 允许匿名访问 /user/login
                );
        return http.build();
    }
    
    //Spring Security过滤链
    @Bean
    public SecurityFilterChain otherFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().hasRole("ADMIN")
                )
                .httpBasic(withDefaults());
        return http.build();
    }
    

}

Order的大小用于指明在第几层,越小越靠上,可以理解为优先级,越小越大

如果不设置order,那么会按照先后顺序进行配置

  • 首先请求先通过order为1的过滤链,就是/user/login的请求,设置为允许匿名访问
  • 而对于没有设置过滤链的请求,就会使用第二个配置otherFilterChain。这个配置被认为在 apiFilterChain 之后,因为它的 @Order 值在 1 之后(没有 @Order 默认为最后)
DBUserDetailsManager

继承了UserDetailsService,当加载用户的时候,就会执行这里的loadUserByUsername

java 复制代码
@Component
public class DBUserDetailsManager implements UserDetailsService {
    @Resource
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.findByMobile(username);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        return new MyUserDetail(user);
    }
}
MyUserDetail

新建自己的UserDetails,继承原来的UserDetails,在里面添加我们自己定义的用户类,这样可以方便的存储我们自己的用户信息

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyUserDetail implements UserDetails {
    private User user; // 这是自己定义的用户类

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getMobile();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

2. 初步测试

在上面已经完成了接口的简单控制

我们可以通过访问localhost:11012/user/login?account=123&password=123

发现可以访问,并且登录成功

但是当我们访问localhost:11012/user/test

需要我们进行springsecurity的登录

复制代码
BasicAuthenticationFilter
相关推荐
CodeSheep6 小时前
稚晖君又开始摇人了,有点猛啊!
前端·后端·程序员
小宁爱Python6 小时前
Django 从环境搭建到第一个项目
后端·python·django
uzong7 小时前
深入浅出:画好技术图
后端·架构
向上的车轮7 小时前
基于Java Spring Boot的云原生TodoList Demo 项目,验证云原生核心特性
java·spring boot·云原生
IT_陈寒7 小时前
Java性能优化:从这8个关键指标开始,让你的应用提速50%
前端·人工智能·后端
程序员清风7 小时前
快手一面:为什么要求用Static来修饰ThreadLocal变量?
java·后端·面试
逍遥德7 小时前
Java8 Comparator接口 和 List Steam 排序使用案例
java·spring boot·list·排序算法
chen_ever7 小时前
golang之go modules
开发语言·后端·golang
Victor3567 小时前
Redis(54)Redis的LRU算法是什么?
后端