springboot3整合SpringSecurity实现登录校验与权限认证(万字超详细讲解)

目录

身份认证:

1、创建一个spring boot项目,并导入一些初始依赖:

2、由于我们加入了spring-boot-starter-security的依赖,所以security就会自动生效了。这时直接编写一个controller控制器,并编写一个接口进行测试:

3、自定义用户的登录认证:

4、使用(SecurityFilterChain)过滤器, 配置用户登录的接口可以暴露出来,被所有人都正常的访问(还应在暴露一个注册接口,但我这里就先不写了)

5、将项目运行起来(我同时还写了一个普通的test方法,类型是get,没有放行,用于测试能不能拦截到):

6、自定义一个登录页面:

7、退出接口

权限校验:

1、基于请求:

2、基于方法:


目前市面上常用的安全框架有:

Spring Security、Shiro,还有一个国人开发的框架目前也备受好评:SaToken

但是与spring boot项目融合度最高的还是Spring Security,所以目前我们讲解一下基于spring boot项目来整合spring security来实现常用的登录校验与权限认证;

Spring Security(安全框架)
1、介绍
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。

如果项目中需要进行权限管理,具有多个角色和多种权限,我们可以使用Spring Security。

采用的是责任链的设计模式,是一堆过滤器链的组合,它有一条很长的过滤器链。

2、功能
Authentication (认证),就是用户登录
Authorization (授权),判断用户拥有什么权限,可以访问什么资源
安全防护,跨站脚本攻击,session攻击等
非常容易结合Springboot项目进行使用,本次就着重与实现认证和授权这两个功能
 


版本spring boot3.1.16、spring security6.x

身份认证:

1、创建一个spring boot项目,并导入一些初始依赖:

<dependencies>
    <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>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>2.0.21</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>
</dependencies>

2、由于我们加入了spring-boot-starter-security的依赖,所以security就会自动生效了。这时直接编写一个controller控制器,并编写一个接口进行测试:

可以看到我们在访问这个接口时出现了拦截,必须要我们进行登录之后才能访问;

那么接下来我们就来实现第一个功能:用户登录认证;

3、自定义用户的登录认证:

Spring Security 6.x 的认证实现流程如下:

  1. 用户提交登录请求
  2. Spring Security 将请求交给 UsernamePasswordAuthenticationFilter 过滤器处理。
  3. UsernamePasswordAuthenticationFilter 获取请求中的用户名和密码,并生成一个 AuthenticationToken 对象,将其交给 AuthenticationManager 进行认证。
  4. AuthenticationManager 通过 UserDetailsService 获取用户信息,然后使用 PasswordEncoder 对用户密码进行校验。
  5. 如果密码正确,AuthenticationManager 会生成一个认证通过的 Authentication 对象,并返回给 UsernamePasswordAuthenticationFilter 过滤器。如果密码不正确,则 AuthenticationManager 抛出一个 AuthenticationException 异常。
  6. UsernamePasswordAuthenticationFilter 将 Authentication 对象交给 SecurityContextHolder 进行管理,并调用 AuthenticationSuccessHandler 处理认证成功的情况。
  7. 如果认证失败,UsernamePasswordAuthenticationFilter 会调用 AuthenticationFailureHandler 处理认证失败的情况。

看起来有点复杂,其实写起来很简单的。spring security的底层就是一堆的过滤器来是实现的,而我们只需要编写一些重要的过滤器即可,其他的就用spring security默认的实现,只要不影响我们正常的登录功能即可。

(创建一个用户表用来进行登录实现,注意这个表中的用户名不能重复,我们将用户名作为每一个用户的唯一凭证,就如同人身份证号一样)表的结构非常简单,一些配置我这里就不在描述了(实体类、mapper、service、controller等)

认证的实现流程:

1、创建一个UserDetailsService实现SpringSecurity的UserDetailsService接口(这里写的是查询用户的逻辑)

UserDetailsService:此接口中定义了登录服务方法,用来实现登录逻辑。方法的返回值是UserDetails,也是spring security框架定义中的一个接口,用来存储用户信息,我们可以自定义一个类用来实现这个接口,将来返回的时候就返回我们自定义的用户实体类。

实现UserDetailsService接口

@Component
public class MyUserDetailsService implements UserDetailsService {

