Spring Security权限框架入门详解与实际使用

在系统开发中,安全是十分重要必不可少的一环,如果没有安全框架,用户信息随时都可能被泄露,篡改等风险,系统将处于十分不可信的状态。为此我们将介绍权限框架之spring security的原理与使用。

Spring Security vs Apache Shiro

Apache Shiro 诞生于 2004 年,当时名为 JSecurity,并于 2008 年被 Apache 基金会接受,Spring Security 于 2003 年作为 Acegi 开始,并在 2008 年首次公开发布时并入 Spring Framework。两种技术都提供身份验证和授权支持以及加密和会话管理解决方案。但是在如今的微服务开发中Shiro 几乎处在被弃用的边缘,那为什么要选择Spring Security呢?

  1. Spring Security是spring Framework下的安全解决方案,社区友好且强大。
  1. 不仅提供权限控制等功能还提供一流的保护,以抵御 CSRF 和会话固定等攻击,功能强大。
  1. Springboot/Cloud微服务项目中配置简单,十分方便。
  1. 业务代码与权限分离,通过切面的形式提供权限控制,不侵入主干代码。

Spring Security简介

主要功能是提供认证和授权服务。

认证

验证用户身份,如账号密码验证,短信验证等。

授权

确定是否有此资源的访问权限。

认证过程

基于过滤器链完成登录认证与授权的功能,如果认证授权过程中发现用户不合法或者无权限访问资源,则会交由失败过滤器处理。

注:这里 Security 提供两种过滤器类:UsernamePasswordAuthenticationFilter 表示表单登陆过滤器 BasicAuthenticationFilter 表示 httpBasic 方式登陆过滤器

如上图,一个请求想要访问到 API 就会以从左到右的形式经过蓝线框框里面的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分负责异常处理,橙色部分则是负责授权。

上述的两个过滤器是 Spring Security 对 form 表单认证和 Basic 认证内置的两个 Filter,有两个自带的叫 formLogin 和 httpBasic 的配置项,而 JWT 认证方式用不上。

java 复制代码
@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().and().formLogin() // formLogin 对应着 form 表单认证方式,即 UsernamePasswordAuthenticationFilter        .and().httpBasic(); // httpBasic 对应着 Basic 认证方式,即 BasicAuthenticationFilter}

换言之,配置了这两种认证方式,过滤器链中才会加入它们,否则它们是不会被加到过滤器链中去的。

因为 Spring Security 自带的过滤器中是没有针对 JWT 的认证方式,所以需要自己写一个 JWT 的认证过滤器,然后放在绿色的位置进行认证工作。下面将介绍JWT认证来了解Security认证过程。

认证组件
  • SecurityContext:上下文对象,Authentication 对象会放在里面。
  • SecurityContextHolder:用于拿到上下文对象的静态工具类。
  • Authentication:认证接口,定义了认证对象的数据形式。
  • AuthenticationManager:用于校验 Authentication,返回一个认证完成后的 Authentication 对象。
SecurityContext

上下文对象,认证后的数据就放在这里面,接口定义如下:

java 复制代码
public interface SecurityContext extends Serializable {// 获取Authentication对象 Authentication getAuthentication();// 放入Authentication对象 void setAuthentication(Authentication authentication);
}

这个接口里面只有两个方法,其主要作用就是 get or set Authentication

SecurityContextHolder

用于拿到上下文对象的静态工具类。

SecurityContextHolder 用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限... 这些都被保存在 SecurityContextHolder 中。SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息。

java 复制代码
public class SecurityContextHolder {
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
}

可以说是 SecurityContext 的工具类,用于 get or set or clear SecurityContext,默认会把数据都存储到当前线程中。

从这个 SecurityContextHolder 取得 UserDetail 的实例:

plaintext 复制代码
public static String getLoginAccount() {// getPrincipal 返回值需要强转为 UserDetail 具体原因看下面的 Authentication那节    return ((UserDetail) SecurityContextHolder.getContext().getAuthentication() // 返回:Authentication    .getPrincipal()) // 这里就是 Authentication 内部保存的 UserDetail 对象    .getUsername();
}
Authentication

