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

发布于:2025-09-08 ⋅ 阅读:(22) ⋅ 点赞:(0)

简单的开始

创建SpringBoot项目

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

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

添加SpringSecurity的依赖

<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项目后,在控制台输出窗口出现:

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

流程如下所示。

filterchain

过滤过程的伪代码

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识别调用。

securityfilterchain

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

multi securityfilterchain

举例说明

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} 前缀,表示密码没有被编码。

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

        @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的注入,并实现构造方法。这样我们就手动创建了自己的配置内容。

@Configuration
public class WebSecurityConfig {
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
        return new InMemoryUserDetailsManager();
    }
}

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

2)初始化用户

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

@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用户管理器,下面我们展示创建两个用户的过程。

@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中添加

<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内容

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)创建实体类

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

@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层
@Repository
public interface UserDao extends JpaRepository<User,Integer> {
    User findByMobileAndPassword(String mobile,String pwd);
    User findByMobile(String mobile);
}
3)仿照InMemoryUserDetailsManager创建MyUserDetailsManager

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

@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请求均会被拒绝

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

springsecurity实战应用

1. 构建项目

项目框架

在这里插入图片描述

配置文件

pom
<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
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

实体类

@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层

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

服务层

public interface UserService {
    String login(String username,String password);
}
@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";
            }
    }
}

控制层

@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


WebSecurityConfig
@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

@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,在里面添加我们自己定义的用户类,这样可以方便的存储我们自己的用户信息

@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

网站公告

今日签到

点亮在社区的每一天
去签到