    /*
    *  UserDetailsService:提供查询用户功能,如根据用户名查询用户,并返回UserDetails
     *UserDetails,SpringSecurity定义的类, 记录用户信息,如用户名、密码、权限等
    * */
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名从数据库中查询用户
        SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .eq(username != null, SysUser::getUsername, username));
if (sysUser==null){
    throw new UsernameNotFoundException("用户不存在");
}
        MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);
        return mySysUserDetails;
    }
}

(在原有数据库表的基础上)实现UserDetails接口:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MySysUserDetails implements UserDetails {


    private Integer id;

    private String username;

    private String password;

//    用户拥有的权限集合,我这里先设置为null,将来会再更改的
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    public MySysUserDetails(SysUser sysUser) {
        this.id = sysUser.getId();
        this.username = sysUser.getUsername();
        this.password = sysUser.getPassword();
    }

    //    后面四个方法都是用户是否可用、是否过期之类的。我都设置为true
    @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、通过配置类对AuthenticationManager与自定义的UserDetails和PasswordEncoder进行关联

Spring Security是通过AuthenticationManager实现的认证,会借此来判断用户名和密码的正确性

密码解析器spring security框架定义的接口:PasswordEncoder

spring security框架强制要求,必须在spring容器中存在PasswordEncoder类型对象,且对象唯一

@Configuration
@EnableWebSecurity //开启webSecurity服务
public class SecurityConfig {

    
    @Autowired
    private MyUserDetailsService myUserDetailsService;

@Bean
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){
    DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
//将编写的UserDetailsService注入进来
    provider.setUserDetailsService(myUserDetailsService);
//将使用的密码编译器加入进来
    provider.setPasswordEncoder(passwordEncoder);
//将provider放置到AuthenticationManager 中
    ProviderManager providerManager=new ProviderManager(provider);
    return providerManager;
}
    /*
     * 在security安全框架中,提供了若干密码解析器实现类型。
     * 其中BCryptPasswordEncoder 叫强散列加密。可以保证相同的明文,多次加密后,
     * 密码有相同的散列数据,而不是相同的结果。
     * 匹配时,是基于相同的散列数据做的匹配。
     * Spring Security 推荐使用 BCryptPasswordEncoder 作为密码加密和解析器。
     * */
@Bean
    public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

3、在登录方法所在的类中注入AuthenticationManager,调用authenticate实现认证逻辑,并且在认证之后返回认证过的用户信息:

controller层:

//    用户登录
    @PostMapping("/login")
    public String login(@RequestBody LoginDto loginDto){

      String token=  sysUserService.login(loginDto);
        return token;
    }

对应的service层的方法:

在这之前,介绍一个非常重要的类:UsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationToken是Spring Security中用于表示基于用户名和密码的身份验证令牌的类。它主要有以下两个构造方法:

  1. UsernamePasswordAuthenticationToken(Object principal, Object credentials)

    • principal参数表示认证主体,通常是用户名或用户对象。在身份验证过程中,这通常是用来标识用户的信息,可以是用户名、邮箱等。
    • credentials参数表示凭据,通常是用户的密码或其他凭证信息。在身份验证过程中,这用于验证用户的身份。
  2. UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)

    • 除了上述两个参数外,这个构造方法还接受一个授权权限集合(authorities参数)。这个集合表示用户所拥有的权限,通常是一个包含用户权限信息的集合。
    • GrantedAuthority接口代表了用户的权限信息,可以通过该接口的实现类来表示用户具体的权限。

这两个构造方法的作用是创建一个包含用户身份信息、凭据信息和权限信息的身份验证令牌,以便在Spring Security中进行身份验证和授权操作。通过这些构造方法,可以将用户的相关信息封装成一个完整的身份验证对象,方便在安全框架中进行处理和验证。

总之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用户名密码身份验证信息的重要类,通过不同的构造方法可以满足不同场景下的需求。

  @Autowired
    private AuthenticationManager authenticationManager;

//    登录接口的具体实现
    @Override
    public String login(LoginDto loginDto) {
//        传入用户名和密码
        UsernamePasswordAuthenticationToken usernamePassword =
                new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());
//是实现登录逻辑,此时就回去调用LoadUserByUsername方法
        Authentication authenticate = authenticationManager.authenticate(usernamePassword);