认证接口,定义了认证对象的数据形式。

java 复制代码
public interface Authentication extends Principal, Serializable {Collection<? extends GrantedAuthority> getAuthorities();Object getCredentials();Object getDetails();// AuthenticationManager 实现通常会返回一个包含更丰富信息的 Authentication 作为供应用程序使用的主体。 // 许多身份验证提供程序将创建一个 UserDetails 对象作为主体// 所以如果 AuthenticationManager 使用的是 ProviderManager 则这里返回值需要强转为 UserDetails Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

这几个方法效果如下:

  • getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
  • getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
  • getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。
  • getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails。
  • isAuthenticated: 获取当前 Authentication 是否已认证。
  • setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。

Authentication 只是定义了一种在 SpringSecurity 进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。

AuthenticationManager
java 复制代码
public interface AuthenticationManager {// 认证方法 Authentication authenticate(Authentication authentication)throws AuthenticationException;
}

AuthenticationManager (接口)是认证相关的核心接口,它定义了一个认证方法,它将一个未认证的 Authentication 传入,返回一个已认证的 Authentication。它的默认实现类是 ProviderManager

注:AuthenticationManager 有多个认证类 AuthenticationManager,ProviderManager ,AuthenticationProvider ...

所以整体的认证流程为:

  • 接收用户请求后,由UsernamePasswordAuthenticationFilter(表单形式) 、BasicAuthenticationFilter(BasicHttp形式)、JWT形式自定义过滤器处理封装成Authentication(通常是Authentication的子类UsernamePasswordAuthenticationToken)
  • 这个 Authentication 经过 AuthenticationManager 的认证(身份管理器负责验证)认证成功后,AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication 实例。
  • SecurityContextHolder 安全上下文容器将上面填充了信息的 Authentication,通过 SecurityContextHolder.getContext().setAuthentication(...) 方法,设置到 SecurityContext 其中。

看到这里大家肯定有个困惑AuthenticationManager是如何进行认证的呢,稍等片刻,小二这就帮你解答,上代码。

AuthenticationManager认证
ProviderManager

是AuthenticationManager的默认实现类,对认证方法进行了具体的实现,后续的处理(获取用户信息,密码校验)等将围绕着次方法进行。

java 复制代码
public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
   Class<? extends Authentication> toTest = authentication.getClass();
   AuthenticationException lastException = null;
   Authentication result = null;
   boolean debug = logger.isDebugEnabled();

   for (AuthenticationProvider provider : getProviders()) {
      if (!provider.supports(toTest)) {
         continue;
      }

      if (debug) {
         logger.debug("Authentication attempt using "
               + provider.getClass().getName());
      }

      try {
        result = provider.authenticate(authentication);

         if (result != null) {
            copyDetails(authentication, result);
            break;
         }
      }
      catch (AccountStatusException e) {
         prepareException(e, authentication);
         // SEC-546: Avoid polling additional providers if auth failure is due to
         // invalid account status
         throw e;
      }
      catch (InternalAuthenticationServiceException e) {
         prepareException(e, authentication);
         throw e;
      }
      catch (AuthenticationException e) {
         lastException = e;
      }
   }

   if (result == null && parent != null) {
      // Allow the parent to try.
      try {
         result = parent.authenticate(authentication);
      }
      catch (ProviderNotFoundException e) {
         // ignore as we will throw below if no other exception occurred prior to
         // calling parent and the parent
         // may throw ProviderNotFound even though a provider in the child already
         // handled the request
      }
      catch (AuthenticationException e) {
         lastException = e;
      }
   }

   if (result != null) {
      if (eraseCredentialsAfterAuthentication
            && (result instanceof CredentialsContainer)) {
         // Authentication is complete. Remove credentials and other secret data
         // from authentication
         ((CredentialsContainer) result).eraseCredentials();
      }

      eventPublisher.publishAuthenticationSuccess(result);
      return result;
   }

