程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

在SpringSecurity中实现RBAC的权限认证

balukai 2024-12-30 01:57:42 文章精选 24 ℃

上一篇文章中使用SpringSecurity实现基于MySQL的数据认证,我介绍了如何在SpringSecurity中使用数据库中的用户数据进行认证,这篇文章主要讲下怎么实现RBAC的权限认证,这就可以基本满足大多数情况下对权限认证的需求。

上篇文章其实已经将设置的用户权限取到了,这一步我们只需要自定义权限认证的逻辑,并交给SpringSecurity使用。

自定义权限认证的逻辑

@Component("authUserAccessConfig")
public class AuthUserAccessConfig {
    private static final Logger logger = LoggerFactory.getLogger(AuthUserAccessConfig.class);
    /**
     * 判断当前访问的uri是否有权限
     * @param request
     * @param authentication
     * @return
     */
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object principal = authentication.getPrincipal();

        String uri = request.getRequestURI();
        // ajax请求都放行
//        if (isAjax(request)) {
//            return true;
//        }
        // 认证阶段放行
        if (isAuth(uri)) {
            return true;
        }
        // 认证成功的情况 - 判断权限相关
        if (principal instanceof UserDetailsPojo) {
            UserDetailsPojo authUser = (UserDetailsPojo) principal;
            // 判断权限
            String chkAuthUri = UriUtil.getChkAuthUri(uri);
            // 获取登录用户的权限
            Set<String> authMenuSet = authUser.getAuthMenuSet();
            return authMenuSet.contains(chkAuthUri);
        }
        return false;
    }

    /**
     * 判断请求是否是ajax
     * @param request
     * @return
     */
    public boolean isAjax(HttpServletRequest request) {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }

    public boolean isAuth(String uri) {
        if (uri.equals("/login")) {
            return true;
        }
        if (uri.equals("/error")) {
            return true;
        }
        return false;
    }
}

这里面会用到上篇文章定义的UserDetails的额外属性 Set<String> authMenuSet

// getter、setter就省略了
public class UserDetailsPojo implements UserDetails {
    private static final long serialVersionUID = -987887L;
    private String password;
    private String username;
    private Collection<? extends GrantedAuthority> authorities;
		// 下面两个属性都是自定义添加的了 Set<String>是用来实现RBAC的权限认证
		private Set<String> authMenuSet = new HashSet<>();
    private long id;
}

通过authMenuSet保存的uri权限,进行判断当前用户是否有访问权限

配置自定义权限认证逻辑

@EnableWebSecurity
public class AuthConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();
        http.authorizeRequests()
                // 二者保其一 !!!!
                .anyRequest().access("@authUserAccessConfig.hasPermission(request, authentication)")
                // 所有请求都要是认证完成的
//                .anyRequest().authenticated()
                ;
    }
}

配置完成后所有的未被过滤的请求都会执行上面自定义的权限认证逻辑。

另外,我们也可以自定义当权限认证失败时该做什么动作。

自定义权限认证失败的动作

要实现自定义权限认证失败的逻辑,只需要实现AccessDeniedHandler接口并在SpringSecurity的配置类完成配置就可以了。

一般访问的请求会分为两种

  • 异步请求Ajax
  • 页面请求

所以当异步请求权限认证失败时,应该返回统一的接口响应格式的数据;当普通页面请求权限认证失败时,应该返回页面的403请求或其他4xx

1)创建实现AccessDeniedHandler接口的类

@Component
public class AuthAccessDeniedHandler implements AccessDeniedHandler {
    private final Logger logger = LoggerFactory.getLogger(AuthAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        logger.info("认证失败逻辑...");
        if (!httpServletResponse.isCommitted()) {
            if (isAjax(httpServletRequest)) {
                logger.error(e.getMessage());
                // 响应json格式数据
                NetUtil.returnForbidden(httpServletResponse);
            } else {
                logger.error("跳转403页面了");
                // 跳转到无权限页面
                httpServletRequest.setAttribute(WebAttributes.ACCESS_DENIED_403, e);
                httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
                RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher("/admin/auth/403");
                dispatcher.forward(httpServletRequest, httpServletResponse);
            }
        }
    }

    /**
     * 判断是否是ajax
     * @param request
     * @return
     */
    public boolean isAjax(HttpServletRequest request) {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }
}

2)配置自定义权限认证失败逻辑

@EnableWebSecurity
public class AuthConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义验证权限失败处理器
        http.exceptionHandling().accessDeniedHandler(new AuthAccessDeniedHandler());
    }
}

完整SpringSecurity配置代码

@EnableWebSecurity
public class AuthConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsConfig userDetailsConfig;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsConfig).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();
        http.authorizeRequests()
                // 设置自定义的登录页可访问权限
                .antMatchers("/sys/custom/login", "/login").permitAll()
                .antMatchers("/druid/**").permitAll()
                // 二者保其一 !!!!
                .anyRequest().access("@authUserAccessConfig.hasPermission(request, authentication)")
                // 所有请求都要是认证完成的
//                .anyRequest().authenticated()
                ;

        // 自定义验证权限失败处理器
        http.exceptionHandling().accessDeniedHandler(new AuthAccessDeniedHandler());
        // 自定义登录页
        http.formLogin().loginPage("/sys/custom/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .failureUrl("/sys/custom/login?error=true")
                .loginProcessingUrl("/login");
        http.csrf().disable();
        http.logout();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 过滤所有前端资源
        web.ignoring().antMatchers("/upfile/**", "/css/**", "/lib/**", "/js/**", "/font/**", "/images/**", "/favicon.ico");
    }
}


小白升级打怪的过程中,最难的还是发现趁手的武器装备。

我是小白,我为小白代言。

Tags:

最近发表
标签列表