//        获取返回的用户信息
        Object principal = authenticate.getPrincipal();
     //强转为MySysUserDetails类型
MySysUserDetails mySysUserDetails = (MySysUserDetails) principal;
//        输出用户信息
        System.err.println(mySysUserDetails);
//返回token
        String token= UUID.randomUUID().toString();
        return token;
    }

  我在test类中设置一些用户数据,并进行测试;

@Autowired private SysUserMapper sysUserMapper;

@Autowired private PasswordEncoder passwordEncoder;

@Test void contextLoads() { //导入了一个用户

SysUser sysUser=new SysUser();

sysUser.setUsername("zhangsan"); sysUser.setPassword(passwordEncoder.encode("123456")); sysUserMapper.insert(sysUser);

}

这里我们已经写好了自定义的登录流程,将项目运行起来(我同时还写了一个普通的test方法,类型是get,用来一起测试)

访问http://localhost:8080/test

这是我们写的一个普通的get方法,我们明明访问的是http://localhost:8080/test这个路径,但是却自动跳转到了Spring Security提供的默认的登录页面;这是因为Spring Security默认所有的请求都要先登录才行,我们在这里登录之后就可以继续访问test页面了;

(由于我们已经实现了UserDetailsService接口,并且在用户表中导入了一条用户数据,那么,这里的用户名和密码就是我们在数据库中存储的用户名和密码)

登录成功之后,我们就可以访问到test的信息了:

既然这个test请求要先进行拦截认证才能访问,那么,我们刚才编写的登录接口sys-user/login岂不是也要先进行拦截认证才能访问,这就与我们编写登录接口的初衷违背了,我们这个接口就是用来登陆的,现在还要先登录认证,之后再访问这个登录接口。那么有没有一种方法,不使用SpringSecurity默认的登录页面呢,使我们编写的登录接口所有人都可以直接访问呢?

4、使用(SecurityFilterChain)过滤器, 配置用户登录的接口可以暴露出来,被所有人都正常的访问(还应在暴露一个注册接口,但我这里就先不写了)

还是在第二步设置的SecurityConfig类中设置过滤器:

在spring security6.x版本之后,原先经常用的and()方法被废除了,现在spring官方推荐使用Lambda表达式的写法。

(因为我们接下来要进行测试,所以禁用CSRF保护,CSRF(Cross-Site Request Forgery)是一种攻击方式,攻击者通过伪造用户的请求来执行恶意操作。)

 /*
     * 配置权限相关的配置
     * 安全框架本质上是一堆的过滤器,称之为过滤器链,每一个过滤器链的功能都不同
     * 设置一些链接不要拦截
     * */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
//       关闭csrf
        httpSecurity.csrf(it->it.disable());
httpSecurity.authorizeHttpRequests(it->
        it.requestMatchers("/sys-user/login").permitAll()  //设置登录路径所有人都可以访问
                .anyRequest().authenticated()  //其他路径都要进行拦截
        );

        return httpSecurity.build();
    }

5、将项目运行起来(我同时还写了一个普通的test方法,类型是get,没有放行,用于测试能不能拦截到):

访问test请求:遇到拦截,说明我们的配置生效了

访问登录页面:能正常访问,且密码正确,返回了一个我们自己生成的一个token。

6、自定义一个登录页面:

SpringSecurity虽然默认有一个登录页面,但是我们一般情况下还是用我们自己写的登录页面,这样可操作性就大了很多;

引入thymeleaf依赖,我们直接在idea项目中建立一个登录页面;

编写一个登录页面,主要是完成用户的登录,同时我们也不再需要频繁的使用postman进行测试了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
    <title>自定义的登录页面</title>
</head>
<body>

<form action="/sys-user/login" method="post">
   用户名: <input type="text" name="username" >
    <br>
    密码: <input type="password" name="password">
    <br>
<input type="submit" value="登录">
</form>

</body>
</html>

这是一个简单的登录页面,就指定了用户名和密码。

并且指定from表单的提交路径为我们自定义的登录接口;将这个页面放在resource/templates目录下,方便我们将来的调用;

HTML中的form表单默认情况下会将数据格式化为key-value形式,而不是JSON格式。

也就是说我们刚刚写的自定义登录接口时是用@RequestBody接受收json类型的数据,这肯定是接受不到的,有两种方法实现:

1、直接用@RequestParam("username")  ,@RequestParam("password")接收这两个参数

2、@ModelAttribute注解:@ModelAttribute("formData") User user   //在@ModelAttribute注解内写表单的id,还能使用对象进行接收

我们也可以在前端将from表单的数据转化为json之后,在进行发送,但那样需要写js,我就直接在后端改一下了。

还是使用使用(SecurityFilterChain)过滤器,指定我们自定义的登录表单路径,(解释一下fromLogin方法):

formLogin 方法是 Spring Security 中用于配置基于表单的登录认证的一种方式。它通常用于传统的 Web 应用程序,其中前端页面由后端动态生成,并且用户在页面中输入用户名和密码来进行登录。在这种情况下,Spring Security 负责处理登录请求、验证用户身份、生成会话等操作。

 /*
     * 配置权限相关的配置
     * 安全框架本质上是一堆的过滤器,称之为过滤器链,每一个过滤器链的功能都不同
     * 设置一些链接不要拦截
     * */

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
//       关闭csrf
        httpSecurity.csrf(it->it.disable());
//        配置路径相关
httpSecurity.authorizeHttpRequests(it->
        it.requestMatchers("/login","sys-user/login").permitAll()  //设置登录路径所有人都可以访问
                .anyRequest().authenticated()  //其他路径都要进行拦截
        );
//表单
httpSecurity.formLogin(from->
        from.loginPage("/login")   //跳转到自定义的登录页面
.loginProcessingUrl("/sys-user/login")  //处理前端的请求,与from表单的action一致即可
     .defaultSuccessUrl("/index")  //默认的请求成功之后的跳转页面,直接访问登录页面
        );

        return httpSecurity.build();
    }

注意,这里还需要将/login这个接口进行放行。

我们知道,不能直接访问login.html这个自定义的登录页面,但是我们可以使用路径映射。先写一个login的get请求,并将这个请求映射到login.html页面。

.defaultSuccessUrl("/index"):这个方法是我们默认的登录成功之后跳转的请求地址。

如果你之前有请求的地址,但是这个地址没有放行或者你没有登录,那么会自动跳转到我们自定义的登录页面,完成登录之后,会跳转到你最先访问的地址;如果你直接访问的就是/login登录地址,那么默认的登录成功之后跳转到我们指定的地址:/index

@Controller
public class Login {


@GetMapping("/login")
public String login(){
    System.out.println("用户进入登录页面");
    return "login";   //没使用json返回,直接映射到自定义登录的页面
}

@GetMapping("/index")
@ResponseBody
    public String index(){
    return "用户登录成功";
}
}

现在我们已经自定义了一个登录页面,将项目启动起来进行测试:

我访问/test地址,这个地址没有放行,而且我们这是没有登录,那么会自动跳转到我们自定义的登录页面:

我们进行登录之后,会跳转到/test请求地址:

可以看到我们的结果与我们设想的一样:

现在我们直接访问/login登录页面:可以看到返回了/index页面的内容(这个是我们设置的默认登录成功之后返回的页面)

7、退出接口

需要注意的是在Spring Security中,没有专门用于处理退出失败的接口。退出(注销)操作通常是由浏览器发起的,Spring Security会拦截注销请求并执行相应的注销逻辑。

退出操作通常是通过调用SecurityContextLogoutHandler来完成的,它会清除用户的安全上下文,包括认证信息和会话信息。

在security框架中,默认提供了退出登陆的功能。请求地址是  /lohout  此为默认值,可以通过配置进行修该。直接请求 /logout ,会实现自动退出登录逻辑(默认的/logout接收get、和post请求)

退出登陆时,会清楚内存中的登录用户主体信息,销毁会话对象等等。

自定义退出接口:

httpSecurity.logout(logout->{
   logout.logoutUrl("/user/login")   //自定义退出接口
           .logoutSuccessHandler(logoutSuccess);  //退出成功之后的逻辑

});

编写退出成功之后的逻辑,我们可以在这里删除掉redis中的数据,设置返回的信息等等....

@Component
public class LogoutSuccess implements LogoutSuccessHandler {

