快速学习安全框架 Springsecurity最新版(6.2)--用户授权模块

时间:2024-02-23 17:59:10

简介

上一节Springsecurity 用户认证
Springsecurity 拥有强大的认证和授权功能并且非常灵活,,一来说我们都i有以下需求
可以帮助应用程序实现以下两种常见的授权需求:

  • 用户-权限-资源:例如张三的权限是添加用户、查看用户列表,李四的权限是查看用户列表

  • 用户-角色-权限-资源:例如 张三是角色是管理员、李四的角色是普通用户,管理员能做所有操作,普通用户只能查看信息

RBAC权限模型

​ RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型 即每个用户对应不同角色,每个角色对应不同功能

用户-角色-权限-资源

RBAC(Role-Based Access Control,基于角色的访问控制)是一种常用的数据库设计方案,它将用户的权限分配和管理与角色相关联。以下是一个基本的RBAC数据库设计方案的示例:

  1. 用户表(User table):包含用户的基本信息,例如用户名、密码和其他身份验证信息。
列名 数据类型 描述
user_id int 用户ID
username varchar 用户名
password varchar 密码
email varchar 电子邮件地址
  1. 角色表(Role table):存储所有可能的角色及其描述。
列名 数据类型 描述
role_id int 角色ID
role_name varchar 角色名称
description varchar 角色描述
  1. 权限表(Permission table):定义系统中所有可能的权限。
列名 数据类型 描述
permission_id int 权限ID
permission_name varchar 权限名称
description varchar 权限描述
  1. 用户角色关联表(User-Role table):将用户与角色关联起来。
列名 数据类型 描述
user_role_id int 用户角色关联ID
user_id int 用户ID
role_id int 角色ID
  1. 角色权限关联表(Role-Permission table):将角色与权限关联起来。
列名 数据类型 描述
role_permission_id int 角色权限关联ID
role_id int 角色ID
permission_id int 权限ID

在这个设计方案中,用户可以被分配一个或多个角色,而每个角色又可以具有一个或多个权限。通过对用户角色关联和角色权限关联表进行操作,可以实现灵活的权限管理和访问控制。

当用户尝试访问系统资源时,系统可以根据用户的角色和权限决定是否允许访问。这样的设计方案使得权限管理更加简单和可维护,因为只需调整角色和权限的分配即可,而不需要针对每个用户进行单独的设置。其实就是权限或者角色不会直接在用户属性里,而是在多表联查中赋予
比如
来获取用户权限或者角色

<select id="selectPermsByUserId" resultType="java.lang.String">
    SELECT
        DISTINCT m.`perms`
    FROM
        sys_user_role ur
        LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
        LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
        LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
    WHERE
        user_id = #{userid}
        AND r.`status` = 0
        AND m.`status` = 0
</select>

基于request的授权

在首页的用户笔记中有项目目录,现在修改其中的俩个接口权限

官方架构
在这里插入图片描述

之前只要携带正确的token 就可以访问
在这里插入图片描述
修改配置文件后

  @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {




        http.csrf(AbstractHttpConfigurer::disable)
                .addFilterBefore(new JwtAuthenticationTokenFilter(redisCache,jwtUtil), UsernamePasswordAuthenticationFilter.class)
                .sessionManagement(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(HttpMethod.POST,"/auth/login").permitAll() // 对登录接口允许匿名访问
                        .requestMatchers("/user/list").hasAuthority("user:list")
                        
                        .requestMatchers("/user/add").hasAuthority("user:add")
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
//                        .requestMatchers("**").permitAll()
                        .anyRequest().authenticated())
                .exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()))
                .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))

            ;

        return http.build();


    }

返回的内容是自己定义的json
在这里插入图片描述
但是响应码确实变成了403 security不允许访问
在这里插入图片描述

实现授权:权限-资源

实现不同权限访问不同接口

配置文件限定接口
@EnableMethodSecurity
@Configuration
@AllArgsConstructor
public class WebSecurityConfig {
    private final ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private RedisCache redisCache;
    @Autowired
    private JwtUtil jwtUtil;
//    从配置文件注入  ioc先扫描配置文件
@Resource
    UserMapper userMapper;

    /**
     * 这个Bean创建了一个认证管理器对象,它是Spring Security认证的核心组件之一。
     * 认证管理器负责协调和管理认证流程,并委托给一个或多个认证提供者(在这里,使用了daoAuthenticationProvider)来进行具体的认证操作。
     * 这里通过创建一个ProviderManager对象,将之前配置的daoAuthenticationProvider添加到认证管理器中。
     * 还通过setAuthenticationEventPublisher()方法设置了一个事件发布器,用于在认证事件发生时发布相关的事件,
     * 这里使用了DefaultAuthenticationEventPublisher,并传入了一个applicationEventPublisher对象,可能用于发布认证事件到Spring的事件机制中。
     * @return
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        List<AuthenticationProvider> providerList = new ArrayList<>();
        providerList.add(daoAuthenticationProvider());
        ProviderManager providerManager = new ProviderManager(providerList);
        providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));

        return providerManager;
    }

    /**
     * 是Spring Security用于处理基于数据库的用户认证的提供者。
     * DaoAuthenticationProvider需要一个UserDetailsService对象来获取用户的详细信息进行认证,
     * 所以通过setUserDetailsService()方法设置了我们之前设置的manager。
     * @return
     */
    @Bean
    DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder( passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(new DBUserDetailsManager(userMapper));

        return daoAuthenticationProvider;
    }

