《应用拆分与平台搭建最佳实践》- 服务化的权限

《应用拆分与平台搭建最佳实践》- 服务化的权限


服务化的权限,也就是权限接口服务化。应用拆分之后,每个应用访问都需要经过授权处理。
授权处理的方式有多种

第一种  基于请求转发的方式

第二种  基于配置权限过滤器,调用远程接口check权限


笔者这里选择第二种方式,第一种方式权限过于集中化,对负载的要求很高。第二种服务化的方式更扁平,应用可以自行决定白名单管理,也可以通过权限平台提供的白名单,仁者见仁。

笔者这里使用的架构是基于spring cloud的方案,所以需要eureka这样肥服务发现系统。


如上图,在eureka上注册的机器一共有4台,login临时充当授权系统,其他为常规子系统。

以www为例,如何使用spring cloud fegin远程调用

首先我们需要配置fegin maven依赖
<!-- 远程调用 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.3.2.RELEASE</version>
</dependency>


然后在login应用中添加权限接口
/**
 *
 * 权限远程调用接口
 *
 * Created by xiaotian.shi on 2017/7/28.
 */
@Controller
public class AuthFeginController {

    private Logger logger = LoggerFactory.getLogger(LoginController.class);

    @Autowired
    private AuthService authService;

    @Value("${app.server.hosts}")
    private String appServer;


    @RequestMapping(value = {"/auth"}, method={RequestMethod.POST})
    @ResponseBody
    public boolean check(
            @RequestParam("loginId") String loginId,
            @RequestParam("target") String target
    ) {

        logger.info("账号访问{} 目标 {}", loginId, target);

        Map<String, List<String>> data = authService.getAuthData();
        if(StringUtils.isEmpty(target)) {
            return false;
        }

        URL targetUrl = null;

        try {
            targetUrl = new URL(target);
        } catch (MalformedURLException e) {

            return false;

        }

        List<String> urlList = data.get(loginId);

        if(CollectionUtils.isEmpty(urlList)) {
            return false;
        }

        boolean isPass = false;

        for(String passUrl : urlList) {

            if(targetUrl.equals(passUrl)) {
                isPass = true;
            }

        }

        return isPass;

    }

    @RequestMapping(value = {"/auth/host"}, method={RequestMethod.POST})
    @ResponseBody
    public String getAuthHost() {

        return appServer;

    }

}

其中一共2个接口,一个权限check,一个获取权限系统的域名地址,当然权限系统复杂可以提供更加复杂的权限接口。


使用权限数据的一方(www)
/**
 * Created by xiaotian.shi on 2017/7/28.
 */
// 指定远程服务器名称
@FeignClient("xiaotian-login")
public interface PlatAuthClient {

    /**
     *
     * 检查是否有访问权限
     *
     * @param loginId 登陆账号
     * @param target 访问目标
     * @return
     */
    @RequestMapping(value = "/auth", method={RequestMethod.POST})
    boolean check(@RequestParam("loginId") String loginId, @RequestParam("target") String target);

    /**
     *
     * 获取权限服务器地址
     *
     * @return
     */
    @RequestMapping(value = {"/auth/host"}, method={RequestMethod.POST})
    public String getAuthHost();

}


权限拦截器
**
 * Created by xiaotian.shi on 2017/7/15.
 */
public class PlatAuthInterceptor implements HandlerInterceptor {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private String loginServer;

    @Autowired
    private PlatAuthClient authClient;

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

        // 登陆的对象
        LoginUser loginUser = null;

        // 获取会话
        HttpSession session = request.getSession();

        Object loginUserObject = session.getAttribute("loginUser");

        // 获取请求地址
        String url = request.getRequestURL().toString();

        String redirectUrl = this.getLoginServer() +"?redirect=" + url ;


        loginUser = (LoginUser) loginUserObject;

        // 有当前页面权限则通过 没有权限跳转到无权限页面
        boolean result = authClient.check(loginUser.getUsername(), url);

        if(result) {
            return true;
        } else {

            // 返回没有权限的信息,这里的实现,
            response.sendRedirect(this.getLoginServer() + "/error401");
            return false;
        }


    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

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

    }

    private String getLoginServer() {

        if(StringUtils.isEmpty(loginServer)) {
            loginServer = authClient.getAuthHost();
        }

        return loginServer;

    }

}

