# spring-security(一)

news2024/11/8 14:17:14

一、权限管理简介

1、什么是权限管理

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

​ 权限管理包括用户身份认证鉴权(授权)两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问

2、认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。

  • Subject :主体

    访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

  • Principal :身份信息

​ 是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

  • credential :凭证信息

​ 是只有主体自己知道的安全信息,如密码、证书等。

3、授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

下图中橙色为授权流程。
授权可简单理解为who对what(which)进行How操作:

  • Who,即主体(Subject),主体需要访问系统中的资源。
  • What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
  • How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。权限分为粗颗粒和细颗粒,粗颗粒权限是指对资源类型的权限,细颗粒权限是对资源实例的权限
  • 二、权限管理解决方案

1、基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:

if(主体.hasRole("总经理角色id")){
     查询工资
}

缺点:以角色进行访问控制粒度较粗,如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断主体的角色是否是总经理或部门经理”,系统可扩展性差。

修改代码如下:
if(主体.hasRole(“总经理角色id”) || 主体.hasRole(“部门经理角色id”)){
查询工资
}

2、基于资源的访问控制

​ RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:

上图中的判断逻辑代码可以理解为:

if(主体.hasPermission(“查询工资权限标识”)){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也只需要将“查询工资信息权限”添加到“部门经理角色”的权限列表中,判断逻辑不用修改,系统可扩展性强。

三、Spring Security概述

1,Spring Security简介

Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(Inversion of Control 控制反转),DI(Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security 拥有以下特性:

  • 对身份验证和授权的全面且可扩展的支持
  • 防御会话固定、点击劫持,跨站请求伪造等攻击
  • 支持 Servlet API 集成
  • 支持与 Spring Web MVC 集成-

2、Spring Security快速入门

2.1、引入依赖

<!--springboot整合security坐标-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.2、创建一个控制器

@RestController
public class HelloController {
    @GetMapping("hello")
    public String hello(){
        return "Hello Spring security";
    }
}

2.3、启动项目

访问:http://localhost:8080/hello 结果打开的是一个登录页面,其实这时候我们的请求已经被保护起来了,要想访问,需要先登录。
Spring Security 默认提供了一个用户名为 user 的用户,其密码在控制台可以找到

四、Spring Security 认证配置

1、WebSecurityConfigurerAdapter

当然还可以通过配置类的方式进行配置,创建一个配置类去继承,实现自定义用户名密码登录

/**
 * Spring Security配置类
 * 在springboot2.7 后WebSecurityConfigurerAdapter弃用了,用2.5.4
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 对密码进行加密。123 是密码明文,现在 Spring Security 强制性要求『不允许明文存储密码』。
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
    }
}

从 5.x 开始,强制性要求必须使用密码加密器(PasswordEncoder)对原始密码(注册密码)进行加密。因此,如果忘记指定 PasswordEncoder 会导致执行时会出现 There is no PasswordEncoder mapped for the id "null" 异常。

这是因为我们在对密码加密的时候使用到了 BCryptPasswordEncoder 对象,而 Spring Security 在对密码比对的过程中不会『自己创建』加密器,因此,需要我们在 Spring IoC 容器中配置、创建好加密器的单例对象,以供它直接使用。

所以,我们还需要在容器中配置、创建加密器的单例对象(上面那个 new 理论上可以改造成注入),修改Spring securitry配置类

/**
 * Spring Security配置类
 * 在springboot2.7后WebSecurityConfigurerAdapter弃用了,用2.5.4
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 对密码进行加密。123 是密码明文,现在 Spring Security 强制性要求『不允许明文存储密码』。
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
    }
       /**
     * 将PasswordEncoder注入到ioc容器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

我们获取用户名和密码都是从数据库中获取,所以用以上方式不合理,引出auth.userDetailsService() ,使用UserDetailsService来实现从数据库中查用户名和密码

Spring Security 内置的 Password Encoder 有:
在这里插入图片描述
上述 Password Encoder 中有一个『无意义』的加密器:NoOpPasswordEncoder 。它对原始密码没有做任何处理(现在也被标记为废弃)。

记得使用 @SuppressWarnings(“deprecation”) 去掉 IDE 的警告信息。

1、基本概念

  • AuthenticationManager

    它是 “表面上” 的做认证和鉴权比对工作的那个人,它是认证和鉴权比对工作的起点。

    ProvierderManager 是 AuthenticationManager 接口的具体实现。

  • AuthenticationProvider

    它是 “实际上” 的做认证和鉴权比对工作的那个人。从命名上很容易看出,Provider 受 ProviderManager 的管理,ProviderManager 调用 Provider 进行认证和鉴权的比对工作。

    我们最常用到 DaoAuthenticationProvider 是 AuthenticationProvider 接口的具体实现。

  • UserDetailsService

    虽然 AuthenticationProvider 负责进行用户名和密码的比对工作,但是它并不清楚用户名和密码的『标准答案』,而标准答案则是由 UserDetailsService 来提供。简单来说,UserDetailsService 负责提供标准答案 ,以供 AuthenticationProvider 使用。

  • UserDetails

    UserDetails 它是存放用户认证信息和权限信息的标准答案的 “容器” ,它也是 UserDetailService “应该” 返回的内容。

  • PasswordEncoder

    Spring Security 要求密码不能是明文,必须经过加密器加密。这样,AuthenticationProvider 在做比对时,就必须知道『当初』密码时使用哪种加密器加密的。所以,AuthenticationProvider 除了要向 UserDetailsService 『要』用户名密码的标准答案之外,它还需要知道配套的加密算法(加密器)是什么

    2、用户名和密码从数据库取

​ Spring Security 要求 UserDetailsService 将用户信息的 “标准答案” 必须封装到一个 UserDetails 对象中,返回给 AuthenticationProvider 使用(做比对工作)。

我们可以直接使用 Spring Security 内置的 UserDetails 的实现类:User

  • 在service包下创建一个UserDetailsService类
/**
 * spring security认证业务类
 */
@Service
public class MyUserDetailsService implements UserDetailsService {
    //为passwordEncoder注入值
    @Autowired
    private UserDao userDao;

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
           //调用dao到数据库中根据username查找用户信息
        Users users = userDao.getByUserName(username);
        
         try {
            //将查找到的用户帐号与密码保存到Security的User对象中由Security进行比对
            return new User(users.getUsername(), passwordEncoder.encode(users.getPassword()),
                    AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN")); //配置登录用户有哪些角色和权限,此处模拟直接写死
        }catch (Exception e){
            throw  new UsernameNotFoundException("用户"+username+"不存在");
        }
    }
}

修改spring security配置类

/**
 * Spring Security配置类
 *
 *
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 属性注入和构造注入区别
     * 1、属性注入时,Spring IOC容器“创建对象”和为对象属性赋值两件事情是分开做的。
     *    构造注入时,Spring IOC容器直接调用类的有参构造,这样“创建对象”和为对象
     *    属性赋值两件事情是一起做的
     * 2、属性注入没法表达对象创建的“先后/依赖关系”,但是构造注入可以
     *    属性注入天然能解决循环依赖问题,但是构造注入要使用@Lazy注解
     * 所以建议单例对象的必要属性用构造注入,可选属性使用属性注入
     */
    @Resource
    private MyUserDetailsService userDetailsService;
    
    public SecurityConfig(@Lazy  MyUserDetailsService myUserDetailsService) {
        this.userDetailsService = myUserDetailsService;
    }

    /**
     * 将PasswordEncoder注入到ioc容器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

ProviderManager/AuthenticationProvider 在做密码密码的比对工作时,会调用 UserDetailsService 的 .loadUserByUsername() 方法,并传入『用户名』,用以查询该用户的密码和权限信息。

3、Spring Security 自带的表单认证

3.1、SpringSecurityConfig 类中的配置代码

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin();//1
        http.authorizeRequests()
                .anyRequest()
                .authenticated();   // 2
        http.csrf().disable();  // 3
    }

代码配置的链式调用连写:
	http.formLogin()
    .and()
    .authorizeRequests()
    .anyRequest().authenticated()
    .and()
    .csrf().disable();

以上配置的意思是:

#说明
1要求用户登陆时,是使用表单页面进行登陆。但是,由于我们有意/无意中没有指明登陆页面,因此,Spring Security 会使用它自己自带的一个登陆页面。
2同上,让 Spring Security 拦截所有请求。要求所有请求都必须通过认证才能放行,否则要求用户登陆。
3同上,暂且认为是固定写法。后续专项讲解。
           |

3.2、使用自定义表单实现认证

  • 准备自定义登录页面(可以是一个纯 html 页面)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="dologin" method="post">
    <!--注意:帐号和密码的名称必须是username和password否则spring security无法识别-->
    <p>帐号:<input type="text" name="username"></p> 
    <p>密码:<input type="text" name="password"></p>
    <p><button type="submit">登录</button></p>
</form>
</body>
</html>

SpringSecurityConfig 类中的配置代码

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

        http.formLogin()
                .loginPage("/login.html")//配置需要显示的登录页面
                .loginProcessingUrl("/dologin") //配置登录请求路径,很from表单的 action 要对应上
                .defaultSuccessUrl("/index") //默认登录成功后跳转地址
                .permitAll()//这句配置很重要,新手容易忘记。放开 login.html和dologin 的访问权
        
                .and().authorizeRequests()
                .anyRequest().authenticated();  // 除了antMatchers() 配的页面,其他都需要认证
        
        http.csrf().disable();
    }

五、鉴权配置

1、鉴权配置

当前用户是否有权限访问某个 URI 的相关配置也是写在 configure(HttpSecurity http) 方法中。

.antMatchers() 方法是一个采用 ANT 风格的 URL 匹配器。

权限表达式说明
permitAll()永远返回 true
denyAll()永远返回 false
anonymous()当前用户是匿名用户(anonymous)时返回 true
rememberMe()当前用户是 rememberMe 用户时返回 true
authentication当前用户不是匿名用户时,返回 true
fullyAuthenticated当前用户既不是匿名用户,也不是 rememberMe 用户时,返回 true
hasRole(“role”)当用户拥有指定身份时,返回 true
hasAnyRole(“role1”, “role2”, …)当用户返回指定身份中的任意一个时,返回 true
hasAuthority(“authority1”)当用于拥有指定权限时,返回 true
hasAnyAuthority(“authority1”, “authority2”)当用户拥有指定权限中的任意一个时,返回 true
hasIpAddress(“xxx.xxx.x.xxx”)发送请求的 ip 符合指定时,返回 true
principal允许直接访问主体对象,表示当前用户

hasRole():数据库用户角色必须加 ROLE_ 前缀,而用hasRole() security会自动加上ROLE_ 前缀,自己不能加上ROLE_ 前缀,例如

AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin")
对上
hasRole("admin")

hasAuthority() 数据库角色名称和方法内容一致 例如:

AuthorityUtils.commaSeparatedStringToAuthorityList("admin")
对上
hasRole("admin")
语法:
  http.authorizeRequests()
    .antMatchers("/user/insert").hasAuthority("user:insert")
    .antMatchers("/user/modify").hasAuthority("user:modify")
    .antMatchers("/user/delete").hasAuthority("user:delete")
    .antMatchers("/user/query").hasAuthority("user:query")
    .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
    .antMatchers("/user-can-do").hasRole("USER") //
    .antMatchers("/admin-can-do").hasRole("ADMIN") // 同上
    .antMatchers("/all-can-do").permitAll()
    .anyRequest().authenticated(); // 除了上面的权限以外的,都必须登录才能访问

提示:本质上 .hasRole("xxx").hasAuthority("xxx") 并没有太大区别,但是,.hashRole() 在做比对时,会在里面内容前面拼上 ROLE_ 。所以,确保你的 Role 的『标准答案』是以 Role_ 开头

在使用hasRole() 和 hasAnyRole() 时候,设置角色时候要加ROLE_ 前缀

 return new User(users.getUsername(), passwordEncoder.encode(users.getPassword()),
                    AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin"));

没有权限跳转到自定义页面

   http.exceptionHandling().accessDeniedPage("/error");  //没有权限跳转到自定义页面
        http.formLogin()
                .loginPage("/hello")//配置需要显示的登录页面
                .loginProcessingUrl("/dologin") //配置登录请求路径
                .defaultSuccessUrl("/index")
                .permitAll()//这句配置很重要,新手容易忘记。放开 login.html和dologin 的访问权

                .and().authorizeRequests()
                .antMatchers("/","/hello").permitAll()// 设置哪些路劲不需要登录,能直接当问
                .antMatchers("/toupdate").hasAuthority("asd")
                .anyRequest().authenticated();  // 除了antMatchers() 配的页面,其他都需要认证

        http.csrf().disable();

2、使用注解实

在实际的使用过程中用户的鉴权并不是通过置来写的而是通过注解来进行,Spring Security 默认是禁用注解的。

要想开启注解功能需要在配置类上加入 @EnableGlobalMethodSecurity注解来判断用户对某个控制层的方法是否具有访问权限。

注解就是用来替换springSecurity配置类中的http.authorizeRequests()配置

Spring Security 支持三套注解:

注解类型注解
jsr250 注解@DenyAll、@PermitAll、@RolesAllowed
secured 注解@Secured
prePost 注解@PreAuthorize、@PostAuthorize

使用什么注解在@EnableGlobalMethodSecurity开启,默认是关闭的,例如

@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled  = true,securedEnabled=true) //开启jsr250和secured注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

secured 注解

@Secured 注解是 jsr250 标准出现之前,Spring Security 框架自己定义的注解。

@Secured 标注于方法上,表示只有具有它所指定的角色的用户才可以调用该方法。如果当前用户不具备所要求的角色,那么,将会抛出 AccessDenied 异常,注解和配置类都要加上ROLE_ 前缀

@RestController
public class UserController {
    @Secured({"ROLE_USER","ROLE_ADMIN"}) // 这里加前缀 ROLE_,
    @RequestMapping("/all-can-do")
    public String show7(){
        return "all-can-do";
    }

    @Secured("ROLE_USER") 
    @RequestMapping("/admin-can-do")
    public String show6(){
        return "admin-can-do";
    }
}

配置类

   return new User(users.getUsername(), passwordEncoder.encode(users.getPassword()),
                    AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); //配置登录用户有哪些角色和权限
  • JSR-250 注解

    @DenyAll: 所有用户都不可以访问

    @PermitAll:所有用户都可以访问

​ @RolesAllowed:用法同@Secured差不多,区别是注解上ROLE_ 可加可不加,但是配置类上必须加ROLE_ 前缀

  • prePost 注解

    @PreAuthorize可以用来控制一个方法是否能够被调用。

    @PreAuthorize(hasRole(ROLE_ADMIN))
    publicvoid addUser(User user) {
    System.out.println(“addUser…” + user);
    
    }
    
    @PreAuthorize(hasRole(ROLE_USER) or hasRole(ROLE_ADMIN))
    public User find(int id) {
    System.out.println(“find user by id…” + id);
    returnnull;
    
    }
    
    • JSR-250 注解

    @DenyAll: 所有用户都不可以访问

    @PermitAll:所有用户都可以访问

​ @RolesAllowed:用法同@Secured差不多,区别是注解上ROLE_ 可加可不加,但是配置类上必须加ROLE_ 前缀

  • prePost 注解

    @PreAuthorize可以用来控制一个方法是否能够被调用。


@PreAuthorize(hasRole(ROLE_ADMIN))
publicvoid addUser(User user) {
System.out.println(“addUser…” + user);

}

@PreAuthorize(hasRole(ROLE_USER) or hasRole(ROLE_ADMIN))
public User find(int id) {
System.out.println(“find user by id…” + id);
returnnull;

}

@PostAuthorize在方法调用完之后进行权限检查。

   @PostAuthorize(“returnObject.id%2==0)
    public User find(int id) {
    User user = new User();
    user.setId(id);
    return user;

}

如果返回值的id是偶数则表示校验通过,否则表示校验失败,将抛出AccessDeniedException

实际开发中最常用的写法 使用 @PreAuthorize(“hasRole(‘admin’)”)

    @RequestMapping("insert")
    @PreAuthorize("hasAnyAuthority('admin')")
    public String insert(){
        return "insert";
    }

3、 登录返回处理

​ 在某些前后端完全分离,仅靠 JSON 完成所有交互的系统中,一般会在登陆成功时返回一段 JSON 数据,告知前端,登陆成功与否。在这里,可以通过 .successHandler 方法和 .failureHandler 方法指定『认证通过』之后和『认证未通过』之后的处理逻辑。

  • 创建SimpleAuthenticationSuccessHandler和SimpleAuthenticationFailureHandler类来处理登录成功和失败的业务
/**
 * 认证成功的处理器类
 */
@Component
public class SimpleAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
                                  HttpServletResponse httpServletResponse,
                                  Authentication authentication) throws IOException, ServletException {

         // authentication 对象携带了当前登陆用户名等相关信息
        //User user = (User) authentication.getPrincipal();
        httpServletResponse.setContentType("application/json;charset=UTF-8");
		ResponseResult<Void> ok = new ResponseResult<>(200, "登录成功");
        
        //使用jacksong将对象序列化为josn字符串
        ObjectMapper mapper=new ObjectMapper();
        String json=mapper.writeValueAsString(ok);
        //输出json字符串到客户端
        PrintWriter printWriter = httpServletResponse.getWriter();
        printWriter.print(json);
        printWriter.flush();
        printWriter.close();
    }
}

