SpringSecurity入门学习记录

SpringSecurity的学习记录

最近观看了rouyi的后端项目,学习了后端的认证逻辑,因此想写一个笔记记录一下。

1.认证逻辑

基于UsernamePasswordAuthenticationFilter.calss的认证与基于自己注入的AuthenticationManager的认证,个人感觉认证逻辑都差不多只不过 UsernamePasswordAuthenticationFilter是看尚硅谷的springSecurity教程,而AuthenticationManager是三更草堂的springSecurity教程。

认证逻辑简化的来说就是 编写一个配置文件 继承两个接口(UserDetailsServiceUserDetails)

1.1 基于UsernamePasswordAuthenticationFilter.calss的认证

1.1.1 认证逻辑流程图

认证的逻辑大概分为一下几个重要的步骤:

1.在UsernamePasswordAuthenticationFilter中执行attemptAuthentication获取用户名和密码,封装为一个Authentication对象,开始执行authenticate方法往下传递进行认证。

2.通过UserDetailsService的loadUserByUsername(String username)方法,通过用户名得到数据库对象进行我们自己的认证逻辑 ,返回一个UserDetails对象。 (这里可能会产生两点问题①这个方法只能得到用户登录时的用户名,无法获取用户登录时的密码,如何获取用户登录时的密码;②我自己如何返回一个UserDetails对象还需要注意哪些细节,这两个问题后面会讲到

3.在AbstractUserDetailsAuthenticationProvider方法中获取到了UserDetails会进行三个检查方法(①preAuthenticationChecks.check
②additionalAuthenticationChecks
③postAuthenticationChecks这三个方法会检查用户是否过期是否被禁止以及匹配密码,后面看源码的时候会讲到),认证成功之后就会返回一个Authentication对象

4.UsernamePasswordAuthenticationFilter的认证方法执行接受之后,就会执行其父类AbstractAuthenticationProcessingFilter的认证功后执行的方法successfulAuthentication与认证失败的方法unsuccessfulAuthentication
在这里插入图片描述

1.1.2 代码实现

基于上面的流程描述在实现方法需要做三步:

1.继承UsernamePasswordAuthenticationFilter实现三个方法 attemptAuthentication(),successfulAuthentication(),unsuccessfulAuthentication

public class myFilter extends UsernamePasswordAuthenticationFilter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        try {
            User user = new ObjectMapper().readValue(request.getInputStream(),User.class);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
            authenticationManager.authenticate(authentication);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

        //执行认证成功的逻辑 比如产生 生成token存到redis 生成jwt等等

    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    //认证失败返回提示信息等

    }
}

2.继承UserDetails,实现它的五个方法,里面的属性字段根据自己的需求定义,重点需要实现它的五个方法,因为上面流程中提到的三个检查方法中会用到

public class LoginUser implements UserDetails
{
   
    /**
     * 数据库里面的用户
     */
    private dbUser user;

    public LoginUser(SysUser user)
    {
        
        this.user = user;
    }

    @Override
    public String getPassword()
    {
        return user.getPassword();
    }

    @Override
    public String getUsername()
    {
        return user.getUserName();
    }

    /**
     * 账户是否未过期,过期无法验证
     */
   
    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     * 
     * @return
     */
    
    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     * 
     * @return
     */
    
    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     * 
     * @return
     */
    
    @Override
    public boolean isEnabled()
    {
        return true;
    }

   
    public SysUser getUser()
    {
        return user;
    }

    public void setUser(SysUser user)
    {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return null;
    }
}

3.实现UserDetailsService接口实现loadUserByUsername方法并且返回一个UserDetails对象

@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
    

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
    
        dbUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user))
        {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("登录用户:" + username + " 不存在");
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
        {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
        {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
        }

       //进行密码校验等
        

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user)
    {
        return new LoginUser(user);
    }
}

