7、幽络源微服务项目实战:SpringSecurity用户权限查询与校验的配置和测试

7、幽络源微服务项目实战:SpringSecurity用户权限查询与校验的配置和测试

前言

上节教程幽络源完成了项目中后端的JWT认证过滤器的配置与测试,通过JWT的过滤器,能够实现通过解析请求头中的JWT,来判断用户是否是属于这个系统的,解析不成功或者说请求头不带有token的请求都会被进行拦截,不让其访问接口。

本节概述

本章教程将在认证的基础上,完成权限的校验,所谓权限的校验,幽络源土拨鼠这里的理解就是,判断一个用户是否具备 模块、按钮、接口 的操作权限,若用户没有这些操作权限则不允许用户去访问。这个功能可以说在99%的系统中都应当是具备的。

启用全局方法安全注解

在多租户模块tenant的启动类上加上注解@EnableGlobalMethodSecurity(prePostEnabled = true),这是 Spring Security 对方法级安全控制的​​总开关注解,如图

fdb345f8-04cc-474d-838f-fa08050e8a5e

修改测试接口

在之前我们创建了一个test接口来测试了认证,现在我们对这个接口加上Security提供的注解,来让这个接口实现必须通过权限校验才能访问,注解很简单,加上@PreAuthorize(“hasAuthority(‘company:manage’)”),其作用为只有当当前用户拥有权限标识 company:manage时,才允许调用该方法。否则抛出 AccessDeniedException异常。代码与图如下:

@GetMapping("/test")
@PreAuthorize("hasAuthority('company:manage')")
public String test(){
    return "test";
}

bb8b6551-fb61-4a39-8cf5-fe1a97f61f82

修改UserDetails的实现对象

代码与图如下,可以看到相较于上节的认证教程,我们对这个UserDetailObject类做了两处增加和修改,一个是我们自定义了一个属性 List<String> permissions,这个属性主要是用于我们在认证时把查询出的用户拥有的所有权限存放到这里面。

但是对于Security来说,他是不认识这个 List<String> permissions的,他必须用自己指定的GrantedAuthority集合去进行权限的判断,因此就有了第二处修改,也就是重写他的getAuthorities方法,这个重写也容易理解,说白了就是将我们的permissions字符串集合转化为Security所需要的集合类型然后返回即可。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDetailObject implements UserDetails {

    //用户信息
    private User user;

    //用户的权限信息-字符串形式
    private List<String> permissions;

    /**
     * 实际Security需要通过这个方法来获取用户的权限信息,因此我们需要将将我们的字符串数组权限信息封装为Security所需要的权限集合
     * @return 权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        for (String permission : permissions) {
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
            grantedAuthorities.add(simpleGrantedAuthority);
        }
        return grantedAuthorities;
    }

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

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

    /**
     * 是否不过期
     * @return
     */
    @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;
    }
}

19bfd407-f123-49c6-bd9e-a09cbabe00d7

查询用户权限编写

上面我们为UserDetailObject对象添加了存放权限的字符串集合,现在就来开发查询用户权限的方法,并且在自定义认证处将查询出的权限也交给UserDetails对象。

涉及到数据库的查询了,因此这里我们要来开发相应的mapper,因为要查询的是权限,在PermissionMapper中添加抽象方法->根据用户ID查询拥有的权限字符串集合,代码与图如下

@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {
    /**
     * 根据用户id查询对应的权限字符串
     * @param userId 用户id
     * @return
     */
    List<String> selectPermissionByUserId(@Param("userId") Long userId);
}

d975a3ff-2924-4580-a517-6b9b9c56d7c6

然后在resource目录下建立mapper目录,对应PermissionMapper创建名为PermissionMapper.xml的文件,然后写该查询的具体的SQL,代码与图如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tenant.mapper.PermissionMapper">
    <!-- List<String> selectPermissionByUserId(@Param("userId") Integer userId); -->
    <select id="selectPermissionByUserId" resultType="String">
        select p.code
        from tbs_user u
        left join tbs_user_role ur on ur.user_id = u.id
        left join tbs_role r on r.id = ur.role_id
        left join tbs_role_permission rp on rp.role_id = r.id
        left join tbs_permission p on p.id = rp.permission_id
        where u.id=#{userId}
    </select>
