Bootstrap

【Spring Security + OAuth2】授权

Spring Security + OAuth2

第一章 Spring Security 快速入门
第二章 Spring Security 自定义配置
第三章 Spring Security 前后端分离配置
第四章 Spring Security 身份认证
第五章 Spring Security 授权
第六章 OAuth2



授权管理的实现在SpringSecurity中非常灵活,可以帮助应用程序实现以下两种常见的授权需求:

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

1、基于request的授权

1.1、用户-权限-资源

需求

  • 具有USER_LIST权限的用户可以访问/user/list
  • 具有USER_ADD权限的用户可以访问/user/add

配置权限

SecurityFilterChain

package com.security.demo.config;
import ...

@Configuration //配置类
public class WebSecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // authorizeHttpRequests() 开启授权保护
    // anyRequest() 对所以请求开启授权保护
    // authenticated()已认证请求会自动授权。
    http.authorizeHttpRequests(
        authorize -> authorize
        //具有USER_LIST权限的用户可以访问/user/list                   
        .requestMatchers("/user/list").hasAnyAuthority("USER_LIST")
       //具有USER_ADD权限的用户可以访问/user/addd
        .requestMatchers("/user/add").hasAnyAuthority("USER_ADD")
        //对所有请求开启授权保护
        .anyRequest()
         //已认证的请求会被自动授权
        .authenticated()
    );
        ...
}

授权权限

DBUserDetailsManager中的loadUserByUsername方法:

 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.eq("username",username);
        User user = userMapper.selectOne(queryWrapper);
        if (user==null){
            throw  new UsernameNotFoundException(username);
        }else{
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            //模拟权限
            authorities.add(()->"USER_LIST");
            authorities.add(()->"USER_ADD");
            return   new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPassword(),
                    user.getEnabled(),
                    true,//用户账号是否过期。
                    true,//用户凭证是否过期
                    true,//未被锁定
                    authorities//权限列表
            );
        }
    }

请求未授权的接口

实现AccessDeniedHandler 接口

package com.security.demo.config;

public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, ServletException {
        //创建结果对象
        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);
    }
}

SecurityFilterChain添加配置请求未授权的处理。

package com.security.demo.config;
import ...

@Configuration //配置类
public class WebSecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	...
	http.exceptionHandling(exception -> {
        exception.accessDeniedHandler(new MyAccessDeniedHandler());//请求未授权的处理
    });
	...
}

1.2、用户-角色-资源

配置角色

package com.security.demo.config;
import ...

@Configuration //配置类
public class WebSecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(
        authorize -> authorize
        .requestMatchers("/user/**").hasRole("ADMIN")
        .anyRequest()
        .authenticated()
    );
        ...
}

授权角色

@Component
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.eq("username",username);
        User user = userMapper.selectOne(queryWrapper);
        if (user==null){
            throw  new UsernameNotFoundException(username);
        }else{
        //模拟授权
         return org.springframework.security.core.userdetails.User
                    .withUsername(user.getUsername())
                    .password(user.getPassword())
                    .disabled(!user.getEnabled())//用户是否禁用
                    .credentialsExpired(false)//是否过期
                    .accountLocked(false)
                    .roles("ADMIN")
                    .build();

        }
    }
}

1.3、用户-角色-权限-资源

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

1、用户表(User table):包含用户的基本信息,例如用户名、密码和其他身份验证信息。

列名数据类型描述
user_idint用户ID
usernamevarchar用户名
passwordvarchar密码
emailvarchar电子邮件

2、角色表(Role table):储存所有可能得角色及其描述。

列名数据类型描述
role_idint角色ID
role_namevarchar角色名称
descriptionvarchar角色描述

3、权限表(Permission table):定义系统中所有可能得权限

列名数据类型描述
permission_idint权限ID
permission_namevarchar权限名称
descriptionvarchar角色描述

4、用户角色关联表(User-Role table):将用户与角色关联起来。

列名数据类型描述
user_role_idint用户角色关联ID
user_idint用户ID
role_idint角色ID

5、角色权限关联表(Role-Permission table):将角色与权限关联起来。

列名数据类型描述
role_permission_idint用户角色关联ID
role_idint角色ID
permission_idint权限ID

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

2、基于方法的授权

2.1、开启方法授权

在配置文件中添加@EnableMethodSecurity注解,并修改authorizeHttpRequests

@Configuration //配置类
@EnableMethodSecurity
public class WebSecurityConfig {
	...
	http.authorizeHttpRequests(
                authorize -> authorize
                        .anyRequest()
                        .authenticated()
        );
}

2.2、给用户授予角色和权限

DBUserDetailsManager中的loadUserByUsername方法:

	 return org.springframework.security.core.userdetails.User
                    .withUsername(user.getUsername())
                    .password(user.getPassword())
                    .disabled(!user.getEnabled())//用户是否禁用
                    .credentialsExpired(false)//是否过期
                    .accountLocked(false)
                    .roles("ADMIN")
                    //authorities和roles不能同时使用会覆盖
                    .authorities("USER_ADD","USER_LIST")
                    .build();

        }

2.3、常用授权注解

    @GetMapping(path = "/list")
    @PreAuthorize("hasRole('ADMIN') and authentication.name == 'abc'")
    public List<User> getList(){
        return userService.list();
    }

    @PreAuthorize("hasAuthority('USER_ADD')")
    @PostMapping(path = "/add")
    public void add(@RequestBody User user){
        userService.saveUserDetails(user);
    }
;