4.编写一个配置类
编写配置类的目的在于:
① 由于在上面l认证流程中 AbstractUserDetailsAuthenticationProvider类的三个检查函数中的②additionalAuthenticationChecks中会进行密码验证,所以我们要指定一个加密方式
② 指定userDetails对象是从我们自己定义的UserDetailsService类中的loadUserByUsername方法中拿到
③AuthenticationManager 无法通过@Autowired的方式直接注入 需要先声明bean
所以配置文件的雏形是这样的后(后面还会再加内容

public class SecurityConfig extends WebSecurityConfigurerAdapter
{
	@Autowired
    private UserDetailsService userDetailsService;
    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }

/**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * 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 允许匿名访问,采用继承UsernamePasswordAuthenticationFilter的方式这个应该是可以不写的
                //.antMatchers("/login", "/register", "/captchaImage").anonymous()
                // 静态资源,可匿名访问
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 强散列哈希加密实现 
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        //我数据库存储的数据是用BCryptPasswordEncoder 加密的,所以我就指定加密方式是BCryptPasswordEncoder类型
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

认证逻辑到这里就差不多结束了

1.2 基于自己注入的AuthenticationManager的认证逻辑

1.2.1 认证逻辑流程图

自己注入的AuthenticationManager逻辑与上一个认证逻辑差不多在这里就布再赘述
在这里插入图片描述

1.2.2 代码实现

与1.1.2代码实现的区别 我们可以不用继承UsernamePasswordAuthenticationFilter类来从请求头里面拿到用户名和密码然后执行校验。我们可以自己从controller层里面出入的用户名和密码然后自己调用AuthenticationManager来进行验证

1.前端调用后端接口,传入用户名和密码,之后自己在service层中注入AuthenticationManager对象进行验证,在验证前还需要创建一个上下文容器,来保存用户当前的密码,(如果不需要自己匹配密码的话就不需要了,因为在三个检查函数中的会进行密码匹配)因为在我们继承的UserDetailsService接口中的loadUserByUsername(String username)中只能得到用户名
* ①创建一个ThreadLocal来保存*

/**
 * 身份验证信息
 * 
 * @author ruoyi
 */
public class AuthenticationContextHolder
{
    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();

    public static Authentication getContext()
    {
        return contextHolder.get();
    }

    public static void setContext(Authentication context)
    {
        contextHolder.set(context);
    }

    public static void clearContext()
    {
        contextHolder.remove();
    }
}

②在service层中

@Component
public class SysLoginService
{


    @Resource
    private AuthenticationManager authenticationManager;

    /**
     * 登录验证
     * 
     * @param username 用户名
     * @param password 密码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password)
    {

        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            // 用户名和密码等信息保存在一个上下文中,只要是同一线程等会就能拿到用户名和密码,也就是能在loadUserByUsername(String username)方法中进行密码验证等
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        
        // 生成token
        return tokenService.createToken(loginUser);
    }


}

2.继承UserDetails,跟上面的代码实现是一样的,自定义

3.UserDetailsService,同样和上面的实现方式一样,只是用了ThreadLocal来得到当前的用户信息,就记录在这里了

@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
    

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
    
        dbUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user))
        {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("登录用户:" + username + " 不存在");
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
        {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
        {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
        }

       //进行密码校验等
        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
        String username = usernamePasswordAuthenticationToken.getName();
        String password = usernamePasswordAuthenticationToken.getCredentials().toString();
        

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user)
    {
        return new LoginUser(user);
    }
}

4.SecurityConfig开放登录接口

1.3 浅查源码-为什么需要继承UserDetailsService类

从认证流程来将,认证流程开始都是通过AuthenticationManager.authenticate()开始的
在这里插入图片描述
之后找到它的实现类ProviderManager,(要是有问题想问为什么偏偏是ProviderManager而不是上面的几个的话可以,到他们祖先类WebSecurityConfigurerAdapter中的authenticate()的方法上打上断点看一看,然后看会教教我)
在这里插入图片描述
ProviderManager的authenticate()方法中会遍历所有的认证器,只要通过一个认证器之后就可以
在这里插入图片描述
我们点进_provider.authenticate(authentication)_ 方法中看看认证结果是从什么地方来的
在这里插入图片描述
进去 AbstractUserDetailsAuthenticationProvider类之后我们就返回UserDetails对象,下面就是UserDetails对象是从什么地方得到的。以及为什么要编写一个人配置类

在这里插入图片描述
在这里插入图片描述

1.4 浅查源码-UserDetails对象为什么需要实现他的五个方法

我们回到刚刚的AbstractUserDetailsAuthenticationProvider类中的authenticate方法中可以看到,有对UserDetails进行三种类型的校验。
在这里插入图片描述
①this.preAuthenticationChecks.check(user);
在这个方法中我们可以看到,就是校验用户的信息,而这些信息的返回值都是true/false,由我们继承UserDetails后重写它的方法得到。
在这里插入图片描述
②this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
a.这个方法里面主要做的事情就是进行密码匹配
在这里插入图片描述
b.密码匹配逻辑
主要匹配的方式的就是通过将用户输入的密码加密后与数据库中的加密密码进行匹配。这里的问题就是就是加密方式有多种,如何用指定的加密方式进行加密呢?

在这里插入图片描述
所以就需要在配置类里面指定加密方法
在这里插入图片描述
③ this.postAuthenticationChecks.check(user)
这个方法检查与①this.preAuthenticationChecks.check(user);检查类似
在这里插入图片描述

1.4 补充配置类

一个有基本功能的配置类,还有一个认证失败处理类,退出处理类以及token处理类

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;
    
    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

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

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

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * 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", "/register", "/captchaImage").anonymous()
                // 静态资源,可匿名访问
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

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

1.4.1 设置一个认证失败的类处理类

设置一个认证失败的处理类,就是在认证的时候抛出异常 的处理,实现方法就是继承AuthenticationEntryPoint接口实现commence方法,例如返回一个认证失败

@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
    {
        int code = HttpStatus.UNAUTHORIZED;
        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
    }
}
1.4.2 设置一个登出处理类

设置一个退出处理的类的话需要继承LogoutSuccessHandler接口实现onLogoutSuccess方法,所作的时候比如用户日志,从redis中删除用户的token缓存等等


@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
    @Autowired
    private TokenService tokenService;

    /**
     * 退出处理
     * 
     * @return
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser))
        {
            
            // 删除用户缓存记录
            
            // 记录用户退出日志
            
        }
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
    }
}
1.4.3 设置一个token认证过滤器

为什么要设置这样一个token认证过滤器?那就得看一下token认证过滤器的作用。
首先在用户登录之后,认证成功结束之后会将用户信息存到reids中,然后返回一个token,用户再次访问其他接口的时候,首先应该需要进行鉴权,那鉴权的逻辑应该是 :
token->redis拿到用户信息->从用户信息中拿到用户的权限->进行鉴权
所以设置一个token认证过滤器的就是拿到用户信息并且放到SecurityContextHolder中,这样每次只要保证是同一个线程的话就能通过

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

拿到用户信息,至于为什么是在Principal可以看到下面
实现token认证过滤器的具体实现(UsernamePasswordAuthenticationToken的构造方法)。

实现token认证过滤器
首先继承OncePerRequestFilter类实现重写doFilterInternal方法

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
    //从redis中获取用户信息
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        {
            tokenService.verifyToken(loginUser);
            //封装用户信息 和用户的权限信息
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            //放入SecurityContextHolder上下文中
           SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

1.5 认证结束了如何进行鉴权?

1.5.1 首先需要在配置类上上面加上注解

在这里插入图片描述
然后在需要鉴权的方法上面加上注解@PreAuthorize之后可以通过hasAuthority方法进行校验。除此之外SpringSecurity还为我们提供了其它方法例如:hasAnyAuthority,hasRole,hasAnyRole等。

 @PreAuthorize("hasAnyAuthority('admin','system:user:list')")
    public String hello(){
        return "hello";
    }
1.5.2 rouyi的鉴权方式

鉴权方式为自定义的,首先定义一个注解,然后实现了鉴权的方法
里面或者当前用户的方法就是在 1.4.3中的token过滤器将用户信息存储到SecurityContextHolder,然后通过SecurityContextHolder来获取

@Service("ss")
public class PermissionService
{
    /** 所有权限标识 */
    private static final String ALL_PERMISSION = "*:*:*";

    /** 管理员角色权限标识 */
    private static final String SUPER_ADMIN = "admin";

    private static final String ROLE_DELIMETER = ",";

    private static final String PERMISSION_DELIMETER = ",";

    /**
     * 验证用户是否具备某权限
     * 
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(String permission)
    {
        if (StringUtils.isEmpty(permission))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        PermissionContextHolder.setContext(permission);
        return hasPermissions(loginUser.getPermissions(), permission);
    }

    /**
     * 验证用户是否不具备某权限,与 hasPermi逻辑相反
     *
     * @param permission 权限字符串
     * @return 用户是否不具备某权限
     */
    public boolean lacksPermi(String permission)
    {
        return hasPermi(permission) != true;
    }

    /**
     * 验证用户是否具有以下任意一个权限
     *
     * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
     * @return 用户是否具有以下任意一个权限
     */
    public boolean hasAnyPermi(String permissions)
    {
        if (StringUtils.isEmpty(permissions))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        PermissionContextHolder.setContext(permissions);
        Set<String> authorities = loginUser.getPermissions();
        for (String permission : permissions.split(PERMISSION_DELIMETER))
        {
            if (permission != null && hasPermissions(authorities, permission))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断用户是否拥有某个角色
     * 
     * @param role 角色字符串
     * @return 用户是否具备某角色
     */
    public boolean hasRole(String role)
    {
        if (StringUtils.isEmpty(role))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
        {
            return false;
        }
        for (SysRole sysRole : loginUser.getUser().getRoles())
        {
            String roleKey = sysRole.getRoleKey();
            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 验证用户是否不具备某角色,与 isRole逻辑相反。
     *
     * @param role 角色名称
     * @return 用户是否不具备某角色
     */
    public boolean lacksRole(String role)
    {
        return hasRole(role) != true;
    }

    /**
     * 验证用户是否具有以下任意一个角色
     *
     * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
     * @return 用户是否具有以下任意一个角色
     */
    public boolean hasAnyRoles(String roles)
    {
        if (StringUtils.isEmpty(roles))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
        {
            return false;
        }
        for (String role : roles.split(ROLE_DELIMETER))
        {
            if (hasRole(role))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否包含权限
     * 
     * @param permissions 权限列表
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    private boolean hasPermissions(Set<String> permissions, String permission)
    {
        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
    }
}

使用方式
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值