/**
 * 登录失败处理器
 */
public class SimpleAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        
        //响应请求转码
        response.setContentType("application/json;charset=UTF-8");
        System.out.println(exception);
        //exception就是登录失败时的异常对象
        ResponseResult<Void> responseResult=null;
        if(exception instanceof InternalAuthenticationServiceException){
            responseResult=new ResponseResult<>(2001, "用户名错误!");
        }
        if(exception instanceof  BadCredentialsException){
            responseResult=new ResponseResult<>(2002, "密码错误!");
        }

        //使用jacksong将对象序列化为josn字符串
        ObjectMapper mapper=new ObjectMapper();
        String json=mapper.writeValueAsString(responseResult);
        //输出json字符串到客户端
        PrintWriter printWriter =response.getWriter();
        printWriter.print(json);
        printWriter.flush();
        printWriter.close();
    }
}

修改spring security配置类

@Resource
private SimpleAuthenticationSuccessHandler simpleAuthenticationSuccessHandler;

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * 资源权限相关配置
         * 配置哪些路径需要什么权限才能访问
         */
        http.authorizeRequests()
                .antMatchers("/user/insert").hasAuthority("user:insert")
                .antMatchers("/user/modify").hasAuthority("user:modify")
                .antMatchers("/user/delete").hasAuthority("user:delete")
                .antMatchers("/user/query").hasAuthority("user:query")
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/user-can-do").hasRole("USER") // 这里本质上应该是 ROLE_USER,但是 ROLE_ 要移除。不过你所提供的标准答案中,又必须要有 ROLE_ !
                .antMatchers("/admin-can-do").hasRole("ADMIN") // 同上
                .antMatchers("/all-can-do").permitAll()
                .anyRequest().authenticated(); // 除了上面的权限以外的,都必须登录才能访问

        //登录相关配置
        http.formLogin()
                .loginPage("/login.html")//配置需要显示的登录页面
                .loginProcessingUrl("/dologin") //告诉spring security登录页面发送这个请求时spring secruity就做登录认证
                .successHandler(new simpleAuthenticationSuccessHandle())//配置登录成功后的处理器
                .failureHandler(new SimpleAuthenticationFailureHandler())//配置登录失败后的处理器
                .permitAll();//这句配置很重要,新手容易忘记。放开 login.html和dologin 的访问权

        http.csrf().disable();
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/74181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习笔记-3-SVM-9-Twin SVM

