单体应用-SpringBoot-(3)权限控制

如题,实现思路如下:

  1. 定义用户,角色,权限,三者关联的实体类,定义用户-数据源的实体类。

  2. 定义各自的Repository,继承JpaRepository。spring jpa本身会自动生成标准的增删改查的方法供使用,也可以按规则定义类似findFirstByUserName(String userName);的方法,不需要自己实现,spring jpa会自动实现该方法。

    更详细的参考Spring Data Jpa的使用

  3. 服务启动后,从数据库取出用户/权限等相关数据,放入Redis,后续用户/权限等操作均从Redis获取数据校验,再往后实现用户管理/权限分配等功能时去修改Redis的数据。

  4. 定义service供controller调用,用户登录时,通过JWT生成token返回给前端。

    主要参考: 集成JWT实现token验证使用JWT做用户登陆token校验

  5. 对除了/login的其他请求进行拦截,校验token,从token解析出用户名,校验用户权限,切换到用户使用的数据源。

代码粗略实现如下:
编写 抄袭一个类,用于返回统一数据格式给前端:
以下放入core模块

public class JsonResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public static final String CODE_TAG = "code";

    public static final String MSG_TAG = "msg";

    public static final String DATA_TAG = "data";

    /**
     * 状态类型
     */
    public enum Type
    {
        /** 成功 */
        SUCCESS(1),
        /**用户没有授权 */
        FAIL(0),
        /** 权限不足 */
        WARN(403),
        /** 错误 */
        ERROR(500);
        private final int value;

        Type(int value)
        {
            this.value = value;
        }

        public int value()
        {
            return this.value;
        }
    }

    /** 状态类型 */
    private Type type;

    /** 状态码 */
    private int code;

    /** 返回内容 */
    private String msg;

    /** 数据对象 */
    private Object data;

    /**
     * 初始化一个新创建的 JsonResult 对象,使其表示一个空消息。
     */
    public JsonResult()
    {
    }

    /**
     * 初始化一个新创建的 JsonResult 对象
     *
     * @param type 状态类型
     * @param msg 返回内容
     */
    public JsonResult(Type type, String msg)
    {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 JsonResult 对象
     *
     * @param type 状态类型
     * @param msg 返回内容
     * @param data 数据对象
     */
    public JsonResult(Type type, String msg, Object data)
    {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
        super.put(DATA_TAG, data);
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static JsonResult success()
    {
        return JsonResult.success("操作成功");
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static JsonResult success(String msg)
    {
        return JsonResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static JsonResult success(String msg, Object data)
    {
        return new JsonResult(Type.SUCCESS, msg, data);
    }

    /**
     * 返回失败消息
     *
     * @return 成功消息
     */
    public static JsonResult fail()
    {
        return JsonResult.fail("没有授权访问");
    }

    /**
     * 返回失败消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static JsonResult fail(String msg)
    {
        return JsonResult.fail(msg, null);
    }

    /**
     * 返回失败消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static JsonResult fail(String msg, Object data)
    {
        return new JsonResult(Type.FAIL, msg, data);
    }


    public static JsonResult warn()
    {
        return JsonResult.warn("权限不足");
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static JsonResult warn(String msg)
    {
        return JsonResult.warn(msg, null);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static JsonResult warn(String msg, Object data)
    {
        return new JsonResult(Type.WARN, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static JsonResult error()
    {
        return JsonResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 错误消息
     */
    public static JsonResult error(String msg)
    {
        return JsonResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 错误消息
     */
    public static JsonResult error(String msg, Object data)
    {
        return new JsonResult(Type.ERROR, msg, data);
    }

    public Type getType()
    {
        return type;
    }

    public void setType(Type type)
    {
        this.type = type;
    }

    public int getCode()
    {
        return code;
    }

    public void setCode(int code)
    {
        this.code = code;
    }

    public String getMsg()
    {
        return msg;
    }

    public void setMsg(String msg)
    {
        this.msg = msg;
    }

    public Object getData()
    {
        return data;
    }

    public void setData(Object data)
    {
        this.data = data;
    }

}

编写 抄袭两个使用到的工具类:
以下放入common模块

public class CodecUtil {
    /**
     * 将字符串 MD5 加密
     */
    public static String encryptMd5(String str) {
        return DigestUtils.md5Hex(str);
    }
}
public class JwtUtil {
    /**
     * 过期时间为12小时
     */
    private static final long EXPIRE_TIME = 12*60*60*1000;

    /**
     * token私钥
     */
    private static final String TOKEN_SECRET = "jfiejf23自己随便写点2fy2fuf3f";

    /**
     * 生成签名
     * @param username
     * @param password
     * @return
     */
    public static String sign(String username, String password){
        //过期时间
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        //私钥及加密算法
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        //设置头信息
        HashMap<String, Object> header = new HashMap<>(2);
        header.put("typ", "JWT");
        header.put("alg", "HS256");
        //附带username和userID生成签名
        return JWT.create()
                .withHeader(header)
                .withClaim("username",username)
                .withClaim("password",password)
                .withExpiresAt(date)
                .sign(algorithm);
    }
    
    /**
     * 校验token 成功返回用户名
     * @param token
     * @return
     */
    public static String verityAndGetUser(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            String userName = jwt.getClaim("username").asString();
            return userName;
        } catch (IllegalArgumentException e) {
            return "";
        } catch (JWTVerificationException e) {
            return "";
        }
    }
}

以下放入dao模块

Entity(略去getter/setter):

用户:

@Entity
@Table(name = "sys_user")
public class SysUser {
    @Id
    private String userName;
    private String passWord;
}

角色:

@Entity
@Table(name = "sys_role")
public class SysRole {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String roleCode;
    private String roleName;
}

权限(permCode其实就是存放请求路径,如 /hello):

@Entity
@Table(name = "sys_perm")
public class SysPerm{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String permCode;
    private String permName;
}

用户-角色 对照表:

@Entity
@Table(name = "sys_user_role")
public class SysUserRole {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String userName;
    private long roleId;
}

角色-权限 对照表:

@Entity
@Table(name = "sys_role_perm")
public class SysRolePerm {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private long roleId;
    private long permId;
}

用户-数据源 对照表:

@Entity
@Table(name = "sys_user_datasource")
public class SysUserCurrentDatasource {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String userName;
    private String dataSourceId;
}
Repository:
(创建每个Entity对应的Repository,继承JpaRepository即可,只列出一个比较特殊的)
public interface SysRoleRepository extends JpaRepository<SysRole, Long>{
    /**
     * 根据用户名查找该用户所有权限
     * @param userName
     * @return List<SysRole>
     */
    @Query(value = "select r.* from sys_role r, sys_user_role ur where ur.user_name = ?1 and ur.role_id = r.id", nativeQuery = true)
    List<SysRole> findRolesOfUser(String userName);

    /**
     * 根据权限Id查找授权的角色
     * @param permId
     * @return List<SysRole>
     */
    @Query(value = "select r.* from sys_role r, sys_role_perm rp where rp.perm_id = ?1 and rp.role_id = r.id", nativeQuery = true)
    List<SysRole> findRolesOfPerm(long permId);
}
初始化Redis:

定义需要用到常量:

public interface RedisConstant {
    /**
     * 缓存保存时间
     */
    long TIME_LONG = 30;
    /**
     * 时间类型
     */
    TimeUnit TIME_UNIT = TimeUnit.DAYS;
    /**
     * 用户-密码
     */
    String USER_TOKEN = "userToken-";
    /**
     * 用户-角色
     */
    String USER_ROLES = "userRoles-";
    /**
     * 用户-数据源ID
     */
    String USER_DATASOURCE = "userDatasource-";
    /**
     * 权限-角色
     */
    String PERM_ROLE = "permRole-";
}

初始化Redis数据,代码仍需优化:

@Component
public class InitRedisData implements CommandLineRunner {
    private static Logger logger = LoggerFactory.getLogger(InitRedisData.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private SysUserRepository userRepository;

    @Autowired
    private SysRoleRepository roleRepository;

    @Autowired
    private SysUserDataSourceRepository sysUserDataSourceRepository;

    @Autowired
    private SysPermRepository sysPermRepository;


    @Override
    public void run(String... args) throws Exception {
        logger.info("开始初始化Redis数据.");
        try {
            List<SysUser> userList = userRepository.findAll();
            for (SysUser user: userList) {
                String userName = user.getUserName();
                //用户账号密码
                stringRedisTemplate.opsForValue().set(RedisConstant.USER_TOKEN + userName, user.getPassWord(), RedisConstant.TIME_LONG, RedisConstant.TIME_UNIT);
                //用户拥有的角色
                List<SysRole> sysRoles = roleRepository.findRolesOfUser(userName);
                String roles = changeToString(sysRoles);
                stringRedisTemplate.opsForValue().set(RedisConstant.USER_ROLES + userName, roles, RedisConstant.TIME_LONG, RedisConstant.TIME_UNIT);
            }

            //用户当前使用的数据库
            List<SysUserCurrentDatasource> userDataSources = sysUserDataSourceRepository.findAll();
            for (SysUserCurrentDatasource userDataSource: userDataSources) {
                stringRedisTemplate.opsForValue().set(RedisConstant.USER_DATASOURCE + userDataSource.getUserName(), userDataSource.getDataSourceId(), RedisConstant.TIME_LONG, RedisConstant.TIME_UNIT);
            }
            //权限所需的角色列表
            List<SysPerm> perms = sysPermRepository.findAll();
            for (SysPerm perm: perms) {
                List<SysRole> sysRoles = roleRepository.findRolesOfPerm(perm.getId());
                String roles = changeToString(sysRoles);
                stringRedisTemplate.opsForValue().set(RedisConstant.PERM_ROLE + perm.getPermCode(), roles, RedisConstant.TIME_LONG, RedisConstant.TIME_UNIT);

            }

        }catch (Exception e){
            logger.error("初始化Redis数据失败:" + e.getMessage());
            throw e;
        }
        logger.info("结束初始化Redis数据.");
    }


    private String changeToString(List<SysRole> sysRoles){
        String roles = "";
        if (sysRoles != null && sysRoles.size()>0){
            StringBuilder sb = new StringBuilder();
            for (SysRole role: sysRoles) {
                sb.append(role.getRoleCode());
                sb.append(",");
            }
            roles = sb.toString().substring(0, sb.toString().length()-1);
        }
        return roles;
    }
}

写个Repository供其他地方调用Redis的数据:

@Repository
public class SystemInfoRedis {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取数据库用户对象(用户名和密码)
     * @param userName
     * @return
     */
    public SysUser getByUserName(String userName){
        SysUser sysUser = new SysUser();
        String password = stringRedisTemplate.opsForValue().get(RedisConstant.USER_TOKEN + userName);
        if (password != null && !password.isEmpty()){
            sysUser.setUserName(userName);
            sysUser.setPassWord(password);
        }
        return sysUser;
    }

    /**
     * 获取用户所使用的数据源
     * @param userName
     * @return dsName
     */
    public String getDataSourceByUserName(String userName){
        return stringRedisTemplate.opsForValue().get(RedisConstant.USER_DATASOURCE + userName);
    }

    /**
     * 根据用户名获取所拥有的角色
     * @param userName
     * @return admin,user,...
     */
    public String getRolesByUserName(String userName){
        String roles = stringRedisTemplate.opsForValue().get(RedisConstant.USER_ROLES + userName);
        return roles == null ? "": roles;
    }

    /**
     * 根据用户名获取所拥有的角色列表
     * @param userName
     * @return List<String>
     */
    public List<String> getRoleListByUserName(String userName){
        List<String> roleList = new ArrayList<>();
        String roles = stringRedisTemplate.opsForValue().get(RedisConstant.USER_ROLES + userName);
        if (roles != null && !"".equals(roles)) {
            String[] roleArray = roles.split(",");
            roleList = Arrays.asList(roleArray);
        }
        return roleList;
    }

    /**
     * 根据权限,获取需要的角色信息
     * @param permCode
     * @return admin,user,...
     */
    public String getRolesByPermCode(String permCode){
        String roles = stringRedisTemplate.opsForValue().get(RedisConstant.PERM_ROLE + permCode);
        return roles == null ? "": roles;
    }

    /**
     * 根据权限,获取需要的角色列表
     * @param permCode
     * @return List<String>
     */
    public List<String> getRoleListByPermCode(String permCode){
        List<String> roleList = new ArrayList<String>();
        String roles = stringRedisTemplate.opsForValue().get(RedisConstant.PERM_ROLE + permCode);
        if (roles != null && !"".equals(roles)){
            String[] roleArray = roles.split(",");
            roleList = Arrays.asList(roleArray);
        }
        return roleList;
    }
}

用户登录用的Service(数据库存放的是加密过的密码):
以下放入service模块

@Service
public class UserService {
    @Autowired
    private SystemInfoRedis systemInfoRedis;

    /**
     * 校验用户名和密码,成功返回TOKEN
     * @param userName
     * @param passWord
     * @return token
     */
    public String checkUser(String userName, String passWord) {
        String token = "";
        //获取Redis中的用户数据
        SysUser user = systemInfoRedis.getByUserName(userName);
        if ((user != null) && (!"".equals(user.getUserName()))){
            //校验密码
            if (CodecUtil.encryptMd5(passWord).equals(user.getPassWord())){
                token = JwtUtil.sign(userName, passWord);
                if (token != null) {
                    return token;
                }
            };
        }
        return token;
    }
}

用户登录访问的Controller:
以下放入web模块

@RestController
public class LoginController {
    @Autowired
    private UserService userService;

    @PostMapping(value = "/login")
    @ResponseBody
    public JsonResult login (@RequestBody Map<String,String> map){
        String userName = map.get("userName");
        String passWord = map.get("passWord");
        //身份验证
        String token = userService.checkUser(userName,passWord);
        if (!"".equals(token)) {
            return JsonResult.success("成功", token);
        }
        return JsonResult.fail("用户名及密码错误");
    }
}
写个拦截器拦截请求:

主要实现校验请求头的token,校验用户权限,切换数据源。
将自定义的CurrentUser对象(存放用户信息)放入Request中,方便后续业务处理时使用。

public class CurrentUser {
    private String token;
    private String userName;
    private String dataSourceId;
    private List<String> roles;
	...
}
@Component
public class RequestInterceptor implements HandlerInterceptor {
    private static final Logger log = LoggerFactory.getLogger(RequestInterceptor.class);

    @Autowired
    private SystemInfoRedis systemInfoRedis;

    /**
     * 进入controller层之前拦截请求
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        //获取请求头的token, 没有的化返回失败,前端需要登录
        String token = request.getHeader("token");
        if (token == null || "".equals(token)){
            returnJson(response, JSON.toJSONString(JsonResult.fail()));
            return false;
        }
        //校验token,过期等原因失败则前端需要重新登录
        String userName = JwtUtil.verityAndGetUser(token);
        if ("".equals(userName)) {
            returnJson(response, JSON.toJSONString(JsonResult.fail()));
            return false;
        }
        //判断权限,失败的话返回警告
        String url = request.getServletPath();
        List<String> needRoleList = systemInfoRedis.getRoleListByPermCode(url);
        List<String> userRoleList = systemInfoRedis.getRoleListByUserName(userName);
        for (String needRole : needRoleList){
            if (!userRoleList.contains(needRole)){
                returnJson(response, JSON.toJSONString(JsonResult.warn()));
                return false;
            }
        }

        //设置用户数据源
        String dataSourceId = "";
        if (userName != null)
        {
            dataSourceId = systemInfoRedis.getDataSourceByUserName(userName);
            if (dataSourceId != null && !dataSourceId.isEmpty()){
                DynamicDataSourceContextHolder.removeDataSourceRouterKey();
                DynamicDataSourceContextHolder.setDataSourceRouterKey(dataSourceId);
            }
        }
        
		//将用户信息对象通过setAttribute放入Request中
        CurrentUser currentUser = new CurrentUser();
        currentUser.setToken(token);
        currentUser.setUserName(userName);
        currentUser.setDataSourceId(dataSourceId);
        currentUser.setRoles(userRoleList);
        request.setAttribute("currentUser", currentUser);
     
        return true;
    }

    private void returnJson(HttpServletResponse response, String json) throws Exception{
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(json);
        } catch (IOException e) {
            log.error("拦截器返回JSON失败", e);
        } finally {
            if (writer != null){
                writer.close();
            }
        }
    }


    /**
     * 处理请求完成后视图渲染之前的处理操作
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        DynamicDataSourceContextHolder.removeDataSourceRouterKey();
    }

    /**
     * 视图渲染之后的操作
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param e
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }

}

配置使拦截器生效,拦截除了/login外的请求:

@Configuration
public class WebAppConfig implements WebMvcConfigurer{
    @Autowired
    private RequestInterceptor requestInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(requestInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }

}

至此,用户权限的功能基本实现,后续进行测试。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值