   // Parent was null, or didn't authenticate (or throw an exception).

   if (lastException == null) {
      lastException = new ProviderNotFoundException(messages.getMessage(
            "ProviderManager.providerNotFound",
            new Object[] { toTest.getName() },
            "No AuthenticationProvider found for {0}"));
   }

   prepareException(lastException, authentication);

   throw lastException;
}
AuthenticationProvider

接口认证(AbstractUserDetailsAuthenticationProvider默认实现)提供了具体的认证逻辑(获取用户信息,密码加密匹配)。

java 复制代码
public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
   Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
         messages.getMessage(
               "AbstractUserDetailsAuthenticationProvider.onlySupports",
               "Only UsernamePasswordAuthenticationToken is supported"));

   // Determine username
   String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
         : authentication.getName();

   boolean cacheWasUsed = true;
   UserDetails user = this.userCache.getUserFromCache(username);

   if (user == null) {
      cacheWasUsed = false;

      try {
         user = retrieveUser(username,               (UsernamePasswordAuthenticationToken) authentication);
      }
      catch (UsernameNotFoundException notFound) {
         logger.debug("User '" + username + "' not found");

         if (hideUserNotFoundExceptions) {
            throw new BadCredentialsException(messages.getMessage(
                  "AbstractUserDetailsAuthenticationProvider.badCredentials",
                  "Bad credentials"));
         }
         else {
            throw notFound;
         }
      }

      Assert.notNull(user,
            "retrieveUser returned null - a violation of the interface contract");
   }

   try {
      // 校验用户状态(停用、被锁、过期等)
      preAuthenticationChecks.check(user);
      
      // 校验密码是否正确
      additionalAuthenticationChecks(user,            (UsernamePasswordAuthenticationToken) authentication);
   }
   catch (AuthenticationException exception) {
      if (cacheWasUsed) {
         // There was a problem, so try again after checking
         // we're using latest data (i.e. not from the cache)
         cacheWasUsed = false;
         user = retrieveUser(username,
               (UsernamePasswordAuthenticationToken) authentication);
         preAuthenticationChecks.check(user);
         additionalAuthenticationChecks(user,
               (UsernamePasswordAuthenticationToken) authentication);
      }
      else {
         throw exception;
      }
   }

   // 校验用户状态(停用、被锁、过期等)
   postAuthenticationChecks.check(user);

   if (!cacheWasUsed) {
      this.userCache.putUserInCache(user);
   }

   Object principalToReturn = user;

   if (forcePrincipalAsString) {
      principalToReturn = user.getUsername();
   }

   return createSuccessAuthentication(principalToReturn, authentication, user);
}
DaoAuthenticationProvider

定义了获取用户信息,密码匹配等方法。

java 复制代码
protected final UserDetails retrieveUser(String username,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
   prepareTimingAttackProtection();
   try {
       // loadUserByUsername是一个抽象方法需要自己实现
      UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
      if (loadedUser == null) {
         throw new InternalAuthenticationServiceException(
               "UserDetailsService returned null, which is an interface contract violation");
      }
      return loadedUser;
   }
   catch (UsernameNotFoundException ex) {
      mitigateAgainstTimingAttack(authentication);
      throw ex;
   }
   catch (InternalAuthenticationServiceException ex) {
      throw ex;
   }
   catch (Exception ex) {
      throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
   }
}
loadUserByUsername

是一个抽象方法需要自己实现