Outline 1. Motivation 2. Geometry 3. Algebraic manipulation 4. Observations ------------------------------ 1. Motivation Twin SVM的基本出发点是做二分类时&#xff0c;为什么我们只用1个分割超平面&#xff0c;为什么不能用两个呢&#xff1f; 这里是想用两个…

CVT变速器中壳体吊机设计

目 录 1 绪论 1 1.1 课题的研究目的和意义 1 1.2 课题研究的内容 1 2 CVT变速器中壳体吊机总体设计 3 2.1 性能参数 3 2.2 确定主要工作机构和金属结构的形式 4 2.2.1 确定主要工作机构形式 4 2.2.2 金属结构选型 11 2.3 载荷的计算 13 2.3.1 自重载荷 13 2.3.2 起升载荷 14 2.…

HTTP常见状态码

网上都有状态码的说明但是有些不全所以我特此在这里整理一下&#xff0c;这个图来自小林大佬的图 1xx 100 表示客户还需要继续发送请求 101 客户要求服务器根据请求转换HTTP协议版本号 2xx 200 成功 201 提示知道新文件的URL 202 接受和处理、但处理未完成 203 返…

Redis缓存优化、本地锁及分布式锁的入门使用思想实现

Redis缓存优化、本地锁及分布式锁的入门使用思想实现 1、依赖启动器引入 <!-- redis --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency><!…

