springbootsecurity实现权限管理详细步骤

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。

下面这个图很重要展示了安全权限流程
这个
Spring Security的核心配置类是 WebSecurityConfigurerAdapter,抽象类
这是权限管理启动的入口,这里我们自定义一个实现类去它。然后编写我们需要处理的控制逻辑。
下面是代码,里面写的注释也比较详细。在里面还依赖了几个自定义的类,都是必须配置的。分别是
HrService,
MyFilterInvocationSecurityMetadataSource,
MyAccessDecisionManager,

MyAccessDeniedHandler,
MyAuthenticationFailureHandler,
MyAuthenticationSuccessHandler,
MyLogoutSuccessHandler
后面会分别解析它们

/**

  • @Author: Galen

  • @Date: 2019/3/27-14:43

  • @Description: spring-security权限管理的核心配置
    **/
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true) //全局
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private HrService hrService; //实现了UserDetailsService接口
    @Autowired
    private MyFilterInvocationSecurityMetadataSource filterMetadataSource; //权限过滤器(当前url所需要的访问权限)
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;//权限决策器
    @Autowired
    private MyAccessDeniedHandler deniedHandler;//自定义错误(403)返回数据

    /**

    • @Author: Galen
    • @Description: 配置userDetails的数据源,密码加密格式
    • @Date: 2019/3/28-9:24
    • @Param: [auth]
    • @return: void
      **/
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(hrService)
      .passwordEncoder(new BCryptPasswordEncoder());
      }

    /**

    • @Author: Galen
    • @Description: 配置放行的资源
    • @Date: 2019/3/28-9:23
    • @Param: [web]
    • @return: void
      /
      @Override
      public void configure(WebSecurity web) throws Exception {
      web.ignoring().antMatchers("/index.html", "/static/
      ", “/login_p”, “/favicon.ico”)
      // 给 swagger 放行;不需要权限能访问的资源
      .antMatchers("/swagger-ui.html", “/swagger-resources/", "/images/”, “/webjars/**”, “/v2/api-docs”, “/configuration/ui”, “/configuration/security”);
      }

    /**

    • @Author: Galen
    • @Description: HttpSecurity包含了原数据(主要是url)
    • 通过withObjectPostProcessor将MyFilterInvocationSecurityMetadataSource和MyAccessDecisionManager注入进来
    • 此url先被MyFilterInvocationSecurityMetadataSource处理,然后 丢给 MyAccessDecisionManager处理
    • 如果不匹配,返回 MyAccessDeniedHandler
    • @Date: 2019/3/27-17:41
    • @Param: [http]
    • @return: void
      **/
      @Override
      protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
      .withObjectPostProcessor(new ObjectPostProcessor() {
      @Override
      public O postProcess(O o) {
      o.setSecurityMetadataSource(filterMetadataSource);
      o.setAccessDecisionManager(myAccessDecisionManager);
      return o;
      }
      })
      .and()
      .formLogin().loginPage("/login_p").loginProcessingUrl("/login")
      .usernameParameter(“username”).passwordParameter(“password”)
      .failureHandler(new MyAuthenticationFailureHandler())
      .successHandler(new MyAuthenticationSuccessHandler())
      .permitAll()
      .and()
      .logout()
      .logoutUrl("/logout")
      .logoutSuccessHandler(new MyLogoutSuccessHandler())
      .permitAll()
      .and().csrf().disable()
      .exceptionHandling().accessDeniedHandler(deniedHandler);
      }
      }
      这里附上一个权限方法图:

HttpSecurity 常用方法及说明:

方法 说明
openidLogin() 用于基于 OpenId 的验证
headers() 将安全标头添加到响应
cors() 配置跨域资源共享( CORS )
sessionManagement() 允许配置会话管理
portMapper() 允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee() 配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509() 配置基于x509的认证
rememberMe 允许配置“记住我”的验证
authorizeRequests() 允许基于使用HttpServletRequest限制访问
requestCache() 允许配置请求缓存
exceptionHandling() 允许配置错误处理
securityContext() 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi() 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf() 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout() 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous() 允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin() 指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login() 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel() 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic() 配置 Http Basic 验证
addFilterAt() 在指定的Filter类的位置添加过滤器

在这里插入图片描述

1.HrService
HrService实现了UserDetailsService接口中的loadUserByUsername方法,方法执行成功后返回UserDetails对象,为构建Authentication对象提供必须的信息。UserDetails中包含了用户名,密码,角色等信息