会话拦截器
/**
 * Created by xiaotian.shi on 2017/7/15.
 */
public class PlatSessionInterceptor implements HandlerInterceptor {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private String loginServer;

    @Autowired
    private PlatAuthClient authClient;

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

        // 登陆的对象
        LoginUser loginUser = null;

        // 获取会话
        HttpSession session = request.getSession();

        Object loginUserObject = session.getAttribute("loginUser");

        // 获取请求地址
        String url = request.getRequestURL().toString();;

        String redirectUrl = this.getLoginServer() +"?redirect=" + url ;

        if(loginUserObject != null) {

            loginUser = (LoginUser) loginUserObject;


        } else {

            // 会话不存在不允许访问
            response.sendRedirect(redirectUrl);
            return false;
        }
        // 会话是否已经登陆
        if(Boolean.TRUE.equals(loginUser.isLogin())) {

            return true;
        } else {
            // 没有登陆
            // 会话未登陆
            response.sendRedirect(redirectUrl);
            return false;
        }

    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

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

    }


    private String getLoginServer() {

        if(StringUtils.isEmpty(loginServer)) {
            loginServer = authClient.getAuthHost();
        }

        return loginServer;

    }

}




发了那么多大段的程序,大家肯定心里有疑问,难道每个应用接入放都要复制这么多文件么?还能自己改动逻辑。
当然不会是这样,我们需要将这些东西,做成一个maven包,通过maven依赖的方式依赖进来。如图,首先我们构建
一个模块plat。



但是问题又来了,如果maven parent不一样,怎么能安全的引入呢?所以我们需要将plat的parent修改成自己定制的公用parent。

新建maven应用,配置pom.xml如下。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>xiaotian.shi</groupId>
    <artifactId>deploy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.1.2</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

然后编译进入本地的maven仓库和私服
mvn -Dmaven.test.skip=true package deploy

如果这里有失败,请忽略,只要pom.xml进了仓库就ok。

 plat的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>deploy</artifactId>
        <groupId>xiaotian.shi</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>plat</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>

    <dependencies>


        <!-- spring boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.5.2.RELEASE</version>
        </dependency>

        <!-- 远程调用 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.3.2.RELEASE</version>
        </dependency>

        <!-- 服务发现 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.3.2.RELEASE</version>
        </dependency>

        <!-- logger -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <version>1.5.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.24</version>
        </dependency>

    </dependencies>

</project>

注意parent标签的配置

然后在需要接入授权的地方,引入这个maven dependency

<dependency>
    <groupId>xiaotian.shi</groupId>
    <artifactId>plat</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>


这样,这个包里的拦截器程序就可以引用了。

但是这样不算完成,有如下两个问题需要解决。
第一   开启fegin配置,配置能扫描到包内的fegin接口
第二   拦截器需要配置进应用中


在www应用中开启配置,在继承了SpringBootServletInitializer的controller中,或者单独配置一个config
增加
@EnableFeignClients(basePackages={"shi.xiaotian"})
@EnableEurekaClient
(demo中可能是top.miledao 这是我个人站的包地址)

在www应用中拦截器配置

/**
 * Created by xiaotian.shi on 2017/6/1.
 */
@Configuration
public class InterceptorConfigure extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 会话拦截器
        registry.addInterceptor(this.sessionInterceptor()).addPathPatterns("/**");

        // 权限过滤器
        registry.addInterceptor(this.authInterceptor()).addPathPatterns("/**");

        // 全局过滤器
        registry.addInterceptor(new GlobalInterceptor()).addPathPatterns("/**");

        // 自定义全局工具过滤器
        registry.addInterceptor(new GlobalToolsInterceptor()).addPathPatterns("/**");
    }

    // 解决拦截器不能注入实例的问题
    @Bean
    @Scope("singleton")
    PlatSessionInterceptor sessionInterceptor() {
        return new PlatSessionInterceptor();
    }

    // 解决拦截器不能注入实例的问题
    @Bean
    @Scope("singleton")
    PlatAuthInterceptor authInterceptor() {
        return new PlatAuthInterceptor();
    }
}


权限配置完成

项目地址  https://github.com/shixiaotian/xiaotian.shi-plat.git
demo http://www.miledao.top/
账户密码
admin admin
user1 user1
user2 user2
user3 user3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值