行为管理(锐捷业务软件篇)

大家好&#xff0c;我是小杜&#xff0c;古人云“好好学习&#xff0c;天天向上”。我要学习“古人”好榜样──三更鸡鸣、五......做为新时代的五好青年只能说“小杜”做不到啊&#xff01;不过提早到公司学习还是可以的。 之前了解了软件产品如何部署实施后。我们今天来看看对…

大一html5期末大作业 :基于html实现非遗文化网页设计题材【传统文化木雕】7个页面

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

如何压缩png图片大小?

png这种图片格式大家应该都熟悉吧&#xff1f;&#xff0c;跟jpg格式有着同样高的使用率。只不过png图片偏于专业化&#xff0c;而jpg更具平常性。举个不恰当的例子Jpg格式就好比相当于口语&#xff0c;而png格式相当于比较正式的书面用语的意思&#xff0c;所以很多小伙伴经常…

C++11标准模板(STL)- 算法(std::make_heap)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 数据结构的堆物理结构是数…

走向大模型、大算力、大数据:特斯拉与毫末的自动驾驶AI路径寻踪

2022行至年终&#xff0c;各种年度总结也纷至沓来。要说最近的大事件&#xff0c;一定少不了&#xff1a;自动驾驶又双叒寒冬了。大量“报忧不报喜”的新闻&#xff0c;说明2022年自动驾驶行业确实出现了一定程度的波动&#xff1a;激光雷达鼻祖德国ibeo和独角兽Argo.ai相继破产…