@Service
@Transactional
public class HrService implements UserDetailsService {

@Autowired
private HrMapper hrMapper;

/**
 * @Author: Galen
 * @Description: 实现了UserDetailsService接口中的loadUserByUsername方法
 * 执行登录,构建Authentication对象必须的信息,
 * 如果用户不存在,则抛出UsernameNotFoundException异常
 * @Date: 2019/3/27-16:04
 * @Param: [s]
 * @return: org.springframework.security.core.userdetails.UserDetails
 **/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    /**
     * @Author: Galen
     * @Description: 查询数据库,获取登录的用户信息
     **/
    Hr hr = hrMapper.loadUserByUsername(s);
    if (hr == null) {
        throw new UsernameNotFoundException("用户名不对");
    }
    return hr;
}

}

2.MyFilterInvocationSecurityMetadataSource
自定义权限过滤器,继承了 SecurityMetadataSource(权限资源接口),过滤所有请求,核查这个请求需要的访问权限;主要实现Collection getAttributes(Object o)方法,此方法中可编写用户逻辑,根据用户预先设定的用户权限列表,返回访问此url需要的权限列表。

package com.galen.security.interceptor;

import com.galen.security.pojo.Menu;
import com.galen.security.model.Role;
import com.galen.security.service.MenuService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**

  • @Author: Galen

  • @Date: 2019/3/27-16:54

  • @Description: FilterInvocationSecurityMetadataSource(权限资源过滤器接口)继承了 SecurityMetadataSource(权限资源接口)

  • Spring Security是通过SecurityMetadataSource来加载访问时所需要的具体权限;Metadata是元数据的意思。

  • 自定义权限资源过滤器,实现动态的权限验证

  • 它的主要责任就是当访问一个url时,返回这个url所需要的访问权限
    **/
    @Component
    public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    private MenuService menuService;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    private static final Logger log = LoggerFactory.getLogger(MyFilterInvocationSecurityMetadataSource.class);

    /**

    • @Author: Galen
    • @Description: 返回本次访问需要的权限,可以有多个权限
    • @Date: 2019/3/27-17:11
    • @Param: [o]
    • @return: java.util.Collection<org.springframework.security.access.ConfigAttribute>
      /
      @Override
      public Collection getAttributes(Object o) {
      String requestUrl = ((FilterInvocation) o).getRequestUrl();
      //去数据库查询资源
      List allMenu = menuService.getAllMenu();
      for (Menu menu : allMenu) {
      if (antPathMatcher.match(menu.getUrl(), requestUrl)
      && menu.getRoles().size() > 0) {
      List roles = menu.getRoles();
      int size = roles.size();
      String[] values = new String[size];
      for (int i = 0; i < size; i++) {
      values[i] = roles.get(i).getName();
      }
      log.info(“当前访问路径是{},这个url所需要的访问权限是{}”, requestUrl, values);
      return SecurityConfig.createList(values);
      }
      }
      /
      • @Author: Galen
      • @Description: 如果本方法返回null的话,意味着当前这个请求不需要任何角色就能访问
      • 此处做逻辑控制,如果没有匹配上的,返回一个默认具体权限,防止漏缺资源配置
        **/
        log.info(“当前访问路径是{},这个url所需要的访问权限是{}”, requestUrl, “ROLE_LOGIN”);
        return SecurityConfig.createList(“ROLE_LOGIN”);
        }

    /**

    • @Author: Galen
    • @Description: 此处方法如果做了实现,返回了定义的权限资源列表,
    • Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
    • 如果不需要校验,这里实现方法,方法体直接返回null即可。
    • @Date: 2019/3/27-17:12
    • @Param: []
    • @return: java.util.Collection<org.springframework.security.access.ConfigAttribute>
      **/
      @Override
      public Collection getAllConfigAttributes() {
      return null;
      }

    /**

    • @Author: Galen
    • @Description: 方法返回类对象是否支持校验,
    • web项目一般使用FilterInvocation来判断,或者直接返回true
    • @Date: 2019/3/27-17:14
    • @Param: [aClass]
    • @return: boolean
      **/
      @Override
      public boolean supports(Class<?> aClass) {
      return FilterInvocation.class.isAssignableFrom(aClass);
      }
      }

3.MyAccessDecisionManager
自定义权限决策管理器,需要实现AccessDecisionManager 的 void decide(Authentication auth, Object object, Collection cas) 方法,在上面的过滤器中,我们已经得到了访问此url需要的权限;那么,decide方法,先查询此用户当前拥有的权限,然后与上面过滤器核查出来的权限列表作对比,以此判断此用户是否具有这个访问权限,决定去留!所以顾名思义为权限决策器。

package com.galen.security.interceptor;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Iterator;

/**

  • @Author: Galen

  • @Date: 2019/3/27-16:59

  • @Description: Decision决定的意思。

  • 有了权限资源(MyFilterInvocationSecurityMetadataSource),知道了当前访问的url需要的具体权限,接下来就是决策当前的访问是否能通过权限验证了

  • MyAccessDecisionManager 自定义权限决策管理器
    /
    @Component
    public class MyAccessDecisionManager implements AccessDecisionManager {
    /

    • @Author: Galen
    • @Description: 取当前用户的权限与这次请求的这个url需要的权限作对比,决定是否放行
    • auth 包含了当前的用户信息,包括拥有的权限,即之前UserDetailsService登录时候存储的用户对象
    • object 就是FilterInvocation对象,可以得到request等web资源。
    • configAttributes 是本次访问需要的权限。即上一步的 MyFilterInvocationSecurityMetadataSource 中查询核对得到的权限列表
    • @Date: 2019/3/27-17:18
    • @Param: [auth, object, cas]
    • @return: void
      **/
      @Override
      public void decide(Authentication auth, Object object, Collection cas) {
      Iterator iterator = cas.iterator();
      while (iterator.hasNext()) {
      if (auth == null) {
      throw new AccessDeniedException(“当前访问没有权限”);
      }
      ConfigAttribute ca = iterator.next();
      //当前请求需要的权限
      String needRole = ca.getAttribute();
      if (“ROLE_LOGIN”.equals(needRole)) {
      if (auth instanceof AnonymousAuthenticationToken) {
      throw new BadCredentialsException(“未登录”);
      } else
      return;
      }
      //当前用户所具有的权限
      Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
      for (GrantedAuthority authority : authorities) {
      if (authority.getAuthority().equals(needRole)) {
      return;
      }
      }
      }
      throw new AccessDeniedException(“权限不足!”);
      }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
    return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
    return true;
    }
    }

4.MyAccessDeniedHandler;MyAuthenticationFailureHandler;MyAuthenticationSuccessHandler;MyLogoutSuccessHandler
之所以一起描述这几个类,因为这几个都是处理器。根据类名也很容易看得出,分别是拒签(403响应)处理器,认证失败处理器,认证成功处理器,注销成功处理器。通过上面的用户认证接口(UserDetails),过滤器,决策器;我们已经成功处理了权限的认证,决定当前用户的去留,然后不同的逻辑启用不同的处理器。

/**
 * @Author: Galen
 * @Date: 2019/3/27-17:36
 * @Description: Denied是拒签的意思
 * 此处我们可以自定义403响应的内容,让他返回我们的错误逻辑提示
 **/
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp,
                       AccessDeniedException e) throws IOException {
        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        resp.setContentType("application/json;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        RespBean error = RespBean.error("权限不足,请联系管理员!");
        out.write(new ObjectMapper().writeValueAsString(error));
        out.flush();
        out.close();
    }
}

/*****************************************************************************************/

/**

  • @Author: Galen

  • @Date: 2019/3/28-9:17

  • @Description: 认证失败的处理
    **/
    public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    response.setContentType(“application/json;charset=utf-8”);
    RespBean respBean;
    if (exception instanceof BadCredentialsException ||
    exception instanceof UsernameNotFoundException) {
    respBean = RespBean.error(“账户名或者密码输入错误!”);
    } else if (exception instanceof LockedException) {
    respBean = RespBean.error(“账户被锁定,请联系管理员!”);
    } else if (exception instanceof CredentialsExpiredException) {
    respBean = RespBean.error(“密码过期,请联系管理员!”);
    } else if (exception instanceof AccountExpiredException) {
    respBean = RespBean.error(“账户过期,请联系管理员!”);
    } else if (exception instanceof DisabledException) {
    respBean = RespBean.error(“账户被禁用,请联系管理员!”);
    } else {
    respBean = RespBean.error(“登录失败!”);
    }
    response.setStatus(401);
    new GalenWebMvcWrite().writeToWeb(response, respBean);
    }
    }
    /*****************************************************************************************/

/**

  • @Author: Galen

  • @Date: 2019/3/28-9:17

  • @Description: 认证成功的处理
    **/
    public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

     @Override
     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
         response.setContentType("application/json;charset=utf-8");
         RespBean respBean = RespBean.ok("登录成功!", HrUtils.getCurrentHr());
         new GalenWebMvcWrite().writeToWeb(response, respBean);
         System.out.println("登录成功!");
     }
    

    }
    /*****************************************************************************************/

/**

  • @Author: Galen

  • @Date: 2019/3/28-9:21

  • @Description: 注销登录处理
    **/
    public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    response.setContentType(“application/json;charset=utf-8”);
    RespBean respBean = RespBean.ok(“注销成功!”);
    new GalenWebMvcWrite().writeToWeb(response, respBean);
    System.out.println(“注销成功!”);
    }
    }
    在这里插入图片描述
    出处:https://blog.csdn.net/zhaoxichen_10/article/details/88713799

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值