精华内容
下载资源
问答
  • 实现功能权限校验的功能有多种方法,其一使用拦截器拦截请求,其二是使用AOP抛异常。首先用拦截器实现未登录时跳转到登录界面功能。注意这里没有使用AOP切入,而是用拦截器拦截,因为AOP一般切入是service层方法...

    实现功能权限校验的功能有多种方法,其一使用拦截器拦截请求,其二是使用AOP抛异常。

    首先用拦截器实现未登录时跳转到登录界面的功能。注意这里没有使用AOP切入,而是用拦截器拦截,因为AOP一般切入的是service层方法,而拦截器是拦截控制器层的请求,它本身也是一个处理器,可以直接中断请求的传递并返回视图,而AOP则不可以。

    1.使用拦截器实现未登录时跳转到登录界面的功能

    1.1 拦截器SecurityInterceptor

    package com.jykj.demo.filter;

    import java.io.PrintWriter;

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletResponse;

    import javax.servlet.http.HttpSession;

    import org.springframework.web.servlet.HandlerInterceptor;

    import org.springframework.web.servlet.ModelAndView;

    import com.alibaba.fastjson.JSON;

    import com.jykj.demo.util.Helper;

    import com.jykj.demo.util.Result;

    public class SecurityInterceptor implements HandlerInterceptor{

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

    throws Exception {

    System.out.println("SecurityInterceptor:"+request.getContextPath()+","+request.getRequestURI()+","+request.getMethod());

    HttpSession session = request.getSession();

    if (session.getAttribute(Helper.SESSION_USER) == null) {

    System.out.println("AuthorizationException:未登录!"+request.getMethod());

    if("POST".equalsIgnoreCase(request.getMethod())){

    response.setContentType("text/html; charset=utf-8");

    PrintWriter out = response.getWriter();

    out.write(JSON.toJSONString(new Result(false,"未登录!")));

    out.flush();

    out.close();

    }else{

    response.sendRedirect(request.getContextPath()+"/login");

    }

    return false;

    } else {

    return true;

    }

    }

    @Override

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

    ModelAndView modelAndView) throws Exception {

    // TODO Auto-generated method stub

    }

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)

    throws Exception {

    // TODO Auto-generated method stub

    }

    }

    1.2.spring-mvc.xml(拦截器配置部分)

    这里需要特别说明:拦截器拦截的路径最好是带有后缀名的,否则一些静态的资源文件不好控制,也就是说请求最好有一个统一的格式如 .do 等等,这样匹配与过滤速度会非常快。如果不这样,例如 用 /** 来拦截所有的请求,则页面渲染速度会非常慢,因为资源文件也被拦截了。

    2.使用AOP实现功能权限校验

    对于功能权限校验也可以类似地用拦截器来实现,只不过会拦截所有的请求,对不需要权限校验的请求没有很好的过滤功能,所以采用AOP指定拦截需要校验的方法的方式来实现之。

    2.1 切面类 PermissionAspect

    package com.jykj.demo.filter;

    import java.io.IOException;

    import java.lang.reflect.Method;

    import org.aspectj.lang.JoinPoint;

    import org.aspectj.lang.reflect.MethodSignature;

    import org.springframework.beans.factory.annotation.Autowired;

    import com.jykj.demo.annotation.ValidatePermission;

    import com.jykj.demo.exception.AccessDeniedException;

    import com.jykj.demo.service.SysUserRolePermService;

    /**

    * 事件日志 切面,凡是带有 @ValidatePermission 以及@ResponseBody注解 控制器 都要进行 功能权限检查,

    * 若无权限,则抛出AccessDeniedException 异常,该异常将请求转发至一个控制器,然后将异常结果返回

    * @author Administrator

    *

    */

    public class PermissionAspect {

    @Autowired

    SysUserRolePermService sysUserRolePermService;

    public void doBefore(JoinPoint jp) throws IOException{

    System.out.println(

    "log PermissionAspect Before method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName());

    Method soruceMethod = getSourceMethod(jp);

    if(soruceMethod!=null){

    ValidatePermission oper = soruceMethod.getAnnotation(ValidatePermission.class);

    if (oper != null) {

    int fIdx = oper.idx();

    Object[] args = jp.getArgs();

    if (fIdx>= 0 &&fIdx

    int functionId = (Integer) args[fIdx];

    String rs = sysUserRolePermService.permissionValidate(functionId);

    System.out.println("permissionValidate:"+rs);

    if(rs.trim().isEmpty()){

    return ;//正常

    }

    }

    }

    }

    throw new AccessDeniedException("您无权操作!");

    }

    private Method getSourceMethod(JoinPoint jp){

    Method proxyMethod = ((MethodSignature) jp.getSignature()).getMethod();

    try {

    return jp.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());

    } catch (NoSuchMethodException e) {

    e.printStackTrace();

    } catch (SecurityException e) {

    e.printStackTrace();

    }

    return null;

    }

    }

    2.2自定义注解ValidatePermission

    package com.jykj.demo.annotation;

    import java.lang.annotation.Documented;

    import java.lang.annotation.ElementType;

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    import java.lang.annotation.Target;

    /**

    * @Descrption该注解是标签型注解,被此注解标注的方法需要进行权限校验

    */

    @Target(value = ElementType.METHOD)

    @Retention(value = RetentionPolicy.RUNTIME)

    @Documented

    public @interface ValidatePermission {

    /**

    * @Description功能Id的参数索引位置 默认为0,表示功能id在第一个参数的位置上,-1则表示未提供,无法进行校验

    */

    int idx() default 0;

    }

    说明: AOP切入的是方法,不是某个控制器请求,所以不能直接返回视图来中断该方法的请求,但可以通过抛异常的方式达到中断方法执行的目的,所以在before通知中,如果通过验证直接return返回继续执行连接点方法,否则抛出一个自定义异常AccessDeniedException来中断连接点方法的执行。该异常的捕获可以通过系统的异常处理器(可以看做控制器)来捕获并跳转到一个视图或者一个请求。这样就达到拦截请求的目的。所以需要配置异常处理器。

    2.3 spring-mvc.xml(异常处理器配置,以及aop配置)

    forward:/accessDenied

    expression="@annotation(com.jykj.demo.annotation.ValidatePermission)

    and @annotation(org.springframework.web.bind.annotation.ResponseBody)

    and execution(* com.jykj.demo.controller..*.*(..)) " />

    2.4 注解需要进行功能校验的控制器请求

    @RequestMapping(value = "/moduleAccess.do", method = RequestMethod.POST, produces="text/html;charset=utf-8")

    @ResponseBody

    @ValidatePermission

    public String moduleAccess(int fid,String action,FrmModule module) {

    System.out.println("fid:"+fid+",action:"+action);

    int rs = -1;

    try{

    if(Helper.F_ACTION_CREATE.equals(action)){

    rs = moduleService.access(module,Helper.DB_ACTION_INSERT);

    //module.setModuleid(rs);

    module = moduleService.selectByPrimaryKey(rs);

    }else if(Helper.F_ACTION_EDIT.equals(action)){

    rs = moduleService.access(module,Helper.DB_ACTION_UPDATE);

    module = moduleService.selectByPrimaryKey(module.getModuleid());

    }else if(Helper.F_ACTION_REMOVE.equals(action)){

    rs = moduleService.access(module,Helper.DB_ACTION_DELETE);

    }else{

    return JSON.toJSONString(new Result(false,"请求参数错误:action"));

    }

    }catch(Exception e){

    e.printStackTrace();

    return JSON.toJSONString(new Result(false,"操作失败,出现异常,请联系管理员!"));

    }

    if(rs<0){

    return JSON.toJSONString(new Result(false,"操作失败,请联系管理员!"));

    }

    return JSON.toJSONString(new Result(true,module));

    }

    2.5 异常处理器将请求转发到的控制器请求 forward:/accessDenied

    @RequestMapping(value = "/accessDenied",produces = "text/html;charset=UTF-8")

    @ResponseBody

    public String accessDenied(){

    return JSON.toJSONString(new Result(false,"您没有权限对此进行操作!"));

    }

    2.6 请求校验不通过时 由上述的控制器返回 结果本身

    如下所示:

    {"info":"您没有权限对此进行操作!","success":false}

    2.7 功能校验service 示例

    /**

    * 校验当前用户在某个模块的某个功能的权限

    * @param functionId

    * @return 空字符串表示 有权限 ,否则是错误信息

    * @throws Exception

    */

    public String permissionValidate(int functionId){

    Object o = request.getSession().getAttribute(Helper.SESSION_USER);

    //if(o==null) throw new AuthorizationException();

    SysUser loginUser= (SysUser)o;

    if(loginUser.getUserid() == 1) return "";

    try{

    return mapper.permissionValidate(loginUser.getUserid(),functionId);

    }catch(Exception ex){

    ex.printStackTrace();

    return "数据库操作出现异常!";

    }

    }

    说明: 这里仅仅是对带有@ValidatePermission和@ResponseBody注解的controller包及其子包所有方法进行切入,这样肯定是不够通用的,应该是对带有@ValidatePermission的方法进行切入,在切面类中通过判断该方法是否有@ResponseBody注解来抛出不一样的异常,若带有@ResponseBody注解则抛出上述的异常返回json字符串,

    否则,应该抛出另一个自定义异常然后将请求重定向到一个合法的视图如error.jsp .

    通过客户端发送 /moduleAccess.do 请求,该请求对应的方法同时具有@ValidatePermission和@ResponseBody,并且有功能Id参数fid,这样AOP可以切入该方法,执行doBefore通知,通过功能参数fid,对它结合用户id进行权限校验,若校验通过直接返回,程序继续执行,否则抛出自定义异常AccessDeniedException,该异常由系统捕获(需要配置异常处理器)并发出请求 forward:/accessDenied ,然后对应的控制器 /accessDenied 处理该请求返回一个包含校验失败信息的json给客户端。这样发送 /moduleAccess.do 请求,如果校验失败,转发到了/accessDenied请求,否则正常执行。绕了这么一个大圈子才实现它。

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    展开全文
  • 权限校验是很多情况都会用到,结合Java注解和拦截器,直接在Controller层的方法上添加一个注解,可以无侵入式进行权限校验。 一.Java注解 1.RequestMapping 我们打开一个最常用Spring注解 可以看到,...

    自己实现注解式权限校验(SpringBoot)

    权限校验是很多情况都会用到的,结合Java注解和拦截器,直接在Controller层的方法上添加一个注解,可以无侵入式的进行权限校验。

    一.Java注解

    1.RequestMapping

    我们打开一个最常用的Spring注解

    在这里插入图片描述
    可以看到,RequestMapping注解上,还有几个注解,分别代表

    Target:注解目标(如:可以在方法、类、参数中使用)

    Retention:是注解的注解,称为元注解。 分为3类 :

    • RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
    • RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
    • RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

    Documented:表明这个注释是由 javadoc记录的。

    Mapping:Spring的注解,不做过多说明

    二.自定义一个判断权限的注解

    1.Permission.java

    在了解了注解的组成后,我们可以尝试着,自己编写一个注解。用来判断权限。

    注意:此注解使用了Spring的AliasFor注解,改注解成对出现,表示双方互为别名属性。(既设置了name,则value同样有该值),使用AliasFor注解后,必须用Spring的AnnotationUtils工具获取注解才可以达到上述效果。

    import org.springframework.core.annotation.AliasFor;
    
    import java.lang.annotation.*;
    
    /**
     * @author litong
     * @date 2019/11/29 16:18
     */
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Documented
    public @interface Permission {
    
    	/**
    	 * 权限
    	 */
    	@AliasFor("value")
    	PermissionEnum[] name() default {};
    
    	/**
    	 * 权限
    	 */
    	@AliasFor("name")
    	PermissionEnum[] value() default {};
    
    }
    
    

    2.PermissionEnum.java

    权限的枚举,使用枚举可以更好的说明参数,易读性高,且可以减少报错几率。

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    
    /**
     * @author litong
     * @date 2019/11/29 16:20
     */
    @Getter
    @AllArgsConstructor
    public enum PermissionEnum {
       /**
        * 用户管理权限
        */
       USER(1, "用户管理权限"),
       /**
        * 教师管理权限
        */
       TEACHER(2, "教师管理权限"),
    
       /**
        * 无需校验,
        */
       NO(-99999, "无需权限"),
       ;
       /**
        * 权限编码
        */
       private Integer code;
       /**
        * 权限名称
        */
       private String msg;
    }
    

    三.自定义拦截器

    注:此拦截器,一般还会有未登录拦截,Token解析,用户注入、权限校验等功能,现只展示其中之一,权限校验。

    1.AuthenticationInterceptor.java

    /**
     * @Author: litong
     * @Date: 2019-09-20 11:50
     * @Description: 拦截器
     */
    @Slf4j
    public class AuthenticationInterceptor implements HandlerInterceptor {
        /**
    	 * 在业务处理器处理请求之前被调用
    	 *
    	 * @param request
    	 * @param response
    	 * @param handler
    	 * @return
    	 * @throws Exception
    	 */
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    		// 如果不是映射到方法直接通过
    		if (!(handler instanceof HandlerMethod)) {
    			return true;
    		}
             // 获取方法中的注解
             HandlerMethod handlerMethod = (HandlerMethod) handler;
    		Method method = handlerMethod.getMethod();
            // 省略判断是否需要登录的方法.....
            // 省略Token解析的方法.....
            // 此处根据自己的系统架构,通过Token或Cookie等获取用户信息。
            UserInfo userInfo = userService.getUserByToken(token);
            
            // 获取类注解
    		Permission permissionClass = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Permission.class);
    		// 获取方法注解
    		Permission permissionMethod = AnnotationUtils.findAnnotation(method,Permission.class);
    		// 判断是否需要权限校验
    		if (permissionClass == null && permissionMethod == null) {
    			// 不需要校验权限,直接放行
    			return true;
    		}
            // 获取该方法注解,优先级:方法注解>类注解
    		PermissionEnum[] permissionEnums;
    		if (permissionClass != null && permissionMethod == null) {
    			// 类注解不为空,方法注解为空,使用类注解
    			permissionEnums = permissionClass.name();
    		} else if (permissionClass == null) {
    			// 类注解为空,使用方法注解
    			permissionEnums = permissionMethod.name();
    		} else {
    			// 都不为空,使用方法注解
    			permissionEnums = permissionMethod.name();
    		}
            // 校验该用户是否有改权限
            // 校验方法可自行实现,拿到permissionEnums中的参数进行比较
            if(userService.checkPermissionForUser(userInfo,permissionEnums)){
                // 拥有权限
                return true;
            } else {
                // 抛出自定义异常,可在全局异常捕获后自行处理。
                throw new AuthTokenException(CheckConstants.PERMISSION_ERROR);
            }
    
        }
        
        /**
    	 * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
    	 *
    	 * @param httpServletRequest
    	 * @param httpServletResponse
    	 * @param o
    	 * @param modelAndView
    	 * @throws Exception
    	 */
    	@Override
    	public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    
    	}
    
    	/**
    	 * 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
    	 *
    	 * @param httpServletRequest
    	 * @param httpServletResponse
    	 * @param o
    	 * @param e
    	 * @throws Exception
    	 */
    	@Override
    	public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    	}
    }
    

    四.使用

    1.类(该类中,所有方法校验权限)

    /**
     * @author litong
     * @date 2019/9/20 13:26
     */
    @RestController
    @RequestMapping("/auth")
    @AllArgsConstructor
    @Slf4j
    @Permission({PermissionEnum.USER})
    public class AuthController {
        
    }
    

    2.方法(此方法校验权限,优先级大于类上的注解)

    @PostMapping("/get")
    @Permission({PermissionEnum.USER})
    public R get() {
    }
    
    展开全文
  • 创建DynamicDataSource类扩展SpringAbstractRoutingDataSource抽象类,重写 determineCurrentLookupKey() 方法5.注册刚创建实体DynamicDataSource并把数据源信息传进去6.创建注解@UseDataSource切面三、配置...

    前言

    在有些场景中需要使用到组合开发(系统一需要使用到系统二所提供的接口,但是系统一已经拥有自己的一套检验逻辑,并且系统二希望使用系统一中的用户进行权限检验),这时就。。。。卧槽!什么乱七八糟的,不知道该怎么讲。就说我现在的应用场景吧!

    本公司把项目外包给乙放。然后一些重要的接口又不想要交给乙方完成,就有自己公司来提供接口,给乙方调用。且乙方项目中的用户进行权限校验。提供接口的程序并没有自己的用户。

    当然仅仅作为参考,取之长嵌入到自己公司项目中去,取之短反馈给我,我这边及时改进。

    事先准备

    1.spring-boot 2.2.5.RELEASE
    2.mysql数据库两台(一台记录用户操作,另一台负责连接用户表,获取相关用户信息)

    实现步骤

    一、新建spring boot项目,并引入依赖

    在pom文件中引入所需要的依赖包

    	<!-- 添加Web依赖(servlet) -->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- 引入lombak插件 -->
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
        </dependency>
    
    	<!-- swagger2 -->
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
          <version>${springfox.swagger2.version}</version>
        </dependency>
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
          <version>${springfox.swagger2.version}</version>
        </dependency>
        
    	<dependency>
          <groupId>dom4j</groupId>
          <artifactId>dom4j</artifactId>
          <version>1.6</version>
        </dependency>
        
    	<!--使用 Spring Data JPA -->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        
        <!-- mysql 链接依赖包 -->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
        </dependency>
    
    	<!-- JWT算法 -->
        <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.7.0</version>
        </dependency>
    
        <!-- JSON 转换 -->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.56</version>
        </dependency>
    

    二、配置多数据源

    配置多个数据源(这里因为一个数据源要存储数据,另一个数据源为了获取其他应用的用户信息)。该多数据源方案仅供参考。

    1.修改配置文件

    spring:
      application:
        name: yqzl
      datasource:
        yqzl:
          pool: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: XXXXXX
          jdbc-url: jdbc:mysql://192.168.1.1:3306/XXX?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
        user:
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: XXXXXX
          jdbc-url: jdbc:mysql://192.168.1.2:3306/XXX?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
      jpa:
        hibernate:
          ddl-auto: update
        show-sql: false
        database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
        open-in-view: false
    

    2.创建切换数据源注解

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CurDataSource {
    
        String value() default "";
    
    }
    

    3.创建数据源类,读取配置文件中配置的数据源

    @Component
    @Data
    @ConfigurationProperties(prefix ="spring.datasource")
    @EnableConfigurationProperties
    public class DBproperties {
    
        private HikariDataSource yqzl;
    
        private HikariDataSource user;
    
    }
    

    3.创建DynamicDataSource类扩展Spring的AbstractRoutingDataSource抽象类,重写 determineCurrentLookupKey() 方法

    /**
     * DynamicDataSource扩展Spring的AbstractRoutingDataSource抽象类,重写 determineCurrentLookupKey() 方法
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        /**
         * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
         * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
         */
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        /**
         * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
         *  @param defaultTargetDataSource 默认数据源
         * @param targetDataSources       目标数据源
         */
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
    
    
        @Override
        protected Object determineCurrentLookupKey() {
            return getDataSource();
        }
    
        public static void setDataSource(String dataSource) {
            CONTEXT_HOLDER.set(dataSource);
        }
    
        public static String getDataSource() {
            return CONTEXT_HOLDER.get();
        }
    
        public static void clearDataSource() {
            CONTEXT_HOLDER.remove();
        }
        
    }
    

    5.注册刚创建的实体DynamicDataSource并把数据源信息传进去

    @Configuration
    public class DynamicDataSourceConfig {
    
        @Autowired
        private DBproperties properties;
    
        @Bean
        @Primary
        public DynamicDataSource dataSource() {
            Map<Object, Object> targetDataSources =new HashMap<>();
            targetDataSources.put("yqzl", properties.getYqzl());
            targetDataSources.put("user", properties.getUser());
            return new DynamicDataSource(properties.getYqzl(),targetDataSources);
        }
    
    }
    

    6.创建注解@UseDataSource的切面

    @Slf4j
    @Aspect
    @Component
    public class DataSourceAspect implements Ordered {
        @Pointcut("@annotation(hyzksz.cn.datasource.CurDataSource)")
        public void dataSourcePointCut() {}
    
        @Around("dataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            CurDataSource ds = method.getAnnotation(CurDataSource.class);
            if (ds == null) {
                DynamicDataSource.setDataSource("yqzl");
    //            log.warn("----数据源使用----- 当前数据源为:yqzl");
            } else {
                DynamicDataSource.setDataSource(ds.value());
    //            log.warn("----数据源使用----- 当前数据源为:"+ds.value());
            }
            try {
                return point.proceed();
            } finally {
                DynamicDataSource.clearDataSource();
            }
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }
    

    到这里多数据源就已经配置完成了,如果还有不懂请参考我的另一篇文章 Spring Boot中动态数据源切换

    三、配置swagger方便测试

    这里就是swagger的一些基本配置。

    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
    
        @Bean
        public Docket buildDocket() {
    
            //添加query参数start
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(buildApiInf())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("hyzksz.cn.controller"))//controller路径
                    .paths(PathSelectors.any()).build()
                    .securitySchemes(securitySchemes());
    //                .securityContexts(securityContexts());
            return docket;
        }
    
        private List<ApiKey> securitySchemes() {
            List<ApiKey> apiKeyList = new ArrayList();
            apiKeyList.add(new ApiKey("x-auth-token", "x-auth-token", "header"));
            return apiKeyList;
        }
    
        private List<SecurityContext> securityContexts() {
            List<SecurityContext> securityContexts = new ArrayList<>();
            securityContexts.add(
                    SecurityContext.builder()
                            .securityReferences(defaultAuth())
                            .forPaths(PathSelectors.regex("^(?!auth).*$"))
                            .build());
            return securityContexts;
        }
    
        List<SecurityReference> defaultAuth() {
            AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
            AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
            authorizationScopes[0] = authorizationScope;
            List<SecurityReference> securityReferences = new ArrayList<>();
            securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
            return securityReferences;
        }
    
        private ApiInfo buildApiInf() {
            return new ApiInfoBuilder()
                    .title("XXXX相关接口")
                    .description("接口文档")
                    .version("1.0v")
                    .build();
        }
    
    }
    

    配置后的效果(可以让你在请求头中添加校验信息)
    在这里插入图片描述

    四、对接口权限进行校验

    这里就是比较重要的一步了,刚好我自己也回顾一下。有需要权限校验但又不想使用框架去检验的可以参考我这个去写。

    1.创建接口请求记录的操作类

    可以根据自己想要记录的数据,自行创建。我这里是记录了请求该接口的用户(另一个系统的用户)、请求的路径、以及发送请求的ip地址和请求时间。

    @Data
    @Entity
    @Table(name = "operation_data")
    @EntityListeners(AuditingEntityListener.class)
    public class OperationData {
    
        @Id
        @Column(length = 36, nullable = false)
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        private String id;
    
        @CreatedDate
        private Date createDate;
    
        private String ip;
    
        private String path;
    
        private String userName;
    
        private String userId;
    
        public OperationData(){}
    
        public OperationData(String ip,String path,String userName,String userId){
            this.ip = ip;
            this.path = path;
            this.userName = userName;
            this.userId = userId;
        }
    
        public OperationData(String ip,String path){
            this.ip = ip;
            this.path = path;
        }
    }
    

    2.创建filter。用于第一时间处理请求。

    @Configuration
    @Component
    @Slf4j
    public class JudgeZuulRequest {
    
        @Autowired
        private OperationDataRep operationDataRep;
        
        @Bean
        public FilterRegistrationBean judgeZuulRequestFilterBean() {
            FilterRegistrationBean judgeZuulRequestFilterBean = new FilterRegistrationBean();
            judgeZuulRequestFilterBean.setFilter(new JudgeZuulRequestFilter());
            judgeZuulRequestFilterBean.setOrder(Ordered.LOWEST_PRECEDENCE);
            List<String> urlPatterns = new ArrayList();
            urlPatterns.add("/*");
            judgeZuulRequestFilterBean.setUrlPatterns(urlPatterns);
            return judgeZuulRequestFilterBean;
        }
    
        public class JudgeZuulRequestFilter implements Filter {
            @Override
            public void init(FilterConfig filterConfig) {
    
            }
    
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
                HttpServletRequest req = (HttpServletRequest) servletRequest;
    
                String path = req.getRequestURI();
                try {
                    //过滤调预检请求
                    if (!StringUtils.isEmpty(req.getHeader("access-control-request-headers"))) {
                        filterChain.doFilter(servletRequest, servletResponse);
                        log.error("access-control-request-headers is NULL!");
                    } else {
                        if (path.contains("/swagger-ui.html")
                                || path.contains("/favicon.ico")
                                || path.contains("/webjars/")
                                || path.contains("/v2/")
                                || path.contains("/swagger-resources")
                        ) {
                            //过滤掉swagger的请求,其他请求做记录
                            filterChain.doFilter(servletRequest, servletResponse);
                        } else {
                            System.out.println(path);
                            String xAuthToken = req.getHeader("x-auth-token");
                            if(path.contains("/bank/getRequestKey")){
                                //请求KEY接口
                                operationDataRep.save(new OperationData(getIpAddr(req), path));
                                filterChain.doFilter(servletRequest, servletResponse);
                            }else if(!StringUtils.isEmpty(xAuthToken)){
                                Map<String,Object> map = JWTToken.getMapFromJwt(xAuthToken);
                                //判断type是否是用作对应的请求
                                if(path.startsWith("/"+ map.get("type"))){
                                    operationDataRep.save(new OperationData(getIpAddr(req), path,(String)map.get("username"),""+map.get("id")));
                                    for(String key : map.keySet()){ req.setAttribute(key,map.get(key)); }
                                    filterChain.doFilter(req, servletResponse);
                                }else{
                                    req.getRequestDispatcher("/error/keyTypeIsError").forward(req, servletResponse);
                                }
                            }else{
                                req.getRequestDispatcher("/error/noKey").forward(req, servletResponse);
                            }
                        }
                    }
                }catch (Exception e){
                    req.setAttribute("ERROR",e);
                    try {
                        req.getRequestDispatcher("/error/errorHandler").forward(req, servletResponse);
                    } catch (ServletException | IOException ex) {
                        ex.printStackTrace();
                    }
                }
    
            }
    
            @Override
            public void destroy() {
    
            }
    
        }
        //获取ip地址
    	private static String getIpAddr(HttpServletRequest request) {
            String ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                String localIp = "127.0.0.1";
                String localIpv6 = "0:0:0:0:0:0:0:1";
                if (ipAddress.equals(localIp) || ipAddress.equals(localIpv6)) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                        ipAddress = inet.getHostAddress();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            String ipSeparate = ",";
            int ipLength = 15;
            if (ipAddress != null && ipAddress.length() > ipLength) {
                if (ipAddress.indexOf(ipSeparate) > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(ipSeparate));
                }
            }
            return ipAddress;
        }
    }
    

    1.在filter中过滤掉预检请求以及swagger请求。

    2.对其他的请求进行判断。其中/bank/getRequestKey为获取请求key的接口,所以也不做拦截。但是数据库会记录操作。在这里插入图片描述
    3.判断请求类型是否正确,如果正确则放行。不正确则返回错误!

    看到这里可能还是会有点蒙:
    1.xAuthToken是什么东西?干嘛的?
    2.Map<String,Object> map = JWTToken.getMapFromJwt(xAuthToken); 这个map是怎么获取到的?map里面又有哪些内容?
    且听我讲完:
    1.x-auth-token/bank/getRequestKey接口中所返回的Key值。用于放在swagger页面中,在请求头中带上做权限校验使用。

    2.Map<String,Object> map = JWTToken.getMapFromJwt(xAuthToken); 是解析x-auth-token所获取的map对象。至于其中有那哪些值还要从/bank/getRequestKey接口说起

    @PostMapping("getRequestKey")
        @ApiOperation(value = "获取请求码")
        public Result getRequestKey(String username,String password,String type){
            if(StringUtils.isNullOrEmpty(username)) return new Result("用户名为空!userName : "+username,null);
            if(StringUtils.isNullOrEmpty(type)) return new Result("请选择操作类型!type : "+type,null);
    
            return bankService.getRequestKey(username,password,type);
        }
    

    其中username为其他系统的用户名,password为登录密码,type为请求类型

    public Result getRequestKey(String username, String password, String type){
            Map<String,Object> userMap = userRepository.findUserByUserName(username);
            if(userMap == null || userMap.isEmpty()){
                return new Result("未找到当前用户的用户信息,请检查用户名是否正确!username : "+username,null);
            }
            //用户密码的加密方式,目前设定为MD5加密
            if(!(userMap.get("password")!=null && userMap.get("password").equals(MD5.getMd5(password)))){
                return new Result("用户名密码错误!",null);
            }
            try {
                Map<String,Object> encryptMap = new LinkedHashMap<>(userMap);
                encryptMap.put("type",type);
                return new Result(JWTToken.buildJwt(encryptMap));
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return new Result("字符串加密出错!",null);
        }
    

    findUserByUserName为切换数据源后查找到其他系统的用户信息
    @CurDataSource("user")就是对数据源的切换操作

    	@CurDataSource("user")
        @Query(value = "select id as id, user_name as username , pwd as password from `user` where user_name = ?1 ",
                nativeQuery = true)
        Map<String,Object> findUserByUserName(String username);
    

    这里查询出来的结果就是刚才提到的Map<String,Object> map = JWTToken.getMapFromJwt(xAuthToken); 中map的信息
    JWTToken.buildJwt(encryptMap)就是把查询出来的map信息编译为JWT字符串

    public static String buildJwt(Map<String,Object> encryptMap) {
            String str = YqzlApplication.encrypDES.encrypt(encryptMap);
            //设置过期时间一分钟
            long time = System.currentTimeMillis() + 60 * 1000;
            String jwt = Jwts
                    .builder()
                    //SECRET_KEY是加密算法对应的密钥,这里使用额是HS256加密算法
                    .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                    //expTime是过期时间
                    .setExpiration(new Date(time))
                    //该方法是在JWT中加入值为vaule的key字段
                    .claim(KEY, str)
                    .compact();
            return jwt;
        }
    

    String str = YqzlApplication.encrypDES.encrypt(encryptMap);把查询出来的用户信息转为加密字符串
    这种转化可以自行百度转换 , 按照自己的加密方式进行。

    最后接着前面的filter说

    for(String key : map.keySet()){ req.setAttribute(key,map.get(key)); }
    

    把map中的值放到req中。方便接口中调用。

    最后就是对的filter的错误进行拦截处理。

    总结

    谢谢各位老铁的捧场!!!

    展开全文
  • 上级给了一个权限校验小程序后端demo,是通过自定义注解、jwt来实现校验token。但是我就是没看明白这个拦截器部分,请问一下Authorize是加到哪里注解?是加到小程序发起请求里吗? 自定义注解Authorize: ...
  • Java注解实现权限管理

    2020-07-02 22:21:09
    一个简单的权限控制场景,已知登录用户id,判断这个用户是否存在数据库中,如果不存在则不允许进行任何操作。 关于java注解介绍请参见 Java自定义注解实现权限管理 基础实现 在每个controller方法中添加用户校验代码...

    一个简单的权限控制场景,已知登录用户id,判断这个用户是否存在数据库中,如果不存在则不允许进行任何操作。

    关于java注解介绍请参见 Java自定义注解实现权限管理

    基础实现

    在每个controller方法中添加用户校验代码,这种可以控制到方法级,但是每个方法都要维护这段重复逻辑。

    @RequestMapping(value = "/task/progress", method = RequestMethod.GET)
        public RestRsp getLabelTaskProgress(
                @RequestParam(name = "taskId", defaultValue = "-1") long taskId,
                @Visitor long userId
        ) {
            // 校验用户
            if (!labelService.checkUserValid(userId)) {
                return RestRsp.success(new ListRsp());
            }
    
            // 获取标注进度
            ... 省略
            return RestRsp.success(listRsp);
        }
    

    拦截器实现

    spring的拦截器实现,好处是增加在进入controller方法前提前拦截非法用户,但是设计到不同controller类或方法不同权限时,只能通过api路径区分,不方便。

    @Slf4j
    public class AuthInterceptor extends HandlerInterceptorAdapter {
    
        private final static String LABEL_PATH_PATTERN = "^/*webapi/+label/*.*$";
        private static final String ERROR_MSG = "您没有权限,请联系管理";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                Object handler) {
            if (debugHost() && hasDebugParam(request)) {
                return true;
            }
    
            String uri = request.getRequestURI();
            log.info("uri:{}", uri);
            String username = SsoUserInfo.getUserName();
    
            if (Pattern.matches(LABEL_PATH_PATTERN, uri)) {
                Set<String> managerSet = LABEL_MANAGER_SET.get();
                if (!managerSet.contains(username)) {
                    throw new ServiceException(ErrorCode.PERMISSION_DENIED);
                }
    
            } else {
                Set<String> userSet = USER_SET.get();
                if (!userSet.contains(username)) {
                    throw ServiceException.ofMessage(ErrorCode.PERMISSION_DENIED, ERROR_MSG);
                }
            }
            return true;
        }
    }
    
    

    注解实现

    注解+拦截器的实现方式,既可以实现不同粒度的权限控制,也可以集中管理权限。

    • 定义权限枚举变量
    public enum  AuthEnum {
    
        USER_LABEL("label_user", "标注用户", "非标注用户禁止使用"),
        ;
    
        private String code;
        private String desc;
        private String info;
    
        AuthEnum(String code, String desc, String info) {
            this.code = code;
            this.desc = desc;
            this.info = info;
        }
    
        public String getCode() {
            return this.code;
        }
    
        public String getDesc() {
            return this.desc;
        }
    
        public String getInfo() {
            return this.info;
        }
    }
    
    • 定义注解
    @Documented
    @Retention(value = RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public @interface AuthAnn {
        AuthEnum[] authType();
    }
    
    • 定义拦截器
    @Slf4j
    @Component
    public class AuthInterceptor extends HandlerInterceptorAdapter {
    
        @Autowired
        private LabelService labelService;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                Object handler) {
    
            Long userId = SSOHelper.getUserId(request);
    
            AuthAnn authAnn = ((HandlerMethod) handler).getMethodAnnotation(AuthAnn.class);
            if (authAnn == null || ObjectUtils.isEmpty(authAnn.authType())) {
                authAnn = ((HandlerMethod) handler).getBeanType().getAnnotation(AuthAnn.class);
            }
            if (authAnn == null || ObjectUtils.isEmpty(authAnn.authType())) {
                return true;
            }
    
            //log.info("userId:{}", userId);
            AuthEnum[] authEnums = authAnn.authType();
    
            for (AuthEnum authEnum : authEnums) {
                if (AuthEnum.USER_LABEL.equals(authEnum)) {
                    if (!labelService.checkUserExist(userId)) {
                        log.info("Invalid Label User, userId:{}", userId);
                        throw ServiceException.ofMessage(ErrorCode.PERMISSION_DENIED, authEnum.getInfo());
                    }
                }
            }
            return true;
        }
    }
    
    • 注册拦截器
    @Configuration
    @Slf4j
    public class ProphetInterceptorConfiguration implements WebMvcConfigurer {
        @Autowired
        private AuthInterceptor authInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) { 
            String[] paths = passportProperties.urlPaths();
            registry.addInterceptor(AuthInterceptor).addPathPatterns(paths);
        }
    }
    
    • 添加权限
    @Slf4j
    @RestController
    @RequestMapping("/api/label")
    @AuthAnn(authType = AuthEnum.USER_LABEL)
    public class LabelController {
    
    }
    
    展开全文
  • (2)新增一个AOP切面,用于将自定义注解标注的方法和shiro权限校验关联起来 (3)校验当前用户是否有足够的权限去访问保护资源 二:编码实现 1、新建PermissionResolver接口 import java.util.Collections; impo
  • Java为什么能运行期间动态确定调用方法?是怎么实现的呢。 在继承关系中,运行时动态确定要调用的方法的...第二、如果在才、C中找到与常量中描述符和简单名称都相同的方法,则进行权限校验,如果通过,则返回这个方法
  • AOP实现权限拦截

    2021-02-01 17:49:15
    AOP实现权限拦截注解名称:CheckUnSysAdmin注解实现类:CommonAspectController层方法上引入 注解名称:CheckUnSysAdmin package com.sf.XWFS.aop; import java.lang.annotation.*; /** * @author cc * Desc 校验除...
  • 基于Spring Security和 JWT的权限系统设计 Spring Boot 工程集成全局唯一ID生成器 Vesta Mybatis-Plus 真好用(乡村爱情加持) 啥?听说你还在手写复杂的参数校验? 如何自制一个Spring Boot Starter并推送到远...
  • 由于项目需要,做c#客户端数据库连接串首先肯定不...最后的方法就是c#这边调用java的api返回连接串(它们那边做了不知道什么权限的)使用HttpRequest,一下是postman里结果(两个入参用于实现每次请求的校验)...
  • 前后端基于json进行交互,接口通过JWT无状态token进行权限校验,使用redis或者数据库进行token缓存,接口完全采用Restful风格,实现按钮级权限控制,可以作为开发项目脚手架,做为基础项目。该版本为mybatis版,...
  • Java中动态代理使用

    千次阅读 2016-06-20 23:08:15
    代理模式使用代理模式必须要让代理类和目标类实现相同接口,客户端通过代理类来调用目标方法,代理类会将所有的方法调用分派到目标对象上反射执行,还可以在分派过程中添加”前置通知”和后置处理(如在调用目标...
  •  声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,...
  • My-Blog : My Blog 是由 SpringBoot + Mybatis + Thymeleaf 等技术实现的 Java 博客系统,页面美观、功能齐全、部署简单及完善代码,一定会给使用者无与伦比体验。 相关文章: 想要搭建个人博客?我调研了 ...
  • java面试题典 java 面试题 经典

    热门讨论 2010-06-18 13:42:36
    8. 用JAVA实现一种排序,JAVA类实现序列化的方法(二种)? 如在COLLECTION框架中,实现比较要实现什么样接口? 49 9. 编程:编写一个截取字符串函数,输入为一个字符串和字节数,输出为按字节截取字符串。 但是...
  • Java代理模式

    2018-02-05 18:37:51
    可以看出代理类ProxySubject和被代理类RealSubject都实现了抽象主题Subject(接口),其中RealSubject是ProxySubject一个成员属性,ProxySubject调用visit()方法会调用RealSubjectvisit()方法,同时会加上自己...
  • Java经典入门教程pdf完整版Java私塾跟我学系列JAⅥ篇网址:htp:/www.lavass.Cn电话:010-86835215 3;Java是一种软件运行平台 3.1:什么是软件运行平台 ...Java虚拟杋是在真实札器中用软件模拟实现的—种想象...
  • HDFS java API 二次开发

    2020-04-11 13:46:44
    文章目录HDFS读写流程API java实现 HDFS读写流程 1.客户端通过调用 DistributedFileSystem create方法,创建一个新文件。 2.DistributedFileSystem 通过 RPC(远程过程调用)调用 NameNode,去创建一个没有...
  • java 面试题 总结

    2009-09-16 08:45:34
    声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其...
  • 问:单例模式 答:spring里面bean一般是单例。 问:工厂模式 答:spring创建bean就是...答:springAOP就是动态代理动态代理实现,mybatis分页,控制器校验参数,事物控制,拦截器实现权限校验。 问:监听...
  • 前面讲了静态代理,现在来总结动态代理,java动态代理有两种,一种是JDK动态代理,一种是cglib动态代理。...那么,同样我们在生产商品之前我们需要进行权限校验。 我们就用到了JDK动态代理。需要
  • 利用拦截器和自定义注解实现未登录拦截实现思路自定义注解拦截器代码实现拦截器加入配置其它微服务中引用使用该登录权限校验代码实现 实现思路 所有需要有登录权限接口先校验是否已登录(登录成功会往redis缓存中...
  • ​ 在方法上加上注解@PreAuth(“xxx”), 根据xxx不同内容,可以判断当前请求是否有访问该接口的权限,如果有则放行,反之则返回无权限. ​ 其中@PreAuth(“xxx”)中 “xxx” 可以填写角色或者资源等来校验(xxx是个...
  • 然而,目前服务路由并没有限制权限这样功能,所有请求都会被毫无保留地转发到具体应用并返回结果,为了实现对客户端请求安全校验权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一...
  • 然而,目前服务路由并没有限制权限这样功能,所有请求都会被毫无保留地转发到具体应用并返回结果,为了实现对客户端请求安全校验权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一...
  • 第3章Java源代码和类、变量 及方法的保护 50 3.1 Java反编译及混淆器的使用 50 3.2从网络资源加载节码文件 55 3.3以任意方式加载字节码文件 60 3.4 加载加密的字节码文件 62 3.5 加载当前目录下的加密字节码文件 65 ...
  • 代理模式使用代理模式必须要让代理类和目标类实现相同接口,客户端通过代理类来调用目标方法,代理类会将所有的方法调用分派到目标对象上反射执行,还可以在分派过程中添加"前置通知"和后置处理(如在调用目标方法...

空空如也

空空如也

1 2 3 4
收藏数 71
精华内容 28
关键字:

java实现方法的权限校验

java 订阅