less基础

less基础 1、维护CSS的弊端 CSS是一门非程序语言&#xff0c;没有变量、函数、SCOPE(作用域) 等概念 CSS需要书写大量看似没有逻辑的代码&#xff0c;CSS冗余度是比较高的不方便维护及扩展&#xff0c;不利于复用CSS没有很好的计算能力非前端开发工程师来讲&#xff0c;往往会因…

[附源码]Python计算机毕业设计Django作业查重系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

AirPods Pro 2用户反馈出现耗电严重情况,Find My功能是其最大亮点

多位 AirPods Pro 2 用户反馈&#xff0c;在升级安装最新版本之后出现了耗电严重的情况。AirPods Pro 2 充电盒的电池即使在不使用的情况下也会出现耗电情况。受影响的用户反馈在不使用状态下&#xff0c;一晚上可以消耗 10% 的电量。 这些受影响的 AirPods Pro 2 用户反馈&…

业界认可+1!网易云信入选首批智慧教育产品和服务供应商名录

近日&#xff0c;2022&#xff08;第二十一届&#xff09;中国互联网大会在深圳成功召开。大会期间&#xff0c;由中国互联网协会智慧教育工作委员会、中国信息通信研究院合办的智慧教育论坛如期举行&#xff0c;论坛以“数智启新聚势&#xff0c;教育点亮未来”为主题&#xf…

