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();
    }

在 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;
    }

获取用户信息

@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);
    
}

实体类:

@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));
        }

    }

}

用户登录后,如果没有对应权限,默认会抛出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();
    }

在 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;
    }

获取用户信息

@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);
    
}

实体类:

@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));
        }

    }

}

用户登录后,如果没有对应权限,默认会抛出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();
    }

在 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;
    }

获取用户信息

@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);
    
}

实体类:

@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));
        }

    }

}

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

Spring Security 授权_第1张图片

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

@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));

    }
    
}

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")

基于方法的授权

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

开启配置:

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

}

给方法增加权限保护

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

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


注解及含义

  • @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的关系。

你可能感兴趣的:(spring,spring,java,spring,boot,前端)