java 复制代码
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private static final Logger log= LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    private final UserV2Service userV2Service;

    private final PermissionV2Service permissionService;

    private final GameGroupService gameGroupService;

    public UserDetailsServiceImpl(UserV2Service userV2Service, PermissionV2Service permissionService, GameGroupService gameGroupService) {
        this.userV2Service = userV2Service;
        this.permissionService = permissionService;
        this.gameGroupService = gameGroupService;
    }

    @Override
    public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
        UserV2 user = userV2Service.getUserByAccount(account);
        if (user == null) {
            log.info("登录账号:{} 用户名或密码错误.", account);
            throw new BussinessException("登录账号:" + account + " 用户名或密码错误");
        } else if (UserV2.STATUS_DISABLE == user.getStatus()) {
            log.info("登录账号:{} 已被停用.", account);
            throw new BussinessException("对不起,您的账号:" + account + " 已停用,请联系管理员");
        }

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(UserV2 user) {
        List<String> gameCodes = new Gson().fromJson(user.getGameCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
        }.getType());
        List<String> mediaCodes = new Gson().fromJson(user.getMediaCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
        }.getType());
        return new LoginUser(user.getId(), user, permissionService.getPermissionV2VoList(user.getId()), gameCodes, mediaCodes);
    }
}
additionalAuthenticationChecks

校验密码是否正确DaoAuthenticationProvider(AbstractUserDetailsAuthenticationProvider的实现类)提供了PasswordEncoder加密匹配密码判断。

java 复制代码
protected void additionalAuthenticationChecks(UserDetails userDetails,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
   if (authentication.getCredentials() == null) {
      logger.debug("Authentication failed: no credentials provided");

      throw new BadCredentialsException(messages.getMessage(
            "AbstractUserDetailsAuthenticationProvider.badCredentials",
            "Bad credentials"));
   }

   String presentedPassword = authentication.getCredentials().toString();

   if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
      logger.debug("Authentication failed: password does not match stored value");

      throw new BadCredentialsException(messages.getMessage(
            "AbstractUserDetailsAuthenticationProvider.badCredentials",
            "Bad credentials"));
   }
}

至此AuthenticationManager认证完成,返回填充了用户数据的认证信息,并将此数据保存到上下文中。AnonymousAuthenticationFilter将认证通过的Authentication 设置到上下文中。

java 复制代码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {

   if (SecurityContextHolder.getContext().getAuthentication() == null) {
      SecurityContextHolder.getContext().setAuthentication(
            createAuthentication((HttpServletRequest) req));

      if (logger.isDebugEnabled()) {
         logger.debug("Populated SecurityContextHolder with anonymous token: '"
               + SecurityContextHolder.getContext().getAuthentication() + "'");
      }
   }
   else {
      if (logger.isDebugEnabled()) {
         logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
               + SecurityContextHolder.getContext().getAuthentication() + "'");
      }
   }

   chain.doFilter(req, res);
}

整合实战

引入依赖

java 复制代码
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

定义JWT过滤器

java 复制代码
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private final TokenService tokenService;

    public JwtAuthenticationTokenFilter(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (loginUser != null && SecurityUtils.getAuthentication() == null) {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

定义用户自定义逻辑类UserDetailsService

java 复制代码
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private static final Logger log= LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    private final UserV2Service userV2Service;

    private final PermissionV2Service permissionService;

    private final GameGroupService gameGroupService;

    public UserDetailsServiceImpl(UserV2Service userV2Service, PermissionV2Service permissionService, GameGroupService gameGroupService) {
        this.userV2Service = userV2Service;
        this.permissionService = permissionService;
        this.gameGroupService = gameGroupService;
    }

    @Override
    public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
        UserV2 user = userV2Service.getUserByAccount(account);
        if (user == null) {
            log.info("登录账号:{} 用户名或密码错误.", account);
            throw new BussinessException("登录账号:" + account + " 用户名或密码错误");
        } else if (UserV2.STATUS_DISABLE == user.getStatus()) {
            log.info("登录账号:{} 已被停用.", account);
            throw new BussinessException("对不起,您的账号:" + account + " 已停用,请联系管理员");
        }

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(UserV2 user) {
        List<String> gameCodes = new Gson().fromJson(user.getGameCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
        }.getType());
        List<String> mediaCodes = new Gson().fromJson(user.getMediaCodes().toJSONString(), new TypeToken<ArrayList<String>>() {
        }.getType());
        return new LoginUser(user.getId(), user, permissionService.getPermissionV2VoList(user.getId()), gameCodes, mediaCodes);
    }
}