    @Resource
    private RedisTemplate<String,String> redisTemplate;

/*
* 登录成功之后的逻辑
* */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        String token = request.getHeader("token");
        //    删除redis中的数据
        redisTemplate.delete(token);
        Map<String,Object> map=new HashMap<>();
        map.put("msg","退出成功");
        map.put("code",200);

response.getWriter().write(JSON.toJSONString(map));
        response.setContentType("application/json;charset=utf-8");
    }

}

权限校验:

我们费了很多功夫完成了身份认证,权限校验相对来说是比较简单的:

首先,我先解释一下角色与权限在SpringSecurity中的作用:

  1. 角色(Role):角色是一组权限的集合,通常代表着用户的身份或职责。在Spring Security中,可以通过配置将角色分配给用户或者用户组,以此来控制用户对系统资源的访问。例如,管理员拥有添加、删除和修改用户的权限,而普通用户只能查看自己的信息。

  2. 权限(Permission):权限是指对某一特定资源的访问控制,例如读写文件、访问数据库等。在Spring Security中,通常使用“资源-操作”命名方式来定义权限,例如“/admin/* - GET”表示允许访问以/admin/开头的所有URL的GET请求。可以将权限分配给角色,也可以将其分配给单独的用户。

角色与权限之间的关系是多对多的;

建立两张简单的表;一张用来存放角色、一张用来存放权限

角色表:

权限表:

这里建立的两张表只是用来进行测试,正常的数据不可能这么少的。建立相应的实体类;

SpringSecurity要求将身份认证信息存到GrantedAuthority对象列表中。代表了当前用户的权限。 GrantedAuthority对象由AuthenticationManager插入到Authentication对象中,然后在做出授权决策 时由AccessDecisionManager实例读取。 GrantedAuthority 接口只有一个方法

AuthorizationManager实例通过该方法来获得GrantedAuthority。通过字符串的形式表示, GrantedAuthority可以很容易地被大多数AuthorizationManager实现读取。如果GrantedAuthority不 能精确地表示为String,则GrantedAuthorization被认为是复杂的,getAuthority()必须返回null

告知权限的流程:

直接在登录时查询用户的权限,并放在我们自定义的实现了UserDetail的接口类中,用来表示登录用户的全部信息;

在MySysUserDetails类中加入两个属性,记录从数据库中查处的角色和权限信息

我这里就简单一点,不在做多表关联查询了。直接把zhangsan用户设置为超级管理员,拥有所有权限;lisi用户设置为普通管理员,拥有基本权限。

在MyUserDetailsService中实现用户权限的赋值:

@Component
public class MyUserDetailsService implements UserDetailsService {

    /*
    *  UserDetailsService:提供查询用户功能,如根据用户名查询用户,并返回UserDetails
     *UserDetails,SpringSecurity定义的类, 记录用户信息,如用户名、密码、权限等
    * */
    @Autowired
    private SysUserMapper sysUserMapper;

@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private SysPermissionsMapper sysPermissionsMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名从数据库中查询用户
        SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .eq(username != null, SysUser::getUsername, username));
if (sysUser==null){
    throw new UsernameNotFoundException("用户不存在");
}
        MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);
if ("zhangsan".equals(username)){
//zhangsan用户是超级管理员,拥有一切权限
    SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "超级管理员"));
    Set<SysRole> roles=new HashSet<>();
    roles.add(sysRole);
    mySysUserDetails.setRoles(roles);
    SysPermissions sysPermissions = sysPermissionsMapper.selectById(1);
    Set<String> permissions=new HashSet<>();
    permissions.add(sysPermissions.getPermissionsName());
    mySysUserDetails.setPermissions(permissions);
}

        if ("lisi".equals(username)){
//lisi用户是普通管理员,拥有基本权限
            SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "普通管理员"));
            Set<SysRole> roles=new HashSet<>();
            roles.add(sysRole);
            mySysUserDetails.setRoles(roles);
            SysPermissions sysPermissions = sysPermissionsMapper.selectById(2);
            Set<String> permissions=new HashSet<>();
            permissions.add(sysPermissions.getPermissionsName());
            mySysUserDetails.setPermissions(permissions);
        }

        return mySysUserDetails;
    }
}

在实现了UserDetailes接口的用户信息类MySysUserDetails中完成角色和权限的赋值:
 

//    角色信息
    private Set<SysRole> roles;
//    权限信息
    private Set<String> permissions;
    
