当前位置:   article > 正文

Spring Security 授权

Spring Security 授权

基于request的授权

HttpSecurity 权限配置

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

        http.authorizeHttpRequests(authorize -> {
            authorize
                    // 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
                    .requestMatchers("/users/get").hasAuthority("admin")
                    // 放行请求:针对含有 tourist 权限的用户放行 /get 接口
                    .requestMatchers("/test/auth").hasAuthority("tourist")
                    // 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
                    .requestMatchers("/users/list").hasRole("ROLE_admin")
                    // 对所有用户放行
                    .requestMatchers("/login", "/test/**").permitAll()
                    // 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
                    .anyRequest()
                    // 以认证的会自动授权
                    .authenticated();
        });
        http.exceptionHandling(exc -> {
                    // 用户未认证
                    exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
                    // 请求被拒绝
                    exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
                }
        );

        // 开启跨域请求
        http.cors(withDefaults());
        return http.build();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username:" + username);
        // 用户名称不唯一,这里的账号名account,不是用户名
        // 这里user实现了Security的UserDetails对象
        UsersDO user = usersMapper.selectByAccount(username);
        if (user == null || StringUtils.isEmpty(user.getPassword())) {
            throw new UsernameNotFoundException(username + ":用户不存在");
        }
        // 获取用户角色列表
        List<RoleDO> list = usersMapper.selectByAccountRole(username);
        user.setList(list);
        return user;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

获取用户信息

@Mapper
public interface UsersMapper extends BaseMapper<Users> {

    /**
     * 分页查询
     */
    List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);

    @Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user  where ACCOUNT =#{account}")
    UsersDO selectByAccount(@Param("account") String account);

    @Select("select t1.ACCOUNT ,t2.role_name from  tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account  \n" +
            "left  join tm_role t3  on t2.role_name =t3.role_name  where  t1.ACCOUNT =#{account}")
    List<RoleDO> selectByAccountRole(@Param("account") String username);
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

实体类:

@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {

    /**
     * 账号
     */
    private String id;

    /**
     * 账号
     */
    private String account;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;


    /**
     * 逻辑删除
     */
    @TableLogic
    @TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
    private String deleted;

    /**
     * 用户角色列表
     */
    private List<RoleDO> list;

    /**
     * 权限列表
     */
    private Set<GrantedAuthority> authorities =new HashSet<>();

    @Override
    public boolean isEnabled() {
        // 通过逻辑删除字段判断是否启用
        return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
    }


    @Override
    public boolean isAccountNonExpired() {
        //用户是否未过期
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 用户凭证是否未过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // 用户是否没有被锁定
        return true;
    }

    /**
     * 获取权限列表,暂时以角色名代表权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 权限列表
        for (RoleDO roleDO : list){
            authorities.add(()->roleDO.getRoleName());
        }
        return authorities;
    }

    /**
     * 设置角色 , 这里参考了 Security 的源码
     * @param roles
     */
    public void roles(String... roles) {
        for (String role : roles) {
            // 这里要加上前缀 “ROLE_”
            Assert.isTrue(!role.startsWith("ROLE_"),
                    () -> role + " cannot start with ROLE_ (it is automatically added)");
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
        }

    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

用户登录后,如果没有对应权限,默认会抛出403异常

基于request的授权

HttpSecurity 权限配置

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

        http.authorizeHttpRequests(authorize -> {
            authorize
                    // 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
                    .requestMatchers("/users/get").hasAuthority("admin")
                    // 放行请求:针对含有 tourist 权限的用户放行 /get 接口
                    .requestMatchers("/test/auth").hasAuthority("tourist")
                    // 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
                    .requestMatchers("/users/list").hasRole("ROLE_admin")
                    // 对所有用户放行
                    .requestMatchers("/login", "/test/**").permitAll()
                    // 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
                    .anyRequest()
                    // 以认证的会自动授权
                    .authenticated();
        });
        http.exceptionHandling(exc -> {
                    // 用户未认证
                    exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
                    // 请求被拒绝
                    exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
                }
        );

        // 开启跨域请求
        http.cors(withDefaults());
        return http.build();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username:" + username);
        // 用户名称不唯一,这里的账号名account,不是用户名
        // 这里user实现了Security的UserDetails对象
        UsersDO user = usersMapper.selectByAccount(username);
        if (user == null || StringUtils.isEmpty(user.getPassword())) {
            throw new UsernameNotFoundException(username + ":用户不存在");
        }
        // 获取用户角色列表
        List<RoleDO> list = usersMapper.selectByAccountRole(username);
        user.setList(list);
        return user;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

获取用户信息

@Mapper
public interface UsersMapper extends BaseMapper<Users> {

    /**
     * 分页查询
     */
    List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);

    @Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user  where ACCOUNT =#{account}")
    UsersDO selectByAccount(@Param("account") String account);

    @Select("select t1.ACCOUNT ,t2.role_name from  tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account  \n" +
            "left  join tm_role t3  on t2.role_name =t3.role_name  where  t1.ACCOUNT =#{account}")
    List<RoleDO> selectByAccountRole(@Param("account") String username);
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

实体类:

@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {

    /**
     * 账号
     */
    private String id;

    /**
     * 账号
     */
    private String account;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;


    /**
     * 逻辑删除
     */
    @TableLogic
    @TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
    private String deleted;

    /**
     * 用户角色列表
     */
    private List<RoleDO> list;

    /**
     * 权限列表
     */
    private Set<GrantedAuthority> authorities =new HashSet<>();

    @Override
    public boolean isEnabled() {
        // 通过逻辑删除字段判断是否启用
        return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
    }


    @Override
    public boolean isAccountNonExpired() {
        //用户是否未过期
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 用户凭证是否未过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // 用户是否没有被锁定
        return true;
    }

    /**
     * 获取权限列表,暂时以角色名代表权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 权限列表
        for (RoleDO roleDO : list){
            authorities.add(()->roleDO.getRoleName());
        }
        return authorities;
    }

    /**
     * 设置角色 , 这里参考了 Security 的源码
     * @param roles
     */
    public void roles(String... roles) {
        for (String role : roles) {
            // 这里要加上前缀 “ROLE_”
            Assert.isTrue(!role.startsWith("ROLE_"),
                    () -> role + " cannot start with ROLE_ (it is automatically added)");
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
        }

    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

用户登录后,如果没有对应权限,默认会抛出403异常

基于request的授权

HttpSecurity 权限配置

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

        http.authorizeHttpRequests(authorize -> {
            authorize
                    // 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
                    .requestMatchers("/users/get").hasAuthority("admin")
                    // 放行请求:针对含有 tourist 权限的用户放行 /get 接口
                    .requestMatchers("/test/auth").hasAuthority("tourist")
                    // 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
                    .requestMatchers("/users/list").hasRole("ROLE_admin")
                    // 对所有用户放行
                    .requestMatchers("/login", "/test/**").permitAll()
                    // 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
                    .anyRequest()
                    // 以认证的会自动授权
                    .authenticated();
        });
        http.exceptionHandling(exc -> {
                    // 用户未认证
                    exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
                    // 请求被拒绝
                    exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
                }
        );

        // 开启跨域请求
        http.cors(withDefaults());
        return http.build();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username:" + username);
        // 用户名称不唯一,这里的账号名account,不是用户名
        // 这里user实现了Security的UserDetails对象
        UsersDO user = usersMapper.selectByAccount(username);
        if (user == null || StringUtils.isEmpty(user.getPassword())) {
            throw new UsernameNotFoundException(username + ":用户不存在");
        }
        // 获取用户角色列表
        List<RoleDO> list = usersMapper.selectByAccountRole(username);
        user.setList(list);
        return user;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

获取用户信息

@Mapper
public interface UsersMapper extends BaseMapper<Users> {

    /**
     * 分页查询
     */
    List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);

    @Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user  where ACCOUNT =#{account}")
    UsersDO selectByAccount(@Param("account") String account);

    @Select("select t1.ACCOUNT ,t2.role_name from  tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account  \n" +
            "left  join tm_role t3  on t2.role_name =t3.role_name  where  t1.ACCOUNT =#{account}")
    List<RoleDO> selectByAccountRole(@Param("account") String username);
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

实体类:

@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {

    /**
     * 账号
     */
    private String id;

    /**
     * 账号
     */
    private String account;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;


    /**
     * 逻辑删除
     */
    @TableLogic
    @TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
    private String deleted;

    /**
     * 用户角色列表
     */
    private List<RoleDO> list;

    /**
     * 权限列表
     */
    private Set<GrantedAuthority> authorities =new HashSet<>();

    @Override
    public boolean isEnabled() {
        // 通过逻辑删除字段判断是否启用
        return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
    }


    @Override
    public boolean isAccountNonExpired() {
        //用户是否未过期
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 用户凭证是否未过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // 用户是否没有被锁定
        return true;
    }

    /**
     * 获取权限列表,暂时以角色名代表权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 权限列表
        for (RoleDO roleDO : list){
            authorities.add(()->roleDO.getRoleName());
        }
        return authorities;
    }

    /**
     * 设置角色 , 这里参考了 Security 的源码
     * @param roles
     */
    public void roles(String... roles) {
        for (String role : roles) {
            // 这里要加上前缀 “ROLE_”
            Assert.isTrue(!role.startsWith("ROLE_"),
                    () -> role + " cannot start with ROLE_ (it is automatically added)");
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
        }

    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

用户登录后,如果没有对应权限,默认会抛出403异常

请添加图片描述

可以自定义请求被拒绝的返回结果:

@Configuration
@Slf4j
public class MyAuthenticationSuccessHandler implements  AccessDeniedHandler {
        
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.info("请求被拒绝-------------->");
        response.setContentType("application/json;charset=UTF-8");
        Map<String, Object> res = new HashMap<>();
        res.put("code", "-1");
        res.put("message", "请求被拒绝");
        Map<String, Object> data = new HashMap<>();
        res.put("data", data);
        response.getWriter().println(JSONUtil.toJsonStr(res));

    }
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

RBAC模型

权限模型

  • RBAC(Role Based Access Controll):基于角色的权限控制,简单来说就是:用户-角色-权限-资源,涉及的表有:
    • 用户(t_user)
    • 用户_角色(t_user_role)【N对N关系需要中间表】
    • 角色(t_role)
    • 角色_权限(t_role_perm)
    • 权限(t_permission)
  • ACL(Access Controll List):基于用户的权限控制,涉及的表有:
    • 用户(t_user)
    • 用户_权限(t_user_perm)
    • 权限(t_permission)

通过 RBAC 获取到用户的具体权限后,再通过 Security 的 用户-权限-资源 来进行权限控制

// 针对某个资源,对某个权限来放行
.requestMatchers("/users/get").hasAuthority("function_id")
  • 1
  • 2

基于方法的授权

使用注解,针对某个方法开启授权保护

开启配置:

@Configuration
// 开启spring Security的自定义配置,在springBoot项目中可以省略此注解,因为Security-stater默认开启了自定义配置
@EnableWebSecurity
// 开启基于方法的授权
@EnableMethodSecurity
public class SecurityConfig {

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

给方法增加权限保护

    @PreAuthorize("hasRole('admin')")
    @GetMapping("/apply")
    public BaseResultModel apply(){

        return BaseResultModel.success("授权返回成功");
    }


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注解及含义

  • @PostAuthorize 在目标方法执行之后进行权限校验
  • @PostFilter 在目标方法执行之后对返回结果进行过滤
  • @PreAuthorize 在目标方法执行之前进行权限校验
  • @PreFilter 在目标方法执行之前对方法参数进行过滤
  • @Secured 访问目标方法必须具备对应的角色
  • @DenyAll 拒绝所有访问
  • @PermitAll 允许所有访问
  • @RolesAll 访问目标方法必须具备对应的角色

授权的原理分析

  • ConfigAttribute在springsecurity中,用户请求一个资源(通常是一个接口或者Java方法)需要的角色会被封装成一个ConfigAttribute对象,在ConfigAttribute中只有一个getAttribute方法,该方法赶回一个String字符串(角色名称)。一般的角色名称都带有一个ROLE_前缀,投票器AccessDecisionVoter所做的事情,其实就是比较用户所具有的角色和请求某个资源所需要的ConfigAttribute之间的关系。
  • AccessDecisionVoter和AccessDecisionManager都有众多实现类。在AccessDecisionManager中会挨个遍历AccessDecisionVoter,进而决定是否允许用户方法,因而AccessDecisionVoter和AccessDecisionManager俩者关系类似于AuthenticationProvicder和ProviderManager的关系。
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号