</mapper>

8aae571e-3567-4db4-b87d-9b49309fdf47

修改自定义认证

查询用户的权限方法有了,现在来修改自定义认证处的方法,将用户的权限查询出来让后存放到UserDetails中,代码与图如下

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private PermissionMapper permissionMapper;
    /**
     * 实现自定义认证
     * authenticationManager.authenticate内部在调用这里
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户新消息
        LambdaQueryWrapper<User> eq = new LambdaQueryWrapper<User>()
                .eq(User::getUsername, username);
        User user = userMapper.selectOne(eq);
        if (!Objects.isNull(user)){
            List<String> permissions = permissionMapper.selectPermissionByUserId(user.getId());
            UserDetailObject userDetailObject = new UserDetailObject(user,permissions);
            return userDetailObject;
        }else{
            throw new UsernameNotFoundException("无此用户");
        }
    }
}

24fb83e1-3f87-4f0d-9be5-61846e2e474f

修改登录方法

在登录方法中会调用认证,认证完成后会得到一个Authentication对象,此时由于我们已经修改了自定义认证,这里的Authentication对象便会包含用户权限信息,我们这里将Authentication对象中的权限信息一并生成到JWT中相应给前端,后续前端携带此JWT请求其他接口时,会通过我们的JWT过滤器来获取到其JWT中的权限信息,并将权限信息交给Security上下文。

如图,我们的登录方法中修改为如下

5d9496ca-e423-408a-9982-2544027caa36

修改Token过滤器

你也可以称之为JWT过滤器,如图可以看到这里修改的其实不多,本质就是将前端传来的JWT解析,然后将权限信息封装为Security所需的GrantedAuthority集合,并将此集合赋值给Security的上下文对象之一UsernamePasswordAuthenticationToken。

6a706750-c145-46c2-b717-d2bd350e4871

过程搞清楚

修改了那么多,那这个执行流程是怎样的呢?Security本身的源码这里就不看了,否则太多了,我们就只搞清楚我们自己的代码来明白整个流程。

登录的过程

搞简单点,如图

当发起登录请求时,首先来到Controller的/auth/login接口

login接口接着调用LoginServiceImpl的登录方法login()

在login方法中,当执行到认证时,也就是authenticationManager.authenticate,便会来到我们自定义的认证方法loadUserByUsername,在自定义的方法中,我们查询了用户的信息和用户的权限进行返回,Security通过一些列的操作用Authentication接收到了,然后我们通过authenticate获取到里面的用户信息和权限信息,进一步通过JWT工具类将其存入JWT中响应给了前端。

5a92f159-4c72-4091-ad14-aef23cb9b25f

认证搞清楚与测试

首先登录获取到JWT,如图,这里我首先用user1来测试认证,然后将JWT放到请求test接口的请求头中,如图可以看到这里响应了403,也就是被拒绝了,因为user1在数据库中他的角色是普通用户,并且不具备“company:manage”的权限。

7d4d9e54-ef8e-4749-a8c8-d112af4065ae

18a59769-b4d9-4cc8-8961-531d6bb7f02a

再来测试超管用户,将超管进行登录,然后将响应的jwt放到请求中去请求test接口,如图可以看到超管用户是能够成功请求test接口的,至此权限的初步校验功能幽络源已完成。

50fae896-c1ea-47fa-a90d-350aba7b39fa

d305ed9c-3db9-4921-b59b-6e2e78a7b941

本章源码链接

https://pan.quark.cn/s/24ec0b8ecee3

结语

如上为幽络源的7、幽络源微服务项目实战:SpringSecurity用户权限查询与校验的配置和测试教程,如有疑问或对微服务感兴趣可加入我们的QQ群询问与交流:307531422

THE END
喜欢就支持一下吧
分享