    /**
     * 把默认的密码加密器换成我们自定义的加密器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
      return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {




        http.csrf(AbstractHttpConfigurer::disable)
                .addFilterBefore(new JwtAuthenticationTokenFilter(redisCache,jwtUtil), UsernamePasswordAuthenticationFilter.class)
                .sessionManagement(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(HttpMethod.POST,"/auth/login").permitAll() // 对登录接口允许匿名访问
                        .requestMatchers("/user/list").hasAuthority("user:list")//对接口定制限定权限
                        .requestMatchers("/user/add").hasAuthority("user:add")
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
//                        .requestMatchers("**").permitAll()
                        //对所有请求开启授权保护
                        .anyRequest()
                        //已认证的请求会被自动授权
                        .authenticated()
                       )
                .exceptionHandling(
            //   异常结果处理 1.认证异常处理2.授权异常处理
					exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint())
                        .accessDeniedHandler((request, response, e)->{
                            //创建结果对象
                            HashMap result = new HashMap();
                            result.put("code", -1);
                            result.put("message", "没有权限");

                            //转换成json字符串
                            String json = JSON.toJSONString(result);

                            //返回响应
                            response.setContentType("application/json;charset=UTF-8");
                            response.getWriter().println(json);
                        })

                )
                .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))

            ;

        return http.build();


    }

}
规范用户类

改造用户类
这里做简单模拟 写一个字段作为权限字段,往往实际开发中是一个json 数组用于转换

/**
 * @TableName user
 */
@TableName(value ="user")
@Data
public class User implements Serializable {
    private Integer id;

    private String username;

    private String password;

    private Integer enabled;
    //实际开发中权限字段多半是json 里卖装的权限数组
    @TableField(exist = false)
    private List<GrantedAuthority> authorities;
    private static final long serialVersionUID = 1L;


}

security定义的规范用户类

@Data

public class UserDetail implements UserDetails {
    private User user;

    //存储SpringSecurity所需要的权限信息的集合 构造函数时候用于将user的json数组转换为这个权限对象

    private List<GrantedAuthority> authorities;
    @Override
    public List<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

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

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

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

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

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

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

userdetailsService的实现类

@Component
@Slf4j
@AllArgsConstructor
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {


    private UserMapper userMapper;
//    这样就可以按照security的规范来使用用户的管理
    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        return null;
    }

    @Override
    public void createUser(UserDetails userDetails) {
//        在sql中插入信息
        User user = new User();
        user.setUsername(userDetails.getUsername());
        user.setPassword(userDetails.getPassword());
        user.setEnabled(1);
        userMapper.insert(user);
    }

    @Override
    public void updateUser(UserDetails user) {

    }

    @Override
    public void deleteUser(String username) {

    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {

    }

    @Override
    public boolean userExists(String username) {
        return false;
    }


    //security底层会根据这个方法来对比用户
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//        这里用户账户是唯一的
    User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUsername, username));
       if (user == null){
           throw new UsernameNotFoundException("系统用户不存在");
       }else{
//           1表示可用
           boolean isenabled = user.getEnabled() == 1;
/**
 * ,任何非零的整数值都会被视为 true,而 0 会被视为 false。
 */

log.info("数据库个根据用户名获取用户"+user);
        //模拟系统权限列表
          List<GrantedAuthority> authorities = new ArrayList<>();
  // 写一个静态数据模拟用户数据库中的权限
           authorities.add(()->"user:list");



           user.setAuthorities(authorities);
           UserDetail detail = new UserDetail();

           detail.setUser(user);
           detail.setAuthorities(authorities);
           return detail;
       }
    }
}
改造过滤器
@Slf4j
@AllArgsConstructor

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    private final RedisCache redisCache;
    private final   JwtUtil jwtUtil;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String token = request.getHeader("token");
            if (tokenNotRequired(request.getRequestURI())) {
//                    登录接口直接放行
                filterChain.doFilter(request, response);
                return;
            }

            if (token == null || token.trim().isEmpty()) {
                throw new AuthenticationException("需要登录") {};
            }

            String username = jwtUtil.getUsernameFromToken(token);
            String redisKey = "logintoken:" + username;
            String jsonString = redisCache.getCacheObject(redisKey);

            if (jsonString == null || jsonString.trim().isEmpty()) {
                throw new AuthenticationException("用户登录已过期") {};
            }

            UserDetail userInfo = JSON.parseObject(jsonString, UserDetail.class);
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(userInfo, null, userInfo.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken); //设置给上下文对象
            filterChain.doFilter(request, response);
        }
        //对所有抛出的异常进行处理
        catch (AuthenticationException e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().println(JSON.toJSONString(Result.nologin(e.getMessage())));
        }
    }

    private boolean tokenNotRequired(String requestURI) {

        return "/auth/login".equals(requestURI) || "/auth/info".equals(requestURI);
    }
}

访问上下文对象

    @GetMapping("/")
    public Result index() {

        SecurityContext context = SecurityContextHolder.getContext();//存储认证对象的上下文
        Authentication authentication = context.getAuthentication();//认证对象
        String username = authentication.getName();//用户名
        Object principal =authentication.getPrincipal();//身份
        Object credentials = authentication.getCredentials();//凭证(脱敏)
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();//权限

        System.out.println(username);
        System.out.println(principal);
        System.out.println(credentials);
        System.out.println(authorities);

        HashMap<String, Object> map = new HashMap<>();

        map.put("认证对象", authentication);
        map.