定义认证失败处理类AuthenticationEntryPointImpl

java 复制代码
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
    private static final long serialVersionUID= -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException {
        String msg = String.format("请求访问:%s,认证失败,无法访问系统资源", request.getRequestURI());
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(new Gson().toJson(BaseResponse.fail(HttpStatusConstants.UNAUTHORIZED, msg)));
    }
}

定义退出登录处理类LogoutSuccessHandlerImpl

java 复制代码
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
    private final TokenService tokenService;

    public LogoutSuccessHandlerImpl(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    /**     * 退出处理     *     * @return*/@Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (loginUser != null) {
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getUuid());
        }
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");

        response.getWriter().print(new Gson().toJson(BaseResponse.successfulWithMessage("退出成功")));
    }
}

配置SecurityConfig

java 复制代码
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**     * 自定义用户认证逻辑     */private final UserDetailsService userDetailsService;

    /**     * 认证失败处理类     */private final AuthenticationEntryPointImpl unauthorizedHandler;

    /**     * 退出处理类     */private final LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**     * token认证过滤器     */private final JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**     * 跨域过滤器     */private final CorsFilter corsFilter;

    private final UserV2Service userV2Service;

    private final RedisTemplate redisTemplate;

    public SecurityConfig(UserDetailsService userDetailsService, AuthenticationEntryPointImpl unauthorizedHandler,
                          LogoutSuccessHandlerImpl logoutSuccessHandler, JwtAuthenticationTokenFilter authenticationTokenFilter,
                          CorsFilter corsFilter, UserV2Service userV2Service, RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.unauthorizedHandler = unauthorizedHandler;
        this.logoutSuccessHandler = logoutSuccessHandler;
        this.authenticationTokenFilter = authenticationTokenFilter;
        this.corsFilter = corsFilter;
        this.userV2Service = userV2Service;
        this.redisTemplate = redisTemplate;
    }

    /**     * 解决 无法直接注入 AuthenticationManager     *     * @return* @throwsException     */@Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**     * anyRequest          |   匹配所有请求路径     * access              |   SpringEl表达式结果为true时可以访问     * anonymous           |   匿名可以访问     * denyAll             |   用户不能访问     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问     * permitAll           |   用户可以任意访问     * rememberMe          |   允许通过remember-me登录的用户访问     * authenticated       |   用户登录后可访问     */@Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                .antMatchers("/login_v2/login").anonymous()
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
                .antMatchers("/test/**").permitAll()
                .antMatchers("/om/**").permitAll()
                .antMatchers("/ver_record/**").permitAll()
                .antMatchers("/ad/db_query").permitAll()
                .antMatchers("/api/**").permitAll()
                .antMatchers("/authCode").permitAll()
                .antMatchers("/asa/**").permitAll()
                .antMatchers("/ad/excel/template", "/ad/media_account_info/template",
                        "/ad/material/tag/download/template", "/ad/material/config/download/template",
                        "/ad/finance/advert/consume/excel/template", "/ad/tool/schedule_task/template",
                        "/ad/jrtt/program/word/export").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        httpSecurity.authenticationProvider(new CustomDaoAuthenticationProvider(userDetailsService, userV2Service, redisTemplate));
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
    }

    /**     * 身份认证接口     */@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
}

认证

提供登录服务
java 复制代码
/** * 登录 * * @paramaccount 账号 * @parampwd     密码 * @return*/
public String login(String account, String pwd) {
    // 用户验证
    // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
    Authentication authentication = authenticationManager
            .authenticate(new UsernamePasswordAuthenticationToken(account, pwd));
    // 清空登录失败计数
    redisTemplate.opsForValue().set(USER_LOGIN_ERROR_KEY + account, 0);
    LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    recordLoginInfo(loginUser.getUser());
    // 生成token
    return tokenService.createToken(loginUser);
}

授权

定义授权控制类
java 复制代码
@Service("auth")
public class AuthorizeService {

    private final UserV2Mapper userV2Mapper;

    private final UserDataPermissionMapper userDataPermissionMapper;