Android AOSP和Android-X86源码下载编译终极普法

Android AOSP和Android-X86源码下载编译终极普法 引言 最近有朋友在询问怎么下载Android AOSP源码和Android-X86源码&#xff0c;编译学习&#xff01;其实这个说简单也简单&#xff0c;说复杂吗也不复杂。但是难在真的干起来&#xff01;凯子哥的风格吗&#xff0c;既然朋友们…

JavaSe

软件&#xff1a; 一系列按照特定顺序组织的计算机数据和指令的集合、有系统软件&#xff08;window、linux&#xff09;和应用软件&#xff08;QQ、微信&#xff09;之分。 人机交互方式&#xff1a; 图形化界面&#xff08;GUI&#xff09;鼠标直接点击&#xff08;简单直观…

留学Assignment写作方法和步骤整理

Assignment主要讨论的是研究性话题&#xff0c;深入剖析和研究主题&#xff0c;对于Assignment的主题进行探讨&#xff0c;从而更深入的了解&#xff0c;科技类Assignment偏向科技实用性&#xff0c;而对于文科类Assignment则偏向于探讨。要让Assignment更具价值&#xff0c;应…

【LSTM时序预测】基于卷积神经网络结合BiLSTM实现时序数据预测附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

最佳实践 | 如何基于GitHub Actions创建 DolphinScheduler Python API的CI/CD?

点亮 ⭐️ Star 照亮开源之路https://github.com/apache/dolphinscheduler01.DolphinScheduler 和 Python API 介绍Apache DolphinScheduler 是一个分布式、可扩展的工作流调度器平台&#xff0c;具有强大的 DAG 可视化界面。它可以帮助用户更轻松地构建和维护任何规模的工作流…

计算机毕业设计springboot+vue基本微信小程序的二手车交易平台

项目介绍 首先,论文一开始便是清楚的论述了小程序的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了小程序的需求基础上需要进一步地设计系统,主要包罗软件架构模式、整体功能模块、数据…

木字楠后台管理系统开发(2):SpringBoot项目代码生成以及基础接口测试

&#x1f3b6; 文章简介&#xff1a;木字楠后台管理系统开发(2)&#xff1a;SpringBoot项目代码生成以及基础接口测试 &#x1f4a1; 创作目的&#xff1a;为了带大家完整的体验木字楠后台管理系统模版的开发流程 ☀️ 今日天气&#xff1a;一场秋雨一场寒&#xff0c;天气越来…