//    用户拥有的权限集合,我这里先设置为null,将来会再更改的
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        System.err.println("进入权限的获取方法");

        List<GrantedAuthority> authorities = new ArrayList<>(); // 授权信息列表
// 将角色名称添加到授权信息列表中
        roles.forEach(role->
        authorities.add(new SimpleGrantedAuthority(role.getRoleName())));
        // 将权限名称添加到授权信息列表中
        permissions.forEach(permission->
        authorities.add(new SimpleGrantedAuthority(permission))
                );
        return authorities; // 返回授权信息列表
    }

用户认证之后,会去存储用户对应的权限,并且给资源设置对应的权限,SpringSecurity支持两种粒度 的权限:

1、基于请求的:在配置文件中配置路径,可以使用**的通配符

2、基于方法的:在方法上使用注解实现

角色配置:在UserDetails接口中存在相关的权限和角色管理,只不过我们在实现这个接口的时候,将这些都设置为了null。现在我们只需要将这些信息实现即可:

1、基于请求:

还是在SecurityFilter过滤器中实现请求地址的权限校验

httpSecurity.authorizeHttpRequests(it->
//hello地址只有超级管理员角色才能访问
it.requestMatchers("/hello").hasRole("超级管理员")
//hello2地址只有"拥有所有权限"的权限才能访问
.requestMatchers("hello2").hasAuthority("拥有所有权限")
                .requestMatchers("/login","sys-user/login").permitAll()  //设置登录路径所有人都可以访问
                .anyRequest().authenticated()  //其他路径都要进行拦截
        );

使用sili进行登录时,访问hello2接口显示权限不够:

使用zhangsan进行登录时,访问hello2接口可以访问到:

2、基于方法:

基于方法的权限认证要在SecurityConfig类上加上@EnableMethodSecurity注解,表示开启了方法权限的使用;

常用的有四个注解:

@PreAuthorize

@PostAuthorize

@PreFilter

@PostFilter

/*测试@PreAuthorize注解
* 作用:使用在类或方法上,拥有指定的权限才能访问(在方法运行前进行校验)
* String类型的参数:语法是Spring的EL表达式
* 有权限:test3权限
* hasRole:会去匹配authorities,但是会在hasRole的参数前加上一个ROLE_前缀,
* 所以在定义权限的时候需要加上ROLE_前缀
* role和authorities的关系是:role是一种复杂的写法,有ROLE_前缀,authorities是role的简化写法
* 如果使用
* hasAnyRole:则匹配的权限是在authorities加上前缀ROLE_
* 推荐使用
* hasAnyAuthority:匹配authorities,但是不用在authorities的参数前加上ROLE_前缀
* */
@PreAuthorize("hasAnyAuthority('拥有所有权限')")
@ResponseBody
@GetMapping("/test3")
public String test3(){
    System.out.println("一个请求");

    return "一个test3请求";
}
/*
 @PostAuthorize:在方法返回时进行校验。
 可以还是校验权限、或者校验一些其他的东西(接下来我们校验返回值的长度)
*返回结果的长度大于3、则认为是合法的
returnObject:固定写法,代指返回对象
* */
@ResponseBody
@PostAuthorize("returnObject.length()>4")
@GetMapping("/test4")
public String test4(){
    System.out.println("一个test4请求");

    return "小张自傲张最终";
}
    /*
* @PreFilter:过滤符合条件的数据进入到接口
* */
    @PostFilter("filterObject.length()>3")
    @ResponseBody
    @GetMapping("/test5")
    public String test5(){
        System.out.println("一个test4请求");
List<String> list = new ArrayList<>();
list.add("张三");
list.add("王麻子");
list.add("狗叫什么");

        return "一个test5请求";
    }
/*
* @PreFilter:过滤符合条件的数据返回,数据必须是Collection、map、Array【数组】
* */
@PreFilter("filterObject.length()>5")
@ResponseBody
@PostMapping("/test6")
public List<String> test6(@RequestBody List<String> list){
    return list;
}

这四个常用的权限校验方法我都写出来了,运行结果我就不在一一截图了。

需要注意的是这些方法不仅仅局限在权限的校验,还能对返回的结果做一定的操作;

最需要注意的就是@PreFilter注解,它要求前端传递的参数一定是数组或集合;

还有在SpringSecurity框架中:

role和authorities的关系是:role是一种复杂的写法,有ROLE_前缀,authorities是role的简化写法

基于方法鉴权 在SpringSecurity6版本中@EnableGlobalMethodSecurity被弃用,取而代之的是 @EnableMethodSecurity。默认情况下,会激活pre-post注解,并在内部使用 AuthorizationManager。

新老API区别 此@EnableMethodSecurity替代了@EnableGlobalMethodSecurity。提供了以下改进: 1. 使用简化的AuthorizationManager。 2. 支持直接基于bean的配置,而不需要扩展GlobalMethodSecurityConfiguration 3. 使用Spring AOP构建,删除抽象并允许您使用Spring AOP构建块进行自定义 4. 检查是否存在冲突的注释,以确保明确的安全配置 5. 符合JSR-250 6. 默认情况下启用@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter

主要的权衡似乎是您希望您的授权规则位于何处。重要的是要记住,当您使用基于注释的方法安全性 时,未注释的方法是不安全的。为了防止这种情况,请在HttpSecurity实例中声明一个兜底授权规则。 如果方法上也定义了权限,则会覆盖类上的权限

注意:使用注解的方式实现,如果接口的权限发生变化,需要修改代码了。

总结:

  1. 登录校验(Authentication):

    • 用户提交用户名和密码进行登录。
    • Spring Security会拦截登录请求,并将用户名和密码与存储在系统中的凭据(如数据库或LDAP)进行比对。
    • 如果用户名和密码匹配,则认为用户通过了身份验证,可以继续访问受限资源。
    • 认证成功后,Spring Security会创建一个包含用户信息和权限的安全上下文(Security Context)。
  2. 权限认证(Authorization):

    • 一旦用户通过了身份验证,Spring Security就会开始进行权限认证。
    • 针对每个受限资源或操作,可以配置相应的权限要求,例如需要哪些角色或权限才能访问。
    • Spring Security会根据配置的权限要求,检查当前用户所拥有的角色和权限,判断是否满足访问条件。
    • 如果用户拥有足够的角色或权限,就被允许访问资源;否则将被拒绝访问,并可能重定向到登录页面或返回相应的错误信息。

Spring Security通过身份验证(Authentication)来确认用户的身份,并通过授权(Authorization)来控制用户对受保护资源的访问。这种分离的设计使得安全配置更加灵活,并且可以轻松地对不同的用户和角色进行管理和控制。

这都是一些基础的理论,下面我将实战中演示使用security的案例:

前后端分离,使用vue3整合SpringSecurity加JWT实现登录认证_springsecurity整合vue3-CSDN博客

  • 36
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Spring Boot整合Spring Security可以实现登录认证和数据权限管理。下面是简单的步骤: 1. 添加依赖 在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 配置Spring Security 在主配置类中添加@EnableWebSecurity注解,开启Spring Security,并且创建一个继承自WebSecurityConfigurerAdapter的配置类,用于配置Spring Security。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 配置登录页面和登录请求的路径 http.formLogin() .loginPage("/login") .loginProcessingUrl("/login") .defaultSuccessURL("/home") .and() // 配置退出登录的路径和跳转页面 .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .and() // 配置拦截规则 .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() // 关闭CSRF保护 .csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 配置用户信息和密码加密方式 auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()) .withUser("admin") .password(new BCryptPasswordEncoder().encode("admin")) .roles("ADMIN"); } } ``` 3. 配置数据权限管理 如果需要实现数据权限管理,可以在配置类中添加一个实现了FilterInvocationSecurityMetadataSource接口的类,用于获取当前请求所需的权限信息,并且在配置类中添加一个实现了AccessDecisionManager接口的类,用于判断当前用户是否有访问该资源的权限。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; @Autowired private CustomAccessDecisionManager customAccessDecisionManager; @Override protected void configure(HttpSecurity http) throws Exception { // 配置拦截规则 http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource); object.setAccessDecisionManager(customAccessDecisionManager); return object; } }) .and() // 关闭CSRF保护 .csrf().disable(); } } ``` 参考资料: 1. Spring Security官方文档:https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/ 2. Spring Boot整合Spring Security实现登录认证和数据权限管理:https://www.jianshu.com/p/04d848c9cb8d

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张乔24

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值