    public AuthorizeService(UserV2Mapper userV2Mapper, UserDataPermissionMapper userDataPermissionMapper) {
        this.userV2Mapper = userV2Mapper;
        this.userDataPermissionMapper = userDataPermissionMapper;
    }


    /**     * 操作权限校验     *     * @parampermission     * @paramdataOwner     * @return*/public boolean hasOperationAuthority(Integer permissionId, int businessId, String mapperBeanName) {
        if (permissionId == null) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null) {
            return false;
        }
        // 超级管理员直接放行
        if (loginUser.getUser().getSuperAdmin() == 1) {
            return true;
        }
        Object bean = SpringUtil.getBean(mapperBeanName);
        if (bean instanceof ExtendMapper) {
            ExtendMapper mapper = (ExtendMapper) bean;
            Object o = mapper.selectByPrimaryKey(businessId);
            Integer operator = (Integer) ReflectionUtil.getFieldValue(o, "operator");
            //查询本人的操作权限范围
            Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
            switch (operationAuthority) {
                case 1:
                    return loginUser.getUserId().equals(operator);
                case 2:
                    // 获取部门的人员
                    Example example = new Example(UserV2.class);
                    example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
                    List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));
                    return userIds.contains(operator);
                case 3:
                    // 公司可见,直接返回true
                    return true;
                default:
                    return false;
            }
        }
        return false;
    }

    /**     * 操作权限校验     *     * @parampermission     * @paramdataOwner     * @return*/public boolean hasOperationAuthorityAndAgent(Integer permissionId, int businessId, String mapperBeanName, String tableName) {
        if (permissionId == null) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null) {
            return false;
        }
        // 超级管理员直接放行
        if (loginUser.getUser().getSuperAdmin() == 1) {
            return true;
        }
        Object bean = SpringUtil.getBean(mapperBeanName);
        if (bean instanceof ExtendMapper) {
            Example userDataPermissionExample = new Example(UserDataPermission.class);
            userDataPermissionExample.createCriteria().andEqualTo("status", 1).andEqualTo("tableName", tableName).andEqualTo("userId", loginUser.getUserId());
            List<UserDataPermission> userDataPermissions = Optional.ofNullable(userDataPermissionMapper.selectByExample(userDataPermissionExample)).orElse(new ArrayList<>(16));
            List<Integer> relatedIds = userDataPermissions.stream().map(UserDataPermission::getRelatedId).collect(Collectors.toList());


            ExtendMapper mapper = (ExtendMapper) bean;
            Object o = mapper.selectByPrimaryKey(businessId);
            Integer operator = (Integer) ReflectionUtil.getFieldValue(o, "operator");
            //查询本人的操作权限范围
            Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
            switch (operationAuthority) {
                case 1:
                    return loginUser.getUserId().equals(operator) || relatedIds.contains(businessId);
                case 2:
                    // 获取部门的人员
                    Example example = new Example(UserV2.class);
                    example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
                    List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));
                    return userIds.contains(operator) || relatedIds.contains(businessId);
                case 3:
                    // 公司可见,直接返回true
                    return true;
                default:
                    return false;
            }
        }
        return false;
    }

    /**     * 操作权限校验     *     * @parampermission     * @paramdataOwner     * @return*/public boolean hasOperationAuthorityAndAgent(Integer permissionId, List<Integer> businessIds, String mapperBeanName, String tableName) {
        if (permissionId == null) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null) {
            return false;
        }
        // 超级管理员直接放行
        if (loginUser.getUser().getSuperAdmin() == 1) {
            return true;
        }
        Object bean = SpringUtil.getBean(mapperBeanName);
        if (bean instanceof ExtendMapper) {
            Example userDataPermissionExample = new Example(UserDataPermission.class);
            userDataPermissionExample.createCriteria().andEqualTo("status", 1).andEqualTo("tableName", tableName).andEqualTo("userId", loginUser.getUserId());
            List<UserDataPermission> userDataPermissions = Optional.ofNullable(userDataPermissionMapper.selectByExample(userDataPermissionExample)).orElse(new ArrayList<>(16));
            List<Integer> relatedIds = userDataPermissions.stream().map(UserDataPermission::getRelatedId).collect(Collectors.toList());

            ExtendMapper mapper = (ExtendMapper) bean;
            //查询本人的操作权限范围
            Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
            List<Integer> operatorList = getOperatorList(businessIds, mapper);
            switch (operationAuthority) {
                case 1:
                    return CollectionUtils.isSubCollection(operatorList, Collections.singleton(loginUser.getUserId())) || CollectionUtils.isSubCollection(businessIds, relatedIds);
                case 2:
                    // 获取部门的人员
                    Example example = new Example(UserV2.class);
                    example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
                    List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));

                    return CollectionUtils.isSubCollection(operatorList, userIds) || CollectionUtils.isSubCollection(businessIds, relatedIds);
                case 3:
                    // 公司可见,直接返回true
                    return true;
                default:
                    return false;
            }
        }
        return false;
    }

    /**     * 操作权限校验     *     * @parampermission     * @paramdataOwner     * @return*/public boolean hasOperationAuthority(Integer permissionId, List<Integer> businessIds, String mapperBeanName) {
        if (permissionId == null) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null) {
            return false;
        }
        // 超级管理员直接放行
        if (loginUser.getUser().getSuperAdmin() == 1) {
            return true;
        }
        Object bean = SpringUtil.getBean(mapperBeanName);
        if (bean instanceof ExtendMapper) {
            ExtendMapper mapper = (ExtendMapper) bean;
            //查询本人的操作权限范围
            Integer operationAuthority = getOperationAuthority(loginUser.getPermissionV2VoList(), permissionId);
            List<Integer> operatorList = getOperatorList(businessIds, mapper);
            switch (operationAuthority) {
                case 1:
                    return CollectionUtils.isSubCollection(operatorList, Collections.singleton(loginUser.getUserId()));
                case 2:
                    // 获取部门的人员
                    Example example = new Example(UserV2.class);
                    example.createCriteria().andEqualTo("departmentId", loginUser.getUser().getDepartmentId());
                    List<Integer> userIds = Optional.of(userV2Mapper.selectByExample(example).stream().map(BaseEntity::getId).collect(Collectors.toList())).orElse(new ArrayList<>(16));

                    return CollectionUtils.isSubCollection(operatorList, userIds);
                case 3:
                    // 公司可见,直接返回true
                    return true;
                default:
                    return false;
            }
        }
        return false;
    }

    public List<Integer> getOperatorList(List<Integer> businessIds, ExtendMapper mapper) {
        return businessIds.stream().map(businessId -> {
            Object o = mapper.selectByPrimaryKey(businessId);
            return (Integer) ReflectionUtil.getFieldValue(o, "operator");
        }).distinct().collect(Collectors.toList());
    }


    public boolean dataAuthorityHandle() {
        return false;
    }


    private Integer getOperationAuthority(List<PermissionV2Vo> permissionV2VoList, Integer permissionId) {
        for (PermissionV2Vo permissionV2Vo : permissionV2VoList) {
            if (permissionV2Vo.getId().equals(permissionId)) {
                return permissionV2Vo.getOperationAuthority();
            }
        }
        return 0;
    }

}
需要控制的接口接入授权
java 复制代码
/** * 修改账号信息 * * @paramuserV2 * @return*/
@PostMapping("modify")
@PreAuthorize("@auth.hasOperationAuthority(38,#userV2.id,'userV2Mapper')")
public BaseResponse modify(@RequestBody @Validated UserV2 userV2) {
   userV2Service.modify(userV2);
   return BaseResponse.successfulWithMessage("修改成功");
}
相关推荐
魔道不误砍柴功41 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_23441 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨44 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
种树人202408191 小时前
如何在 Spring Boot 中启用定时任务
spring boot
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity3 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天3 小时前
java的threadlocal为何内存泄漏
java
caridle3 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^4 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋34 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx